1use super::{
2 CRITICAL_HP_COLOR, HudInfo, LOW_HP_COLOR, Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN, cr_color,
3 img_ids::{Imgs, ImgsRot},
4 item_imgs::ItemImgs,
5 slots::{ArmorSlot, EquipSlot, InventorySlot, SlotManager},
6 util,
7};
8use crate::{
9 GlobalState,
10 game_input::GameInput,
11 settings::{
12 HudPositionSettings,
13 hud_position::{
14 DEFAULT_OTHER_BAG_HEIGHT, DEFAULT_OTHER_BAG_WIDTH, DEFAULT_OWN_BAG_HEIGHT,
15 DEFAULT_OWN_BAG_WIDTH,
16 },
17 },
18 ui::{
19 ImageFrame, ItemTooltip, ItemTooltipManager, ItemTooltipable, Tooltip, TooltipManager,
20 Tooltipable,
21 fonts::Fonts,
22 slot::{ContentSize, SlotMaker},
23 },
24};
25use client::Client;
26use common::{
27 assets::AssetExt,
28 combat::{Damage, combat_rating, perception_dist_multiplier_from_stealth},
29 comp::{
30 Body, Energy, Health, Inventory, Poise, SkillSet, Stats,
31 inventory::{InventorySortOrder, slot::Slot},
32 item::{ItemDef, ItemDesc, ItemI18n, MaterialStatManifest, Quality},
33 },
34 recipe::RecipeBookManifest,
35};
36use conrod_core::{
37 Color, Colorable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, color,
38 widget::{self, Button, Image, Rectangle, Scrollbar, State as ConrodState, Text},
39 widget_ids,
40};
41use i18n::Localization;
42use std::borrow::Cow;
43
44use crate::hud::slots::SlotKind;
45use specs::Entity as EcsEntity;
46use std::{borrow::Borrow, sync::Arc};
47use vek::{Vec2, approx::AbsDiffEq};
48
49widget_ids! {
50 pub struct InventoryScrollerIds {
51 draggable_area,
52 test,
53 bag_close,
54 inv_alignment,
55 inv_grid_1,
56 inv_grid_2,
57 inv_scrollbar,
58 inv_slots_0,
59 inv_slots[],
60 inv_slot_names[],
61 inv_slot_amounts[],
62 bg,
63 bg_frame,
64 char_ico,
65 coin_ico,
66 space_txt,
67 coin_txt,
68 inventory_title,
69 inventory_title_bg,
70 scrollbar_bg,
71 second_phase_scrollbar_bg,
72 scrollbar_slots,
73 left_scrollbar_slots,
74 }
75}
76
77pub struct InventoryScrollerState {
78 ids: InventoryScrollerIds,
79}
80
81pub enum InventoryScrollerEvent {
82 Drag(Vec2<f64>),
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 localized_strings: &'a Localization,
97 item_i18n: &'a ItemI18n,
98 show_stats: bool,
99 show_bag_inv: bool,
100 on_right: bool,
101 global_state: &'a GlobalState,
102 item_tooltip: &'a ItemTooltip<'a>,
103 playername: String,
104 entity: EcsEntity,
105 is_us: bool,
106 inventory: &'a Inventory,
107 bg_ids: &'a BackgroundIds,
108 show_salvage: bool,
109 details_mode: bool,
110}
111
112impl<'a> InventoryScroller<'a> {
113 #[expect(clippy::too_many_arguments)]
114 pub fn new(
115 client: &'a Client,
116 global_state: &'a GlobalState,
117 imgs: &'a Imgs,
118 item_imgs: &'a ItemImgs,
119 fonts: &'a Fonts,
120 item_tooltip_manager: &'a mut ItemTooltipManager,
121 slot_manager: &'a mut SlotManager,
122 pulse: f32,
123 localized_strings: &'a Localization,
124 item_i18n: &'a ItemI18n,
125 show_stats: bool,
126 show_bag_inv: bool,
127 on_right: bool,
128 item_tooltip: &'a ItemTooltip<'a>,
129 playername: String,
130 entity: EcsEntity,
131 is_us: bool,
132 inventory: &'a Inventory,
133 bg_ids: &'a BackgroundIds,
134 show_salvage: bool,
135 details_mode: bool,
136 ) -> Self {
137 InventoryScroller {
138 client,
139 imgs,
140 item_imgs,
141 fonts,
142 common: widget::CommonBuilder::default(),
143 item_tooltip_manager,
144 slot_manager,
145 pulse,
146 localized_strings,
147 item_i18n,
148 show_stats,
149 show_bag_inv,
150 on_right,
151 global_state,
152 item_tooltip,
153 playername,
154 entity,
155 is_us,
156 inventory,
157 bg_ids,
158 show_salvage,
159 details_mode,
160 }
161 }
162
163 fn background(&mut self, ui: &mut UiCell<'_>) {
164 let bag_settings = &self.global_state.settings.hud_position;
165 let bag_pos = if !self.on_right {
166 bag_settings.bag.other
167 } else {
168 bag_settings.bag.own
169 };
170
171 let bg_id = if !self.on_right {
172 self.imgs.inv_bg_bag
173 } else {
174 self.imgs.player_inv_bg_bag
175 };
176
177 let img_id = if !self.on_right {
178 self.imgs.inv_frame_bag
179 } else {
180 self.imgs.player_inv_frame_bag
181 };
182
183 let mut bg = Image::new(if self.show_stats {
184 self.imgs.inv_bg_stats
185 } else if self.show_bag_inv {
186 bg_id
187 } else {
188 self.imgs.inv_bg_armor
189 })
190 .w_h(
191 424.0,
192 if self.show_bag_inv && !self.on_right {
193 548.0
194 } else {
195 708.0
196 },
197 );
198
199 if self.on_right {
200 bg = bg.bottom_right_with_margins_on(ui.window, bag_pos.y, bag_pos.x);
201 } else {
202 bg = bg.bottom_left_with_margins_on(ui.window, bag_pos.y, bag_pos.x);
203 }
204
205 bg.color(Some(UI_MAIN)).set(self.bg_ids.bg, ui);
206
207 Image::new(if self.show_bag_inv {
208 img_id
209 } else {
210 self.imgs.inv_frame
211 })
212 .w_h(
213 424.0,
214 if self.show_bag_inv && !self.on_right {
215 548.0
216 } else {
217 708.0
218 },
219 )
220 .middle_of(self.bg_ids.bg)
221 .color(Some(UI_HIGHLIGHT_0))
222 .set(self.bg_ids.bg_frame, ui);
223 }
224
225 fn title(&mut self, state: &ConrodState<'_, InventoryScrollerState>, ui: &mut UiCell<'_>) {
226 Text::new(
227 &self
228 .localized_strings
229 .get_msg_ctx("hud-bag-inventory", &i18n::fluent_args! {
230 "playername" => &*self.playername,
231 }),
232 )
233 .mid_top_with_margin_on(self.bg_ids.bg_frame, 9.0)
234 .font_id(self.fonts.cyri.conrod_id)
235 .font_size(self.fonts.cyri.scale(22))
236 .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
237 .set(state.ids.inventory_title_bg, ui);
238 Text::new(
239 &self
240 .localized_strings
241 .get_msg_ctx("hud-bag-inventory", &i18n::fluent_args! {
242 "playername" => &*self.playername,
243 }),
244 )
245 .top_left_with_margins_on(state.ids.inventory_title_bg, 2.0, 2.0)
246 .font_id(self.fonts.cyri.conrod_id)
247 .font_size(self.fonts.cyri.scale(22))
248 .color(TEXT_COLOR)
249 .set(state.ids.inventory_title, ui);
250 }
251
252 fn scrollbar_and_slots(
253 &mut self,
254 state: &mut ConrodState<'_, InventoryScrollerState>,
255 ui: &mut UiCell<'_>,
256 ) {
257 let space_max = self.inventory.slots().count();
258
259 if space_max > 45 && !self.show_bag_inv {
261 Image::new(self.imgs.scrollbar_bg)
263 .w_h(9.0, 173.0)
264 .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
265 .color(Some(UI_HIGHLIGHT_0))
266 .set(state.ids.scrollbar_bg, ui);
267 Scrollbar::y_axis(state.ids.inv_alignment)
269 .thickness(5.0)
270 .h(123.0)
271 .color(UI_MAIN)
272 .middle_of(state.ids.scrollbar_bg)
273 .set(state.ids.scrollbar_slots, ui);
274 } else if space_max > 135 && self.on_right {
275 Image::new(self.imgs.scrollbar_bg_big)
277 .w_h(9.0, 592.0)
278 .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
279 .color(Some(UI_HIGHLIGHT_0))
280 .set(state.ids.scrollbar_bg, ui);
281 Scrollbar::y_axis(state.ids.inv_alignment)
283 .thickness(5.0)
284 .h(542.0)
285 .color(UI_MAIN)
286 .middle_of(state.ids.scrollbar_bg)
287 .set(state.ids.scrollbar_slots, ui);
288 };
289
290 if space_max >= 108 && !self.on_right && self.show_bag_inv {
292 Image::new(self.imgs.second_phase_scrollbar_bg)
294 .w_h(9.0, 434.0)
295 .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
296 .color(Some(UI_HIGHLIGHT_0))
297 .set(state.ids.second_phase_scrollbar_bg, ui);
298 Scrollbar::y_axis(state.ids.inv_alignment)
300 .thickness(5.0)
301 .h(384.0)
302 .color(UI_MAIN)
303 .middle_of(state.ids.second_phase_scrollbar_bg)
304 .set(state.ids.left_scrollbar_slots, ui);
305 }
306
307 let grid_width = 362.0;
308 let grid_height = if self.show_bag_inv && !self.on_right {
309 440.0 } else if self.show_bag_inv && self.on_right {
311 600.0 } else {
313 200.0
314 };
315
316 Rectangle::fill_with([grid_width, grid_height], color::TRANSPARENT)
318 .bottom_left_with_margins_on(
319 self.bg_ids.bg_frame,
320 29.0,
321 if self.show_bag_inv && !self.on_right {
322 28.0
323 } else {
324 46.5
325 },
326 )
327 .scroll_kids_vertically()
328 .set(state.ids.inv_alignment, ui);
329
330 if state.ids.inv_slots.len() < self.inventory.capacity() {
333 state.update(|s| {
334 s.ids.inv_slots.resize(
335 self.inventory.capacity() + self.inventory.overflow_items().count(),
336 &mut ui.widget_id_generator(),
337 );
338 });
339 }
340 if state.ids.inv_slot_names.len() < self.inventory.capacity() {
341 state.update(|s| {
342 s.ids
343 .inv_slot_names
344 .resize(self.inventory.capacity(), &mut ui.widget_id_generator());
345 });
346 }
347 if state.ids.inv_slot_amounts.len() < self.inventory.capacity() {
348 state.update(|s| {
349 s.ids
350 .inv_slot_amounts
351 .resize(self.inventory.capacity(), &mut ui.widget_id_generator());
352 });
353 }
354 let mouseover_loadout_slots = self
357 .slot_manager
358 .mouse_over_slot
359 .and_then(|x| {
360 if let SlotKind::Equip(e) = x {
361 self.inventory.get_slot_range_for_equip_slot(e)
362 } else {
363 None
364 }
365 })
366 .unwrap_or(0usize..0usize);
367
368 let mut slot_maker = SlotMaker {
370 empty_slot: self.imgs.inv_slot,
371 filled_slot: self.imgs.inv_slot,
372 selected_slot: self.imgs.inv_slot_sel,
373 background_color: Some(UI_MAIN),
374 content_size: ContentSize {
375 width_height_ratio: 1.0,
376 max_fraction: 0.75,
377 },
378 selected_content_scale: 1.067,
379 amount_font: self.fonts.cyri.conrod_id,
380 amount_margins: Vec2::new(-4.0, 0.0),
381 amount_font_size: self.fonts.cyri.scale(12),
382 amount_text_color: TEXT_COLOR,
383 content_source: self.inventory,
384 image_source: self.item_imgs,
385 slot_manager: Some(self.slot_manager),
386 pulse: self.pulse,
387 };
388
389 let mut i = 0;
390 let mut items = self
391 .inventory
392 .slots_with_id()
393 .map(|(slot, item)| (Slot::Inventory(slot), item.as_ref()))
394 .chain(
395 self.inventory
396 .overflow_items()
397 .enumerate()
398 .map(|(i, item)| (Slot::Overflow(i), Some(item))),
399 )
400 .collect::<Vec<_>>();
401 if self.details_mode && !self.is_us {
402 items.sort_by_cached_key(|(_, item)| {
403 (
404 item.is_none(),
405 item.as_ref().map(|i| {
406 (
407 std::cmp::Reverse(i.quality()),
408 {
409 let (name, _) =
411 util::item_text(i, self.localized_strings, self.item_i18n);
412 name
413 },
414 i.amount(),
415 )
416 }),
417 )
418 });
419 }
420 for (pos, item) in items.into_iter() {
421 if self.details_mode && !self.is_us && item.is_none() {
422 continue;
423 }
424 let (x, y) = if self.details_mode {
425 (0, i)
426 } else {
427 (i % 9, i / 9)
428 };
429 let slot_size = if self.details_mode { 20.0 } else { 40.0 };
430
431 let mut slot_widget = slot_maker
433 .fabricate(
434 InventorySlot {
435 slot: pos,
436 ours: self.is_us,
437 entity: self.entity,
438 },
439 [slot_size as f32; 2],
440 )
441 .top_left_with_margins_on(
442 state.ids.inv_alignment,
443 0.0 + y as f64 * slot_size,
444 0.0 + x as f64 * slot_size,
445 );
446
447 if mouseover_loadout_slots.contains(&i) {
449 slot_widget = slot_widget.with_background_color(Color::Rgba(1.0, 1.0, 1.0, 1.0));
450 }
451
452 if self.show_salvage && item.as_ref().is_some_and(|item| item.is_salvageable()) {
453 slot_widget = slot_widget.with_background_color(Color::Rgba(1.0, 1.0, 1.0, 1.0));
454 }
455
456 if matches!(pos, Slot::Overflow(_)) {
458 slot_widget = slot_widget.with_background_color(Color::Rgba(1.0, 0.0, 0.0, 1.0));
459 }
460
461 if let Some(item) = item {
462 let quality_col_img = match item.quality() {
463 Quality::Low => self.imgs.inv_slot_grey,
464 Quality::Common => self.imgs.inv_slot_common,
465 Quality::Moderate => self.imgs.inv_slot_green,
466 Quality::High => self.imgs.inv_slot_blue,
467 Quality::Epic => self.imgs.inv_slot_purple,
468 Quality::Legendary => self.imgs.inv_slot_gold,
469 Quality::Artifact => self.imgs.inv_slot_orange,
470 _ => self.imgs.inv_slot_red,
471 };
472
473 let prices_info = self
474 .client
475 .pending_trade()
476 .as_ref()
477 .and_then(|(_, _, prices)| prices.clone());
478
479 if self.show_salvage && item.is_salvageable() {
480 let salvage_result: Vec<_> = item
481 .salvage_output()
482 .map(|(material_id, _)| Arc::<ItemDef>::load_expect_cloned(material_id))
483 .map(|item| item as Arc<dyn ItemDesc>)
484 .collect();
485
486 let items = salvage_result
487 .iter()
488 .map(|item| item.borrow())
489 .chain(core::iter::once(item as &dyn ItemDesc));
490
491 slot_widget
492 .filled_slot(quality_col_img)
493 .with_item_tooltip(
494 self.item_tooltip_manager,
495 items,
496 &prices_info,
497 self.item_tooltip,
498 )
499 .set(state.ids.inv_slots[i], ui);
500 } else {
501 slot_widget
502 .filled_slot(quality_col_img)
503 .with_item_tooltip(
504 self.item_tooltip_manager,
505 core::iter::once(item as &dyn ItemDesc),
506 &prices_info,
507 self.item_tooltip,
508 )
509 .set(state.ids.inv_slots[i], ui);
510 }
511 if self.details_mode {
512 let (name, _) = util::item_text(item, self.localized_strings, self.item_i18n);
513 Text::new(&name)
514 .top_left_with_margins_on(
515 state.ids.inv_alignment,
516 0.0 + y as f64 * slot_size,
517 30.0 + x as f64 * slot_size,
518 )
519 .font_id(self.fonts.cyri.conrod_id)
520 .font_size(self.fonts.cyri.scale(14))
521 .color(color::WHITE)
522 .set(state.ids.inv_slot_names[i], ui);
523
524 Text::new(&format!("{}", item.amount()))
525 .top_left_with_margins_on(
526 state.ids.inv_alignment,
527 0.0 + y as f64 * slot_size,
528 grid_width - 40.0 + x as f64 * slot_size,
529 )
530 .font_id(self.fonts.cyri.conrod_id)
531 .font_size(self.fonts.cyri.scale(14))
532 .color(color::WHITE)
533 .set(state.ids.inv_slot_amounts[i], ui);
534 }
535 } else {
536 slot_widget.set(state.ids.inv_slots[i], ui);
537 }
538 i += 1;
539 }
540 }
541
542 fn footer_metrics(
543 &mut self,
544 state: &ConrodState<'_, InventoryScrollerState>,
545 ui: &mut UiCell<'_>,
546 ) {
547 let space_used = self.inventory.populated_slots();
548 let space_max = self.inventory.slots().count();
549 let bag_space = format!("{}/{}", space_used, space_max);
550 let bag_space_percentage = space_used as f32 / space_max as f32;
551 Text::new(&bag_space)
584 .bottom_right_with_margins_on(self.bg_ids.bg_frame, 6.0, 43.0)
585 .font_id(self.fonts.cyri.conrod_id)
586 .font_size(self.fonts.cyri.scale(14))
587 .color(if bag_space_percentage < 0.8 {
588 TEXT_COLOR
589 } else if bag_space_percentage < 1.0 {
590 LOW_HP_COLOR
591 } else {
592 CRITICAL_HP_COLOR
593 })
594 .set(state.ids.space_txt, ui);
595 }
596
597 fn draggable_area(
598 &self,
599 state: &ConrodState<'_, InventoryScrollerState>,
600 events: &mut Vec<InventoryScrollerEvent>,
601 ui: &mut UiCell<'_>,
602 ) {
603 let bag_settings = &self.global_state.settings.hud_position;
604 let bag_pos = if !self.on_right {
605 bag_settings.bag.other
606 } else {
607 bag_settings.bag.own
608 };
609
610 let bag_size: Vec2<f64> = if !self.on_right {
611 [DEFAULT_OTHER_BAG_WIDTH, DEFAULT_OTHER_BAG_HEIGHT].into()
612 } else {
613 [DEFAULT_OWN_BAG_WIDTH, DEFAULT_OWN_BAG_HEIGHT].into()
614 };
615
616 let pos_delta: Vec2<f64> = ui
617 .widget_input(state.ids.draggable_area)
618 .drags()
619 .left()
620 .map(|drag| Vec2::<f64>::from(drag.delta_xy))
621 .sum();
622
623 let pos_delta: Vec2<f64> = if !self.on_right {
624 pos_delta
626 } else {
627 pos_delta.with_x(-pos_delta.x)
631 };
632
633 let window_clamp = Vec2::new(ui.win_w, ui.win_h) - bag_size;
634
635 let new_pos = (bag_pos + pos_delta)
636 .map(|e| e.max(0.))
637 .map2(window_clamp, |e, bounds| e.min(bounds));
638
639 if new_pos.abs_diff_ne(&bag_pos, f64::EPSILON) {
640 events.push(InventoryScrollerEvent::Drag(new_pos));
641 }
642
643 if ui
644 .widget_input(state.ids.draggable_area)
645 .clicks()
646 .right()
647 .count()
648 == 1
649 {
650 events.push(InventoryScrollerEvent::Drag(if self.on_right {
651 HudPositionSettings::default().bag.own
652 } else {
653 HudPositionSettings::default().bag.other
654 }));
655 }
656
657 Rectangle::fill_with([424.0, 48.0], color::TRANSPARENT)
658 .top_left_with_margin_on(self.bg_ids.bg_frame, 0.0)
659 .set(state.ids.draggable_area, ui);
660 }
661}
662
663impl Widget for InventoryScroller<'_> {
664 type Event = Vec<InventoryScrollerEvent>;
665 type State = InventoryScrollerState;
666 type Style = ();
667
668 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
669 InventoryScrollerState {
670 ids: InventoryScrollerIds::new(id_gen),
671 }
672 }
673
674 fn style(&self) -> Self::Style {}
675
676 fn update(mut self, args: widget::UpdateArgs<Self>) -> Self::Event {
677 let widget::UpdateArgs { state, ui, .. } = args;
678 let mut events = Vec::new();
679 self.background(ui);
680 self.title(state, ui);
681 self.scrollbar_and_slots(state, ui);
682 self.footer_metrics(state, ui);
683 if self
684 .global_state
685 .settings
686 .interface
687 .toggle_draggable_windows
688 {
689 self.draggable_area(state, &mut events, ui);
690 }
691 events
692 }
693}
694
695widget_ids! {
696 pub struct BackgroundIds {
697 bg,
698 bg_frame,
699 }
700}
701
702widget_ids! {
703 pub struct BagIds {
704 test,
705 inventory_scroller,
706 bag_close,
707 char_ico,
709 coin_ico,
710 space_txt,
711 inventory_title,
712 inventory_title_bg,
713 inventory_sort,
714 inventory_sort_selected,
715 scrollbar_bg,
716 scrollbar_slots,
717 tab_1,
718 tab_2,
719 tab_3,
720 tab_4,
721 bag_expand_btn,
722 bag_details_btn,
723 slots_bg,
725 head_slot,
726 neck_slot,
727 chest_slot,
728 shoulders_slot,
729 hands_slot,
730 legs_slot,
731 belt_slot,
732 lantern_slot,
733 ring1_slot,
734 ring2_slot,
735 feet_slot,
736 back_slot,
737 tabard_slot,
738 glider_slot,
739 active_mainhand_slot,
740 active_offhand_slot,
741 inactive_mainhand_slot,
742 inactive_offhand_slot,
743 swap_equipped_weapons_btn,
744 bag1_slot,
745 bag2_slot,
746 bag3_slot,
747 bag4_slot,
748 stat_icons[],
750 stat_txts[],
751 }
752}
753
754#[derive(WidgetCommon)]
755pub struct Bag<'a> {
756 client: &'a Client,
757 info: &'a HudInfo<'a>,
758 global_state: &'a GlobalState,
759 imgs: &'a Imgs,
760 item_imgs: &'a ItemImgs,
761 fonts: &'a Fonts,
762 #[conrod(common_builder)]
763 common: widget::CommonBuilder,
764 rot_imgs: &'a ImgsRot,
765 tooltip_manager: &'a mut TooltipManager,
766 item_tooltip_manager: &'a mut ItemTooltipManager,
767 slot_manager: &'a mut SlotManager,
768 pulse: f32,
769 localized_strings: &'a Localization,
770 item_i18n: &'a ItemI18n,
771 stats: &'a Stats,
772 skill_set: &'a SkillSet,
773 health: &'a Health,
774 energy: &'a Energy,
775 show: &'a Show,
776 body: &'a Body,
777 msm: &'a MaterialStatManifest,
778 rbm: &'a RecipeBookManifest,
779 poise: &'a Poise,
780}
781
782impl<'a> Bag<'a> {
783 #[expect(clippy::too_many_arguments)]
784 pub fn new(
785 client: &'a Client,
786 info: &'a HudInfo,
787 global_state: &'a GlobalState,
788 imgs: &'a Imgs,
789 item_imgs: &'a ItemImgs,
790 fonts: &'a Fonts,
791 rot_imgs: &'a ImgsRot,
792 tooltip_manager: &'a mut TooltipManager,
793 item_tooltip_manager: &'a mut ItemTooltipManager,
794 slot_manager: &'a mut SlotManager,
795 pulse: f32,
796 localized_strings: &'a Localization,
797 item_i18n: &'a ItemI18n,
798 stats: &'a Stats,
799 skill_set: &'a SkillSet,
800 health: &'a Health,
801 energy: &'a Energy,
802 show: &'a Show,
803 body: &'a Body,
804 msm: &'a MaterialStatManifest,
805 rbm: &'a RecipeBookManifest,
806 poise: &'a Poise,
807 ) -> Self {
808 Self {
809 client,
810 info,
811 global_state,
812 imgs,
813 item_imgs,
814 fonts,
815 common: widget::CommonBuilder::default(),
816 rot_imgs,
817 tooltip_manager,
818 item_tooltip_manager,
819 slot_manager,
820 pulse,
821 localized_strings,
822 item_i18n,
823 stats,
824 skill_set,
825 energy,
826 health,
827 show,
828 body,
829 msm,
830 rbm,
831 poise,
832 }
833 }
834}
835const STATS: [&str; 6] = [
836 "Health",
837 "Energy",
838 "Protection",
839 "Combat Rating",
840 "Stun Resilience",
841 "Stealth",
842];
843
844pub struct BagState {
845 ids: BagIds,
846 bg_ids: BackgroundIds,
847}
848
849pub enum Event {
850 BagExpand,
851 Close,
852 ChangeInventorySortOrder(InventorySortOrder),
853 SortInventory(InventorySortOrder),
854 SwapEquippedWeapons,
855 SetDetailsMode(bool),
856 MoveBag(Vec2<f64>),
857}
858
859impl Widget for Bag<'_> {
860 type Event = Vec<Event>;
861 type State = BagState;
862 type Style = ();
863
864 fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State {
865 BagState {
866 bg_ids: BackgroundIds {
867 bg: id_gen.next(),
868 bg_frame: id_gen.next(),
869 },
870 ids: BagIds::new(id_gen),
871 }
872 }
873
874 fn style(&self) -> Self::Style {}
875
876 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
877 common_base::prof_span!("Bag::update");
878 let widget::UpdateArgs { state, ui, .. } = args;
879 let i18n = &self.localized_strings;
880
881 let mut events = Vec::new();
882 let bag_tooltip = Tooltip::new({
883 let edge = &self.rot_imgs.tt_side;
886 let corner = &self.rot_imgs.tt_corner;
887 ImageFrame::new(
888 [edge.cw180, edge.none, edge.cw270, edge.cw90],
889 [corner.none, corner.cw270, corner.cw90, corner.cw180],
890 Color::Rgba(0.08, 0.07, 0.04, 1.0),
891 5.0,
892 )
893 })
894 .title_font_size(self.fonts.cyri.scale(15))
895 .parent(ui.window)
896 .desc_font_size(self.fonts.cyri.scale(12))
897 .font_id(self.fonts.cyri.conrod_id)
898 .desc_text_color(TEXT_COLOR);
899
900 if let Some(inventory) = self.client.inventories().get(self.info.viewpoint_entity) {
901 let tooltip = Tooltip::new({
903 let edge = &self.rot_imgs.tt_side;
906 let corner = &self.rot_imgs.tt_corner;
907 ImageFrame::new(
908 [edge.cw180, edge.none, edge.cw270, edge.cw90],
909 [corner.none, corner.cw270, corner.cw90, corner.cw180],
910 Color::Rgba(0.08, 0.07, 0.04, 1.0),
911 5.0,
912 )
913 })
914 .title_font_size(self.fonts.cyri.scale(15))
915 .parent(ui.window)
916 .desc_font_size(self.fonts.cyri.scale(12))
917 .font_id(self.fonts.cyri.conrod_id)
918 .desc_text_color(TEXT_COLOR);
919
920 let item_tooltip = ItemTooltip::new(
921 {
922 let edge = &self.rot_imgs.tt_side;
925 let corner = &self.rot_imgs.tt_corner;
926 ImageFrame::new(
927 [edge.cw180, edge.none, edge.cw270, edge.cw90],
928 [corner.none, corner.cw270, corner.cw90, corner.cw180],
929 Color::Rgba(0.08, 0.07, 0.04, 1.0),
930 5.0,
931 )
932 },
933 self.client,
934 self.info,
935 self.imgs,
936 self.item_imgs,
937 self.pulse,
938 self.msm,
939 self.rbm,
940 Some(inventory),
941 self.localized_strings,
942 self.item_i18n,
943 )
944 .title_font_size(self.fonts.cyri.scale(20))
945 .parent(ui.window)
946 .desc_font_size(self.fonts.cyri.scale(12))
947 .font_id(self.fonts.cyri.conrod_id)
948 .desc_text_color(TEXT_COLOR);
949
950 for event in InventoryScroller::new(
951 self.client,
952 self.global_state,
953 self.imgs,
954 self.item_imgs,
955 self.fonts,
956 self.item_tooltip_manager,
957 self.slot_manager,
958 self.pulse,
959 self.localized_strings,
960 self.item_i18n,
961 self.show.stats,
962 self.show.bag_inv,
963 true,
964 &item_tooltip,
965 self.localized_strings.get_content(&self.stats.name),
966 self.info.viewpoint_entity,
967 true,
968 inventory,
969 &state.bg_ids,
970 self.show.crafting_fields.salvage,
971 self.show.bag_details,
972 )
973 .set(state.ids.inventory_scroller, ui)
974 {
975 let InventoryScrollerEvent::Drag(pos) = event;
977 events.push(Event::MoveBag(pos));
978 }
979
980 Image::new(self.imgs.char_art)
982 .w_h(40.0, 37.0)
983 .top_left_with_margins_on(state.bg_ids.bg, 4.0, 2.0)
984 .set(state.ids.char_ico, ui);
985
986 let buttons_top = if self.show.bag_inv { 53.0 } else { 460.0 };
987 let (txt, btn, hover, press) = if self.show.bag_details {
988 (
989 "Grid mode",
990 self.imgs.grid_btn,
991 self.imgs.grid_btn_hover,
992 self.imgs.grid_btn_press,
993 )
994 } else {
995 (
996 "List mode",
997 self.imgs.list_btn,
998 self.imgs.list_btn_hover,
999 self.imgs.list_btn_press,
1000 )
1001 };
1002 let details_btn = Button::image(btn)
1003 .w_h(32.0, 17.0)
1004 .hover_image(hover)
1005 .press_image(press);
1006 if details_btn
1007 .mid_top_with_margin_on(state.bg_ids.bg_frame, buttons_top)
1008 .with_tooltip(self.tooltip_manager, txt, "", &bag_tooltip, TEXT_COLOR)
1009 .set(state.ids.bag_details_btn, ui)
1010 .was_clicked()
1011 {
1012 events.push(Event::SetDetailsMode(!self.show.bag_details));
1013 }
1014 let (txt, btn, hover, press) = if self.show.bag_inv {
1016 (
1017 "Show Loadout",
1018 self.imgs.collapse_btn,
1019 self.imgs.collapse_btn_hover,
1020 self.imgs.collapse_btn_press,
1021 )
1022 } else {
1023 (
1024 "Expand Bag",
1025 self.imgs.expand_btn,
1026 self.imgs.expand_btn_hover,
1027 self.imgs.expand_btn_press,
1028 )
1029 };
1030 let expand_btn = Button::image(btn)
1031 .w_h(30.0, 17.0)
1032 .hover_image(hover)
1033 .press_image(press);
1034
1035 if (inventory.slots().count() > 45 || self.show.bag_inv)
1037 && expand_btn
1038 .top_right_with_margins_on(state.bg_ids.bg_frame, buttons_top, 37.0)
1039 .with_tooltip(self.tooltip_manager, txt, "", &bag_tooltip, TEXT_COLOR)
1040 .set(state.ids.bag_expand_btn, ui)
1041 .was_clicked()
1042 {
1043 events.push(Event::BagExpand);
1044 }
1045
1046 if Button::image(self.imgs.inv_sort_btn)
1048 .w_h(30.0, 17.0)
1049 .hover_image(self.imgs.inv_sort_btn_hover)
1050 .press_image(self.imgs.inv_sort_btn_press)
1051 .top_left_with_margins_on(state.bg_ids.bg_frame, buttons_top, 87.0) .with_tooltip(
1053 self.tooltip_manager,
1054 &(match self.global_state.settings.inventory.sort_order.next() {
1055 InventorySortOrder::Name => i18n.get_msg("hud-bag-change_to_sort_by_name"),
1056 InventorySortOrder::Quality => i18n.get_msg("hud-bag-change_to_sort_by_quality"),
1057 InventorySortOrder::Category => i18n.get_msg("hud-bag-change_to_sort_by_category"),
1058 InventorySortOrder::Tag => i18n.get_msg("hud-bag-change_to_sort_by_tag"),
1059 InventorySortOrder::Amount => i18n.get_msg("hud-bag-change_to_sort_by_quantity"),
1060 }),
1061 "",
1062 &tooltip,
1063 color::WHITE,
1064 )
1065 .set(state.ids.inventory_sort, ui)
1066 .was_clicked()
1067 {
1068 events.push(Event::ChangeInventorySortOrder(
1070 self.global_state.settings.inventory.sort_order.next(),
1071 ));
1072 }
1073 if Button::image(self.imgs.inv_sort_selected_btn)
1075 .w_h(30.0, 17.0)
1076 .hover_image(self.imgs.inv_sort_selected_btn_hover)
1077 .press_image(self.imgs.inv_sort_selected_btn_press)
1078 .top_left_with_margins_on(state.bg_ids.bg_frame, buttons_top, 47.0)
1079 .with_tooltip(
1080 self.tooltip_manager,
1081 &(match self.global_state.settings.inventory.sort_order {
1082 InventorySortOrder::Name => i18n.get_msg("hud-bag-sort_by_name"),
1083 InventorySortOrder::Quality => i18n.get_msg("hud-bag-sort_by_quality"),
1084 InventorySortOrder::Category => i18n.get_msg("hud-bag-sort_by_category"),
1085 InventorySortOrder::Tag => i18n.get_msg("hud-bag-sort_by_tag"),
1086 InventorySortOrder::Amount => i18n.get_msg("hud-bag-sort_by_quantity"),
1087 }),
1088 "",
1089 &tooltip,
1090 color::WHITE,
1091 )
1092 .set(state.ids.inventory_sort_selected, ui)
1093 .was_clicked()
1094 {
1095 events.push(Event::SortInventory(
1096 self.global_state.settings.inventory.sort_order,
1097 ));
1098 }
1099
1100 let mut slot_maker = SlotMaker {
1102 empty_slot: self.imgs.armor_slot_empty,
1103 filled_slot: self.imgs.armor_slot,
1104 selected_slot: self.imgs.armor_slot_sel,
1105 background_color: Some(UI_HIGHLIGHT_0),
1106 content_size: ContentSize {
1107 width_height_ratio: 1.0,
1108 max_fraction: 0.75, },
1112 selected_content_scale: 1.067,
1113 amount_font: self.fonts.cyri.conrod_id,
1114 amount_margins: Vec2::new(-4.0, 0.0),
1115 amount_font_size: self.fonts.cyri.scale(12),
1116 amount_text_color: TEXT_COLOR,
1117 content_source: inventory,
1118 image_source: self.item_imgs,
1119 slot_manager: Some(self.slot_manager),
1120 pulse: self.pulse,
1121 };
1122
1123 macro_rules! set_tooltip {
1144 ($slot_maker:expr, $slot_id:expr, $slot:expr, $desc:expr) => {
1145 if let Some(item) = inventory.equipped($slot) {
1146 let manager = &mut *self.item_tooltip_manager;
1147 $slot_maker
1148 .with_item_tooltip(
1149 manager,
1150 core::iter::once(item as &dyn ItemDesc),
1151 &None,
1152 &item_tooltip,
1153 )
1154 .set($slot_id, ui)
1155 } else {
1156 let manager = &mut *self.tooltip_manager;
1157 $slot_maker
1158 .with_tooltip(manager, &i18n.get_msg($desc), "", &tooltip, color::WHITE)
1159 .set($slot_id, ui)
1160 }
1161 };
1162 }
1163
1164 let filled_slot = self.imgs.armor_slot;
1165 if !self.show.bag_inv {
1166 state.update(|s| {
1168 s.ids
1169 .stat_icons
1170 .resize(STATS.len(), &mut ui.widget_id_generator())
1171 });
1172 state.update(|s| {
1173 s.ids
1174 .stat_txts
1175 .resize(STATS.len(), &mut ui.widget_id_generator())
1176 });
1177 let combat_rating = combat_rating(
1179 inventory,
1180 self.health,
1181 self.energy,
1182 self.poise,
1183 self.skill_set,
1184 *self.body,
1185 self.msm,
1186 )
1187 .min(999.9);
1188 let indicator_col = cr_color(combat_rating);
1189 for i in STATS.iter().copied().enumerate() {
1190 let btn = Button::image(match i.1 {
1191 "Health" => self.imgs.health_ico,
1192 "Energy" => self.imgs.energy_ico,
1193 "Combat Rating" => self.imgs.combat_rating_ico,
1194 "Protection" => self.imgs.protection_ico,
1195 "Stun Resilience" => self.imgs.stun_res_ico,
1196 "Stealth" => self.imgs.stealth_rating_ico,
1197 _ => self.imgs.nothing,
1198 })
1199 .w_h(20.0, 20.0)
1200 .image_color(if i.1 == "Combat Rating" {
1201 indicator_col
1202 } else {
1203 TEXT_COLOR
1204 });
1205 let protection_txt = format!(
1206 "{}%",
1207 (100.0
1208 * Damage::compute_damage_reduction(
1209 None,
1210 Some(inventory),
1211 Some(self.stats),
1212 self.msm
1213 )) as i32
1214 );
1215 let health_txt = format!("{}", self.health.maximum().round() as usize);
1216 let energy_txt = format!("{}", self.energy.maximum().round() as usize);
1217 let combat_rating_txt = format!("{}", (combat_rating * 10.0) as usize);
1218 let stun_res_txt = format!(
1219 "{}",
1220 (100.0
1221 * Poise::compute_poise_damage_reduction(
1222 Some(inventory),
1223 self.msm,
1224 None,
1225 Some(self.stats),
1226 )) as i32
1227 );
1228 let stealth_txt = format!(
1229 "{:.1}%",
1230 ((1.0
1231 - perception_dist_multiplier_from_stealth(
1232 Some(inventory),
1233 None,
1234 self.msm
1235 ))
1236 * 100.0)
1237 );
1238 let btn = if i.0 == 0 {
1239 btn.top_left_with_margins_on(state.bg_ids.bg_frame, 55.0, 10.0)
1240 } else {
1241 btn.down_from(state.ids.stat_icons[i.0 - 1], 7.0)
1242 };
1243 let tooltip_head = match i.1 {
1244 "Health" => i18n.get_msg("hud-bag-health"),
1245 "Energy" => i18n.get_msg("hud-bag-energy"),
1246 "Combat Rating" => i18n.get_msg("hud-bag-combat_rating"),
1247 "Protection" => i18n.get_msg("hud-bag-protection"),
1248 "Stun Resilience" => i18n.get_msg("hud-bag-stun_res"),
1249 "Stealth" => i18n.get_msg("hud-bag-stealth"),
1250 _ => Cow::Borrowed(""),
1251 };
1252 let tooltip_txt = match i.1 {
1253 "Combat Rating" => i18n.get_msg("hud-bag-combat_rating_desc"),
1254 "Protection" => i18n.get_msg("hud-bag-protection_desc"),
1255 "Stun Resilience" => i18n.get_msg("hud-bag-stun_res_desc"),
1256 _ => Cow::Borrowed(""),
1257 };
1258 btn.with_tooltip(
1259 self.tooltip_manager,
1260 &tooltip_head,
1261 &tooltip_txt,
1262 &bag_tooltip,
1263 TEXT_COLOR,
1264 )
1265 .set(state.ids.stat_icons[i.0], ui);
1266 Text::new(match i.1 {
1267 "Health" => &health_txt,
1268 "Energy" => &energy_txt,
1269 "Combat Rating" => &combat_rating_txt,
1270 "Protection" => &protection_txt,
1271 "Stun Resilience" => &stun_res_txt,
1272 "Stealth" => &stealth_txt,
1273 _ => "",
1274 })
1275 .right_from(state.ids.stat_icons[i.0], 10.0)
1276 .font_id(self.fonts.cyri.conrod_id)
1277 .font_size(self.fonts.cyri.scale(14))
1278 .color(TEXT_COLOR)
1279 .graphics_for(state.ids.stat_icons[i.0])
1280 .set(state.ids.stat_txts[i.0], ui);
1281 }
1282 let item_slot = EquipSlot::Armor(ArmorSlot::Head);
1285 let slot = slot_maker
1286 .fabricate(item_slot, [45.0; 2])
1287 .mid_top_with_margin_on(state.bg_ids.bg_frame, 60.0)
1288 .with_icon(self.imgs.head_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN))
1289 .filled_slot(filled_slot);
1290
1291 let slot_id = state.ids.head_slot;
1292 set_tooltip!(slot, slot_id, item_slot, "hud-bag-head");
1293
1294 let item_slot = EquipSlot::Armor(ArmorSlot::Neck);
1296 let slot = slot_maker
1297 .fabricate(item_slot, [45.0; 2])
1298 .mid_bottom_with_margin_on(state.ids.head_slot, -55.0)
1299 .with_icon(self.imgs.necklace_bg, Vec2::new(40.0, 31.0), Some(UI_MAIN))
1300 .filled_slot(filled_slot);
1301
1302 let slot_id = state.ids.neck_slot;
1303 set_tooltip!(slot, slot_id, item_slot, "hud-bag-neck");
1304
1305 let item_slot = EquipSlot::Armor(ArmorSlot::Chest);
1308 let slot = slot_maker
1309 .fabricate(item_slot, [85.0; 2])
1310 .mid_bottom_with_margin_on(state.ids.neck_slot, -95.0)
1311 .with_icon(self.imgs.chest_bg, Vec2::new(64.0, 42.0), Some(UI_MAIN))
1312 .filled_slot(filled_slot);
1313
1314 let slot_id = state.ids.chest_slot;
1315 set_tooltip!(slot, slot_id, item_slot, "hud-bag-chest");
1316
1317 let item_slot = EquipSlot::Armor(ArmorSlot::Shoulders);
1319 let slot = slot_maker
1320 .fabricate(item_slot, [70.0; 2])
1321 .bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0)
1322 .with_icon(self.imgs.shoulders_bg, Vec2::new(60.0, 36.0), Some(UI_MAIN))
1323 .filled_slot(filled_slot);
1324
1325 let slot_id = state.ids.shoulders_slot;
1326 set_tooltip!(slot, slot_id, item_slot, "hud-bag-shoulders");
1327
1328 let item_slot = EquipSlot::Armor(ArmorSlot::Hands);
1330 let slot = slot_maker
1331 .fabricate(item_slot, [70.0; 2])
1332 .bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0)
1333 .with_icon(self.imgs.hands_bg, Vec2::new(55.0, 60.0), Some(UI_MAIN))
1334 .filled_slot(filled_slot);
1335
1336 let slot_id = state.ids.hands_slot;
1337 set_tooltip!(slot, slot_id, item_slot, "hud-bag-hands");
1338
1339 let item_slot = EquipSlot::Armor(ArmorSlot::Belt);
1341 let slot = slot_maker
1342 .fabricate(item_slot, [45.0; 2])
1343 .mid_bottom_with_margin_on(state.ids.chest_slot, -55.0)
1344 .with_icon(self.imgs.belt_bg, Vec2::new(40.0, 23.0), Some(UI_MAIN))
1345 .filled_slot(filled_slot);
1346
1347 let slot_id = state.ids.belt_slot;
1348 set_tooltip!(slot, slot_id, item_slot, "hud-bag-belt");
1349
1350 let item_slot = EquipSlot::Armor(ArmorSlot::Legs);
1352 let slot = slot_maker
1353 .fabricate(item_slot, [85.0; 2])
1354 .mid_bottom_with_margin_on(state.ids.belt_slot, -95.0)
1355 .with_icon(self.imgs.legs_bg, Vec2::new(48.0, 70.0), Some(UI_MAIN))
1356 .filled_slot(filled_slot);
1357
1358 let slot_id = state.ids.legs_slot;
1359 set_tooltip!(slot, slot_id, item_slot, "hud-bag-legs");
1360
1361 let item_slot = EquipSlot::Armor(ArmorSlot::Ring1);
1363 let slot = slot_maker
1364 .fabricate(item_slot, [45.0; 2])
1365 .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0)
1366 .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN))
1367 .filled_slot(filled_slot);
1368
1369 let slot_id = state.ids.ring1_slot;
1370 set_tooltip!(slot, slot_id, item_slot, "hud-bag-ring");
1371
1372 let item_slot = EquipSlot::Armor(ArmorSlot::Ring2);
1374 let slot = slot_maker
1375 .fabricate(item_slot, [45.0; 2])
1376 .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0)
1377 .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN))
1378 .filled_slot(filled_slot);
1379
1380 let slot_id = state.ids.ring2_slot;
1381 set_tooltip!(slot, slot_id, item_slot, "hud-bag-ring");
1382
1383 let item_slot = EquipSlot::Armor(ArmorSlot::Back);
1385 let slot = slot_maker
1386 .fabricate(item_slot, [45.0; 2])
1387 .down_from(state.ids.ring2_slot, 10.0)
1388 .with_icon(self.imgs.back_bg, Vec2::new(33.0, 40.0), Some(UI_MAIN))
1389 .filled_slot(filled_slot);
1390
1391 let slot_id = state.ids.back_slot;
1392 set_tooltip!(slot, slot_id, item_slot, "hud-bag-back");
1393
1394 let item_slot = EquipSlot::Armor(ArmorSlot::Feet);
1396 let slot = slot_maker
1397 .fabricate(item_slot, [45.0; 2])
1398 .down_from(state.ids.ring1_slot, 10.0)
1399 .with_icon(self.imgs.feet_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN))
1400 .filled_slot(filled_slot);
1401
1402 let slot_id = state.ids.feet_slot;
1403 set_tooltip!(slot, slot_id, item_slot, "hud-bag-feet");
1404
1405 let item_slot = EquipSlot::Lantern;
1407 let slot = slot_maker
1408 .fabricate(item_slot, [45.0; 2])
1409 .top_right_with_margins_on(state.bg_ids.bg_frame, 60.0, 5.0)
1410 .with_icon(self.imgs.lantern_bg, Vec2::new(24.0, 38.0), Some(UI_MAIN))
1411 .filled_slot(filled_slot);
1412
1413 let slot_id = state.ids.lantern_slot;
1414 set_tooltip!(slot, slot_id, item_slot, "hud-bag-lantern");
1415
1416 let item_slot = EquipSlot::Glider;
1418 let slot = slot_maker
1419 .fabricate(item_slot, [45.0; 2])
1420 .down_from(state.ids.lantern_slot, 5.0)
1421 .with_icon(self.imgs.glider_bg, Vec2::new(38.0, 38.0), Some(UI_MAIN))
1422 .filled_slot(filled_slot);
1423
1424 let slot_id = state.ids.glider_slot;
1425 set_tooltip!(slot, slot_id, item_slot, "hud-bag-glider");
1426
1427 let item_slot = EquipSlot::Armor(ArmorSlot::Tabard);
1429 let slot = slot_maker
1430 .fabricate(item_slot, [45.0; 2])
1431 .down_from(state.ids.glider_slot, 5.0)
1432 .with_icon(self.imgs.tabard_bg, Vec2::new(38.0, 38.0), Some(UI_MAIN))
1433 .filled_slot(filled_slot);
1434
1435 let slot_id = state.ids.tabard_slot;
1436 set_tooltip!(slot, slot_id, item_slot, "hud-bag-tabard");
1437
1438 let item_slot = EquipSlot::ActiveMainhand;
1440 let slot = slot_maker
1441 .fabricate(item_slot, [85.0; 2])
1442 .bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0)
1443 .with_icon(self.imgs.mainhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
1444 .filled_slot(filled_slot);
1445
1446 let slot_id = state.ids.active_mainhand_slot;
1447 set_tooltip!(slot, slot_id, item_slot, "hud-bag-mainhand");
1448
1449 let item_slot = EquipSlot::ActiveOffhand;
1451 let slot = slot_maker
1452 .fabricate(item_slot, [85.0; 2])
1453 .bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0)
1454 .with_icon(self.imgs.offhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
1455 .filled_slot(filled_slot);
1456
1457 let slot_id = state.ids.active_offhand_slot;
1458 set_tooltip!(slot, slot_id, item_slot, "hud-bag-offhand");
1459
1460 let item_slot = EquipSlot::InactiveMainhand;
1462 let slot = slot_maker
1463 .fabricate(item_slot, [40.0; 2])
1464 .bottom_right_with_margins_on(state.ids.active_mainhand_slot, 3.0, -47.0)
1465 .with_icon(self.imgs.mainhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN))
1466 .filled_slot(filled_slot);
1467
1468 let slot_id = state.ids.inactive_mainhand_slot;
1469 set_tooltip!(slot, slot_id, item_slot, "hud-bag-inactive_mainhand");
1470
1471 let item_slot = EquipSlot::InactiveOffhand;
1473 let slot = slot_maker
1474 .fabricate(item_slot, [40.0; 2])
1475 .bottom_left_with_margins_on(state.ids.active_offhand_slot, 3.0, -47.0)
1476 .with_icon(self.imgs.offhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN))
1477 .filled_slot(filled_slot);
1478
1479 let slot_id = state.ids.inactive_offhand_slot;
1480 set_tooltip!(slot, slot_id, item_slot, "hud-bag-inactive_offhand");
1481
1482 if Button::image(self.imgs.swap_equipped_weapons_btn)
1483 .hover_image(self.imgs.swap_equipped_weapons_btn_hover)
1484 .press_image(self.imgs.swap_equipped_weapons_btn_press)
1485 .w_h(32.0, 40.0)
1486 .bottom_left_with_margins_on(state.bg_ids.bg_frame, 0.0, 23.3)
1487 .align_middle_y_of(state.ids.active_mainhand_slot)
1488 .with_tooltip(
1489 self.tooltip_manager,
1490 &i18n.get_msg("hud-bag-swap_equipped_weapons_title"),
1491 &(if let Some(key) = self
1492 .global_state
1493 .settings
1494 .controls
1495 .get_binding(GameInput::SwapLoadout)
1496 {
1497 i18n.get_msg_ctx(
1498 "hud-bag-swap_equipped_weapons_desc",
1499 &i18n::fluent_args! {
1500 "key" => key.display_string()
1501 },
1502 )
1503 } else {
1504 Cow::Borrowed("")
1505 }),
1506 &tooltip,
1507 color::WHITE,
1508 )
1509 .set(state.ids.swap_equipped_weapons_btn, ui)
1510 .was_clicked()
1511 {
1512 events.push(Event::SwapEquippedWeapons);
1513 }
1514 }
1515
1516 let item_slot = EquipSlot::Armor(ArmorSlot::Bag1);
1518 let slot = slot_maker
1519 .fabricate(item_slot, [35.0; 2])
1520 .bottom_left_with_margins_on(
1521 state.bg_ids.bg_frame,
1522 if self.show.bag_inv { 600.0 } else { 167.0 },
1523 3.0,
1524 )
1525 .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
1526 .filled_slot(filled_slot);
1527
1528 let slot_id = state.ids.bag1_slot;
1529 set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
1530
1531 let item_slot = EquipSlot::Armor(ArmorSlot::Bag2);
1533 let slot = slot_maker
1534 .fabricate(item_slot, [35.0; 2])
1535 .down_from(state.ids.bag1_slot, 2.0)
1536 .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
1537 .filled_slot(filled_slot);
1538
1539 let slot_id = state.ids.bag2_slot;
1540 set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
1541
1542 let item_slot = EquipSlot::Armor(ArmorSlot::Bag3);
1544 let slot = slot_maker
1545 .fabricate(item_slot, [35.0; 2])
1546 .down_from(state.ids.bag2_slot, 2.0)
1547 .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
1548 .filled_slot(filled_slot);
1549
1550 let slot_id = state.ids.bag3_slot;
1551 set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
1552
1553 let item_slot = EquipSlot::Armor(ArmorSlot::Bag4);
1555 let slot = slot_maker
1556 .fabricate(item_slot, [35.0; 2])
1557 .down_from(state.ids.bag3_slot, 2.0)
1558 .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
1559 .filled_slot(filled_slot);
1560
1561 let slot_id = state.ids.bag4_slot;
1562 set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
1563
1564 if Button::image(self.imgs.close_btn)
1566 .w_h(24.0, 25.0)
1567 .hover_image(self.imgs.close_btn_hover)
1568 .press_image(self.imgs.close_btn_press)
1569 .top_right_with_margins_on(state.bg_ids.bg, 0.0, 0.0)
1570 .set(state.ids.bag_close, ui)
1571 .was_clicked()
1572 {
1573 events.push(Event::Close);
1574 }
1575 }
1576
1577 events
1578 }
1579}