1use super::{
2 CRITICAL_HP_COLOR, HudInfo, LOW_HP_COLOR, Show, SlotGrid, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
3 cr_color,
4 img_ids::{Imgs, ImgsRot},
5 item_imgs::ItemImgs,
6 slot_grid::SlotEvents,
7 slots::{ArmorSlot, EquipSlot, SlotManager},
8};
9use crate::{
10 GlobalState,
11 game_input::GameInput,
12 hud::{controller_icons as icon_utils, slot_grid::TabFilters},
13 settings::{
14 HudPositionSettings,
15 hud_position::{
16 DEFAULT_OTHER_BAG_HEIGHT, DEFAULT_OTHER_BAG_WIDTH, DEFAULT_OWN_BAG_HEIGHT,
17 DEFAULT_OWN_BAG_WIDTH,
18 },
19 },
20 ui::{
21 ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, RichText, Tooltip,
22 TooltipManager, Tooltipable,
23 fonts::Fonts,
24 slot::{ContentSize, SlotMaker},
25 },
26 window::{LastInput, MenuInput},
27};
28use client::Client;
29use common::{
30 combat::{Damage, combat_rating, perception_dist_multiplier_from_stealth},
31 comp::{
32 Body, Energy, Health, Inventory, Poise, SkillSet, Stats,
33 inventory::InventorySortOrder,
34 item::{ItemDesc, ItemI18n, MaterialStatManifest},
35 },
36 recipe::RecipeBookManifest,
37};
38use conrod_core::{
39 Color, Colorable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, builder_methods, color,
40 widget::{self, Button, Image, Rectangle, Scrollbar, State as ConrodState, Text},
41 widget_ids,
42};
43use i18n::Localization;
44use std::borrow::Cow;
45
46use specs::Entity as EcsEntity;
47use vek::{Vec2, approx::AbsDiffEq};
48
49const STATS: [&str; 6] = [
50 "Health",
51 "Energy",
52 "Protection",
53 "Combat Rating",
54 "Stun Resilience",
55 "Stealth",
56];
57
58widget_ids! {
59 pub struct InventoryScrollerIds {
60 draggable_area,
61 inv_alignment,
62 spacing_above,
63 slot_grid,
64 space_txt,
66 inventory_title,
68 inventory_title_bg,
69 scrollbar_bg,
70 second_phase_scrollbar_bg,
71 scrollbar_slots,
72 left_scrollbar_slots,
73 }
74}
75
76pub struct InventoryScrollerState {
77 ids: InventoryScrollerIds,
78}
79
80pub enum InventoryScrollerEvent {
81 Drag(Vec2<f64>),
82 Close,
83}
84
85#[derive(WidgetCommon)]
86pub struct InventoryScroller<'a> {
87 client: &'a Client,
88 imgs: &'a Imgs,
89 item_imgs: &'a ItemImgs,
90 fonts: &'a Fonts,
91 #[conrod(common_builder)]
92 common: widget::CommonBuilder,
93 item_tooltip_manager: &'a mut ItemTooltipManager,
94 slot_manager: &'a mut SlotManager,
95 pulse: f32,
96 menu_events: &'a Vec<MenuInput>,
97 active_content: usize,
98 localized_strings: &'a Localization,
99 item_i18n: &'a ItemI18n,
100 show_stats: bool,
101 show_bag_inv: bool,
102 on_right: bool,
103 global_state: &'a GlobalState,
104 item_tooltip: &'a ItemTooltip<'a>,
105 playername: String,
106 entity: EcsEntity,
107 is_us: bool,
108 inventory: &'a Inventory,
109 bg_ids: &'a BackgroundIds,
110 show_salvage: bool,
111 details_mode: bool,
112}
113
114impl<'a> InventoryScroller<'a> {
115 #[expect(clippy::too_many_arguments)]
118 pub fn new(
119 client: &'a Client,
120 global_state: &'a GlobalState,
121 imgs: &'a Imgs,
122 item_imgs: &'a ItemImgs,
123 fonts: &'a Fonts,
124 item_tooltip_manager: &'a mut ItemTooltipManager,
125 slot_manager: &'a mut SlotManager,
126 pulse: f32,
127 menu_events: &'a Vec<MenuInput>,
128 active_content: usize,
129 localized_strings: &'a Localization,
130 item_i18n: &'a ItemI18n,
131 show_stats: bool,
132 show_bag_inv: bool,
133 on_right: bool,
134 item_tooltip: &'a ItemTooltip<'a>,
135 playername: String,
136 entity: EcsEntity,
137 is_us: bool,
138 inventory: &'a Inventory,
139 bg_ids: &'a BackgroundIds,
140 show_salvage: bool,
141 details_mode: bool,
142 ) -> Self {
143 InventoryScroller {
144 client,
145 imgs,
146 item_imgs,
147 fonts,
148 common: widget::CommonBuilder::default(),
149 item_tooltip_manager,
150 slot_manager,
151 pulse,
152 menu_events,
153 active_content,
154 localized_strings,
155 item_i18n,
156 show_stats,
157 show_bag_inv,
158 on_right,
159 global_state,
160 item_tooltip,
161 playername,
162 entity,
163 is_us,
164 inventory,
165 bg_ids,
166 show_salvage,
167 details_mode,
168 }
169 }
170
171 fn background(&mut self, ui: &mut UiCell<'_>) {
172 let bag_settings = &self.global_state.settings.hud_position;
173 let bag_pos = if !self.on_right {
174 bag_settings.bag.other
175 } else {
176 bag_settings.bag.own
177 };
178
179 let bg_id = if !self.on_right {
180 self.imgs.inv_bg_bag
181 } else {
182 self.imgs.player_inv_bg_bag
183 };
184
185 let img_id = if !self.on_right {
186 self.imgs.inv_frame_bag
187 } else {
188 self.imgs.player_inv_frame_bag
189 };
190
191 let mut bg = Image::new(if self.show_stats {
192 self.imgs.inv_bg_stats
193 } else if self.show_bag_inv {
194 bg_id
195 } else {
196 self.imgs.inv_bg_armor
197 })
198 .w_h(
199 424.0,
200 if self.show_bag_inv && !self.on_right {
201 548.0
202 } else {
203 708.0
204 },
205 );
206
207 if self.on_right {
208 bg = bg.bottom_right_with_margins_on(ui.window, bag_pos.y, bag_pos.x);
209 } else {
210 bg = bg.bottom_left_with_margins_on(ui.window, bag_pos.y, bag_pos.x);
211 }
212
213 bg.color(Some(UI_MAIN)).set(self.bg_ids.bg, ui);
214
215 Image::new(if self.show_bag_inv {
216 img_id
217 } else {
218 self.imgs.inv_frame
219 })
220 .w_h(
221 424.0,
222 if self.show_bag_inv && !self.on_right {
223 548.0
224 } else {
225 708.0
226 },
227 )
228 .middle_of(self.bg_ids.bg)
229 .color(Some(UI_HIGHLIGHT_0))
230 .set(self.bg_ids.bg_frame, ui);
231 }
232
233 fn title(&mut self, state: &ConrodState<'_, InventoryScrollerState>, ui: &mut UiCell<'_>) {
234 Text::new(
235 &self
236 .localized_strings
237 .get_msg_ctx("hud-bag-inventory", &i18n::fluent_args! {
238 "playername" => &*self.playername,
239 }),
240 )
241 .mid_top_with_margin_on(self.bg_ids.bg_frame, 9.0)
242 .font_id(self.fonts.cyri.conrod_id)
243 .font_size(self.fonts.cyri.scale(22))
244 .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
245 .set(state.ids.inventory_title_bg, ui);
246 Text::new(
247 &self
248 .localized_strings
249 .get_msg_ctx("hud-bag-inventory", &i18n::fluent_args! {
250 "playername" => &*self.playername,
251 }),
252 )
253 .top_left_with_margins_on(state.ids.inventory_title_bg, 2.0, 2.0)
254 .font_id(self.fonts.cyri.conrod_id)
255 .font_size(self.fonts.cyri.scale(22))
256 .color(TEXT_COLOR)
257 .set(state.ids.inventory_title, ui);
258 }
259
260 fn scrollbar_and_slots(
261 &mut self,
262 state: &mut ConrodState<'_, InventoryScrollerState>,
263 events: &mut Vec<InventoryScrollerEvent>,
264 ui: &mut UiCell<'_>,
265 ) {
266 if self.active_content == 1 {
269 for event in self.menu_events {
270 match *event {
271 MenuInput::Apply => {
272 },
274 MenuInput::Back => {
275 events.push(InventoryScrollerEvent::Close);
276 },
277 _ => {},
278 }
279 }
280 }
281
282 let grid_width = 376.0;
283 let grid_height = if self.show_bag_inv && !self.on_right {
284 450.0 } else if self.show_bag_inv && self.on_right {
286 600.0 } else {
288 200.0
289 };
290
291 Rectangle::fill_with([grid_width, grid_height], color::TRANSPARENT)
293 .mid_bottom_with_margin_on(self.bg_ids.bg_frame, 21.0)
294 .scroll_kids_vertically()
295 .set(state.ids.inv_alignment, ui);
296
297 Rectangle::fill_with([grid_width, 5.0], color::TRANSPARENT)
299 .mid_top_of(state.ids.inv_alignment)
300 .set(state.ids.spacing_above, ui);
301
302 let mut space_max = 0;
303 let compact_mode = self
304 .global_state
305 .settings
306 .interface
307 .toggle_compact_item_slots;
308 let columns = if compact_mode { 9 } else { 6 };
309 let spacing = if compact_mode { 2.0 } else { 5.8 };
310 let slot_size = if compact_mode { 40.0 } else { 57.8 };
311
312 for event in SlotGrid::new(
314 self.client,
315 self.global_state,
316 self.imgs,
317 self.item_imgs,
318 self.fonts,
319 self.item_tooltip_manager,
320 self.slot_manager,
321 self.inventory,
322 self.item_tooltip,
323 self.localized_strings,
324 self.item_i18n,
325 self.entity,
326 self.pulse,
327 self.menu_events,
328 self.is_us,
329 self.details_mode,
330 self.show_salvage,
331 )
332 .columns(columns)
333 .spacing(if self.details_mode { 0.0 } else { spacing })
334 .slot_size(if self.details_mode { 20.0 } else { slot_size })
335 .w_of(state.ids.inv_alignment)
336 .down_from(state.ids.spacing_above, 0.0)
337 .set(state.ids.slot_grid, ui)
338 {
339 match event {
340 SlotEvents::Close => {
341 events.push(InventoryScrollerEvent::Close);
342 },
343 SlotEvents::FilteredSize(size) => {
344 space_max = size;
345 },
346 _ => {},
347 }
348 }
349
350 if space_max > 45 && !self.show_bag_inv {
352 Image::new(self.imgs.scrollbar_bg)
354 .w_h(9.0, 173.0)
355 .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
356 .color(Some(UI_HIGHLIGHT_0))
357 .set(state.ids.scrollbar_bg, ui);
358 Scrollbar::y_axis(state.ids.inv_alignment)
360 .thickness(5.0)
361 .h(123.0)
362 .color(UI_MAIN)
363 .middle_of(state.ids.scrollbar_bg)
364 .set(state.ids.scrollbar_slots, ui);
365 } else if space_max > 135 && self.on_right {
366 Image::new(self.imgs.scrollbar_bg_big)
368 .w_h(9.0, 592.0)
369 .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
370 .color(Some(UI_HIGHLIGHT_0))
371 .set(state.ids.scrollbar_bg, ui);
372 Scrollbar::y_axis(state.ids.inv_alignment)
374 .thickness(5.0)
375 .h(542.0)
376 .color(UI_MAIN)
377 .middle_of(state.ids.scrollbar_bg)
378 .set(state.ids.scrollbar_slots, ui);
379 };
380
381 if space_max >= 108 && !self.on_right && self.show_bag_inv {
383 Image::new(self.imgs.second_phase_scrollbar_bg)
385 .w_h(9.0, 434.0)
386 .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
387 .color(Some(UI_HIGHLIGHT_0))
388 .set(state.ids.second_phase_scrollbar_bg, ui);
389 Scrollbar::y_axis(state.ids.inv_alignment)
391 .thickness(5.0)
392 .h(384.0)
393 .color(UI_MAIN)
394 .middle_of(state.ids.second_phase_scrollbar_bg)
395 .set(state.ids.left_scrollbar_slots, ui);
396 }
397 }
398
399 fn footer_metrics(
400 &mut self,
401 state: &ConrodState<'_, InventoryScrollerState>,
402 ui: &mut UiCell<'_>,
403 ) {
404 let space_used = self.inventory.populated_slots();
405 let space_max = self.inventory.slots().count();
406 let bag_space = format!("{}/{}", space_used, space_max);
407 let bag_space_percentage = space_used as f32 / space_max as f32;
408 Text::new(&bag_space)
441 .bottom_right_with_margins_on(self.bg_ids.bg_frame, 6.0, 43.0)
442 .font_id(self.fonts.cyri.conrod_id)
443 .font_size(self.fonts.cyri.scale(14))
444 .color(if bag_space_percentage < 0.8 {
445 TEXT_COLOR
446 } else if bag_space_percentage < 1.0 {
447 LOW_HP_COLOR
448 } else {
449 CRITICAL_HP_COLOR
450 })
451 .set(state.ids.space_txt, ui);
452 }
453
454 fn draggable_area(
455 &self,
456 state: &ConrodState<'_, InventoryScrollerState>,
457 events: &mut Vec<InventoryScrollerEvent>,
458 ui: &mut UiCell<'_>,
459 ) {
460 let bag_settings = &self.global_state.settings.hud_position;
461 let bag_pos = if !self.on_right {
462 bag_settings.bag.other
463 } else {
464 bag_settings.bag.own
465 };
466
467 let bag_size: Vec2<f64> = if !self.on_right {
468 [DEFAULT_OTHER_BAG_WIDTH, DEFAULT_OTHER_BAG_HEIGHT].into()
469 } else {
470 [DEFAULT_OWN_BAG_WIDTH, DEFAULT_OWN_BAG_HEIGHT].into()
471 };
472
473 let pos_delta: Vec2<f64> = ui
474 .widget_input(state.ids.draggable_area)
475 .drags()
476 .left()
477 .map(|drag| Vec2::<f64>::from(drag.delta_xy))
478 .sum();
479
480 let pos_delta: Vec2<f64> = if !self.on_right {
481 pos_delta
483 } else {
484 pos_delta.with_x(-pos_delta.x)
488 };
489
490 let window_clamp = Vec2::new(ui.win_w, ui.win_h) - bag_size;
491
492 let new_pos = (bag_pos + pos_delta)
493 .map(|e| e.max(0.))
494 .map2(window_clamp, |e, bounds| e.min(bounds));
495
496 if new_pos.abs_diff_ne(&bag_pos, f64::EPSILON) {
497 events.push(InventoryScrollerEvent::Drag(new_pos));
498 }
499
500 if ui
501 .widget_input(state.ids.draggable_area)
502 .clicks()
503 .right()
504 .count()
505 == 1
506 {
507 events.push(InventoryScrollerEvent::Drag(if self.on_right {
508 HudPositionSettings::default().bag.own
509 } else {
510 HudPositionSettings::default().bag.other
511 }));
512 }
513
514 Rectangle::fill_with([424.0, 48.0], color::TRANSPARENT)
515 .top_left_with_margin_on(self.bg_ids.bg_frame, 0.0)
516 .set(state.ids.draggable_area, ui);
517 }
518}
519
520impl Widget for InventoryScroller<'_> {
521 type Event = Vec<InventoryScrollerEvent>;
522 type State = InventoryScrollerState;
523 type Style = ();
524
525 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
526 InventoryScrollerState {
527 ids: InventoryScrollerIds::new(id_gen),
528 }
529 }
530
531 fn style(&self) -> Self::Style {}
532
533 fn update(mut self, args: widget::UpdateArgs<Self>) -> Self::Event {
534 let widget::UpdateArgs { state, ui, .. } = args;
535 let mut events = Vec::new();
536 self.background(ui);
537 self.title(state, ui);
538 self.scrollbar_and_slots(state, &mut events, ui);
539 self.footer_metrics(state, ui);
540 if self
541 .global_state
542 .settings
543 .interface
544 .toggle_draggable_windows
545 {
546 self.draggable_area(state, &mut events, ui);
547 }
548 events
549 }
550}
551
552widget_ids! {
553 pub struct BackgroundIds {
554 bg,
555 bg_frame,
556 }
557}
558
559#[derive(Copy, Clone, PartialEq, Eq)]
560pub enum BagTab {
561 Gear,
562 Inventory,
563 }
567
568pub struct TabPackage<'a> {
571 global_state: &'a GlobalState,
572 client: &'a Client,
573 info: &'a HudInfo<'a>,
574 imgs: &'a Imgs,
575 item_imgs: &'a ItemImgs,
576 fonts: &'a Fonts,
577 localized_strings: &'a Localization,
578 item_i18n: &'a ItemI18n,
579 pulse: f32,
580}
581
582widget_ids! {
583 pub struct BagManagerIds {
584 primary_bag,
585 secondary_bag,
586 }
587}
588
589pub struct BagManagerState {
590 ids: BagManagerIds,
591 bg_ids_primary: BackgroundIds,
592 bg_ids_secondary: BackgroundIds,
593}
594
595pub enum BagEvent {
596 Close,
597 MoveBag(Vec2<f64>),
598 BagExpand,
599 SetDetailsMode(bool),
600 ChangeInventorySortOrder(InventorySortOrder),
601 SortInventory(InventorySortOrder),
602 SwapEquippedWeapons,
603}
604
605#[derive(WidgetCommon)]
606pub struct BagManager<'a> {
607 global_state: &'a GlobalState,
608 client: &'a Client,
609 info: &'a HudInfo<'a>,
610 imgs: &'a Imgs,
611 item_imgs: &'a ItemImgs,
612 fonts: &'a Fonts,
613 #[conrod(common_builder)]
614 common: widget::CommonBuilder,
615 rot_imgs: &'a ImgsRot,
616 tooltip_manager: &'a mut TooltipManager,
617 item_tooltip_manager: &'a mut ItemTooltipManager,
618 slot_manager: &'a mut SlotManager,
619 pulse: f32,
620 localized_strings: &'a Localization,
621 item_i18n: &'a ItemI18n,
622 stats: &'a Stats,
623 skill_set: &'a SkillSet,
624 health: &'a Health,
625 energy: &'a Energy,
626 show: &'a Show,
627 body: &'a Body,
628 msm: &'a MaterialStatManifest,
629 rbm: &'a RecipeBookManifest,
630 poise: &'a Poise,
631 menu_events: &'a Vec<MenuInput>,
632}
633
634impl<'a> BagManager<'a> {
635 #[expect(clippy::too_many_arguments)]
639 pub fn new(
640 global_state: &'a GlobalState,
641 client: &'a Client,
642 info: &'a HudInfo<'a>,
643 imgs: &'a Imgs,
644 item_imgs: &'a ItemImgs,
645 fonts: &'a Fonts,
646 rot_imgs: &'a ImgsRot,
647 tooltip_manager: &'a mut TooltipManager,
648 item_tooltip_manager: &'a mut ItemTooltipManager,
649 slot_manager: &'a mut SlotManager,
650 pulse: f32,
651 localized_strings: &'a Localization,
652 item_i18n: &'a ItemI18n,
653 stats: &'a Stats,
654 skill_set: &'a SkillSet,
655 health: &'a Health,
656 energy: &'a Energy,
657 show: &'a Show,
658 body: &'a Body,
659 msm: &'a MaterialStatManifest,
660 rbm: &'a RecipeBookManifest,
661 poise: &'a Poise,
662 menu_events: &'a Vec<MenuInput>,
663 ) -> Self {
664 Self {
665 global_state,
666 client,
667 info,
668 imgs,
669 item_imgs,
670 fonts,
671 common: widget::CommonBuilder::default(),
672 rot_imgs,
673 tooltip_manager,
674 item_tooltip_manager,
675 slot_manager,
676 pulse,
677 localized_strings,
678 item_i18n,
679 stats,
680 skill_set,
681 health,
682 energy,
683 show,
684 body,
685 msm,
686 rbm,
687 poise,
688 menu_events,
689 }
690 }
691}
692
693impl Widget for BagManager<'_> {
694 type Event = Vec<BagEvent>;
695 type State = BagManagerState;
696 type Style = ();
697
698 fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State {
699 BagManagerState {
700 bg_ids_primary: BackgroundIds {
701 bg: id_gen.next(),
702 bg_frame: id_gen.next(),
703 },
704 bg_ids_secondary: BackgroundIds {
705 bg: id_gen.next(),
706 bg_frame: id_gen.next(),
707 },
708 ids: BagManagerIds::new(id_gen),
709 }
710 }
711
712 fn style(&self) -> Self::Style {}
713
714 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
715 common_base::prof_span!("Bag::update");
716 let widget::UpdateArgs { state, ui, .. } = args;
717 let mut events = Vec::new();
718
719 for event in BagWindow::new(
721 self.global_state,
722 self.client,
723 self.info,
724 self.imgs,
725 self.item_imgs,
726 self.fonts,
727 self.rot_imgs,
728 self.tooltip_manager,
729 self.item_tooltip_manager,
730 self.slot_manager,
731 self.pulse,
732 self.localized_strings,
733 self.item_i18n,
734 self.stats,
735 self.skill_set,
736 self.health,
737 self.energy,
738 self.show,
739 self.body,
740 self.msm,
741 self.rbm,
742 self.poise,
743 self.menu_events,
744 &state.bg_ids_primary,
745 )
746 .set(state.ids.primary_bag, ui)
747 {
748 events.push(event)
749 }
750
751 if self.show.bag_menu_split && !self.show.trade {
755 for event in BagWindow::new(
756 self.global_state,
757 self.client,
758 self.info,
759 self.imgs,
760 self.item_imgs,
761 self.fonts,
762 self.rot_imgs,
763 self.tooltip_manager,
764 self.item_tooltip_manager,
765 self.slot_manager,
766 self.pulse,
767 self.localized_strings,
768 self.item_i18n,
769 self.stats,
770 self.skill_set,
771 self.health,
772 self.energy,
773 self.show,
774 self.body,
775 self.msm,
776 self.rbm,
777 self.poise,
778 self.menu_events,
779 &state.bg_ids_secondary,
780 )
781 .add_secondary(&state.bg_ids_primary)
782 .set(state.ids.secondary_bag, ui)
783 {
784 events.push(event)
785 }
786 }
787
788 events
789 }
790}
791
792widget_ids! {
793 pub struct BagWindowIds {
794 bag_title_anchor,
795 bag_title_dropshadow,
796 bag_title,
797 close_button,
798 tab_anchor,
799 gear_tab,
800 inventory_tab,
801 gear_tab_content,
805 inventory_tab_content,
806 left_button,
810 right_button,
811 draggable_area,
812 }
813}
814
815pub struct BagWindowState {
816 ids: BagWindowIds,
817 active_tab: BagTab,
818}
819
820#[derive(WidgetCommon)]
821pub struct BagWindow<'a> {
822 global_state: &'a GlobalState,
823 client: &'a Client,
824 info: &'a HudInfo<'a>,
825 imgs: &'a Imgs,
826 item_imgs: &'a ItemImgs,
827 fonts: &'a Fonts,
828 #[conrod(common_builder)]
829 common: widget::CommonBuilder,
830 rot_imgs: &'a ImgsRot,
831 tooltip_manager: &'a mut TooltipManager,
832 item_tooltip_manager: &'a mut ItemTooltipManager,
833 slot_manager: &'a mut SlotManager,
834 pulse: f32,
835 localized_strings: &'a Localization,
836 item_i18n: &'a ItemI18n,
837 stats: &'a Stats,
838 skill_set: &'a SkillSet,
839 health: &'a Health,
840 energy: &'a Energy,
841 show: &'a Show,
842 body: &'a Body,
843 msm: &'a MaterialStatManifest,
844 rbm: &'a RecipeBookManifest,
845 poise: &'a Poise,
846 menu_events: &'a Vec<MenuInput>,
847 bg_ids: &'a BackgroundIds,
848 is_player: bool,
849 primary_bg_ids: Option<&'a BackgroundIds>,
850}
851
852impl<'a> BagWindow<'a> {
853 builder_methods! {
854 is_player { is_player = bool }
855 add_secondary { primary_bg_ids = Some(&'a BackgroundIds) }
856 }
857
858 #[expect(clippy::too_many_arguments)]
861 pub fn new(
862 global_state: &'a GlobalState,
863 client: &'a Client,
864 info: &'a HudInfo<'a>,
865 imgs: &'a Imgs,
866 item_imgs: &'a ItemImgs,
867 fonts: &'a Fonts,
868 rot_imgs: &'a ImgsRot,
869 tooltip_manager: &'a mut TooltipManager,
870 item_tooltip_manager: &'a mut ItemTooltipManager,
871 slot_manager: &'a mut SlotManager,
872 pulse: f32,
873 localized_strings: &'a Localization,
874 item_i18n: &'a ItemI18n,
875 stats: &'a Stats,
876 skill_set: &'a SkillSet,
877 health: &'a Health,
878 energy: &'a Energy,
879 show: &'a Show,
880 body: &'a Body,
881 msm: &'a MaterialStatManifest,
882 rbm: &'a RecipeBookManifest,
883 poise: &'a Poise,
884 menu_events: &'a Vec<MenuInput>,
885 bg_ids: &'a BackgroundIds,
886 ) -> Self {
887 Self {
888 global_state,
889 client,
890 info,
891 imgs,
892 item_imgs,
893 fonts,
894 common: widget::CommonBuilder::default(),
895 rot_imgs,
896 tooltip_manager,
897 item_tooltip_manager,
898 slot_manager,
899 pulse,
900 localized_strings,
901 item_i18n,
902 stats,
903 skill_set,
904 health,
905 energy,
906 show,
907 body,
908 msm,
909 rbm,
910 poise,
911 menu_events,
912 bg_ids,
913 is_player: true,
914 primary_bg_ids: None,
915 }
916 }
917
918 fn draggable_area(
919 &self,
920 state: &ConrodState<'_, BagWindowState>,
921 events: &mut Vec<BagEvent>,
922 ui: &mut UiCell<'_>,
923 ) {
924 let bag_settings = &self.global_state.settings.hud_position;
925 let bag_pos = if self.is_player {
926 bag_settings.bag.own
927 } else {
928 bag_settings.bag.other
929 };
930
931 let bag_size: Vec2<f64> = if self.is_player {
932 [DEFAULT_OWN_BAG_WIDTH, DEFAULT_OWN_BAG_HEIGHT].into()
933 } else {
934 [DEFAULT_OTHER_BAG_WIDTH, DEFAULT_OTHER_BAG_HEIGHT].into()
935 };
936
937 let pos_delta: Vec2<f64> = ui
938 .widget_input(state.ids.draggable_area)
939 .drags()
940 .left()
941 .map(|drag| Vec2::<f64>::from(drag.delta_xy))
942 .sum();
943
944 let pos_delta: Vec2<f64> = if self.is_player {
945 pos_delta.with_x(-pos_delta.x)
949 } else {
950 pos_delta
952 };
953
954 let window_clamp = Vec2::new(ui.win_w, ui.win_h) - bag_size;
955
956 let new_pos = (bag_pos + pos_delta)
957 .map(|e| e.max(0.))
958 .map2(window_clamp, |e, bounds| e.min(bounds));
959
960 if new_pos.abs_diff_ne(&bag_pos, f64::EPSILON) {
961 events.push(BagEvent::MoveBag(new_pos));
962 }
963
964 if ui
965 .widget_input(state.ids.draggable_area)
966 .clicks()
967 .right()
968 .count()
969 == 1
970 {
971 events.push(BagEvent::MoveBag(if self.is_player {
972 HudPositionSettings::default().bag.own
973 } else {
974 HudPositionSettings::default().bag.other
975 }));
976 }
977
978 Rectangle::fill_with([424.0, 71.0], color::TRANSPARENT)
979 .top_left_with_margin_on(self.bg_ids.bg_frame, 0.0)
980 .set(state.ids.draggable_area, ui);
981 }
982}
983
984impl Widget for BagWindow<'_> {
985 type Event = Vec<BagEvent>;
986 type State = BagWindowState;
987 type Style = ();
988
989 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
990 BagWindowState {
991 ids: BagWindowIds::new(id_gen),
992 active_tab: BagTab::Inventory,
993 }
994 }
995
996 fn style(&self) -> Self::Style {}
997
998 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
999 common_base::prof_span!("BagWindow::update");
1000 let widget::UpdateArgs { state, ui, .. } = args;
1001 let _i18n = &self.localized_strings;
1002 let mut events = Vec::new();
1003
1004 let is_primary = self.primary_bg_ids.is_none();
1006 let block_gear_tab = self.show.bag_menu_split && !self.show.trade;
1009
1010 if !is_primary {
1014 if state.active_tab != BagTab::Gear {
1016 state.update(|s| {
1017 s.active_tab = BagTab::Gear;
1018 })
1019 }
1020 } else {
1021 for event in self.menu_events {
1023 match *event {
1024 MenuInput::PageDown => {
1025 state.update(|s| {
1026 if s.active_tab == BagTab::Inventory {
1027 s.active_tab = BagTab::Gear;
1028 } else {
1029 s.active_tab = BagTab::Inventory;
1030 }
1031
1032 self.slot_manager.idle();
1033 });
1034 },
1035 MenuInput::PageUp => state.update(|s| {
1036 if s.active_tab == BagTab::Gear {
1037 s.active_tab = BagTab::Inventory;
1038 } else {
1039 s.active_tab = BagTab::Gear;
1040 }
1041
1042 self.slot_manager.idle();
1043 }),
1044 _ => {},
1046 }
1047 }
1048 }
1049
1050 if block_gear_tab && state.active_tab == BagTab::Gear && is_primary {
1053 state.update(|s| {
1054 s.active_tab = BagTab::Inventory;
1055 })
1056 }
1057
1058 let bag_pos = if self.is_player {
1060 &self.global_state.settings.hud_position.bag.own
1061 } else {
1062 &self.global_state.settings.hud_position.bag.other
1063 };
1064 let bg_img = if self.is_player {
1065 self.imgs.player_inv_bg_bag
1066 } else {
1067 self.imgs.player_inv_bg_bag
1069 };
1070 let bg_frame_img = self.imgs.player_inv_frame_bag;
1071
1072 let mut bg = Image::new(bg_img).w_h(424.0, 700.0).color(Some(UI_MAIN));
1073 if self.is_player && is_primary {
1074 bg = bg.bottom_right_with_margins_on(ui.window, bag_pos.y, bag_pos.x);
1075 } else if !self.is_player {
1076 bg = bg.bottom_left_with_margins_on(ui.window, bag_pos.y, bag_pos.x);
1077 } else {
1078 if let Some(primary_ids) = self.primary_bg_ids {
1080 bg = bg.left_from(primary_ids.bg, 0.0);
1081 } else {
1082 bg = bg.bottom_right_with_margins_on(ui.window, bag_pos.y, bag_pos.x)
1085 }
1086 }
1087 bg.set(self.bg_ids.bg, ui);
1088
1089 Image::new(bg_frame_img)
1090 .w_h(424.0, 700.0)
1091 .middle_of(self.bg_ids.bg)
1092 .color(Some(UI_HIGHLIGHT_0))
1093 .set(self.bg_ids.bg_frame, ui);
1094
1095 let title_txt = match state.active_tab {
1097 BagTab::Gear => &self.localized_strings.get_msg("hud-bag-gear-tab"),
1098 BagTab::Inventory => &self.localized_strings.get_msg("gameinput-inventory"),
1099 };
1103 Rectangle::fill_with([0.0, 0.0], color::TRANSPARENT)
1104 .mid_top_with_margin_on(self.bg_ids.bg_frame, 16.5)
1105 .set(state.ids.bag_title_anchor, ui);
1106 Text::new(title_txt)
1107 .x_y_relative_to(state.ids.bag_title_anchor, -2.0, -2.0)
1108 .font_id(self.fonts.cyri.conrod_id)
1109 .font_size(self.fonts.cyri.scale(22))
1110 .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
1111 .set(state.ids.bag_title_dropshadow, ui);
1112 Text::new(title_txt)
1113 .x_y_relative_to(state.ids.bag_title_dropshadow, 2.0, 2.0)
1114 .font_id(self.fonts.cyri.conrod_id)
1115 .font_size(self.fonts.cyri.scale(22))
1116 .color(TEXT_COLOR)
1117 .set(state.ids.bag_title, ui);
1118
1119 if self
1121 .global_state
1122 .settings
1123 .interface
1124 .toggle_draggable_windows
1125 {
1126 self.draggable_area(state, &mut events, ui);
1127 }
1128
1129 if is_primary {
1130 if Button::image(self.imgs.close_btn)
1132 .w_h(24.0, 25.0)
1133 .hover_image(self.imgs.close_btn_hover)
1134 .press_image(self.imgs.close_btn_press)
1135 .top_right_of(self.bg_ids.bg_frame)
1136 .set(state.ids.close_button, ui)
1137 .was_clicked()
1138 {
1139 events.push(BagEvent::Close);
1140 }
1141
1142 let tab_space = 12.5;
1143
1144 Rectangle::fill_with([0.0, 0.0], color::TRANSPARENT)
1146 .mid_top_with_margin_on(self.bg_ids.bg_frame, 35.5)
1147 .set(state.ids.tab_anchor, ui);
1148
1149 let tab_img = if state.active_tab == BagTab::Inventory {
1151 self.imgs.bag_tab_press
1152 } else {
1153 self.imgs.bag_tab
1154 };
1155 if Button::image(tab_img)
1156 .w_h(30.0, 30.0)
1157 .right_from(state.ids.tab_anchor, tab_space / 2.0)
1158 .set(state.ids.inventory_tab, ui)
1159 .was_clicked()
1160 {
1161 state.update(|s| {
1162 s.active_tab = BagTab::Inventory;
1163 })
1164 }
1165
1166 let tab_img = if state.active_tab == BagTab::Gear || block_gear_tab {
1168 self.imgs.gear_tab_press
1169 } else {
1170 self.imgs.gear_tab
1171 };
1172 if Button::image(tab_img)
1173 .w_h(30.0, 30.0)
1174 .left_from(state.ids.tab_anchor, tab_space / 2.0)
1175 .set(state.ids.gear_tab, ui)
1176 .was_clicked()
1177 {
1178 if is_primary && !block_gear_tab {
1180 state.update(|s| {
1181 s.active_tab = BagTab::Gear;
1182 })
1183 }
1184 }
1185
1186 let right_key = match self.global_state.window.last_input() {
1188 LastInput::Controller => icon_utils::get_controller_input_string_menu(
1189 MenuInput::PageUp,
1190 &self.global_state.settings,
1191 self.global_state.window.controller_type(),
1192 )
1193 .unwrap_or_default(),
1194 LastInput::Keyboard | LastInput::Mouse => self
1195 .global_state
1196 .settings
1197 .controls
1198 .get_menu_binding(MenuInput::PageUp)
1199 .map_or_else(|| "".into(), |key| key.display_string()),
1200 };
1201 let left_key = match self.global_state.window.last_input() {
1202 LastInput::Controller => icon_utils::get_controller_input_string_menu(
1203 MenuInput::PageDown,
1204 &self.global_state.settings,
1205 self.global_state.window.controller_type(),
1206 )
1207 .unwrap_or_default(),
1208 LastInput::Keyboard | LastInput::Mouse => self
1209 .global_state
1210 .settings
1211 .controls
1212 .get_menu_binding(MenuInput::PageDown)
1213 .map_or_else(|| "".into(), |key| key.display_string()),
1214 };
1215
1216 RichText::new(&left_key, self.imgs)
1217 .left_from(state.ids.gear_tab, tab_space)
1218 .font_id(self.fonts.cyri.conrod_id)
1219 .font_size(self.fonts.cyri.scale(22))
1220 .color(TEXT_COLOR)
1221 .set(state.ids.left_button, ui);
1222
1223 RichText::new(&right_key, self.imgs)
1224 .right_from(state.ids.inventory_tab, tab_space)
1225 .font_id(self.fonts.cyri.conrod_id)
1226 .font_size(self.fonts.cyri.scale(22))
1227 .color(TEXT_COLOR)
1228 .set(state.ids.right_button, ui);
1229 }
1230
1231 if let Some(inventory) = self.client.inventories().get(self.info.viewpoint_entity) {
1233 let tooltip = Tooltip::new({
1235 let edge = &self.rot_imgs.tt_side;
1238 let corner = &self.rot_imgs.tt_corner;
1239 ImageFrame::new(
1240 [edge.cw180, edge.none, edge.cw270, edge.cw90],
1241 [corner.none, corner.cw270, corner.cw90, corner.cw180],
1242 Color::Rgba(0.08, 0.07, 0.04, 1.0),
1243 5.0,
1244 )
1245 })
1246 .title_font_size(self.fonts.cyri.scale(15))
1247 .parent(ui.window)
1248 .desc_font_size(self.fonts.cyri.scale(12))
1249 .font_id(self.fonts.cyri.conrod_id)
1250 .desc_text_color(TEXT_COLOR);
1251
1252 let bag_tooltip = Tooltip::new({
1253 let edge = &self.rot_imgs.tt_side;
1256 let corner = &self.rot_imgs.tt_corner;
1257 ImageFrame::new(
1258 [edge.cw180, edge.none, edge.cw270, edge.cw90],
1259 [corner.none, corner.cw270, corner.cw90, corner.cw180],
1260 Color::Rgba(0.08, 0.07, 0.04, 1.0),
1261 5.0,
1262 )
1263 })
1264 .title_font_size(self.fonts.cyri.scale(15))
1265 .parent(ui.window)
1266 .desc_font_size(self.fonts.cyri.scale(12))
1267 .font_id(self.fonts.cyri.conrod_id)
1268 .desc_text_color(TEXT_COLOR);
1269
1270 let item_tooltip = ItemTooltip::new(
1271 {
1272 let edge = &self.rot_imgs.tt_side;
1275 let corner = &self.rot_imgs.tt_corner;
1276 ImageFrame::new(
1277 [edge.cw180, edge.none, edge.cw270, edge.cw90],
1278 [corner.none, corner.cw270, corner.cw90, corner.cw180],
1279 Color::Rgba(0.08, 0.07, 0.04, 1.0),
1280 5.0,
1281 )
1282 },
1283 self.client,
1284 self.info,
1285 self.imgs,
1286 self.item_imgs,
1287 self.pulse,
1288 self.msm,
1289 self.rbm,
1290 Some(inventory),
1291 self.localized_strings,
1292 self.item_i18n,
1293 )
1294 .title_font_size(self.fonts.cyri.scale(20))
1295 .parent(ui.window)
1296 .desc_font_size(self.fonts.cyri.scale(12))
1297 .font_id(self.fonts.cyri.conrod_id)
1298 .desc_text_color(TEXT_COLOR);
1299
1300 let tab_package = TabPackage {
1301 global_state: self.global_state,
1302 client: self.client,
1303 info: self.info,
1304 imgs: self.imgs,
1305 item_imgs: self.item_imgs,
1306 fonts: self.fonts,
1307 localized_strings: self.localized_strings,
1308 item_i18n: self.item_i18n,
1309 pulse: self.pulse,
1310 };
1311
1312 match state.active_tab {
1313 BagTab::Gear => {
1314 for event in GearMenu::new(
1316 &tab_package,
1317 inventory,
1318 &tooltip,
1319 &bag_tooltip,
1320 &item_tooltip,
1321 self.tooltip_manager,
1322 self.item_tooltip_manager,
1323 self.slot_manager,
1324 self.stats,
1325 self.skill_set,
1326 self.health,
1327 self.energy,
1328 self.show,
1329 self.body,
1330 self.msm,
1331 self.poise,
1332 self.menu_events,
1333 self.bg_ids,
1334 )
1335 .set(state.ids.gear_tab_content, ui)
1336 {
1337 match event {
1338 GearMenuEvent::Close => events.push(BagEvent::Close),
1339 GearMenuEvent::SwapEquippedWeapons => {
1340 events.push(BagEvent::SwapEquippedWeapons)
1341 },
1342 }
1343 }
1344 },
1345 BagTab::Inventory => {
1346 for event in InventoryMenu::new(
1348 &tab_package,
1349 inventory,
1350 &tooltip,
1351 &bag_tooltip,
1352 &item_tooltip,
1353 self.tooltip_manager,
1354 self.item_tooltip_manager,
1355 self.slot_manager,
1356 self.menu_events,
1357 self.bg_ids,
1358 self.show.bag_details,
1359 self.show.crafting_fields.salvage,
1360 )
1361 .expanded_window(self.show.bag_menu_split)
1362 .trading(self.show.trade)
1363 .set(state.ids.inventory_tab_content, ui)
1364 {
1365 match event {
1366 InventoryMenuEvent::Close => events.push(BagEvent::Close),
1367 InventoryMenuEvent::BagExpand => events.push(BagEvent::BagExpand),
1368 InventoryMenuEvent::SetDetailsMode => {
1369 events.push(BagEvent::SetDetailsMode(!self.show.bag_details))
1370 },
1371 InventoryMenuEvent::ChangeInventorySortOrder => {
1372 events.push(BagEvent::ChangeInventorySortOrder(
1373 self.global_state.settings.inventory.sort_order.next(),
1374 ))
1375 },
1376 InventoryMenuEvent::SortInventory => {
1377 events.push(BagEvent::SortInventory(
1378 self.global_state.settings.inventory.sort_order,
1379 ));
1380 },
1381 }
1382 }
1383 },
1384 }
1385 }
1386
1387 events
1388 }
1389}
1390
1391widget_ids! {
1392 pub struct InventoryMenuIds {
1393 scrollbar_bg,
1394 scrollbar_slots,
1395 inv_alignment,
1396 slot_grid,
1397 bag_details_btn,
1398 inventory_sort,
1399 inventory_sort_selected,
1400 bag_expand_btn,
1401 space_txt,
1402 scroll_divider,
1403 spacing_above,
1404 }
1405}
1406
1407pub struct InventoryMenuState {
1408 ids: InventoryMenuIds,
1409}
1410
1411pub enum InventoryMenuEvent {
1412 Close,
1413 BagExpand,
1414 SetDetailsMode,
1415 ChangeInventorySortOrder,
1416 SortInventory,
1417}
1418
1419#[derive(WidgetCommon)]
1420pub struct InventoryMenu<'a> {
1421 tab_package: &'a TabPackage<'a>,
1422 inventory: &'a Inventory,
1423 tooltip: &'a Tooltip<'a>,
1424 bag_tooltip: &'a Tooltip<'a>,
1425 item_tooltip: &'a ItemTooltip<'a>,
1426 #[conrod(common_builder)]
1427 common: widget::CommonBuilder,
1428 tooltip_manager: &'a mut TooltipManager,
1429 item_tooltip_manager: &'a mut ItemTooltipManager,
1430 slot_manager: &'a mut SlotManager,
1431 menu_events: &'a Vec<MenuInput>,
1432 bag_ids: &'a BackgroundIds,
1433 details_mode: bool,
1434 show_salvage: bool,
1435 filter: TabFilters,
1436 expanded_window: bool,
1437 trading: bool,
1438}
1439
1440impl<'a> InventoryMenu<'a> {
1441 builder_methods! {
1442 pub filter { filter = TabFilters }
1443 pub expanded_window { expanded_window = bool }
1444 pub trading { trading = bool }
1445 }
1446
1447 pub fn new(
1449 tab_package: &'a TabPackage<'a>,
1450 inventory: &'a Inventory,
1451 tooltip: &'a Tooltip<'a>,
1452 bag_tooltip: &'a Tooltip<'a>,
1453 item_tooltip: &'a ItemTooltip<'a>,
1454 tooltip_manager: &'a mut TooltipManager,
1455 item_tooltip_manager: &'a mut ItemTooltipManager,
1456 slot_manager: &'a mut SlotManager,
1457 menu_events: &'a Vec<MenuInput>,
1458 bag_ids: &'a BackgroundIds,
1459 details_mode: bool,
1460 show_salvage: bool,
1461 ) -> Self {
1462 Self {
1463 tab_package,
1464 inventory,
1465 tooltip,
1466 bag_tooltip,
1467 item_tooltip,
1468 common: widget::CommonBuilder::default(),
1469 tooltip_manager,
1470 item_tooltip_manager,
1471 slot_manager,
1472 menu_events,
1473 bag_ids,
1474 details_mode,
1475 show_salvage,
1476 filter: TabFilters::None,
1477 expanded_window: false,
1478 trading: false,
1479 }
1480 }
1481}
1482
1483impl Widget for InventoryMenu<'_> {
1484 type Event = Vec<InventoryMenuEvent>;
1485 type State = InventoryMenuState;
1486 type Style = ();
1487
1488 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
1489 InventoryMenuState {
1490 ids: InventoryMenuIds::new(id_gen),
1491 }
1492 }
1493
1494 fn style(&self) -> Self::Style {}
1495
1496 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
1497 common_base::prof_span!("InventoryMenu::update");
1498 let widget::UpdateArgs { state, ui, .. } = args;
1499 let i18n = &self.tab_package.localized_strings;
1500 let mut events = Vec::new();
1501
1502 let grid_width = 376.0;
1514 let grid_height = 586.0;
1515
1516 Rectangle::fill_with([grid_width, grid_height], color::TRANSPARENT)
1518 .mid_bottom_with_margin_on(self.bag_ids.bg_frame, 3.5)
1519 .scroll_kids_vertically()
1520 .set(state.ids.inv_alignment, ui);
1521
1522 Rectangle::fill_with([grid_width, 5.0], color::TRANSPARENT)
1524 .mid_top_of(state.ids.inv_alignment)
1525 .set(state.ids.spacing_above, ui);
1526
1527 let mut space_max = 0;
1528 let compact_mode = self
1529 .tab_package
1530 .global_state
1531 .settings
1532 .interface
1533 .toggle_compact_item_slots;
1534 let columns = if compact_mode { 9 } else { 6 };
1535 let spacing = if compact_mode { 2.0 } else { 5.8 };
1536 let slot_size = if compact_mode { 40.0 } else { 57.8 };
1537 let scroll_size = if compact_mode { 117 } else { 54 };
1538
1539 for event in SlotGrid::new(
1541 self.tab_package.client,
1542 self.tab_package.global_state,
1543 self.tab_package.imgs,
1544 self.tab_package.item_imgs,
1545 self.tab_package.fonts,
1546 self.item_tooltip_manager,
1547 self.slot_manager,
1548 self.inventory,
1549 self.item_tooltip,
1550 self.tab_package.localized_strings,
1551 self.tab_package.item_i18n,
1552 self.tab_package.info.viewpoint_entity,
1553 self.tab_package.pulse,
1554 self.menu_events,
1555 true, self.details_mode,
1557 self.show_salvage,
1558 )
1559 .columns(columns)
1560 .spacing(if self.details_mode { 0.0 } else { spacing })
1561 .slot_size(if self.details_mode { 20.0 } else { slot_size })
1562 .filter(self.filter)
1563 .w_of(state.ids.inv_alignment)
1564 .down_from(state.ids.spacing_above, 0.0)
1565 .set(state.ids.slot_grid, ui)
1566 {
1567 match event {
1568 SlotEvents::Close => events.push(InventoryMenuEvent::Close),
1569 SlotEvents::FilteredSize(size) => {
1570 space_max = size;
1571 },
1572 _ => {},
1573 }
1574 }
1575
1576 if space_max > scroll_size {
1578 Image::new(self.tab_package.imgs.scrollbar_bg_big)
1580 .w_h(9.0, 577.0)
1581 .bottom_right_with_margins_on(self.bag_ids.bg_frame, 24.0, 3.0)
1582 .color(Some(UI_HIGHLIGHT_0))
1583 .set(state.ids.scrollbar_bg, ui);
1584 Scrollbar::y_axis(state.ids.inv_alignment)
1586 .thickness(6.0)
1587 .h(530.0)
1588 .color(UI_MAIN)
1589 .middle_of(state.ids.scrollbar_bg)
1590 .set(state.ids.scrollbar_slots, ui);
1591 };
1592
1593 Rectangle::fill_with([grid_width, 1.5], Color::Rgba(0.314, 0.443, 0.443, 1.0))
1595 .up_from(state.ids.inv_alignment, 0.0)
1596 .set(state.ids.scroll_divider, ui);
1597
1598 if !self.trading {
1602 if Button::image(if self.expanded_window {
1604 self.tab_package.imgs.expand_btn
1605 } else {
1606 self.tab_package.imgs.collapse_btn
1607 })
1608 .w_h(30.0, 17.0)
1609 .hover_image(if self.expanded_window {
1610 self.tab_package.imgs.expand_btn_hover
1611 } else {
1612 self.tab_package.imgs.collapse_btn_hover
1613 })
1614 .press_image(if self.expanded_window {
1615 self.tab_package.imgs.expand_btn_press
1616 } else {
1617 self.tab_package.imgs.collapse_btn_press
1618 })
1619 .align_left_of(state.ids.scroll_divider)
1620 .up_from(state.ids.scroll_divider, 5.0)
1621 .with_tooltip(
1622 self.tooltip_manager,
1623 &i18n.get_msg("hud-bag-toggle-expanded-window"),
1624 "",
1625 self.tooltip,
1626 color::WHITE,
1627 )
1628 .set(state.ids.bag_expand_btn, ui)
1629 .was_clicked()
1630 {
1631 events.push(InventoryMenuEvent::BagExpand);
1632 }
1633 }
1634
1635 if Button::image(self.tab_package.imgs.inv_sort_selected_btn)
1637 .w_h(30.0, 17.0)
1638 .hover_image(self.tab_package.imgs.inv_sort_selected_btn_hover)
1639 .press_image(self.tab_package.imgs.inv_sort_selected_btn_press)
1640 .align_middle_x_of(state.ids.scroll_divider)
1641 .up_from(state.ids.scroll_divider, 5.0)
1642 .with_tooltip(
1643 self.tooltip_manager,
1644 &(match self.tab_package.global_state.settings.inventory.sort_order {
1645 InventorySortOrder::Name => i18n.get_msg("hud-bag-sort_by_name"),
1646 InventorySortOrder::Quality => i18n.get_msg("hud-bag-sort_by_quality"),
1647 InventorySortOrder::Category => i18n.get_msg("hud-bag-sort_by_category"),
1648 InventorySortOrder::Tag => i18n.get_msg("hud-bag-sort_by_tag"),
1649 InventorySortOrder::Amount => i18n.get_msg("hud-bag-sort_by_quantity"),
1650 }),
1651 "",
1652 self.tooltip,
1653 color::WHITE,
1654 )
1655 .set(state.ids.inventory_sort_selected, ui)
1656 .was_clicked()
1657 {
1658 events.push(InventoryMenuEvent::SortInventory);
1659 }
1660
1661 if Button::image(self.tab_package.imgs.inv_sort_btn)
1663 .w_h(30.0, 17.0)
1664 .hover_image(self.tab_package.imgs.inv_sort_btn_hover)
1665 .press_image(self.tab_package.imgs.inv_sort_btn_press)
1666 .left_from(state.ids.inventory_sort_selected, 10.0)
1667 .with_tooltip(
1668 self.tooltip_manager,
1669 &(match self
1670 .tab_package
1671 .global_state
1672 .settings
1673 .inventory
1674 .sort_order
1675 .next()
1676 {
1677 InventorySortOrder::Name => i18n.get_msg("hud-bag-change_to_sort_by_name"),
1678 InventorySortOrder::Quality => {
1679 i18n.get_msg("hud-bag-change_to_sort_by_quality")
1680 },
1681 InventorySortOrder::Category => {
1682 i18n.get_msg("hud-bag-change_to_sort_by_category")
1683 },
1684 InventorySortOrder::Tag => i18n.get_msg("hud-bag-change_to_sort_by_tag"),
1685 InventorySortOrder::Amount => {
1686 i18n.get_msg("hud-bag-change_to_sort_by_quantity")
1687 },
1688 }),
1689 "",
1690 self.tooltip,
1691 color::WHITE,
1692 )
1693 .set(state.ids.inventory_sort, ui)
1694 .was_clicked()
1695 {
1696 events.push(InventoryMenuEvent::ChangeInventorySortOrder);
1697 }
1698
1699 let (txt, btn, hover, press) = if self.details_mode {
1701 (
1702 "Grid mode",
1703 self.tab_package.imgs.grid_btn,
1704 self.tab_package.imgs.grid_btn_hover,
1705 self.tab_package.imgs.grid_btn_press,
1706 )
1707 } else {
1708 (
1709 "List mode",
1710 self.tab_package.imgs.list_btn,
1711 self.tab_package.imgs.list_btn_hover,
1712 self.tab_package.imgs.list_btn_press,
1713 )
1714 };
1715 if Button::image(btn)
1716 .w_h(32.0, 17.0)
1717 .hover_image(hover)
1718 .press_image(press)
1719 .right_from(state.ids.inventory_sort_selected, 10.0)
1720 .with_tooltip(self.tooltip_manager, txt, "", self.bag_tooltip, TEXT_COLOR)
1721 .set(state.ids.bag_details_btn, ui)
1722 .was_clicked()
1723 {
1724 events.push(InventoryMenuEvent::SetDetailsMode);
1725 }
1726
1727 let space_used = self.inventory.populated_slots();
1729 let space_max = self.inventory.slots().count();
1730 let bag_space = format!("{}/{}", space_used, space_max);
1731 let bag_space_percentage = space_used as f32 / space_max as f32;
1732 Text::new(&bag_space)
1733 .align_right_of(state.ids.scroll_divider)
1734 .up_from(state.ids.scroll_divider, 7.5)
1735 .font_id(self.tab_package.fonts.cyri.conrod_id)
1736 .font_size(self.tab_package.fonts.cyri.scale(14))
1737 .color(if bag_space_percentage < 0.8 {
1738 TEXT_COLOR
1739 } else if bag_space_percentage < 1.0 {
1740 LOW_HP_COLOR
1741 } else {
1742 CRITICAL_HP_COLOR
1743 })
1744 .set(state.ids.space_txt, ui);
1745
1746 events
1747 }
1748}
1749
1750widget_ids! {
1751 pub struct GearMenuIds {
1752 inv_alignment,
1753 slot_grid,
1754 scrollbar_bg,
1755 scrollbar_slots,
1756 scroll_divider,
1757 spacing_above,
1758 head_slot,
1760 neck_slot,
1761 chest_slot,
1762 shoulders_slot,
1763 hands_slot,
1764 legs_slot,
1765 belt_slot,
1766 lantern_slot,
1767 ring1_slot,
1768 ring2_slot,
1769 feet_slot,
1770 back_slot,
1771 tabard_slot,
1772 glider_slot,
1773 active_mainhand_slot,
1774 active_offhand_slot,
1775 inactive_mainhand_slot,
1776 inactive_offhand_slot,
1777 swap_equipped_weapons_btn,
1778 bag1_slot,
1779 bag2_slot,
1780 bag3_slot,
1781 bag4_slot,
1782 stat_icons[],
1784 stat_txts[],
1785 }
1786}
1787
1788pub struct GearMenuState {
1789 ids: GearMenuIds,
1790 is_focused: bool,
1791 active_gear_slot: usize,
1792}
1793
1794pub enum GearMenuEvent {
1795 Close,
1796 SwapEquippedWeapons,
1797}
1798
1799#[derive(WidgetCommon)]
1800pub struct GearMenu<'a> {
1801 tab_package: &'a TabPackage<'a>,
1802 inventory: &'a Inventory,
1803 tooltip: &'a Tooltip<'a>,
1804 bag_tooltip: &'a Tooltip<'a>,
1805 item_tooltip: &'a ItemTooltip<'a>,
1806 #[conrod(common_builder)]
1807 common: widget::CommonBuilder,
1808 tooltip_manager: &'a mut TooltipManager,
1809 item_tooltip_manager: &'a mut ItemTooltipManager,
1810 slot_manager: &'a mut SlotManager,
1811 stats: &'a Stats,
1812 skill_set: &'a SkillSet,
1813 health: &'a Health,
1814 energy: &'a Energy,
1815 show: &'a Show,
1816 body: &'a Body,
1817 msm: &'a MaterialStatManifest,
1818 poise: &'a Poise,
1819 menu_events: &'a Vec<MenuInput>,
1820 bag_ids: &'a BackgroundIds,
1821}
1822
1823impl<'a> GearMenu<'a> {
1824 #[expect(clippy::too_many_arguments)]
1827 pub fn new(
1828 tab_package: &'a TabPackage,
1829 inventory: &'a Inventory,
1830 tooltip: &'a Tooltip<'a>,
1831 bag_tooltip: &'a Tooltip<'a>,
1832 item_tooltip: &'a ItemTooltip<'a>,
1833 tooltip_manager: &'a mut TooltipManager,
1834 item_tooltip_manager: &'a mut ItemTooltipManager,
1835 slot_manager: &'a mut SlotManager,
1836 stats: &'a Stats,
1837 skill_set: &'a SkillSet,
1838 health: &'a Health,
1839 energy: &'a Energy,
1840 show: &'a Show,
1841 body: &'a Body,
1842 msm: &'a MaterialStatManifest,
1843 poise: &'a Poise,
1844 menu_events: &'a Vec<MenuInput>,
1845 bag_ids: &'a BackgroundIds,
1846 ) -> Self {
1847 Self {
1848 tab_package,
1849 inventory,
1850 tooltip,
1851 bag_tooltip,
1852 item_tooltip,
1853 common: widget::CommonBuilder::default(),
1854 tooltip_manager,
1855 item_tooltip_manager,
1856 slot_manager,
1857 stats,
1858 skill_set,
1859 health,
1860 energy,
1861 show,
1862 body,
1863 msm,
1864 poise,
1865 menu_events,
1866 bag_ids,
1867 }
1868 }
1869}
1870
1871impl Widget for GearMenu<'_> {
1872 type Event = Vec<GearMenuEvent>;
1873 type State = GearMenuState;
1874 type Style = ();
1875
1876 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
1877 GearMenuState {
1878 ids: GearMenuIds::new(id_gen),
1879 is_focused: false,
1880 active_gear_slot: 2,
1881 }
1882 }
1883
1884 fn style(&self) -> Self::Style {}
1885
1886 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
1887 common_base::prof_span!("GearMenu::update");
1888 let widget::UpdateArgs { state, ui, .. } = args;
1889 let i18n = &self.tab_package.localized_strings;
1890 let mut events = Vec::new();
1891
1892 if state.is_focused {
1900 for event in self.menu_events {
1901 match *event {
1902 MenuInput::Up => state.update(|s| {
1903 match s.active_gear_slot {
1905 0 => {},
1907 1 => s.active_gear_slot = 5,
1909 2 => s.active_gear_slot = 6,
1911 3 => s.active_gear_slot = 6,
1913 4 => s.active_gear_slot = 7,
1915 5 => s.active_gear_slot = 8,
1917 6 => s.active_gear_slot = 9,
1919 7 => s.active_gear_slot = 10,
1921 8 => s.active_gear_slot = 11,
1923 9 => s.active_gear_slot = 12,
1925 10 => s.active_gear_slot = 13,
1927 11 => s.active_gear_slot = 14,
1929 12 => s.active_gear_slot = 14,
1931 13 => s.active_gear_slot = 14,
1933 14 => s.active_gear_slot = 15,
1935 15 => {},
1937 16 => s.active_gear_slot = 17,
1939 17 => s.active_gear_slot = 18,
1941 18 => {},
1943 19 => s.active_gear_slot = 16,
1945 20 => s.active_gear_slot = 19,
1947 21 => s.active_gear_slot = 20,
1949 22 => s.active_gear_slot = 21,
1951 _ => s.active_gear_slot = 1,
1953 }
1954 }),
1955 MenuInput::Down => state.update(|s| {
1956 match s.active_gear_slot {
1957 0 => s.is_focused = false,
1959 1 => s.is_focused = false,
1961 2 => s.is_focused = false,
1963 3 => s.is_focused = false,
1965 4 => s.is_focused = false,
1967 5 => s.active_gear_slot = 1,
1969 6 => s.active_gear_slot = 2,
1971 7 => s.active_gear_slot = 4,
1973 8 => s.active_gear_slot = 5,
1975 9 => s.active_gear_slot = 6,
1977 10 => s.active_gear_slot = 7,
1979 11 => s.active_gear_slot = 8,
1981 12 => s.active_gear_slot = 9,
1983 13 => s.active_gear_slot = 10,
1985 14 => s.active_gear_slot = 12,
1987 15 => s.active_gear_slot = 14,
1989 16 => s.active_gear_slot = 19,
1991 17 => s.active_gear_slot = 16,
1993 18 => s.active_gear_slot = 17,
1995 19 => s.active_gear_slot = 20,
1997 20 => s.active_gear_slot = 21,
1999 21 => s.active_gear_slot = 22,
2001 22 => s.is_focused = false,
2003 _ => s.active_gear_slot = 1,
2005 }
2006 }),
2007 MenuInput::Left => state.update(|s| {
2008 match s.active_gear_slot {
2009 0 => {},
2011 1 => s.active_gear_slot = 0 + 1,
2013 2 => s.active_gear_slot = 1,
2015 3 => s.active_gear_slot = 2,
2017 4 => s.active_gear_slot = 3,
2019 5 => {},
2021 6 => s.active_gear_slot = 5,
2023 7 => s.active_gear_slot = 6,
2025 8 => {},
2027 9 => s.active_gear_slot = 8,
2029 10 => s.active_gear_slot = 9,
2031 11 => {},
2033 12 => s.active_gear_slot = 11,
2035 13 => s.active_gear_slot = 12,
2037 14 => {},
2039 15 => {},
2041 16 => s.active_gear_slot = 13,
2043 17 => s.active_gear_slot = 14,
2045 18 => s.active_gear_slot = 15,
2047 19 => s.active_gear_slot = 13,
2049 20 => s.active_gear_slot = 10,
2051 21 => s.active_gear_slot = 7,
2053 22 => s.active_gear_slot = 4,
2055 _ => s.active_gear_slot = 1,
2057 }
2058 }),
2059 MenuInput::Right => state.update(|s| {
2060 match s.active_gear_slot {
2061 0 => s.active_gear_slot = 1,
2063 1 => s.active_gear_slot = 2,
2065 2 => s.active_gear_slot = 3,
2067 3 => s.active_gear_slot = 4,
2069 4 => s.active_gear_slot = 22,
2071 5 => s.active_gear_slot = 6,
2073 6 => s.active_gear_slot = 7,
2075 7 => s.active_gear_slot = 21,
2077 8 => s.active_gear_slot = 9,
2079 9 => s.active_gear_slot = 10,
2081 10 => s.active_gear_slot = 20,
2083 11 => s.active_gear_slot = 12,
2085 12 => s.active_gear_slot = 13,
2087 13 => s.active_gear_slot = 19,
2089 14 => s.active_gear_slot = 17,
2091 15 => s.active_gear_slot = 18,
2093 16 => {},
2095 17 => {},
2097 18 => {},
2099 19 => {},
2101 20 => {},
2103 21 => {},
2105 22 => {},
2107 _ => s.active_gear_slot = 1,
2109 }
2110 }),
2111 MenuInput::Apply => {
2112 },
2114 MenuInput::Back => events.push(GearMenuEvent::Close),
2115 _ => {},
2116 }
2117 }
2118 }
2119
2120 let grid_width = 376.0;
2122 let grid_height = 200.0;
2123
2124 Rectangle::fill_with([grid_width, grid_height], color::TRANSPARENT)
2125 .mid_bottom_with_margin_on(self.bag_ids.bg_frame, 3.5)
2126 .scroll_kids_vertically()
2127 .set(state.ids.inv_alignment, ui);
2128
2129 if !self.show.bag_menu_split || self.show.trade {
2130 Rectangle::fill_with([grid_width, 5.0], color::TRANSPARENT)
2132 .mid_top_of(state.ids.inv_alignment)
2133 .set(state.ids.spacing_above, ui);
2134
2135 let mut space_max = 0;
2136 let compact_mode = self
2137 .tab_package
2138 .global_state
2139 .settings
2140 .interface
2141 .toggle_compact_item_slots;
2142 let columns = if compact_mode { 9 } else { 6 };
2143 let spacing = if compact_mode { 2.0 } else { 5.8 };
2144 let slot_size = if compact_mode { 40.0 } else { 57.8 };
2145 let scroll_size = if compact_mode { 36 } else { 24 };
2146
2147 for event in SlotGrid::new(
2149 self.tab_package.client,
2150 self.tab_package.global_state,
2151 self.tab_package.imgs,
2152 self.tab_package.item_imgs,
2153 self.tab_package.fonts,
2154 self.item_tooltip_manager,
2155 self.slot_manager,
2156 self.inventory,
2157 self.item_tooltip,
2158 self.tab_package.localized_strings,
2159 self.tab_package.item_i18n,
2160 self.tab_package.info.viewpoint_entity,
2161 self.tab_package.pulse,
2162 self.menu_events,
2163 true, self.show.bag_details, self.show.crafting_fields.salvage,
2166 )
2167 .columns(columns)
2168 .spacing(if self.show.bag_details { 0.0 } else { spacing })
2169 .is_focused(!state.is_focused)
2171 .slot_size(if self.show.bag_details { 20.0 } else { slot_size })
2172 .filter(TabFilters::Gear)
2173 .w_of(state.ids.inv_alignment)
2174 .down_from(state.ids.spacing_above, 0.0)
2175 .set(state.ids.slot_grid, ui)
2176 {
2177 match event {
2178 SlotEvents::Close => events.push(GearMenuEvent::Close),
2179 SlotEvents::ExitUp => {
2180 state.update(|s| {
2183 s.is_focused = true;
2184 })
2185 },
2186 SlotEvents::FilteredSize(size) => {
2187 space_max = size;
2188 },
2189 _ => {},
2190 }
2191 }
2192
2193 if space_max > scroll_size {
2195 Image::new(self.tab_package.imgs.scrollbar_bg)
2197 .w_h(9.0, 180.0)
2198 .bottom_right_with_margins_on(self.bag_ids.bg_frame, 24.0, 3.0)
2199 .color(Some(UI_HIGHLIGHT_0))
2200 .set(state.ids.scrollbar_bg, ui);
2201 Scrollbar::y_axis(state.ids.inv_alignment)
2203 .thickness(6.0)
2204 .h(133.0)
2205 .color(UI_MAIN)
2206 .middle_of(state.ids.scrollbar_bg)
2207 .set(state.ids.scrollbar_slots, ui);
2208 };
2209 }
2210
2211 Rectangle::fill_with([grid_width, 1.5], Color::Rgba(0.314, 0.443, 0.443, 1.0))
2213 .up_from(state.ids.inv_alignment, 0.0)
2214 .set(state.ids.scroll_divider, ui);
2215
2216 let mut slot_maker = SlotMaker {
2218 empty_slot: self.tab_package.imgs.armor_slot_empty,
2219 hovered_slot: self.tab_package.imgs.skillbar_index,
2220 filled_slot: self.tab_package.imgs.armor_slot,
2221 selected_slot: self.tab_package.imgs.armor_slot_sel,
2222 background_color: Some(UI_HIGHLIGHT_0),
2223 content_size: ContentSize {
2224 width_height_ratio: 1.0,
2225 max_fraction: 0.75, },
2229 selected_content_scale: 1.067,
2230 amount_font: self.tab_package.fonts.cyri.conrod_id,
2231 amount_margins: Vec2::new(-4.0, 0.0),
2232 amount_font_size: self.tab_package.fonts.cyri.scale(12),
2233 amount_text_color: TEXT_COLOR,
2234 content_source: self.inventory,
2235 image_source: self.tab_package.item_imgs,
2236 slot_manager: Some(self.slot_manager),
2237 global_state: self.tab_package.global_state,
2238 pulse: self.tab_package.pulse,
2239 };
2240
2241 macro_rules! set_tooltip {
2262 ($slot_maker:expr, $slot_id:expr, $slot:expr, $desc:expr) => {
2263 if let Some(item) = self.inventory.equipped($slot) {
2264 let manager = &mut *self.item_tooltip_manager;
2265 $slot_maker
2266 .with_item_tooltip(
2267 manager,
2268 core::iter::once(item as &dyn ItemDesc),
2269 &None,
2270 self.item_tooltip,
2271 )
2272 .set($slot_id, ui)
2273 } else {
2274 let manager = &mut *self.tooltip_manager;
2275 $slot_maker
2276 .with_tooltip(
2277 manager,
2278 &i18n.get_msg($desc),
2279 "",
2280 self.tooltip,
2281 color::WHITE,
2282 )
2283 .set($slot_id, ui)
2284 }
2285 };
2286 }
2287
2288 let filled_slot = self.tab_package.imgs.armor_slot;
2289 state.update(|s| {
2291 s.ids
2292 .stat_icons
2293 .resize(STATS.len(), &mut ui.widget_id_generator())
2294 });
2295 state.update(|s| {
2296 s.ids
2297 .stat_txts
2298 .resize(STATS.len(), &mut ui.widget_id_generator())
2299 });
2300 let block_gear_tab = self.show.bag_menu_split && !self.show.trade;
2302 let combat_rating = combat_rating(
2303 self.inventory,
2304 self.health,
2305 self.energy,
2306 self.poise,
2307 self.skill_set,
2308 *self.body,
2309 self.msm,
2310 )
2311 .min(999.9);
2312 let indicator_col = cr_color(combat_rating);
2313 let img_size = if block_gear_tab { 25.0 } else { 20.0 };
2314 for i in STATS.iter().copied().enumerate() {
2315 let btn = Button::image(match i.1 {
2316 "Health" => self.tab_package.imgs.health_ico,
2317 "Energy" => self.tab_package.imgs.energy_ico,
2318 "Combat Rating" => self.tab_package.imgs.combat_rating_ico,
2319 "Protection" => self.tab_package.imgs.protection_ico,
2320 "Stun Resilience" => self.tab_package.imgs.stun_res_ico,
2321 "Stealth" => self.tab_package.imgs.stealth_rating_ico,
2322 _ => self.tab_package.imgs.nothing,
2323 })
2324 .w_h(img_size, img_size)
2325 .image_color(if i.1 == "Combat Rating" {
2326 indicator_col
2327 } else {
2328 TEXT_COLOR
2329 });
2330 let protection_txt = format!(
2331 "{}%",
2332 (100.0
2333 * Damage::compute_damage_reduction(
2334 None,
2335 Some(self.inventory),
2336 Some(self.stats),
2337 self.msm
2338 )) as i32
2339 );
2340 let health_txt = format!("{}", self.health.maximum().round() as usize);
2341 let energy_txt = format!("{}", self.energy.maximum().round() as usize);
2342 let combat_rating_txt = format!("{}", (combat_rating * 10.0) as usize);
2343 let stun_res_txt = format!(
2344 "{}",
2345 (100.0
2346 * Poise::compute_poise_damage_reduction(
2347 Some(self.inventory),
2348 self.msm,
2349 None,
2350 Some(self.stats),
2351 )) as i32
2352 );
2353 let stealth_txt = format!(
2354 "{:.1}%",
2355 ((1.0
2356 - perception_dist_multiplier_from_stealth(
2357 Some(self.inventory),
2358 None,
2359 self.msm
2360 ))
2361 * 100.0)
2362 );
2363 let btn = if i.0 == 0 {
2364 if !self.show.bag_menu_split || self.show.trade {
2365 btn.top_left_with_margins_on(self.bag_ids.bg_frame, 95.0, 10.0)
2367 } else {
2368 btn.top_left_with_margins_on(state.ids.inv_alignment, 10.0, 25.0)
2370 }
2371 } else {
2372 if !self.show.bag_menu_split || self.show.trade {
2373 btn.down_from(state.ids.stat_icons[i.0 - 1], 7.0)
2375 } else {
2376 if i.0 % 2 != 0 {
2378 btn.down_from(state.ids.stat_icons[i.0 - 1], 15.0)
2380 } else {
2381 btn.right_from(state.ids.stat_icons[i.0 - 2], 90.0)
2383 }
2384 }
2385 };
2386 let tooltip_head = match i.1 {
2387 "Health" => i18n.get_msg("hud-bag-health"),
2388 "Energy" => i18n.get_msg("hud-bag-energy"),
2389 "Combat Rating" => i18n.get_msg("hud-bag-combat_rating"),
2390 "Protection" => i18n.get_msg("hud-bag-protection"),
2391 "Stun Resilience" => i18n.get_msg("hud-bag-stun_res"),
2392 "Stealth" => i18n.get_msg("hud-bag-stealth"),
2393 _ => Cow::Borrowed(""),
2394 };
2395 let tooltip_txt = match i.1 {
2396 "Combat Rating" => i18n.get_msg("hud-bag-combat_rating_desc"),
2397 "Protection" => i18n.get_msg("hud-bag-protection_desc"),
2398 "Stun Resilience" => i18n.get_msg("hud-bag-stun_res_desc"),
2399 _ => Cow::Borrowed(""),
2400 };
2401 btn.with_tooltip(
2402 self.tooltip_manager,
2403 &tooltip_head,
2404 &tooltip_txt,
2405 self.bag_tooltip,
2406 TEXT_COLOR,
2407 )
2408 .set(state.ids.stat_icons[i.0], ui);
2409 Text::new(match i.1 {
2410 "Health" => &health_txt,
2411 "Energy" => &energy_txt,
2412 "Combat Rating" => &combat_rating_txt,
2413 "Protection" => &protection_txt,
2414 "Stun Resilience" => &stun_res_txt,
2415 "Stealth" => &stealth_txt,
2416 _ => "",
2417 })
2418 .right_from(state.ids.stat_icons[i.0], 10.0)
2419 .font_id(self.tab_package.fonts.cyri.conrod_id)
2420 .font_size(if block_gear_tab {
2421 self.tab_package.fonts.cyri.scale(20)
2422 } else {
2423 self.tab_package.fonts.cyri.scale(14)
2424 })
2425 .color(TEXT_COLOR)
2426 .graphics_for(state.ids.stat_icons[i.0])
2427 .set(state.ids.stat_txts[i.0], ui);
2428 }
2429 let item_slot = EquipSlot::Armor(ArmorSlot::Head);
2432 let slot = slot_maker
2433 .fabricate(
2434 item_slot,
2435 [40.0; 2],
2436 state.active_gear_slot == 15 && state.is_focused,
2437 false,
2438 )
2439 .mid_top_with_margin_on(self.bag_ids.bg_frame, 100.0)
2440 .with_icon(
2441 self.tab_package.imgs.head_bg,
2442 Vec2::new(32.0, 40.0),
2443 Some(UI_MAIN),
2444 )
2445 .filled_slot(filled_slot);
2446
2447 let slot_id = state.ids.head_slot;
2448 set_tooltip!(slot, slot_id, item_slot, "hud-bag-head");
2449
2450 let item_slot = EquipSlot::Armor(ArmorSlot::Neck);
2452 let slot = slot_maker
2453 .fabricate(
2454 item_slot,
2455 [40.0; 2],
2456 state.active_gear_slot == 14 && state.is_focused,
2457 false,
2458 )
2459 .mid_bottom_with_margin_on(state.ids.head_slot, -50.0)
2460 .with_icon(
2461 self.tab_package.imgs.necklace_bg,
2462 Vec2::new(40.0, 31.0),
2463 Some(UI_MAIN),
2464 )
2465 .filled_slot(filled_slot);
2466
2467 let slot_id = state.ids.neck_slot;
2468 set_tooltip!(slot, slot_id, item_slot, "hud-bag-neck");
2469
2470 let item_slot = EquipSlot::Armor(ArmorSlot::Chest);
2473 let slot = slot_maker
2474 .fabricate(
2475 item_slot,
2476 [80.0; 2],
2477 state.active_gear_slot == 12 && state.is_focused,
2478 false,
2479 )
2480 .mid_bottom_with_margin_on(state.ids.neck_slot, -90.0)
2481 .with_icon(
2482 self.tab_package.imgs.chest_bg,
2483 Vec2::new(64.0, 42.0),
2484 Some(UI_MAIN),
2485 )
2486 .filled_slot(filled_slot);
2487
2488 let slot_id = state.ids.chest_slot;
2489 set_tooltip!(slot, slot_id, item_slot, "hud-bag-chest");
2490
2491 let item_slot = EquipSlot::Armor(ArmorSlot::Shoulders);
2493 let slot = slot_maker
2494 .fabricate(
2495 item_slot,
2496 [65.0; 2],
2497 state.active_gear_slot == 11 && state.is_focused,
2498 false,
2499 )
2500 .bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0)
2501 .with_icon(
2502 self.tab_package.imgs.shoulders_bg,
2503 Vec2::new(60.0, 36.0),
2504 Some(UI_MAIN),
2505 )
2506 .filled_slot(filled_slot);
2507
2508 let slot_id = state.ids.shoulders_slot;
2509 set_tooltip!(slot, slot_id, item_slot, "hud-bag-shoulders");
2510
2511 let item_slot = EquipSlot::Armor(ArmorSlot::Hands);
2513 let slot = slot_maker
2514 .fabricate(
2515 item_slot,
2516 [65.0; 2],
2517 state.active_gear_slot == 13 && state.is_focused,
2518 false,
2519 )
2520 .bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0)
2521 .with_icon(
2522 self.tab_package.imgs.hands_bg,
2523 Vec2::new(55.0, 60.0),
2524 Some(UI_MAIN),
2525 )
2526 .filled_slot(filled_slot);
2527
2528 let slot_id = state.ids.hands_slot;
2529 set_tooltip!(slot, slot_id, item_slot, "hud-bag-hands");
2530
2531 let item_slot = EquipSlot::Armor(ArmorSlot::Belt);
2533 let slot = slot_maker
2534 .fabricate(
2535 item_slot,
2536 [40.0; 2],
2537 state.active_gear_slot == 9 && state.is_focused,
2538 false,
2539 )
2540 .mid_bottom_with_margin_on(state.ids.chest_slot, -50.0)
2541 .with_icon(
2542 self.tab_package.imgs.belt_bg,
2543 Vec2::new(40.0, 23.0),
2544 Some(UI_MAIN),
2545 )
2546 .filled_slot(filled_slot);
2547
2548 let slot_id = state.ids.belt_slot;
2549 set_tooltip!(slot, slot_id, item_slot, "hud-bag-belt");
2550
2551 let item_slot = EquipSlot::Armor(ArmorSlot::Legs);
2553 let slot = slot_maker
2554 .fabricate(
2555 item_slot,
2556 [80.0; 2],
2557 state.active_gear_slot == 6 && state.is_focused,
2558 false,
2559 )
2560 .mid_bottom_with_margin_on(state.ids.belt_slot, -90.0)
2561 .with_icon(
2562 self.tab_package.imgs.legs_bg,
2563 Vec2::new(48.0, 70.0),
2564 Some(UI_MAIN),
2565 )
2566 .filled_slot(filled_slot);
2567
2568 let slot_id = state.ids.legs_slot;
2569 set_tooltip!(slot, slot_id, item_slot, "hud-bag-legs");
2570
2571 let item_slot = EquipSlot::Armor(ArmorSlot::Ring1);
2573 let slot = slot_maker
2574 .fabricate(
2575 item_slot,
2576 [40.0; 2],
2577 state.active_gear_slot == 10 && state.is_focused,
2578 false,
2579 )
2580 .bottom_left_with_margins_on(state.ids.hands_slot, -50.0, 0.0)
2581 .with_icon(
2582 self.tab_package.imgs.ring_bg,
2583 Vec2::new(36.0, 40.0),
2584 Some(UI_MAIN),
2585 )
2586 .filled_slot(filled_slot);
2587
2588 let slot_id = state.ids.ring1_slot;
2589 set_tooltip!(slot, slot_id, item_slot, "hud-bag-ring");
2590
2591 let item_slot = EquipSlot::Armor(ArmorSlot::Ring2);
2593 let slot = slot_maker
2594 .fabricate(
2595 item_slot,
2596 [40.0; 2],
2597 state.active_gear_slot == 8 && state.is_focused,
2598 false,
2599 )
2600 .bottom_right_with_margins_on(state.ids.shoulders_slot, -50.0, 0.0)
2601 .with_icon(
2602 self.tab_package.imgs.ring_bg,
2603 Vec2::new(36.0, 40.0),
2604 Some(UI_MAIN),
2605 )
2606 .filled_slot(filled_slot);
2607
2608 let slot_id = state.ids.ring2_slot;
2609 set_tooltip!(slot, slot_id, item_slot, "hud-bag-ring");
2610
2611 let item_slot = EquipSlot::Armor(ArmorSlot::Back);
2613 let slot = slot_maker
2614 .fabricate(
2615 item_slot,
2616 [40.0; 2],
2617 state.active_gear_slot == 5 && state.is_focused,
2618 false,
2619 )
2620 .down_from(state.ids.ring2_slot, 10.0)
2621 .with_icon(
2622 self.tab_package.imgs.back_bg,
2623 Vec2::new(33.0, 40.0),
2624 Some(UI_MAIN),
2625 )
2626 .filled_slot(filled_slot);
2627
2628 let slot_id = state.ids.back_slot;
2629 set_tooltip!(slot, slot_id, item_slot, "hud-bag-back");
2630
2631 let item_slot = EquipSlot::Armor(ArmorSlot::Feet);
2633 let slot = slot_maker
2634 .fabricate(
2635 item_slot,
2636 [40.0; 2],
2637 state.active_gear_slot == 7 && state.is_focused,
2638 false,
2639 )
2640 .down_from(state.ids.ring1_slot, 10.0)
2641 .with_icon(
2642 self.tab_package.imgs.feet_bg,
2643 Vec2::new(32.0, 40.0),
2644 Some(UI_MAIN),
2645 )
2646 .filled_slot(filled_slot);
2647
2648 let slot_id = state.ids.feet_slot;
2649 set_tooltip!(slot, slot_id, item_slot, "hud-bag-feet");
2650
2651 let item_slot = EquipSlot::Lantern;
2653 let slot = slot_maker
2654 .fabricate(
2655 item_slot,
2656 [40.0; 2],
2657 state.active_gear_slot == 18 && state.is_focused,
2658 false,
2659 )
2660 .top_right_with_margins_on(self.bag_ids.bg_frame, 100.0, 5.0)
2661 .with_icon(
2662 self.tab_package.imgs.lantern_bg,
2663 Vec2::new(24.0, 38.0),
2664 Some(UI_MAIN),
2665 )
2666 .filled_slot(filled_slot);
2667
2668 let slot_id = state.ids.lantern_slot;
2669 set_tooltip!(slot, slot_id, item_slot, "hud-bag-lantern");
2670
2671 let item_slot = EquipSlot::Glider;
2673 let slot = slot_maker
2674 .fabricate(
2675 item_slot,
2676 [40.0; 2],
2677 state.active_gear_slot == 17 && state.is_focused,
2678 false,
2679 )
2680 .down_from(state.ids.lantern_slot, 5.0)
2681 .with_icon(
2682 self.tab_package.imgs.glider_bg,
2683 Vec2::new(38.0, 38.0),
2684 Some(UI_MAIN),
2685 )
2686 .filled_slot(filled_slot);
2687
2688 let slot_id = state.ids.glider_slot;
2689 set_tooltip!(slot, slot_id, item_slot, "hud-bag-glider");
2690
2691 let item_slot = EquipSlot::Armor(ArmorSlot::Tabard);
2693 let slot = slot_maker
2694 .fabricate(
2695 item_slot,
2696 [40.0; 2],
2697 state.active_gear_slot == 16 && state.is_focused,
2698 false,
2699 )
2700 .down_from(state.ids.glider_slot, 5.0)
2701 .with_icon(
2702 self.tab_package.imgs.tabard_bg,
2703 Vec2::new(38.0, 38.0),
2704 Some(UI_MAIN),
2705 )
2706 .filled_slot(filled_slot);
2707
2708 let slot_id = state.ids.tabard_slot;
2709 set_tooltip!(slot, slot_id, item_slot, "hud-bag-tabard");
2710
2711 let item_slot = EquipSlot::ActiveMainhand;
2713 let slot = slot_maker
2714 .fabricate(
2715 item_slot,
2716 [80.0; 2],
2717 state.active_gear_slot == 1 && state.is_focused,
2718 false,
2719 )
2720 .bottom_right_with_margins_on(state.ids.back_slot, -90.0, 0.0)
2721 .with_icon(
2722 self.tab_package.imgs.mainhand_bg,
2723 Vec2::new(75.0, 75.0),
2724 Some(UI_MAIN),
2725 )
2726 .filled_slot(filled_slot);
2727
2728 let slot_id = state.ids.active_mainhand_slot;
2729 set_tooltip!(slot, slot_id, item_slot, "hud-bag-mainhand");
2730
2731 let item_slot = EquipSlot::ActiveOffhand;
2733 let slot = slot_maker
2734 .fabricate(
2735 item_slot,
2736 [80.0; 2],
2737 state.active_gear_slot == 4 && state.is_focused,
2738 false,
2739 )
2740 .bottom_left_with_margins_on(state.ids.feet_slot, -90.0, 0.0)
2741 .with_icon(
2742 self.tab_package.imgs.offhand_bg,
2743 Vec2::new(75.0, 75.0),
2744 Some(UI_MAIN),
2745 )
2746 .filled_slot(filled_slot);
2747
2748 let slot_id = state.ids.active_offhand_slot;
2749 set_tooltip!(slot, slot_id, item_slot, "hud-bag-offhand");
2750
2751 let item_slot = EquipSlot::InactiveMainhand;
2753 let slot = slot_maker
2754 .fabricate(
2755 item_slot,
2756 [35.0; 2],
2757 state.active_gear_slot == 2 && state.is_focused,
2758 false,
2759 )
2760 .bottom_right_with_margins_on(state.ids.active_mainhand_slot, 0.0, -47.0)
2761 .with_icon(
2762 self.tab_package.imgs.mainhand_bg,
2763 Vec2::new(35.0, 35.0),
2764 Some(UI_MAIN),
2765 )
2766 .filled_slot(filled_slot);
2767
2768 let slot_id = state.ids.inactive_mainhand_slot;
2769 set_tooltip!(slot, slot_id, item_slot, "hud-bag-inactive_mainhand");
2770
2771 let item_slot = EquipSlot::InactiveOffhand;
2773 let slot = slot_maker
2774 .fabricate(
2775 item_slot,
2776 [35.0; 2],
2777 state.active_gear_slot == 3 && state.is_focused,
2778 false,
2779 )
2780 .bottom_left_with_margins_on(state.ids.active_offhand_slot, 0.0, -47.0)
2781 .with_icon(
2782 self.tab_package.imgs.offhand_bg,
2783 Vec2::new(35.0, 35.0),
2784 Some(UI_MAIN),
2785 )
2786 .filled_slot(filled_slot);
2787
2788 let slot_id = state.ids.inactive_offhand_slot;
2789 set_tooltip!(slot, slot_id, item_slot, "hud-bag-inactive_offhand");
2790
2791 if Button::image(self.tab_package.imgs.swap_equipped_weapons_btn)
2792 .hover_image(self.tab_package.imgs.swap_equipped_weapons_btn_hover)
2793 .press_image(self.tab_package.imgs.swap_equipped_weapons_btn_press)
2794 .w_h(32.0, 40.0)
2795 .bottom_left_with_margins_on(self.bag_ids.bg_frame, 0.0, 23.3)
2796 .align_middle_y_of(state.ids.active_mainhand_slot)
2797 .with_tooltip(
2798 self.tooltip_manager,
2799 &i18n.get_msg("hud-bag-swap_equipped_weapons_title"),
2800 &(if let Some(key) = self
2801 .tab_package
2802 .global_state
2803 .settings
2804 .controls
2805 .get_binding(GameInput::SwapLoadout)
2806 {
2807 i18n.get_msg_ctx("hud-bag-swap_equipped_weapons_desc", &i18n::fluent_args! {
2808 "key" => key.display_string()
2809 })
2810 } else {
2811 Cow::Borrowed("")
2812 }),
2813 self.tooltip,
2814 color::WHITE,
2815 )
2816 .set(state.ids.swap_equipped_weapons_btn, ui)
2817 .was_clicked()
2818 {
2819 events.push(GearMenuEvent::SwapEquippedWeapons);
2820 }
2821
2822 let item_slot = EquipSlot::Armor(ArmorSlot::Bag1);
2824 let slot = slot_maker
2825 .fabricate(
2826 item_slot,
2827 [40.0; 2],
2828 state.active_gear_slot == 19 && state.is_focused,
2829 false,
2830 )
2831 .down_from(state.ids.tabard_slot, 25.0)
2832 .with_icon(
2833 self.tab_package.imgs.bag_bg,
2834 Vec2::new(28.0, 24.0),
2835 Some(UI_MAIN),
2836 )
2837 .filled_slot(filled_slot);
2838
2839 let slot_id = state.ids.bag1_slot;
2840 set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
2841
2842 let item_slot = EquipSlot::Armor(ArmorSlot::Bag2);
2844 let slot = slot_maker
2845 .fabricate(
2846 item_slot,
2847 [40.0; 2],
2848 state.active_gear_slot == 20 && state.is_focused,
2849 false,
2850 )
2851 .down_from(state.ids.bag1_slot, 5.0)
2852 .with_icon(
2853 self.tab_package.imgs.bag_bg,
2854 Vec2::new(28.0, 24.0),
2855 Some(UI_MAIN),
2856 )
2857 .filled_slot(filled_slot);
2858
2859 let slot_id = state.ids.bag2_slot;
2860 set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
2861
2862 let item_slot = EquipSlot::Armor(ArmorSlot::Bag3);
2864 let slot = slot_maker
2865 .fabricate(
2866 item_slot,
2867 [40.0; 2],
2868 state.active_gear_slot == 21 && state.is_focused,
2869 false,
2870 )
2871 .down_from(state.ids.bag2_slot, 5.0)
2872 .with_icon(
2873 self.tab_package.imgs.bag_bg,
2874 Vec2::new(28.0, 24.0),
2875 Some(UI_MAIN),
2876 )
2877 .filled_slot(filled_slot);
2878
2879 let slot_id = state.ids.bag3_slot;
2880 set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
2881
2882 let item_slot = EquipSlot::Armor(ArmorSlot::Bag4);
2884 let slot = slot_maker
2885 .fabricate(
2886 item_slot,
2887 [40.0; 2],
2888 state.active_gear_slot == 22 && state.is_focused,
2889 false,
2890 )
2891 .down_from(state.ids.bag3_slot, 2.0)
2892 .with_icon(
2893 self.tab_package.imgs.bag_bg,
2894 Vec2::new(28.0, 24.0),
2895 Some(UI_MAIN),
2896 )
2897 .filled_slot(filled_slot);
2898
2899 let slot_id = state.ids.bag4_slot;
2900 set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
2901
2902 events
2903 }
2904}