1use conrod_core::{
2 Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, color,
3 position::Relative,
4 widget::{self, Button, Image, Rectangle, State as ConrodState, Text, TextEdit},
5 widget_ids,
6};
7use specs::{Entity as EcsEntity, WorldExt};
8use vek::*;
9
10use client::Client;
11use common::{
12 comp::{
13 Inventory, Stats,
14 inventory::item::{ItemDesc, ItemI18n, MaterialStatManifest, Quality},
15 },
16 recipe::RecipeBookManifest,
17 trade::{PendingTrade, SitePrices, TradeAction, TradePhase},
18};
19use common_net::sync::WorldSyncExt;
20use i18n::Localization;
21
22use crate::{
23 hud::{
24 Event as HudEvent, PromptDialogSettings,
25 bag::{BackgroundIds, InventoryScroller},
26 },
27 ui::{
28 ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager,
29 Tooltipable,
30 fonts::Fonts,
31 slot::{ContentSize, SlotMaker},
32 },
33};
34
35use super::{
36 Hud, HudInfo, Show, TEXT_COLOR, TEXT_GRAY_COLOR, TradeAmountInput, UI_HIGHLIGHT_0, UI_MAIN,
37 img_ids::{Imgs, ImgsRot},
38 item_imgs::ItemImgs,
39 slots::{SlotKind, SlotManager, TradeSlot},
40 util,
41};
42use std::borrow::Cow;
43
44#[allow(clippy::large_enum_variant)]
45pub enum TradeEvent {
46 TradeAction(TradeAction),
47 SetDetailsMode(bool),
48 HudUpdate(HudUpdate),
49 ShowPrompt(PromptDialogSettings),
50}
51
52#[derive(Debug)]
53pub enum HudUpdate {
54 Focus(widget::Id),
55 Submit,
56}
57
58pub struct State {
59 ids: Ids,
60 bg_ids: BackgroundIds,
61}
62
63widget_ids! {
64 pub struct Ids {
65 trade_close,
66 bg,
67 bg_frame,
68 trade_title_bg,
69 trade_title,
70 inv_alignment[],
71 inv_slots[],
72 inv_textslots[],
73 offer_headers[],
74 accept_indicators[],
75 phase_indicator,
76 accept_button,
77 decline_button,
78 inventory_scroller,
79 amount_bg,
80 amount_notice,
81 amount_open_label,
82 amount_open_btn,
83 amount_open_ovlay,
84 amount_input,
85 amount_btn,
86 trade_details_btn,
87 }
88}
89
90#[derive(WidgetCommon)]
91pub struct Trade<'a> {
92 client: &'a Client,
93 info: &'a HudInfo<'a>,
94 imgs: &'a Imgs,
95 item_imgs: &'a ItemImgs,
96 fonts: &'a Fonts,
97 rot_imgs: &'a ImgsRot,
98 tooltip_manager: &'a mut TooltipManager,
99 item_tooltip_manager: &'a mut ItemTooltipManager,
100 #[conrod(common_builder)]
101 common: widget::CommonBuilder,
102 slot_manager: &'a mut SlotManager,
103 localized_strings: &'a Localization,
104 item_i18n: &'a ItemI18n,
105 msm: &'a MaterialStatManifest,
106 rbm: &'a RecipeBookManifest,
107 pulse: f32,
108 show: &'a mut Show,
109 needs_thirdconfirm: bool,
110}
111
112impl<'a> Trade<'a> {
113 pub fn new(
114 client: &'a Client,
115 info: &'a HudInfo,
116 imgs: &'a Imgs,
117 item_imgs: &'a ItemImgs,
118 fonts: &'a Fonts,
119 rot_imgs: &'a ImgsRot,
120 tooltip_manager: &'a mut TooltipManager,
121 item_tooltip_manager: &'a mut ItemTooltipManager,
122 slot_manager: &'a mut SlotManager,
123 localized_strings: &'a Localization,
124 item_i18n: &'a ItemI18n,
125 msm: &'a MaterialStatManifest,
126 rbm: &'a RecipeBookManifest,
127 pulse: f32,
128 show: &'a mut Show,
129 ) -> Self {
130 Self {
131 client,
132 info,
133 imgs,
134 item_imgs,
135 fonts,
136 rot_imgs,
137 tooltip_manager,
138 item_tooltip_manager,
139 common: widget::CommonBuilder::default(),
140 slot_manager,
141 localized_strings,
142 item_i18n,
143 msm,
144 rbm,
145 pulse,
146 show,
147 needs_thirdconfirm: false,
148 }
149 }
150}
151
152const MAX_TRADE_SLOTS: usize = 16;
153
154impl<'a> Trade<'a> {
155 fn background(&mut self, state: &mut ConrodState<'_, State>, ui: &mut UiCell<'_>) {
156 Image::new(self.imgs.inv_middle_bg_bag)
157 .w_h(424.0, 482.0)
158 .color(Some(UI_MAIN))
159 .mid_bottom_with_margin_on(ui.window, 295.0)
160 .set(state.ids.bg, ui);
161 Image::new(self.imgs.inv_middle_frame)
162 .w_h(424.0, 482.0)
163 .middle_of(state.ids.bg)
164 .color(Some(UI_HIGHLIGHT_0))
165 .set(state.ids.bg_frame, ui);
166 }
167
168 fn title(&mut self, state: &mut ConrodState<'_, State>, ui: &mut UiCell<'_>) {
169 Text::new(&self.localized_strings.get_msg("hud-trade-trade_window"))
170 .mid_top_with_margin_on(state.ids.bg_frame, 9.0)
171 .font_id(self.fonts.cyri.conrod_id)
172 .font_size(self.fonts.cyri.scale(20))
173 .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
174 .set(state.ids.trade_title_bg, ui);
175 Text::new(&self.localized_strings.get_msg("hud-trade-trade_window"))
176 .top_left_with_margins_on(state.ids.trade_title_bg, 2.0, 2.0)
177 .font_id(self.fonts.cyri.conrod_id)
178 .font_size(self.fonts.cyri.scale(20))
179 .color(TEXT_COLOR)
180 .set(state.ids.trade_title, ui);
181 }
182
183 fn phase_indicator(
184 &mut self,
185 state: &mut ConrodState<'_, State>,
186 ui: &mut UiCell<'_>,
187 trade: &'a PendingTrade,
188 ) {
189 let phase_text = match trade.phase() {
190 TradePhase::Mutate => self
191 .localized_strings
192 .get_msg("hud-trade-phase1_description"),
193 TradePhase::Review => self
194 .localized_strings
195 .get_msg("hud-trade-phase2_description"),
196 TradePhase::Complete => self
197 .localized_strings
198 .get_msg("hud-trade-phase3_description"),
199 };
200
201 Text::new(&phase_text)
202 .mid_top_with_margin_on(state.ids.bg, 70.0)
203 .font_id(self.fonts.cyri.conrod_id)
204 .font_size(self.fonts.cyri.scale(20))
205 .color(Color::Rgba(1.0, 1.0, 1.0, 1.0))
206 .set(state.ids.phase_indicator, ui);
207 }
208
209 fn item_pane(
210 &mut self,
211 state: &mut ConrodState<'_, State>,
212 ui: &mut UiCell<'_>,
213 trade: &'a PendingTrade,
214 prices: &'a Option<SitePrices>,
215 ours: bool,
216 ) -> Option<TradeEvent> {
217 let mut event = None;
218 let inventories = self.client.inventories();
219 let check_if_us = |who: usize| -> Option<_> {
220 let uid = trade.parties[who];
221 let entity = self.client.state().ecs().entity_from_uid(uid)?;
222 let is_ours = entity == self.client.entity();
223 Some(((who, uid, entity), is_ours))
224 };
225 let (who, uid, entity) = match check_if_us(0)? {
226 (x, is_ours) if ours == is_ours => x,
227 _ => check_if_us(1)?.0,
228 };
229
230 let inventory = inventories.get(entity)?;
232 let our_inventory = if entity == self.client.entity() {
235 inventory
236 } else {
237 let uid = trade.parties[if who == 0 { 1 } else { 0 }];
238 let entity = self.client.state().ecs().entity_from_uid(uid)?;
239 inventories.get(entity)?
240 };
241
242 let mut alignment = Rectangle::fill_with([200.0, 180.0], color::TRANSPARENT);
244 if !ours {
245 alignment = alignment.top_left_with_margins_on(state.ids.bg, 180.0, 32.5);
246 } else {
247 alignment = alignment.right_from(state.ids.inv_alignment[1 - who], 0.0);
248 }
249 alignment
250 .scroll_kids_vertically()
251 .set(state.ids.inv_alignment[who], ui);
252
253 let name = self
255 .client
256 .player_list()
257 .get(&uid)
258 .map(|info| info.player_alias.clone())
259 .or_else(|| {
260 self.client
261 .state()
262 .read_storage::<Stats>()
263 .get(entity)
264 .map(|e| self.localized_strings.get_content(&e.name))
265 })
266 .unwrap_or_else(|| {
267 let ecs = self.client.state().ecs();
269 let bodies = ecs.read_storage::<common::comp::Body>();
270 let body = bodies.get(entity);
271 let gender_enum = body.and_then(|b| b.humanoid_gender());
272
273 let gender_str = match gender_enum {
275 Some(common::comp::body::Gender::Feminine) => "she",
276 Some(common::comp::body::Gender::Masculine) => "he",
277 _ => "other", };
279
280 self.localized_strings
281 .get_msg_ctx("hud-trade-player_who", &i18n::fluent_args! {
282 "player_who" => who,
283 "player_gender" => gender_str
284 })
285 .into_owned()
286 });
287
288 let offer_header = if ours {
289 self.localized_strings.get_msg("hud-trade-your_offer")
290 } else {
291 self.localized_strings.get_msg("hud-trade-their_offer")
292 };
293
294 Text::new(&offer_header)
295 .up_from(state.ids.inv_alignment[who], 20.0)
296 .font_id(self.fonts.cyri.conrod_id)
297 .font_size(self.fonts.cyri.scale(20))
298 .color(Color::Rgba(1.0, 1.0, 1.0, 1.0))
299 .set(state.ids.offer_headers[who], ui);
300
301 let has_accepted = trade.accept_flags[who];
302 let accept_indicator =
303 self.localized_strings
304 .get_msg_ctx("hud-trade-has_accepted", &i18n::fluent_args! {
305 "playername" => &name,
306 });
307 Text::new(&accept_indicator)
308 .down_from(state.ids.inv_alignment[who], 50.0)
309 .font_id(self.fonts.cyri.conrod_id)
310 .font_size(self.fonts.cyri.scale(20))
311 .color(Color::Rgba(
312 1.0,
313 1.0,
314 1.0,
315 if has_accepted { 1.0 } else { 0.0 },
316 ))
317 .set(state.ids.accept_indicators[who], ui);
318
319 let mut invslots: Vec<_> = trade.offers[who].iter().map(|(k, v)| (*k, *v)).collect();
320 invslots.sort();
321 let tradeslots: Vec<_> = invslots
322 .into_iter()
323 .enumerate()
324 .map(|(index, (k, quantity))| TradeSlot {
325 index,
326 quantity,
327 invslot: Some(k),
328 ours,
329 entity,
330 })
331 .collect();
332
333 if matches!(trade.phase(), TradePhase::Mutate) {
334 event = self
335 .phase1_itemwidget(
336 state,
337 ui,
338 inventory,
339 our_inventory,
340 who,
341 ours,
342 entity,
343 name,
344 prices,
345 &tradeslots,
346 )
347 .or(event);
348 } else {
349 self.phase2_itemwidget(state, ui, inventory, who, ours, entity, &tradeslots);
350 }
351
352 event
353 }
354
355 fn phase1_itemwidget(
356 &mut self,
357 state: &mut ConrodState<'_, State>,
358 ui: &mut UiCell<'_>,
359 inventory: &Inventory,
360 our_inventory: &Inventory,
362 who: usize,
363 ours: bool,
364 entity: EcsEntity,
365 name: String,
366 prices: &'a Option<SitePrices>,
367 tradeslots: &[TradeSlot],
368 ) -> Option<TradeEvent> {
369 let mut event = None;
370 let item_tooltip = ItemTooltip::new(
372 {
373 let edge = &self.rot_imgs.tt_side;
376 let corner = &self.rot_imgs.tt_corner;
377 ImageFrame::new(
378 [edge.cw180, edge.none, edge.cw270, edge.cw90],
379 [corner.none, corner.cw270, corner.cw90, corner.cw180],
380 Color::Rgba(0.08, 0.07, 0.04, 1.0),
381 5.0,
382 )
383 },
384 self.client,
385 self.info,
386 self.imgs,
387 self.item_imgs,
388 self.pulse,
389 self.msm,
390 self.rbm,
391 Some(our_inventory),
392 self.localized_strings,
393 self.item_i18n,
394 )
395 .title_font_size(self.fonts.cyri.scale(20))
396 .parent(ui.window)
397 .desc_font_size(self.fonts.cyri.scale(12))
398 .font_id(self.fonts.cyri.conrod_id)
399 .desc_text_color(TEXT_COLOR);
400
401 if !ours {
402 InventoryScroller::new(
403 self.client,
404 self.imgs,
405 self.item_imgs,
406 self.fonts,
407 self.item_tooltip_manager,
408 self.slot_manager,
409 self.pulse,
410 self.localized_strings,
411 self.item_i18n,
412 false,
413 true,
414 false,
415 &item_tooltip,
416 name,
417 entity,
418 false,
419 inventory,
420 &state.bg_ids,
421 false,
422 self.show.trade_details,
423 )
424 .set(state.ids.inventory_scroller, ui);
425
426 let bag_tooltip = Tooltip::new({
427 let edge = &self.rot_imgs.tt_side;
430 let corner = &self.rot_imgs.tt_corner;
431 ImageFrame::new(
432 [edge.cw180, edge.none, edge.cw270, edge.cw90],
433 [corner.none, corner.cw270, corner.cw90, corner.cw180],
434 Color::Rgba(0.08, 0.07, 0.04, 1.0),
435 5.0,
436 )
437 })
438 .title_font_size(self.fonts.cyri.scale(15))
439 .parent(ui.window)
440 .desc_font_size(self.fonts.cyri.scale(12))
441 .font_id(self.fonts.cyri.conrod_id)
442 .desc_text_color(TEXT_COLOR);
443
444 let buttons_top = 53.0;
445 let (txt, btn, hover, press) = if self.show.trade_details {
446 (
447 "Grid mode",
448 self.imgs.grid_btn,
449 self.imgs.grid_btn_hover,
450 self.imgs.grid_btn_press,
451 )
452 } else {
453 (
454 "List mode",
455 self.imgs.list_btn,
456 self.imgs.list_btn_hover,
457 self.imgs.list_btn_press,
458 )
459 };
460 let details_btn = Button::image(btn)
461 .w_h(32.0, 17.0)
462 .hover_image(hover)
463 .press_image(press);
464 if details_btn
465 .mid_top_with_margin_on(state.bg_ids.bg_frame, buttons_top)
466 .with_tooltip(self.tooltip_manager, txt, "", &bag_tooltip, TEXT_COLOR)
467 .set(state.ids.trade_details_btn, ui)
468 .was_clicked()
469 {
470 event = Some(TradeEvent::SetDetailsMode(!self.show.trade_details));
471 }
472 }
473
474 let mut slot_maker = SlotMaker {
475 empty_slot: self.imgs.inv_slot,
476 filled_slot: self.imgs.inv_slot,
477 selected_slot: self.imgs.inv_slot_sel,
478 background_color: Some(UI_MAIN),
479 content_size: ContentSize {
480 width_height_ratio: 1.0,
481 max_fraction: 0.75,
482 },
483 selected_content_scale: 1.067,
484 amount_font: self.fonts.cyri.conrod_id,
485 amount_margins: Vec2::new(-4.0, 0.0),
486 amount_font_size: self.fonts.cyri.scale(12),
487 amount_text_color: TEXT_COLOR,
488 content_source: inventory,
489 image_source: self.item_imgs,
490 slot_manager: Some(self.slot_manager),
491 pulse: self.pulse,
492 };
493
494 if state.ids.inv_slots.len() < 2 * MAX_TRADE_SLOTS {
495 state.update(|s| {
496 s.ids
497 .inv_slots
498 .resize(2 * MAX_TRADE_SLOTS, &mut ui.widget_id_generator());
499 });
500 }
501
502 for i in 0..MAX_TRADE_SLOTS {
503 let x = i % 4;
504 let y = i / 4;
505
506 let slot = tradeslots.get(i).cloned().unwrap_or(TradeSlot {
507 index: i,
508 quantity: 0,
509 invslot: None,
510 ours,
511 entity,
512 });
513 let slot_widget = slot_maker
515 .fabricate(slot, [40.0; 2])
516 .top_left_with_margins_on(
517 state.ids.inv_alignment[who],
518 0.0 + y as f64 * (40.0),
519 0.0 + x as f64 * (40.0),
520 );
521 let slot_id = state.ids.inv_slots[i + who * MAX_TRADE_SLOTS];
522 if let Some(Some(item)) = slot.invslot.and_then(|slotid| inventory.slot(slotid)) {
523 let quality_col_img = match item.quality() {
524 Quality::Low => self.imgs.inv_slot_grey,
525 Quality::Common => self.imgs.inv_slot_common,
526 Quality::Moderate => self.imgs.inv_slot_green,
527 Quality::High => self.imgs.inv_slot_blue,
528 Quality::Epic => self.imgs.inv_slot_purple,
529 Quality::Legendary => self.imgs.inv_slot_gold,
530 Quality::Artifact => self.imgs.inv_slot_orange,
531 _ => self.imgs.inv_slot_red,
532 };
533
534 slot_widget
535 .filled_slot(quality_col_img)
536 .with_item_tooltip(
537 self.item_tooltip_manager,
538 core::iter::once(item as &dyn ItemDesc),
539 prices,
540 &item_tooltip,
541 )
542 .set(slot_id, ui);
543 } else {
544 slot_widget.set(slot_id, ui);
545 }
546 }
547 event
548 }
549
550 fn phase2_itemwidget(
551 &mut self,
552 state: &mut ConrodState<'_, State>,
553 ui: &mut UiCell<'_>,
554 inventory: &Inventory,
555 who: usize,
556 ours: bool,
557 entity: EcsEntity,
558 tradeslots: &[TradeSlot],
559 ) {
560 if state.ids.inv_textslots.len() < 2 * MAX_TRADE_SLOTS {
561 state.update(|s| {
562 s.ids
563 .inv_textslots
564 .resize(2 * MAX_TRADE_SLOTS, &mut ui.widget_id_generator());
565 });
566 }
567 let max_width = 170.0;
568 let mut total_text_height = 0.0;
569 let mut total_quantity = 0;
570 for i in 0..MAX_TRADE_SLOTS {
571 let slot = tradeslots.get(i).cloned().unwrap_or(TradeSlot {
572 index: i,
573 quantity: 0,
574 invslot: None,
575 ours,
576 entity,
577 });
578 total_quantity += slot.quantity;
579 let itemname = slot
580 .invslot
581 .and_then(|i| inventory.get(i))
582 .map(|i| {
583 let (name, _) = util::item_text(&i, self.localized_strings, self.item_i18n);
584
585 Cow::Owned(name)
586 })
587 .unwrap_or(Cow::Borrowed(""));
588 let is_present = slot.quantity > 0 && slot.invslot.is_some();
589 Text::new(&format!("{}x {}", slot.quantity, &itemname))
590 .top_left_with_margins_on(
591 state.ids.inv_alignment[who],
592 15.0 + i as f64 * 20.0 + total_text_height,
593 0.0,
594 )
595 .font_id(self.fonts.cyri.conrod_id)
596 .font_size(self.fonts.cyri.scale(20))
597 .wrap_by_word()
598 .w(max_width)
599 .color(Color::Rgba(
600 1.0,
601 1.0,
602 1.0,
603 if is_present { 1.0 } else { 0.0 },
604 ))
605 .set(state.ids.inv_textslots[i + who * MAX_TRADE_SLOTS], ui);
606 let label_height = match ui
607 .widget_graph()
608 .widget(state.ids.inv_textslots[i + who * MAX_TRADE_SLOTS])
609 .map(|widget| widget.rect)
610 {
611 Some(label_rect) => label_rect.h(),
612 None => 10.0,
613 };
614 total_text_height += label_height;
615 }
616 if total_quantity == 0 {
617 Text::new("Nothing!")
618 .top_left_with_margins_on(state.ids.inv_alignment[who], 10.0, 0.0)
619 .font_id(self.fonts.cyri.conrod_id)
620 .font_size(self.fonts.cyri.scale(20))
621 .color(Color::Rgba(
622 1.0,
623 0.25 + 0.25 * (4.0 * self.pulse).sin(),
624 0.0,
625 1.0,
626 ))
627 .set(state.ids.inv_textslots[who * MAX_TRADE_SLOTS], ui);
628
629 if !ours {
630 self.needs_thirdconfirm = true;
631 }
632 }
633 }
634
635 fn accept_decline_buttons(
636 &mut self,
637 state: &mut ConrodState<'_, State>,
638 ui: &mut UiCell<'_>,
639 trade: &'a PendingTrade,
640 ) -> Option<TradeEvent> {
641 let mut event = None;
642 let (hover_img, press_img, accept_button_luminance) = if trade.is_empty_trade() {
643 (
645 self.imgs.button,
646 self.imgs.button,
647 Color::Rgba(0.6, 0.6, 0.6, 1.0),
648 )
649 } else {
650 (
651 self.imgs.button_hover,
652 self.imgs.button_press,
653 Color::Rgba(1.0, 1.0, 1.0, 1.0),
654 )
655 };
656 if Button::image(self.imgs.button)
657 .w_h(31.0 * 5.0, 12.0 * 2.0)
658 .hover_image(hover_img)
659 .press_image(press_img)
660 .image_color(accept_button_luminance)
661 .bottom_left_with_margins_on(state.ids.bg, 90.0, 47.0)
662 .label(&self.localized_strings.get_msg("hud-trade-accept"))
663 .label_font_size(self.fonts.cyri.scale(14))
664 .label_color(TEXT_COLOR)
665 .label_font_id(self.fonts.cyri.conrod_id)
666 .label_y(Relative::Scalar(2.0))
667 .set(state.ids.accept_button, ui)
668 .was_clicked()
669 {
670 if matches!(trade.phase, TradePhase::Review) && self.needs_thirdconfirm {
671 event = Some(TradeEvent::ShowPrompt(PromptDialogSettings::new(
672 self.localized_strings
673 .get_msg("hud-confirm-trade-for-nothing")
674 .to_string(),
675 HudEvent::TradeAction(TradeAction::Accept(trade.phase())),
676 None,
677 )));
678 } else {
679 event = Some(TradeEvent::TradeAction(TradeAction::Accept(trade.phase())));
680 }
681 }
682
683 if Button::image(self.imgs.button)
684 .w_h(31.0 * 5.0, 12.0 * 2.0)
685 .hover_image(self.imgs.button_hover)
686 .press_image(self.imgs.button_press)
687 .right_from(state.ids.accept_button, 20.0)
688 .label(&self.localized_strings.get_msg("hud-trade-decline"))
689 .label_font_size(self.fonts.cyri.scale(14))
690 .label_color(TEXT_COLOR)
691 .label_font_id(self.fonts.cyri.conrod_id)
692 .label_y(Relative::Scalar(2.0))
693 .set(state.ids.decline_button, ui)
694 .was_clicked()
695 {
696 event = Some(TradeEvent::TradeAction(TradeAction::Decline));
697 }
698 event
699 }
700
701 fn input_item_amount(
702 &mut self,
703 state: &mut ConrodState<'_, State>,
704 ui: &mut UiCell<'_>,
705 trade: &'a PendingTrade,
706 ) -> Option<TradeEvent> {
707 let mut event = None;
708 let selected = self.slot_manager.selected().and_then(|s| match s {
709 SlotKind::Trade(t_s) => t_s.invslot.and_then(|slot| {
710 let who: usize = trade.offers[0].get(&slot).and(Some(0)).unwrap_or(1);
711 self.client
712 .inventories()
713 .get(t_s.entity)?
714 .get(slot)
715 .map(|item| (t_s.ours, slot, item.amount(), who))
716 }),
717 _ => None,
718 });
719 Rectangle::fill([132.0, 20.0])
720 .bottom_right_with_margins_on(state.ids.bg_frame, 16.0, 32.0)
721 .hsla(
722 0.0,
723 0.0,
724 0.0,
725 if self.show.trade_amount_input_key.is_some() {
726 0.75
727 } else {
728 0.35
729 },
730 )
731 .set(state.ids.amount_bg, ui);
732 if let Some((ours, slot, inv, who)) = selected {
733 self.show.trade_amount_input_key = None;
734 let input = trade.offers[who]
736 .get(&slot)
737 .map(|u| format!("{}", u))
738 .unwrap_or_else(String::new);
739 Text::new(&input)
740 .top_left_with_margins_on(state.ids.amount_bg, 0.0, 22.0)
741 .font_id(self.fonts.cyri.conrod_id)
742 .font_size(self.fonts.cyri.scale(14))
743 .color(TEXT_COLOR.alpha(0.7))
744 .set(state.ids.amount_open_label, ui);
745 if Button::image(self.imgs.edit_btn)
746 .hover_image(self.imgs.edit_btn_hover)
747 .press_image(self.imgs.edit_btn_press)
748 .mid_left_with_margin_on(state.ids.amount_bg, 2.0)
749 .w_h(16.0, 16.0)
750 .set(state.ids.amount_open_btn, ui)
751 .was_clicked()
752 {
753 event = Some(HudUpdate::Focus(state.ids.amount_input));
754 self.slot_manager.idle();
755 self.show.trade_amount_input_key =
756 Some(TradeAmountInput::new(slot, input, inv, ours, who));
757 }
758 Rectangle::fill_with([132.0, 20.0], color::TRANSPARENT)
759 .top_left_of(state.ids.amount_bg)
760 .graphics_for(state.ids.amount_open_btn)
761 .set(state.ids.amount_open_ovlay, ui);
762 } else if let Some(key) = &mut self.show.trade_amount_input_key {
763 if !Hud::is_captured::<TextEdit>(ui) && key.input_painted {
764 event = Some(HudUpdate::Submit);
766 }
767
768 if Button::image(self.imgs.close_btn)
769 .hover_image(self.imgs.close_btn_hover)
770 .press_image(self.imgs.close_btn_press)
771 .mid_left_with_margin_on(state.ids.amount_bg, 2.0)
772 .w_h(16.0, 16.0)
773 .set(state.ids.amount_btn, ui)
774 .was_clicked()
775 {
776 event = Some(HudUpdate::Submit);
777 }
778 key.input_painted = true;
780 let text_color = key.err.as_ref().and(Some(color::RED)).unwrap_or(TEXT_COLOR);
781 if let Some(new_input) = TextEdit::new(&key.input)
782 .mid_left_with_margin_on(state.ids.amount_bg, 22.0)
783 .w_h(138.0, 20.0)
784 .font_id(self.fonts.cyri.conrod_id)
785 .font_size(self.fonts.cyri.scale(14))
786 .color(text_color)
787 .set(state.ids.amount_input, ui)
788 {
789 if new_input != key.input {
790 new_input.trim().clone_into(&mut key.input);
791 if !key.input.is_empty() {
792 let amount = *trade.offers[key.who].get(&key.slot).unwrap_or(&0);
794 match key.input.parse::<i32>() {
795 Ok(new_amount) => {
796 key.input = format!("{}", new_amount);
797 if new_amount > -1 && new_amount <= key.inv as i32 {
798 key.err = None;
799 let delta = new_amount - amount as i32;
800 key.submit_action =
801 TradeAction::item(key.slot, delta, key.ours);
802 } else {
803 key.err = Some("out of range".to_owned());
804 key.submit_action = None;
805 }
806 },
807 Err(_) => {
808 key.err = Some("bad quantity".to_owned());
809 key.submit_action = None;
810 },
811 }
812 } else {
813 key.submit_action = None;
814 }
815 }
816 }
817 } else {
818 Text::new(&self.localized_strings.get_msg("hud-trade-amount_input"))
820 .middle_of(state.ids.amount_bg)
821 .font_id(self.fonts.cyri.conrod_id)
822 .font_size(self.fonts.cyri.scale(14))
823 .color(TEXT_GRAY_COLOR.alpha(0.25))
824 .set(state.ids.amount_notice, ui);
825 }
826 event.map(TradeEvent::HudUpdate)
827 }
828
829 fn close_button(
830 &mut self,
831 state: &mut ConrodState<'_, State>,
832 ui: &mut UiCell<'_>,
833 ) -> Option<TradeEvent> {
834 if Button::image(self.imgs.close_btn)
835 .w_h(24.0, 25.0)
836 .hover_image(self.imgs.close_btn_hover)
837 .press_image(self.imgs.close_btn_press)
838 .top_right_with_margins_on(state.ids.bg, 0.0, 0.0)
839 .set(state.ids.trade_close, ui)
840 .was_clicked()
841 {
842 Some(TradeEvent::TradeAction(TradeAction::Decline))
843 } else {
844 None
845 }
846 }
847}
848
849impl Widget for Trade<'_> {
850 type Event = Option<TradeEvent>;
851 type State = State;
852 type Style = ();
853
854 fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State {
855 State {
856 bg_ids: BackgroundIds {
857 bg: id_gen.next(),
858 bg_frame: id_gen.next(),
859 },
860 ids: Ids::new(id_gen),
861 }
862 }
863
864 fn style(&self) -> Self::Style {}
865
866 fn update(mut self, args: widget::UpdateArgs<Self>) -> Self::Event {
867 common_base::prof_span!("Trade::update");
868 let widget::UpdateArgs { state, ui, .. } = args;
869
870 let mut event = None;
871 let (trade, prices) = match self.client.pending_trade() {
872 Some((_, trade, prices)) => (trade, prices),
873 None => return Some(TradeEvent::TradeAction(TradeAction::Decline)),
874 };
875
876 if state.ids.inv_alignment.len() < 2 {
877 state.update(|s| {
878 s.ids.inv_alignment.resize(2, &mut ui.widget_id_generator());
879 });
880 }
881 if state.ids.offer_headers.len() < 2 {
882 state.update(|s| {
883 s.ids.offer_headers.resize(2, &mut ui.widget_id_generator());
884 });
885 }
886 if state.ids.accept_indicators.len() < 2 {
887 state.update(|s| {
888 s.ids
889 .accept_indicators
890 .resize(2, &mut ui.widget_id_generator());
891 });
892 }
893
894 self.background(state, ui);
895 self.title(state, ui);
896 self.phase_indicator(state, ui, trade);
897
898 event = self.item_pane(state, ui, trade, prices, false).or(event);
899 event = self.item_pane(state, ui, trade, prices, true).or(event);
900 event = self.accept_decline_buttons(state, ui, trade).or(event);
901 event = self.close_button(state, ui).or(event);
902 self.input_item_amount(state, ui, trade).or(event)
903 }
904}