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