Skip to main content

veloren_voxygen/hud/
bag.rs

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    slots::{ArmorSlot, EquipSlot, SlotManager},
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    window::MenuInput,
25};
26use client::Client;
27use common::{
28    combat::{Damage, combat_rating, perception_dist_multiplier_from_stealth},
29    comp::{
30        Body, Energy, Health, Inventory, Poise, SkillSet, Stats,
31        inventory::InventorySortOrder,
32        item::{ItemDesc, ItemI18n, MaterialStatManifest},
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 specs::Entity as EcsEntity;
45use vek::{Vec2, approx::AbsDiffEq};
46
47widget_ids! {
48    pub struct InventoryScrollerIds {
49        draggable_area,
50        inv_alignment,
51        slot_grid,
52        //coin_ico,
53        space_txt,
54        //coin_txt,
55        inventory_title,
56        inventory_title_bg,
57        scrollbar_bg,
58        second_phase_scrollbar_bg,
59        scrollbar_slots,
60        left_scrollbar_slots,
61    }
62}
63
64pub struct InventoryScrollerState {
65    ids: InventoryScrollerIds,
66}
67
68pub enum InventoryScrollerEvent {
69    Drag(Vec2<f64>),
70    ChangeLocalFocus(usize),
71    Close,
72}
73
74#[derive(WidgetCommon)]
75pub struct InventoryScroller<'a> {
76    client: &'a Client,
77    imgs: &'a Imgs,
78    item_imgs: &'a ItemImgs,
79    fonts: &'a Fonts,
80    #[conrod(common_builder)]
81    common: widget::CommonBuilder,
82    item_tooltip_manager: &'a mut ItemTooltipManager,
83    slot_manager: &'a mut SlotManager,
84    pulse: f32,
85    menu_events: &'a Vec<MenuInput>,
86    active_content: usize,
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        menu_events: &'a Vec<MenuInput>,
115        active_content: usize,
116        localized_strings: &'a Localization,
117        item_i18n: &'a ItemI18n,
118        show_stats: bool,
119        show_bag_inv: bool,
120        on_right: bool,
121        item_tooltip: &'a ItemTooltip<'a>,
122        playername: String,
123        entity: EcsEntity,
124        is_us: bool,
125        inventory: &'a Inventory,
126        bg_ids: &'a BackgroundIds,
127        show_salvage: bool,
128        details_mode: bool,
129    ) -> Self {
130        InventoryScroller {
131            client,
132            imgs,
133            item_imgs,
134            fonts,
135            common: widget::CommonBuilder::default(),
136            item_tooltip_manager,
137            slot_manager,
138            pulse,
139            menu_events,
140            active_content,
141            localized_strings,
142            item_i18n,
143            show_stats,
144            show_bag_inv,
145            on_right,
146            global_state,
147            item_tooltip,
148            playername,
149            entity,
150            is_us,
151            inventory,
152            bg_ids,
153            show_salvage,
154            details_mode,
155        }
156    }
157
158    fn background(&mut self, ui: &mut UiCell<'_>) {
159        let bag_settings = &self.global_state.settings.hud_position;
160        let bag_pos = if !self.on_right {
161            bag_settings.bag.other
162        } else {
163            bag_settings.bag.own
164        };
165
166        let bg_id = if !self.on_right {
167            self.imgs.inv_bg_bag
168        } else {
169            self.imgs.player_inv_bg_bag
170        };
171
172        let img_id = if !self.on_right {
173            self.imgs.inv_frame_bag
174        } else {
175            self.imgs.player_inv_frame_bag
176        };
177
178        let mut bg = Image::new(if self.show_stats {
179            self.imgs.inv_bg_stats
180        } else if self.show_bag_inv {
181            bg_id
182        } else {
183            self.imgs.inv_bg_armor
184        })
185        .w_h(
186            424.0,
187            if self.show_bag_inv && !self.on_right {
188                548.0
189            } else {
190                708.0
191            },
192        );
193
194        if self.on_right {
195            bg = bg.bottom_right_with_margins_on(ui.window, bag_pos.y, bag_pos.x);
196        } else {
197            bg = bg.bottom_left_with_margins_on(ui.window, bag_pos.y, bag_pos.x);
198        }
199
200        bg.color(Some(UI_MAIN)).set(self.bg_ids.bg, ui);
201
202        Image::new(if self.show_bag_inv {
203            img_id
204        } else {
205            self.imgs.inv_frame
206        })
207        .w_h(
208            424.0,
209            if self.show_bag_inv && !self.on_right {
210                548.0
211            } else {
212                708.0
213            },
214        )
215        .middle_of(self.bg_ids.bg)
216        .color(Some(UI_HIGHLIGHT_0))
217        .set(self.bg_ids.bg_frame, ui);
218    }
219
220    fn title(&mut self, state: &ConrodState<'_, InventoryScrollerState>, ui: &mut UiCell<'_>) {
221        Text::new(
222            &self
223                .localized_strings
224                .get_msg_ctx("hud-bag-inventory", &i18n::fluent_args! {
225                    "playername" => &*self.playername,
226                }),
227        )
228        .mid_top_with_margin_on(self.bg_ids.bg_frame, 9.0)
229        .font_id(self.fonts.cyri.conrod_id)
230        .font_size(self.fonts.cyri.scale(22))
231        .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
232        .set(state.ids.inventory_title_bg, ui);
233        Text::new(
234            &self
235                .localized_strings
236                .get_msg_ctx("hud-bag-inventory", &i18n::fluent_args! {
237                    "playername" => &*self.playername,
238                }),
239        )
240        .top_left_with_margins_on(state.ids.inventory_title_bg, 2.0, 2.0)
241        .font_id(self.fonts.cyri.conrod_id)
242        .font_size(self.fonts.cyri.scale(22))
243        .color(TEXT_COLOR)
244        .set(state.ids.inventory_title, ui);
245    }
246
247    fn scrollbar_and_slots(
248        &mut self,
249        state: &mut ConrodState<'_, InventoryScrollerState>,
250        events: &mut Vec<InventoryScrollerEvent>,
251        ui: &mut UiCell<'_>,
252    ) {
253        // MENU INPUTS: change the inventory button/filter focus
254        // LocalFocus: change local window focus
255        if self.active_content == 1 {
256            for event in self.menu_events {
257                match *event {
258                    MenuInput::LocalFocus => {
259                        events.push(InventoryScrollerEvent::ChangeLocalFocus(2));
260                    },
261                    MenuInput::Apply => {
262                        // TODO
263                    },
264                    MenuInput::Back => {
265                        events.push(InventoryScrollerEvent::Close);
266                    },
267                    _ => {},
268                }
269            }
270        }
271
272        let space_max = self.inventory.slots().count();
273
274        // Slots Scrollbar
275        if space_max > 45 && !self.show_bag_inv {
276            // Scrollbar-BG
277            Image::new(self.imgs.scrollbar_bg)
278                .w_h(9.0, 173.0)
279                .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
280                .color(Some(UI_HIGHLIGHT_0))
281                .set(state.ids.scrollbar_bg, ui);
282            // Scrollbar
283            Scrollbar::y_axis(state.ids.inv_alignment)
284                .thickness(5.0)
285                .h(123.0)
286                .color(UI_MAIN)
287                .middle_of(state.ids.scrollbar_bg)
288                .set(state.ids.scrollbar_slots, ui);
289        } else if space_max > 135 && self.on_right {
290            // Scrollbar-BG
291            Image::new(self.imgs.scrollbar_bg_big)
292                .w_h(9.0, 592.0)
293                .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
294                .color(Some(UI_HIGHLIGHT_0))
295                .set(state.ids.scrollbar_bg, ui);
296            // Scrollbar
297            Scrollbar::y_axis(state.ids.inv_alignment)
298                .thickness(5.0)
299                .h(542.0)
300                .color(UI_MAIN)
301                .middle_of(state.ids.scrollbar_bg)
302                .set(state.ids.scrollbar_slots, ui);
303        };
304
305        // This is just for the offeror inventory scrollbar
306        if space_max >= 108 && !self.on_right && self.show_bag_inv {
307            // Left bag scrollbar background
308            Image::new(self.imgs.second_phase_scrollbar_bg)
309                .w_h(9.0, 434.0)
310                .bottom_right_with_margins_on(self.bg_ids.bg_frame, 42.0, 3.0)
311                .color(Some(UI_HIGHLIGHT_0))
312                .set(state.ids.second_phase_scrollbar_bg, ui);
313            // Left bag scrollbar
314            Scrollbar::y_axis(state.ids.inv_alignment)
315                .thickness(5.0)
316                .h(384.0)
317                .color(UI_MAIN)
318                .middle_of(state.ids.second_phase_scrollbar_bg)
319                .set(state.ids.left_scrollbar_slots, ui);
320        }
321
322        let grid_width = 362.0;
323        let grid_height = if self.show_bag_inv && !self.on_right {
324            440.0 // This for the left bag
325        } else if self.show_bag_inv && self.on_right {
326            600.0 // This for the expanded right bag
327        } else {
328            200.0
329        };
330
331        // Alignment for Grid
332        Rectangle::fill_with([grid_width, grid_height], color::TRANSPARENT)
333            .bottom_left_with_margins_on(
334                self.bg_ids.bg_frame,
335                29.0,
336                if self.show_bag_inv && !self.on_right {
337                    28.0
338                } else {
339                    46.5
340                },
341            )
342            .scroll_kids_vertically()
343            .set(state.ids.inv_alignment, ui);
344
345        // Bag Slots
346        // Create available inventory slot widgets
347        for event in SlotGrid::new(
348            self.client,
349            self.imgs,
350            self.item_imgs,
351            self.fonts,
352            self.item_tooltip_manager,
353            self.slot_manager,
354            self.inventory,
355            self.item_tooltip,
356            self.localized_strings,
357            self.item_i18n,
358            self.entity,
359            &self.global_state.window.last_input(),
360            self.pulse,
361            self.menu_events,
362            self.active_content,
363        )
364        .columns(9) // 6 columns and default spacing is better imo
365        .spacing(0.0)
366        .is_us(self.is_us)
367        .details_mode(self.details_mode)
368        .show_salvage(self.show_salvage)
369        .slot_size(if self.details_mode { 20.0 } else { 40.0 }) // 55.0 for 6 columns
370        .wh_of(state.ids.inv_alignment)
371        .top_left_of(state.ids.inv_alignment)
372        .set(state.ids.slot_grid, ui)
373        {
374            match event {
375                super::slot_grid::SlotEvents::ChangeLocalFocus(change) => {
376                    events.push(InventoryScrollerEvent::ChangeLocalFocus(change));
377                },
378                super::slot_grid::SlotEvents::Close => {
379                    events.push(InventoryScrollerEvent::Close);
380                },
381            }
382        }
383    }
384
385    fn footer_metrics(
386        &mut self,
387        state: &ConrodState<'_, InventoryScrollerState>,
388        ui: &mut UiCell<'_>,
389    ) {
390        let space_used = self.inventory.populated_slots();
391        let space_max = self.inventory.slots().count();
392        let bag_space = format!("{}/{}", space_used, space_max);
393        let bag_space_percentage = space_used as f32 / space_max as f32;
394        //let coin_itemdef =
395        // Arc::<ItemDef>::load_expect_cloned("common.items.utility.coins"); let
396        // coin_count = self.inventory.item_count(&coin_itemdef); TODO: Reuse
397        // this to generally count a stackable item the player selected
398        // let cheese_itemdef =
399        // Arc::<ItemDef>::load_expect_cloned("common.items.food.cheese");
400        // let cheese_count = self.inventory.item_count(&cheese_itemdef);
401
402        // Coin Icon and Coin Text
403        /*Image::new(self.imgs.coin_ico)
404            .w_h(16.0, 17.0)
405            .bottom_left_with_margins_on(self.bg_ids.bg_frame, 2.0, 43.0)
406            .set(state.ids.coin_ico, ui);
407        Text::new(&format!("{}", coin_count))
408            .bottom_left_with_margins_on(self.bg_ids.bg_frame, 6.0, 64.0)
409            .font_id(self.fonts.cyri.conrod_id)
410            .font_size(self.fonts.cyri.scale(14))
411            .color(Color::Rgba(0.871, 0.863, 0.05, 1.0))
412            .set(state.ids.coin_txt, ui);*/
413        // TODO: Add a customizable counter for stackable items here
414        // TODO: Cheese is funny until it's real
415        /*Image::new(self.imgs.cheese_ico)
416            .w_h(16.0, 17.0)
417            .bottom_left_with_margins_on(self.bg_ids.bg_frame, 2.0, 110.0)
418            .set(state.ids.cheese_ico, ui);
419        Text::new(&format!("{}", cheese_count))
420            .bottom_left_with_margins_on(self.bg_ids.bg_frame, 6.0, 144.0)
421            .font_id(self.fonts.cyri.conrod_id)
422            .font_size(self.fonts.cyri.scale(14))
423            .color(Color::Rgba(0.871, 0.863, 0.05, 1.0))
424            .set(state.ids.cheese_txt, ui);*/
425        //Free Bag-Space
426        Text::new(&bag_space)
427            .bottom_right_with_margins_on(self.bg_ids.bg_frame, 6.0, 43.0)
428            .font_id(self.fonts.cyri.conrod_id)
429            .font_size(self.fonts.cyri.scale(14))
430            .color(if bag_space_percentage < 0.8 {
431                TEXT_COLOR
432            } else if bag_space_percentage < 1.0 {
433                LOW_HP_COLOR
434            } else {
435                CRITICAL_HP_COLOR
436            })
437            .set(state.ids.space_txt, ui);
438    }
439
440    fn draggable_area(
441        &self,
442        state: &ConrodState<'_, InventoryScrollerState>,
443        events: &mut Vec<InventoryScrollerEvent>,
444        ui: &mut UiCell<'_>,
445    ) {
446        let bag_settings = &self.global_state.settings.hud_position;
447        let bag_pos = if !self.on_right {
448            bag_settings.bag.other
449        } else {
450            bag_settings.bag.own
451        };
452
453        let bag_size: Vec2<f64> = if !self.on_right {
454            [DEFAULT_OTHER_BAG_WIDTH, DEFAULT_OTHER_BAG_HEIGHT].into()
455        } else {
456            [DEFAULT_OWN_BAG_WIDTH, DEFAULT_OWN_BAG_HEIGHT].into()
457        };
458
459        let pos_delta: Vec2<f64> = ui
460            .widget_input(state.ids.draggable_area)
461            .drags()
462            .left()
463            .map(|drag| Vec2::<f64>::from(drag.delta_xy))
464            .sum();
465
466        let pos_delta: Vec2<f64> = if !self.on_right {
467            // Others (left side) bags use bottom_left_with_margins_on
468            pos_delta
469        } else {
470            // Own (right side) bags use bottom_right_with_margins_on
471            // which means we have to use positive margins to move left
472            // so we have to invert the x value from the delta.
473            pos_delta.with_x(-pos_delta.x)
474        };
475
476        let window_clamp = Vec2::new(ui.win_w, ui.win_h) - bag_size;
477
478        let new_pos = (bag_pos + pos_delta)
479            .map(|e| e.max(0.))
480            .map2(window_clamp, |e, bounds| e.min(bounds));
481
482        if new_pos.abs_diff_ne(&bag_pos, f64::EPSILON) {
483            events.push(InventoryScrollerEvent::Drag(new_pos));
484        }
485
486        if ui
487            .widget_input(state.ids.draggable_area)
488            .clicks()
489            .right()
490            .count()
491            == 1
492        {
493            events.push(InventoryScrollerEvent::Drag(if self.on_right {
494                HudPositionSettings::default().bag.own
495            } else {
496                HudPositionSettings::default().bag.other
497            }));
498        }
499
500        Rectangle::fill_with([424.0, 48.0], color::TRANSPARENT)
501            .top_left_with_margin_on(self.bg_ids.bg_frame, 0.0)
502            .set(state.ids.draggable_area, ui);
503    }
504}
505
506impl Widget for InventoryScroller<'_> {
507    type Event = Vec<InventoryScrollerEvent>;
508    type State = InventoryScrollerState;
509    type Style = ();
510
511    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
512        InventoryScrollerState {
513            ids: InventoryScrollerIds::new(id_gen),
514        }
515    }
516
517    fn style(&self) -> Self::Style {}
518
519    fn update(mut self, args: widget::UpdateArgs<Self>) -> Self::Event {
520        let widget::UpdateArgs { state, ui, .. } = args;
521        let mut events = Vec::new();
522        self.background(ui);
523        self.title(state, ui);
524        self.scrollbar_and_slots(state, &mut events, ui);
525        self.footer_metrics(state, ui);
526        if self
527            .global_state
528            .settings
529            .interface
530            .toggle_draggable_windows
531        {
532            self.draggable_area(state, &mut events, ui);
533        }
534        events
535    }
536}
537
538widget_ids! {
539    pub struct BackgroundIds {
540        bg,
541        bg_frame,
542    }
543}
544
545widget_ids! {
546    pub struct BagIds {
547        inventory_scroller,
548        bag_close,
549        char_ico,
550        inventory_sort,
551        inventory_sort_selected,
552        bag_expand_btn,
553        bag_details_btn,
554        // Armor Slots
555        head_slot,
556        neck_slot,
557        chest_slot,
558        shoulders_slot,
559        hands_slot,
560        legs_slot,
561        belt_slot,
562        lantern_slot,
563        ring1_slot,
564        ring2_slot,
565        feet_slot,
566        back_slot,
567        tabard_slot,
568        glider_slot,
569        active_mainhand_slot,
570        active_offhand_slot,
571        inactive_mainhand_slot,
572        inactive_offhand_slot,
573        swap_equipped_weapons_btn,
574        bag1_slot,
575        bag2_slot,
576        bag3_slot,
577        bag4_slot,
578        // Stats
579        stat_icons[],
580        stat_txts[],
581    }
582}
583
584#[derive(WidgetCommon)]
585pub struct Bag<'a> {
586    client: &'a Client,
587    info: &'a HudInfo<'a>,
588    global_state: &'a GlobalState,
589    imgs: &'a Imgs,
590    item_imgs: &'a ItemImgs,
591    fonts: &'a Fonts,
592    #[conrod(common_builder)]
593    common: widget::CommonBuilder,
594    rot_imgs: &'a ImgsRot,
595    tooltip_manager: &'a mut TooltipManager,
596    item_tooltip_manager: &'a mut ItemTooltipManager,
597    slot_manager: &'a mut SlotManager,
598    pulse: f32,
599    localized_strings: &'a Localization,
600    item_i18n: &'a ItemI18n,
601    stats: &'a Stats,
602    skill_set: &'a SkillSet,
603    health: &'a Health,
604    energy: &'a Energy,
605    show: &'a Show,
606    body: &'a Body,
607    msm: &'a MaterialStatManifest,
608    rbm: &'a RecipeBookManifest,
609    poise: &'a Poise,
610    menu_events: &'a Vec<MenuInput>,
611}
612
613impl<'a> Bag<'a> {
614    #[expect(clippy::too_many_arguments)]
615    pub fn new(
616        client: &'a Client,
617        info: &'a HudInfo,
618        global_state: &'a GlobalState,
619        imgs: &'a Imgs,
620        item_imgs: &'a ItemImgs,
621        fonts: &'a Fonts,
622        rot_imgs: &'a ImgsRot,
623        tooltip_manager: &'a mut TooltipManager,
624        item_tooltip_manager: &'a mut ItemTooltipManager,
625        slot_manager: &'a mut SlotManager,
626        pulse: f32,
627        localized_strings: &'a Localization,
628        item_i18n: &'a ItemI18n,
629        stats: &'a Stats,
630        skill_set: &'a SkillSet,
631        health: &'a Health,
632        energy: &'a Energy,
633        show: &'a Show,
634        body: &'a Body,
635        msm: &'a MaterialStatManifest,
636        rbm: &'a RecipeBookManifest,
637        poise: &'a Poise,
638        menu_events: &'a Vec<MenuInput>,
639    ) -> Self {
640        Self {
641            client,
642            info,
643            global_state,
644            imgs,
645            item_imgs,
646            fonts,
647            common: widget::CommonBuilder::default(),
648            rot_imgs,
649            tooltip_manager,
650            item_tooltip_manager,
651            slot_manager,
652            pulse,
653            localized_strings,
654            item_i18n,
655            stats,
656            skill_set,
657            energy,
658            health,
659            show,
660            body,
661            msm,
662            rbm,
663            poise,
664            menu_events,
665        }
666    }
667}
668const STATS: [&str; 6] = [
669    "Health",
670    "Energy",
671    "Protection",
672    "Combat Rating",
673    "Stun Resilience",
674    "Stealth",
675];
676
677pub struct BagState {
678    ids: BagIds,
679    bg_ids: BackgroundIds,
680
681    active_content: usize,
682    active_gear_slot: usize,
683}
684
685pub enum Event {
686    BagExpand,
687    Close,
688    ChangeInventorySortOrder(InventorySortOrder),
689    SortInventory(InventorySortOrder),
690    SwapEquippedWeapons,
691    SetDetailsMode(bool),
692    MoveBag(Vec2<f64>),
693}
694
695impl Widget for Bag<'_> {
696    type Event = Vec<Event>;
697    type State = BagState;
698    type Style = ();
699
700    fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State {
701        BagState {
702            bg_ids: BackgroundIds {
703                bg: id_gen.next(),
704                bg_frame: id_gen.next(),
705            },
706            ids: BagIds::new(id_gen),
707            active_content: 0,
708            active_gear_slot: 1,
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 i18n = &self.localized_strings;
718
719        let mut events = Vec::new();
720
721        // If I change local focus to 0 immidiately, it will also be read by the
722        // inventory which will also register the same input and change it 1. A fix for
723        // the current set-up is to just change focus after the inventory has
724        // been calculated
725        let mut change_local_focus = false;
726
727        // MENU INPUTS: manage gear elements
728        // LocalFocus: change which parts of the screen you interact with (0 =
729        // inventory, 1 = inventory filters/buttons, 2 = gear)
730        // Up: try to go up in the gear list
731        // Down: try to go down the gear list
732        // Left: try to move left in the gear list
733        // Right: try to move right in the gear list
734        // Apply: TODO
735        // Back: close the bag when gear menu is in focus
736        if state.active_content == 2 {
737            for event in self.menu_events {
738                match *event {
739                    MenuInput::LocalFocus => {
740                        // Reset back to 0 (inventory)
741                        // This whole interaction logic should probably be improved sometime
742                        change_local_focus = true;
743                    },
744                    MenuInput::Up => state.update(|s| {
745                        // So many values to manual set...
746                        match s.active_gear_slot {
747                            // weapon switch button
748                            0 => {},
749                            // primary weapon left
750                            1 => s.active_gear_slot = 5,
751                            // secondary weapon left
752                            2 => s.active_gear_slot = 6,
753                            // secondary weapon right
754                            3 => s.active_gear_slot = 6,
755                            // primary weapon right
756                            4 => s.active_gear_slot = 7,
757                            // back
758                            5 => s.active_gear_slot = 8,
759                            // pants
760                            6 => s.active_gear_slot = 9,
761                            // shoes
762                            7 => s.active_gear_slot = 10,
763                            // jewelry left
764                            8 => s.active_gear_slot = 11,
765                            // belt
766                            9 => s.active_gear_slot = 12,
767                            // jewelry right
768                            10 => s.active_gear_slot = 13,
769                            // shoulder
770                            11 => s.active_gear_slot = 14,
771                            // chest
772                            12 => s.active_gear_slot = 14,
773                            // hands/gloves
774                            13 => s.active_gear_slot = 14,
775                            // jewelry center
776                            14 => s.active_gear_slot = 15,
777                            // hat
778                            15 => {},
779                            // tabard
780                            16 => s.active_gear_slot = 17,
781                            // glider
782                            17 => s.active_gear_slot = 18,
783                            // lantern
784                            18 => {},
785                            // reset to 0 if unexpected
786                            _ => s.active_gear_slot = 0,
787                        }
788                    }),
789                    MenuInput::Down => state.update(|s| {
790                        match s.active_gear_slot {
791                            // weapon switch button
792                            0 => {},
793                            // primary weapon 1
794                            1 => {},
795                            // secondary weapon 1
796                            2 => {},
797                            // secondary weapon 2
798                            3 => {},
799                            // primary weapon 1
800                            4 => {},
801                            // back
802                            5 => s.active_gear_slot = 1,
803                            // pants
804                            6 => s.active_gear_slot = 2,
805                            // shoes
806                            7 => s.active_gear_slot = 4,
807                            // jewelry left
808                            8 => s.active_gear_slot = 5,
809                            // belt
810                            9 => s.active_gear_slot = 6,
811                            // jewelry right
812                            10 => s.active_gear_slot = 7,
813                            // shoulder
814                            11 => s.active_gear_slot = 8,
815                            // chest
816                            12 => s.active_gear_slot = 9,
817                            // hands/gloves
818                            13 => s.active_gear_slot = 10,
819                            // jewelry center
820                            14 => s.active_gear_slot = 12,
821                            // hat
822                            15 => s.active_gear_slot = 14,
823                            // tabard
824                            16 => {},
825                            // glider
826                            17 => s.active_gear_slot = 16,
827                            // lantern
828                            18 => s.active_gear_slot = 17,
829                            // reset to 0 if unexpected
830                            _ => s.active_gear_slot = 0,
831                        }
832                    }),
833                    MenuInput::Left => state.update(|s| {
834                        match s.active_gear_slot {
835                            // weapon switch button
836                            0 => {},
837                            // primary weapon 1
838                            1 => s.active_gear_slot = 0 + 1,
839                            // secondary weapon 1
840                            2 => s.active_gear_slot = 1,
841                            // secondary weapon 2
842                            3 => s.active_gear_slot = 2,
843                            // primary weapon 1
844                            4 => s.active_gear_slot = 3,
845                            // back
846                            5 => {},
847                            // pants
848                            6 => s.active_gear_slot = 5,
849                            // shoes
850                            7 => s.active_gear_slot = 6,
851                            // jewelry left
852                            8 => {},
853                            // belt
854                            9 => s.active_gear_slot = 8,
855                            // jewelry right
856                            10 => s.active_gear_slot = 9,
857                            // shoulder
858                            11 => {},
859                            // chest
860                            12 => s.active_gear_slot = 11,
861                            // hands/gloves
862                            13 => s.active_gear_slot = 12,
863                            // jewelry center
864                            14 => {},
865                            // hat
866                            15 => {},
867                            // tabard
868                            16 => s.active_gear_slot = 13,
869                            // glider
870                            17 => s.active_gear_slot = 14,
871                            // lantern
872                            18 => s.active_gear_slot = 15,
873                            // reset to 0 if unexpected
874                            _ => s.active_gear_slot = 0,
875                        }
876                    }),
877                    MenuInput::Right => state.update(|s| {
878                        match s.active_gear_slot {
879                            // weapon switch button
880                            0 => s.active_gear_slot = 1,
881                            // primary weapon 1
882                            1 => s.active_gear_slot = 2,
883                            // secondary weapon 1
884                            2 => s.active_gear_slot = 3,
885                            // secondary weapon 2
886                            3 => s.active_gear_slot = 4,
887                            // primary weapon 1
888                            4 => s.active_gear_slot = 16,
889                            // back
890                            5 => s.active_gear_slot = 6,
891                            // pants
892                            6 => s.active_gear_slot = 7,
893                            // shoes
894                            7 => s.active_gear_slot = 16,
895                            // jewelry left
896                            8 => s.active_gear_slot = 9,
897                            // belt
898                            9 => s.active_gear_slot = 10,
899                            // jewelry right
900                            10 => s.active_gear_slot = 16,
901                            // shoulder
902                            11 => s.active_gear_slot = 12,
903                            // chest
904                            12 => s.active_gear_slot = 13,
905                            // hands/gloves
906                            13 => s.active_gear_slot = 16,
907                            // jewelry center
908                            14 => s.active_gear_slot = 17,
909                            // hat
910                            15 => s.active_gear_slot = 18,
911                            // tabard
912                            16 => {},
913                            // glider
914                            17 => {},
915                            // lantern
916                            18 => {},
917                            // reset to 0 if unexpected
918                            _ => s.active_gear_slot = 0,
919                        }
920                    }),
921                    MenuInput::Apply => {
922                        // TODO
923                    },
924                    MenuInput::Back => {
925                        // Typically, we want child widgets to handle their own back events
926                        // This back event only applies to the gear, which is in this widget
927                        events.push(Event::Close);
928                    },
929                    _ => {},
930                }
931            }
932        }
933
934        let bag_tooltip = Tooltip::new({
935            // Edge images [t, b, r, l]
936            // Corner images [tr, tl, br, bl]
937            let edge = &self.rot_imgs.tt_side;
938            let corner = &self.rot_imgs.tt_corner;
939            ImageFrame::new(
940                [edge.cw180, edge.none, edge.cw270, edge.cw90],
941                [corner.none, corner.cw270, corner.cw90, corner.cw180],
942                Color::Rgba(0.08, 0.07, 0.04, 1.0),
943                5.0,
944            )
945        })
946        .title_font_size(self.fonts.cyri.scale(15))
947        .parent(ui.window)
948        .desc_font_size(self.fonts.cyri.scale(12))
949        .font_id(self.fonts.cyri.conrod_id)
950        .desc_text_color(TEXT_COLOR);
951
952        if let Some(inventory) = self.client.inventories().get(self.info.viewpoint_entity) {
953            // Tooltips
954            let tooltip = Tooltip::new({
955                // Edge images [t, b, r, l]
956                // Corner images [tr, tl, br, bl]
957                let edge = &self.rot_imgs.tt_side;
958                let corner = &self.rot_imgs.tt_corner;
959                ImageFrame::new(
960                    [edge.cw180, edge.none, edge.cw270, edge.cw90],
961                    [corner.none, corner.cw270, corner.cw90, corner.cw180],
962                    Color::Rgba(0.08, 0.07, 0.04, 1.0),
963                    5.0,
964                )
965            })
966            .title_font_size(self.fonts.cyri.scale(15))
967            .parent(ui.window)
968            .desc_font_size(self.fonts.cyri.scale(12))
969            .font_id(self.fonts.cyri.conrod_id)
970            .desc_text_color(TEXT_COLOR);
971
972            let item_tooltip = ItemTooltip::new(
973                {
974                    // Edge images [t, b, r, l]
975                    // Corner images [tr, tl, br, bl]
976                    let edge = &self.rot_imgs.tt_side;
977                    let corner = &self.rot_imgs.tt_corner;
978                    ImageFrame::new(
979                        [edge.cw180, edge.none, edge.cw270, edge.cw90],
980                        [corner.none, corner.cw270, corner.cw90, corner.cw180],
981                        Color::Rgba(0.08, 0.07, 0.04, 1.0),
982                        5.0,
983                    )
984                },
985                self.client,
986                self.info,
987                self.imgs,
988                self.item_imgs,
989                self.pulse,
990                self.msm,
991                self.rbm,
992                Some(inventory),
993                self.localized_strings,
994                self.item_i18n,
995            )
996            .title_font_size(self.fonts.cyri.scale(20))
997            .parent(ui.window)
998            .desc_font_size(self.fonts.cyri.scale(12))
999            .font_id(self.fonts.cyri.conrod_id)
1000            .desc_text_color(TEXT_COLOR);
1001
1002            for event in InventoryScroller::new(
1003                self.client,
1004                self.global_state,
1005                self.imgs,
1006                self.item_imgs,
1007                self.fonts,
1008                self.item_tooltip_manager,
1009                self.slot_manager,
1010                self.pulse,
1011                self.menu_events,
1012                state.active_content,
1013                self.localized_strings,
1014                self.item_i18n,
1015                self.show.stats,
1016                self.show.bag_inv,
1017                true,
1018                &item_tooltip,
1019                self.localized_strings.get_content(&self.stats.name),
1020                self.info.viewpoint_entity,
1021                true,
1022                inventory,
1023                &state.bg_ids,
1024                self.show.crafting_fields.salvage,
1025                self.show.bag_details,
1026            )
1027            .set(state.ids.inventory_scroller, ui)
1028            {
1029                match event {
1030                    InventoryScrollerEvent::Drag(pos) => {
1031                        events.push(Event::MoveBag(pos));
1032                    },
1033                    InventoryScrollerEvent::ChangeLocalFocus(change) => state.update(|s| {
1034                        s.active_content = change;
1035                    }),
1036                    InventoryScrollerEvent::Close => {
1037                        events.push(Event::Close);
1038                    },
1039                }
1040            }
1041
1042            // change local focus from gear to inventory after inventory actions have been
1043            // registered
1044            if change_local_focus {
1045                state.update(|s| {
1046                    s.active_content = 0;
1047                })
1048            }
1049
1050            // Char Pixel-Art
1051            Image::new(self.imgs.char_art)
1052                .w_h(40.0, 37.0)
1053                .top_left_with_margins_on(state.bg_ids.bg, 4.0, 2.0)
1054                .set(state.ids.char_ico, ui);
1055
1056            let buttons_top = if self.show.bag_inv { 53.0 } else { 460.0 };
1057            let (txt, btn, hover, press) = if self.show.bag_details {
1058                (
1059                    "Grid mode",
1060                    self.imgs.grid_btn,
1061                    self.imgs.grid_btn_hover,
1062                    self.imgs.grid_btn_press,
1063                )
1064            } else {
1065                (
1066                    "List mode",
1067                    self.imgs.list_btn,
1068                    self.imgs.list_btn_hover,
1069                    self.imgs.list_btn_press,
1070                )
1071            };
1072            let details_btn = Button::image(btn)
1073                .w_h(32.0, 17.0)
1074                .hover_image(hover)
1075                .press_image(press);
1076            if details_btn
1077                .mid_top_with_margin_on(state.bg_ids.bg_frame, buttons_top)
1078                .with_tooltip(self.tooltip_manager, txt, "", &bag_tooltip, TEXT_COLOR)
1079                .set(state.ids.bag_details_btn, ui)
1080                .was_clicked()
1081            {
1082                events.push(Event::SetDetailsMode(!self.show.bag_details));
1083            }
1084            // Button to expand bag
1085            let (txt, btn, hover, press) = if self.show.bag_inv {
1086                (
1087                    "Show Loadout",
1088                    self.imgs.collapse_btn,
1089                    self.imgs.collapse_btn_hover,
1090                    self.imgs.collapse_btn_press,
1091                )
1092            } else {
1093                (
1094                    "Expand Bag",
1095                    self.imgs.expand_btn,
1096                    self.imgs.expand_btn_hover,
1097                    self.imgs.expand_btn_press,
1098                )
1099            };
1100            let expand_btn = Button::image(btn)
1101                .w_h(30.0, 17.0)
1102                .hover_image(hover)
1103                .press_image(press);
1104
1105            // Only show expand button when it's needed...
1106            if (inventory.slots().count() > 45 || self.show.bag_inv)
1107                && expand_btn
1108                    .top_right_with_margins_on(state.bg_ids.bg_frame, buttons_top, 37.0)
1109                    .with_tooltip(self.tooltip_manager, txt, "", &bag_tooltip, TEXT_COLOR)
1110                    .set(state.ids.bag_expand_btn, ui)
1111                    .was_clicked()
1112            {
1113                events.push(Event::BagExpand);
1114            }
1115
1116            // Sort mode inventory button
1117            if Button::image(self.imgs.inv_sort_btn)
1118            .w_h(30.0, 17.0)
1119            .hover_image(self.imgs.inv_sort_btn_hover)
1120            .press_image(self.imgs.inv_sort_btn_press)
1121            .top_left_with_margins_on(state.bg_ids.bg_frame, buttons_top, 87.0) // 30 + 10 + 47
1122            .with_tooltip(
1123                self.tooltip_manager,
1124                &(match self.global_state.settings.inventory.sort_order.next() {
1125                    InventorySortOrder::Name => i18n.get_msg("hud-bag-change_to_sort_by_name"),
1126                    InventorySortOrder::Quality => i18n.get_msg("hud-bag-change_to_sort_by_quality"),
1127                    InventorySortOrder::Category => i18n.get_msg("hud-bag-change_to_sort_by_category"),
1128                    InventorySortOrder::Tag => i18n.get_msg("hud-bag-change_to_sort_by_tag"),
1129                    InventorySortOrder::Amount => i18n.get_msg("hud-bag-change_to_sort_by_quantity"),
1130                }),
1131                "",
1132                &tooltip,
1133                color::WHITE,
1134            )
1135            .set(state.ids.inventory_sort, ui)
1136            .was_clicked()
1137            {
1138                // cycle sorting mode
1139                events.push(Event::ChangeInventorySortOrder(
1140                    self.global_state.settings.inventory.sort_order.next(),
1141                ));
1142            }
1143            // Sort inventory button with selected mode
1144            if Button::image(self.imgs.inv_sort_selected_btn)
1145                .w_h(30.0, 17.0)
1146                .hover_image(self.imgs.inv_sort_selected_btn_hover)
1147                .press_image(self.imgs.inv_sort_selected_btn_press)
1148                .top_left_with_margins_on(state.bg_ids.bg_frame, buttons_top, 47.0)
1149                .with_tooltip(
1150                    self.tooltip_manager,
1151                    &(match self.global_state.settings.inventory.sort_order {
1152                        InventorySortOrder::Name => i18n.get_msg("hud-bag-sort_by_name"),
1153                        InventorySortOrder::Quality => i18n.get_msg("hud-bag-sort_by_quality"),
1154                        InventorySortOrder::Category => i18n.get_msg("hud-bag-sort_by_category"),
1155                        InventorySortOrder::Tag => i18n.get_msg("hud-bag-sort_by_tag"),
1156                        InventorySortOrder::Amount => i18n.get_msg("hud-bag-sort_by_quantity"),
1157                    }),
1158                    "",
1159                    &tooltip,
1160                    color::WHITE,
1161                )
1162                .set(state.ids.inventory_sort_selected, ui)
1163                .was_clicked()
1164            {
1165                events.push(Event::SortInventory(
1166                    self.global_state.settings.inventory.sort_order,
1167                ));
1168            }
1169
1170            // Armor Slots
1171            let mut slot_maker = SlotMaker {
1172                empty_slot: self.imgs.armor_slot_empty,
1173                hovered_slot: self.imgs.skillbar_index,
1174                filled_slot: self.imgs.armor_slot,
1175                selected_slot: self.imgs.armor_slot_sel,
1176                background_color: Some(UI_HIGHLIGHT_0),
1177                content_size: ContentSize {
1178                    width_height_ratio: 1.0,
1179                    max_fraction: 0.75, /* Changes the item image size by setting a maximum
1180                                         * fraction
1181                                         * of either the width or height */
1182                },
1183                selected_content_scale: 1.067,
1184                amount_font: self.fonts.cyri.conrod_id,
1185                amount_margins: Vec2::new(-4.0, 0.0),
1186                amount_font_size: self.fonts.cyri.scale(12),
1187                amount_text_color: TEXT_COLOR,
1188                content_source: inventory,
1189                image_source: self.item_imgs,
1190                slot_manager: Some(self.slot_manager),
1191                last_input: &self.global_state.window.last_input(),
1192                pulse: self.pulse,
1193            };
1194
1195            // NOTE: Yes, macros considered harmful.
1196            // Though, this code mutably captures two different fields of `self`
1197            // This works because it's different branches of if-let
1198            // so in reality borrow checker allows you to do this as you
1199            // capture only one field.
1200            //
1201            // The less impossible, but still tricky part is denote type of
1202            // `$slot_maker` which has 1 lifetype parameter and 3 type parameters
1203            // in such way that it implements all traits conrod needs.
1204            //
1205            // And final part is that this uses that much of arguments
1206            // that just by passing all of them, you will get about the same
1207            // amount of lines this macro has or even more.
1208            //
1209            // So considering how many times we copy-paste this code
1210            // and how easy this macro looks it sounds like lawful evil.
1211            //
1212            // What this actually does is checks if we have equipped item on this slot
1213            // and if we do, display item tooltip for it.
1214            // If not, just show text of slot name.
1215            macro_rules! set_tooltip {
1216                ($slot_maker:expr, $slot_id:expr, $slot:expr, $desc:expr) => {
1217                    if let Some(item) = inventory.equipped($slot) {
1218                        let manager = &mut *self.item_tooltip_manager;
1219                        $slot_maker
1220                            .with_item_tooltip(
1221                                manager,
1222                                core::iter::once(item as &dyn ItemDesc),
1223                                &None,
1224                                &item_tooltip,
1225                            )
1226                            .set($slot_id, ui)
1227                    } else {
1228                        let manager = &mut *self.tooltip_manager;
1229                        $slot_maker
1230                            .with_tooltip(manager, &i18n.get_msg($desc), "", &tooltip, color::WHITE)
1231                            .set($slot_id, ui)
1232                    }
1233                };
1234            }
1235
1236            let filled_slot = self.imgs.armor_slot;
1237            if !self.show.bag_inv {
1238                // Stat icons and text
1239                state.update(|s| {
1240                    s.ids
1241                        .stat_icons
1242                        .resize(STATS.len(), &mut ui.widget_id_generator())
1243                });
1244                state.update(|s| {
1245                    s.ids
1246                        .stat_txts
1247                        .resize(STATS.len(), &mut ui.widget_id_generator())
1248                });
1249                // Stats
1250                let combat_rating = combat_rating(
1251                    inventory,
1252                    self.health,
1253                    self.energy,
1254                    self.poise,
1255                    self.skill_set,
1256                    *self.body,
1257                    self.msm,
1258                )
1259                .min(999.9);
1260                let indicator_col = cr_color(combat_rating);
1261                for i in STATS.iter().copied().enumerate() {
1262                    let btn = Button::image(match i.1 {
1263                        "Health" => self.imgs.health_ico,
1264                        "Energy" => self.imgs.energy_ico,
1265                        "Combat Rating" => self.imgs.combat_rating_ico,
1266                        "Protection" => self.imgs.protection_ico,
1267                        "Stun Resilience" => self.imgs.stun_res_ico,
1268                        "Stealth" => self.imgs.stealth_rating_ico,
1269                        _ => self.imgs.nothing,
1270                    })
1271                    .w_h(20.0, 20.0)
1272                    .image_color(if i.1 == "Combat Rating" {
1273                        indicator_col
1274                    } else {
1275                        TEXT_COLOR
1276                    });
1277                    let protection_txt = format!(
1278                        "{}%",
1279                        (100.0
1280                            * Damage::compute_damage_reduction(
1281                                None,
1282                                Some(inventory),
1283                                Some(self.stats),
1284                                self.msm
1285                            )) as i32
1286                    );
1287                    let health_txt = format!("{}", self.health.maximum().round() as usize);
1288                    let energy_txt = format!("{}", self.energy.maximum().round() as usize);
1289                    let combat_rating_txt = format!("{}", (combat_rating * 10.0) as usize);
1290                    let stun_res_txt = format!(
1291                        "{}",
1292                        (100.0
1293                            * Poise::compute_poise_damage_reduction(
1294                                Some(inventory),
1295                                self.msm,
1296                                None,
1297                                Some(self.stats),
1298                            )) as i32
1299                    );
1300                    let stealth_txt = format!(
1301                        "{:.1}%",
1302                        ((1.0
1303                            - perception_dist_multiplier_from_stealth(
1304                                Some(inventory),
1305                                None,
1306                                self.msm
1307                            ))
1308                            * 100.0)
1309                    );
1310                    let btn = if i.0 == 0 {
1311                        btn.top_left_with_margins_on(state.bg_ids.bg_frame, 55.0, 10.0)
1312                    } else {
1313                        btn.down_from(state.ids.stat_icons[i.0 - 1], 7.0)
1314                    };
1315                    let tooltip_head = match i.1 {
1316                        "Health" => i18n.get_msg("hud-bag-health"),
1317                        "Energy" => i18n.get_msg("hud-bag-energy"),
1318                        "Combat Rating" => i18n.get_msg("hud-bag-combat_rating"),
1319                        "Protection" => i18n.get_msg("hud-bag-protection"),
1320                        "Stun Resilience" => i18n.get_msg("hud-bag-stun_res"),
1321                        "Stealth" => i18n.get_msg("hud-bag-stealth"),
1322                        _ => Cow::Borrowed(""),
1323                    };
1324                    let tooltip_txt = match i.1 {
1325                        "Combat Rating" => i18n.get_msg("hud-bag-combat_rating_desc"),
1326                        "Protection" => i18n.get_msg("hud-bag-protection_desc"),
1327                        "Stun Resilience" => i18n.get_msg("hud-bag-stun_res_desc"),
1328                        _ => Cow::Borrowed(""),
1329                    };
1330                    btn.with_tooltip(
1331                        self.tooltip_manager,
1332                        &tooltip_head,
1333                        &tooltip_txt,
1334                        &bag_tooltip,
1335                        TEXT_COLOR,
1336                    )
1337                    .set(state.ids.stat_icons[i.0], ui);
1338                    Text::new(match i.1 {
1339                        "Health" => &health_txt,
1340                        "Energy" => &energy_txt,
1341                        "Combat Rating" => &combat_rating_txt,
1342                        "Protection" => &protection_txt,
1343                        "Stun Resilience" => &stun_res_txt,
1344                        "Stealth" => &stealth_txt,
1345                        _ => "",
1346                    })
1347                    .right_from(state.ids.stat_icons[i.0], 10.0)
1348                    .font_id(self.fonts.cyri.conrod_id)
1349                    .font_size(self.fonts.cyri.scale(14))
1350                    .color(TEXT_COLOR)
1351                    .graphics_for(state.ids.stat_icons[i.0])
1352                    .set(state.ids.stat_txts[i.0], ui);
1353                }
1354                // Loadout Slots
1355                // Head
1356                let item_slot = EquipSlot::Armor(ArmorSlot::Head);
1357                let slot = slot_maker
1358                    .fabricate(
1359                        item_slot,
1360                        [45.0; 2],
1361                        state.active_gear_slot == 15 && state.active_content == 2,
1362                        false,
1363                    )
1364                    .mid_top_with_margin_on(state.bg_ids.bg_frame, 60.0)
1365                    .with_icon(self.imgs.head_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN))
1366                    .filled_slot(filled_slot);
1367
1368                let slot_id = state.ids.head_slot;
1369                set_tooltip!(slot, slot_id, item_slot, "hud-bag-head");
1370
1371                // Necklace
1372                let item_slot = EquipSlot::Armor(ArmorSlot::Neck);
1373                let slot = slot_maker
1374                    .fabricate(
1375                        item_slot,
1376                        [45.0; 2],
1377                        state.active_gear_slot == 14 && state.active_content == 2,
1378                        false,
1379                    )
1380                    .mid_bottom_with_margin_on(state.ids.head_slot, -55.0)
1381                    .with_icon(self.imgs.necklace_bg, Vec2::new(40.0, 31.0), Some(UI_MAIN))
1382                    .filled_slot(filled_slot);
1383
1384                let slot_id = state.ids.neck_slot;
1385                set_tooltip!(slot, slot_id, item_slot, "hud-bag-neck");
1386
1387                // Chest
1388                //Image::new(self.imgs.armor_slot) // different graphics for empty/non empty
1389                let item_slot = EquipSlot::Armor(ArmorSlot::Chest);
1390                let slot = slot_maker
1391                    .fabricate(
1392                        item_slot,
1393                        [85.0; 2],
1394                        state.active_gear_slot == 12 && state.active_content == 2,
1395                        false,
1396                    )
1397                    .mid_bottom_with_margin_on(state.ids.neck_slot, -95.0)
1398                    .with_icon(self.imgs.chest_bg, Vec2::new(64.0, 42.0), Some(UI_MAIN))
1399                    .filled_slot(filled_slot);
1400
1401                let slot_id = state.ids.chest_slot;
1402                set_tooltip!(slot, slot_id, item_slot, "hud-bag-chest");
1403
1404                // Shoulders
1405                let item_slot = EquipSlot::Armor(ArmorSlot::Shoulders);
1406                let slot = slot_maker
1407                    .fabricate(
1408                        item_slot,
1409                        [70.0; 2],
1410                        state.active_gear_slot == 11 && state.active_content == 2,
1411                        false,
1412                    )
1413                    .bottom_left_with_margins_on(state.ids.chest_slot, 0.0, -80.0)
1414                    .with_icon(self.imgs.shoulders_bg, Vec2::new(60.0, 36.0), Some(UI_MAIN))
1415                    .filled_slot(filled_slot);
1416
1417                let slot_id = state.ids.shoulders_slot;
1418                set_tooltip!(slot, slot_id, item_slot, "hud-bag-shoulders");
1419
1420                // Hands
1421                let item_slot = EquipSlot::Armor(ArmorSlot::Hands);
1422                let slot = slot_maker
1423                    .fabricate(
1424                        item_slot,
1425                        [70.0; 2],
1426                        state.active_gear_slot == 13 && state.active_content == 2,
1427                        false,
1428                    )
1429                    .bottom_right_with_margins_on(state.ids.chest_slot, 0.0, -80.0)
1430                    .with_icon(self.imgs.hands_bg, Vec2::new(55.0, 60.0), Some(UI_MAIN))
1431                    .filled_slot(filled_slot);
1432
1433                let slot_id = state.ids.hands_slot;
1434                set_tooltip!(slot, slot_id, item_slot, "hud-bag-hands");
1435
1436                // Belt
1437                let item_slot = EquipSlot::Armor(ArmorSlot::Belt);
1438                let slot = slot_maker
1439                    .fabricate(
1440                        item_slot,
1441                        [45.0; 2],
1442                        state.active_gear_slot == 9 && state.active_content == 2,
1443                        false,
1444                    )
1445                    .mid_bottom_with_margin_on(state.ids.chest_slot, -55.0)
1446                    .with_icon(self.imgs.belt_bg, Vec2::new(40.0, 23.0), Some(UI_MAIN))
1447                    .filled_slot(filled_slot);
1448
1449                let slot_id = state.ids.belt_slot;
1450                set_tooltip!(slot, slot_id, item_slot, "hud-bag-belt");
1451
1452                // Legs
1453                let item_slot = EquipSlot::Armor(ArmorSlot::Legs);
1454                let slot = slot_maker
1455                    .fabricate(
1456                        item_slot,
1457                        [85.0; 2],
1458                        state.active_gear_slot == 6 && state.active_content == 2,
1459                        false,
1460                    )
1461                    .mid_bottom_with_margin_on(state.ids.belt_slot, -95.0)
1462                    .with_icon(self.imgs.legs_bg, Vec2::new(48.0, 70.0), Some(UI_MAIN))
1463                    .filled_slot(filled_slot);
1464
1465                let slot_id = state.ids.legs_slot;
1466                set_tooltip!(slot, slot_id, item_slot, "hud-bag-legs");
1467
1468                // Ring right
1469                let item_slot = EquipSlot::Armor(ArmorSlot::Ring1);
1470                let slot = slot_maker
1471                    .fabricate(
1472                        item_slot,
1473                        [45.0; 2],
1474                        state.active_gear_slot == 10 && state.active_content == 2,
1475                        false,
1476                    )
1477                    .bottom_left_with_margins_on(state.ids.hands_slot, -55.0, 0.0)
1478                    .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN))
1479                    .filled_slot(filled_slot);
1480
1481                let slot_id = state.ids.ring1_slot;
1482                set_tooltip!(slot, slot_id, item_slot, "hud-bag-ring");
1483
1484                // Ring left
1485                let item_slot = EquipSlot::Armor(ArmorSlot::Ring2);
1486                let slot = slot_maker
1487                    .fabricate(
1488                        item_slot,
1489                        [45.0; 2],
1490                        state.active_gear_slot == 8 && state.active_content == 2,
1491                        false,
1492                    )
1493                    .bottom_right_with_margins_on(state.ids.shoulders_slot, -55.0, 0.0)
1494                    .with_icon(self.imgs.ring_bg, Vec2::new(36.0, 40.0), Some(UI_MAIN))
1495                    .filled_slot(filled_slot);
1496
1497                let slot_id = state.ids.ring2_slot;
1498                set_tooltip!(slot, slot_id, item_slot, "hud-bag-ring");
1499
1500                // Back
1501                let item_slot = EquipSlot::Armor(ArmorSlot::Back);
1502                let slot = slot_maker
1503                    .fabricate(
1504                        item_slot,
1505                        [45.0; 2],
1506                        state.active_gear_slot == 5 && state.active_content == 2,
1507                        false,
1508                    )
1509                    .down_from(state.ids.ring2_slot, 10.0)
1510                    .with_icon(self.imgs.back_bg, Vec2::new(33.0, 40.0), Some(UI_MAIN))
1511                    .filled_slot(filled_slot);
1512
1513                let slot_id = state.ids.back_slot;
1514                set_tooltip!(slot, slot_id, item_slot, "hud-bag-back");
1515
1516                // Foot
1517                let item_slot = EquipSlot::Armor(ArmorSlot::Feet);
1518                let slot = slot_maker
1519                    .fabricate(
1520                        item_slot,
1521                        [45.0; 2],
1522                        state.active_gear_slot == 7 && state.active_content == 2,
1523                        false,
1524                    )
1525                    .down_from(state.ids.ring1_slot, 10.0)
1526                    .with_icon(self.imgs.feet_bg, Vec2::new(32.0, 40.0), Some(UI_MAIN))
1527                    .filled_slot(filled_slot);
1528
1529                let slot_id = state.ids.feet_slot;
1530                set_tooltip!(slot, slot_id, item_slot, "hud-bag-feet");
1531
1532                // Lantern
1533                let item_slot = EquipSlot::Lantern;
1534                let slot = slot_maker
1535                    .fabricate(
1536                        item_slot,
1537                        [45.0; 2],
1538                        state.active_gear_slot == 18 && state.active_content == 2,
1539                        false,
1540                    )
1541                    .top_right_with_margins_on(state.bg_ids.bg_frame, 60.0, 5.0)
1542                    .with_icon(self.imgs.lantern_bg, Vec2::new(24.0, 38.0), Some(UI_MAIN))
1543                    .filled_slot(filled_slot);
1544
1545                let slot_id = state.ids.lantern_slot;
1546                set_tooltip!(slot, slot_id, item_slot, "hud-bag-lantern");
1547
1548                // Glider
1549                let item_slot = EquipSlot::Glider;
1550                let slot = slot_maker
1551                    .fabricate(
1552                        item_slot,
1553                        [45.0; 2],
1554                        state.active_gear_slot == 17 && state.active_content == 2,
1555                        false,
1556                    )
1557                    .down_from(state.ids.lantern_slot, 5.0)
1558                    .with_icon(self.imgs.glider_bg, Vec2::new(38.0, 38.0), Some(UI_MAIN))
1559                    .filled_slot(filled_slot);
1560
1561                let slot_id = state.ids.glider_slot;
1562                set_tooltip!(slot, slot_id, item_slot, "hud-bag-glider");
1563
1564                // Tabard
1565                let item_slot = EquipSlot::Armor(ArmorSlot::Tabard);
1566                let slot = slot_maker
1567                    .fabricate(
1568                        item_slot,
1569                        [45.0; 2],
1570                        state.active_gear_slot == 16 && state.active_content == 2,
1571                        false,
1572                    )
1573                    .down_from(state.ids.glider_slot, 5.0)
1574                    .with_icon(self.imgs.tabard_bg, Vec2::new(38.0, 38.0), Some(UI_MAIN))
1575                    .filled_slot(filled_slot);
1576
1577                let slot_id = state.ids.tabard_slot;
1578                set_tooltip!(slot, slot_id, item_slot, "hud-bag-tabard");
1579
1580                // Active Mainhand/Left-Slot
1581                let item_slot = EquipSlot::ActiveMainhand;
1582                let slot = slot_maker
1583                    .fabricate(
1584                        item_slot,
1585                        [85.0; 2],
1586                        state.active_gear_slot == 1 && state.active_content == 2,
1587                        false,
1588                    )
1589                    .bottom_right_with_margins_on(state.ids.back_slot, -95.0, 0.0)
1590                    .with_icon(self.imgs.mainhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
1591                    .filled_slot(filled_slot);
1592
1593                let slot_id = state.ids.active_mainhand_slot;
1594                set_tooltip!(slot, slot_id, item_slot, "hud-bag-mainhand");
1595
1596                // Active Offhand/Right-Slot
1597                let item_slot = EquipSlot::ActiveOffhand;
1598                let slot = slot_maker
1599                    .fabricate(
1600                        item_slot,
1601                        [85.0; 2],
1602                        state.active_gear_slot == 4 && state.active_content == 2,
1603                        false,
1604                    )
1605                    .bottom_left_with_margins_on(state.ids.feet_slot, -95.0, 0.0)
1606                    .with_icon(self.imgs.offhand_bg, Vec2::new(75.0, 75.0), Some(UI_MAIN))
1607                    .filled_slot(filled_slot);
1608
1609                let slot_id = state.ids.active_offhand_slot;
1610                set_tooltip!(slot, slot_id, item_slot, "hud-bag-offhand");
1611
1612                // Inactive Mainhand/Left-Slot
1613                let item_slot = EquipSlot::InactiveMainhand;
1614                let slot = slot_maker
1615                    .fabricate(
1616                        item_slot,
1617                        [40.0; 2],
1618                        state.active_gear_slot == 2 && state.active_content == 2,
1619                        false,
1620                    )
1621                    .bottom_right_with_margins_on(state.ids.active_mainhand_slot, 3.0, -47.0)
1622                    .with_icon(self.imgs.mainhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN))
1623                    .filled_slot(filled_slot);
1624
1625                let slot_id = state.ids.inactive_mainhand_slot;
1626                set_tooltip!(slot, slot_id, item_slot, "hud-bag-inactive_mainhand");
1627
1628                // Inactive Offhand/Right-Slot
1629                let item_slot = EquipSlot::InactiveOffhand;
1630                let slot = slot_maker
1631                    .fabricate(
1632                        item_slot,
1633                        [40.0; 2],
1634                        state.active_gear_slot == 3 && state.active_content == 2,
1635                        false,
1636                    )
1637                    .bottom_left_with_margins_on(state.ids.active_offhand_slot, 3.0, -47.0)
1638                    .with_icon(self.imgs.offhand_bg, Vec2::new(35.0, 35.0), Some(UI_MAIN))
1639                    .filled_slot(filled_slot);
1640
1641                let slot_id = state.ids.inactive_offhand_slot;
1642                set_tooltip!(slot, slot_id, item_slot, "hud-bag-inactive_offhand");
1643
1644                if Button::image(self.imgs.swap_equipped_weapons_btn)
1645                    .hover_image(self.imgs.swap_equipped_weapons_btn_hover)
1646                    .press_image(self.imgs.swap_equipped_weapons_btn_press)
1647                    .w_h(32.0, 40.0)
1648                    .bottom_left_with_margins_on(state.bg_ids.bg_frame, 0.0, 23.3)
1649                    .align_middle_y_of(state.ids.active_mainhand_slot)
1650                    .with_tooltip(
1651                        self.tooltip_manager,
1652                        &i18n.get_msg("hud-bag-swap_equipped_weapons_title"),
1653                        &(if let Some(key) = self
1654                            .global_state
1655                            .settings
1656                            .controls
1657                            .get_binding(GameInput::SwapLoadout)
1658                        {
1659                            i18n.get_msg_ctx(
1660                                "hud-bag-swap_equipped_weapons_desc",
1661                                &i18n::fluent_args! {
1662                                    "key" => key.display_string()
1663                                },
1664                            )
1665                        } else {
1666                            Cow::Borrowed("")
1667                        }),
1668                        &tooltip,
1669                        color::WHITE,
1670                    )
1671                    .set(state.ids.swap_equipped_weapons_btn, ui)
1672                    .was_clicked()
1673                {
1674                    events.push(Event::SwapEquippedWeapons);
1675                }
1676            }
1677
1678            // Bag 1
1679            let item_slot = EquipSlot::Armor(ArmorSlot::Bag1);
1680            let slot = slot_maker
1681                .fabricate(item_slot, [35.0; 2], false, false)
1682                .bottom_left_with_margins_on(
1683                    state.bg_ids.bg_frame,
1684                    if self.show.bag_inv { 600.0 } else { 167.0 },
1685                    3.0,
1686                )
1687                .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
1688                .filled_slot(filled_slot);
1689
1690            let slot_id = state.ids.bag1_slot;
1691            set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
1692
1693            // Bag 2
1694            let item_slot = EquipSlot::Armor(ArmorSlot::Bag2);
1695            let slot = slot_maker
1696                .fabricate(item_slot, [35.0; 2], false, false)
1697                .down_from(state.ids.bag1_slot, 2.0)
1698                .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
1699                .filled_slot(filled_slot);
1700
1701            let slot_id = state.ids.bag2_slot;
1702            set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
1703
1704            // Bag 3
1705            let item_slot = EquipSlot::Armor(ArmorSlot::Bag3);
1706            let slot = slot_maker
1707                .fabricate(item_slot, [35.0; 2], false, false)
1708                .down_from(state.ids.bag2_slot, 2.0)
1709                .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
1710                .filled_slot(filled_slot);
1711
1712            let slot_id = state.ids.bag3_slot;
1713            set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
1714
1715            // Bag 4
1716            let item_slot = EquipSlot::Armor(ArmorSlot::Bag4);
1717            let slot = slot_maker
1718                .fabricate(item_slot, [35.0; 2], false, false)
1719                .down_from(state.ids.bag3_slot, 2.0)
1720                .with_icon(self.imgs.bag_bg, Vec2::new(28.0, 24.0), Some(UI_MAIN))
1721                .filled_slot(filled_slot);
1722
1723            let slot_id = state.ids.bag4_slot;
1724            set_tooltip!(slot, slot_id, item_slot, "hud-bag-bag");
1725
1726            // Close button
1727            if Button::image(self.imgs.close_btn)
1728                .w_h(24.0, 25.0)
1729                .hover_image(self.imgs.close_btn_hover)
1730                .press_image(self.imgs.close_btn_press)
1731                .top_right_with_margins_on(state.bg_ids.bg, 0.0, 0.0)
1732                .set(state.ids.bag_close, ui)
1733                .was_clicked()
1734            {
1735                events.push(Event::Close);
1736            }
1737        }
1738
1739        events
1740    }
1741}