Skip to main content

veloren_voxygen/ui/widgets/
slot.rs

1//! A widget for selecting a single value along some linear range.
2use crate::{hud::animate_by_pulse, window::LastInput};
3use conrod_core::{
4    Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, builder_methods, image,
5    input::{keyboard::ModifierKey, state::mouse},
6    text::font,
7    widget::{self, Image, Text},
8    widget_ids,
9};
10use vek::*;
11
12const AMOUNT_SHADOW_OFFSET: [f64; 2] = [1.0, 1.0];
13
14pub trait SlotKey<C, I>: Copy {
15    type ImageKey: PartialEq + Send + 'static;
16    /// Returns an Option since the slot could be empty
17    fn image_key(&self, source: &C) -> Option<(Self::ImageKey, Option<Color>)>;
18    fn amount(&self, source: &C) -> Option<u32>;
19    fn image_ids(key: &Self::ImageKey, source: &I) -> Vec<image::Id>;
20}
21
22pub trait SumSlot: Sized + PartialEq + Copy + Send + 'static {
23    fn drag_size(&self) -> Option<[f64; 2]>;
24}
25
26pub struct ContentSize {
27    // Width divided by height
28    pub width_height_ratio: f32,
29    // Max fraction of slot widget size that each side can be
30    pub max_fraction: f32,
31}
32
33pub struct SlotMaker<'a, C, I, S: SumSlot> {
34    pub empty_slot: image::Id,
35    pub hovered_slot: image::Id,
36    pub filled_slot: image::Id,
37    pub selected_slot: image::Id,
38    // Is this useful?
39    pub background_color: Option<Color>,
40    pub content_size: ContentSize,
41    // How to scale content size relative to base content size when selected
42    pub selected_content_scale: f32,
43    pub amount_font: font::Id,
44    pub amount_font_size: u32,
45    pub amount_margins: Vec2<f32>,
46    pub amount_text_color: Color,
47    pub content_source: &'a C,
48    pub image_source: &'a I,
49    pub slot_manager: Option<&'a mut SlotManager<S>>,
50    pub last_input: &'a LastInput,
51    pub pulse: f32,
52}
53
54impl<C, I, S> SlotMaker<'_, C, I, S>
55where
56    S: SumSlot,
57{
58    /// Creates a new Slot widget
59    ///
60    /// # arguments
61    /// * `contents` - the contents of the slot type
62    /// * `wh` - the width and height of the slot
63    /// * `menu_hover` - is the slot being highlighted by menu nav
64    /// * `menu_clicked` - was the hovered slot clicked via menu nav
65    pub fn fabricate<K: SlotKey<C, I> + Into<S>>(
66        &mut self,
67        contents: K,
68        wh: [f32; 2],
69        menu_hover: bool,
70        menu_clicked: bool,
71    ) -> Slot<'_, K, C, I, S> {
72        let content_size = {
73            let ContentSize {
74                max_fraction,
75                width_height_ratio,
76            } = self.content_size;
77            let w_max = max_fraction * wh[0];
78            let h_max = max_fraction * wh[1];
79            let max_ratio = w_max / h_max;
80            let (w, h) = if max_ratio > width_height_ratio {
81                (width_height_ratio * h_max, w_max)
82            } else {
83                (w_max, w_max / width_height_ratio)
84            };
85            Vec2::new(w, h)
86        };
87        Slot::new(
88            contents,
89            self.empty_slot,
90            self.hovered_slot,
91            self.selected_slot,
92            self.filled_slot,
93            content_size,
94            self.selected_content_scale,
95            self.amount_font,
96            self.amount_font_size,
97            self.amount_margins,
98            self.amount_text_color,
99            self.content_source,
100            self.image_source,
101            menu_hover,
102            menu_clicked,
103            self.last_input,
104            self.pulse,
105        )
106        .wh([wh[0] as f64, wh[1] as f64])
107        .and_then(self.background_color, |s, c| s.with_background_color(c))
108        .and_then(self.slot_manager.as_mut(), |s, m| s.with_manager(m))
109    }
110}
111
112#[derive(Clone, Copy)]
113enum ManagerState<K> {
114    Dragging(
115        widget::Id,
116        K,
117        image::Id,
118        /// Amount of items being dragged in the stack.
119        Option<u32>,
120    ),
121    Selected(widget::Id, K),
122    Idle,
123}
124
125enum Interaction {
126    Selected,
127    Dragging,
128    None,
129}
130
131pub enum Event<K> {
132    // Dragged to another slot
133    Dragged(K, K),
134    // Dragged to open space
135    Dropped(K),
136    // Dropped half of the stack
137    SplitDropped(K),
138    // Dragged half of the stack
139    SplitDragged(K, K),
140    // Right click when not dragging
141    Used(K),
142    // {Shift,Ctrl}-clicked
143    Request { slot: K, auto_quantity: bool },
144}
145// Handles interactions with slots
146pub struct SlotManager<S: SumSlot> {
147    state: ManagerState<S>,
148    // Rebuilt every frame
149    slot_ids: Vec<widget::Id>,
150    // Rebuilt every frame
151    slots: Vec<S>,
152    events: Vec<Event<S>>,
153    // Widget id for dragging image
154    drag_id: widget::Id,
155    // Size to display dragged content
156    // Note: could potentially be specialized for each slot if needed
157    drag_img_size: Vec2<f32>,
158    pub mouse_over_slot: Option<S>,
159    // Si prefixes settings
160    use_prefixes: bool,
161    prefix_switch_point: u32,
162    /* TODO(heyzoos) Will be useful for whoever works on rendering the number of items "in
163     * hand".
164     *
165     * drag_amount_id: widget::Id,
166     * drag_amount_shadow_id: widget::Id, */
167
168    /* Asset ID pointing to a font set.
169     * amount_font: font::Id, */
170
171    /* Specifies the size of the font used to display number of items held in
172     * a stack when dragging.
173     * amount_font_size: u32, */
174
175    /* Specifies how much space should be used in the margins of the item
176     * amount relative to the slot.
177     * amount_margins: Vec2<f32>, */
178
179    /* Specifies the color of the text used to display the number of items held
180     * in a stack when dragging.
181     * amount_text_color: Color, */
182}
183
184impl<S> SlotManager<S>
185where
186    S: SumSlot,
187{
188    pub fn new(
189        mut generator: widget::id::Generator,
190        drag_img_size: Vec2<f32>,
191        use_prefixes: bool,
192        prefix_switch_point: u32,
193        /* TODO(heyzoos) Will be useful for whoever works on rendering the number of items "in
194         * hand". amount_font: font::Id,
195         * amount_margins: Vec2<f32>,
196         * amount_font_size: u32,
197         * amount_text_color: Color, */
198    ) -> Self {
199        Self {
200            state: ManagerState::Idle,
201            slot_ids: Vec::new(),
202            slots: Vec::new(),
203            events: Vec::new(),
204            drag_id: generator.next(),
205            mouse_over_slot: None,
206            use_prefixes,
207            prefix_switch_point,
208            // TODO(heyzoos) Will be useful for whoever works on rendering the number of items "in
209            // hand". drag_amount_id: generator.next(),
210            // drag_amount_shadow_id: generator.next(),
211            // amount_font,
212            // amount_font_size,
213            // amount_margins,
214            // amount_text_color,
215            drag_img_size,
216        }
217    }
218
219    pub fn maintain(&mut self, ui: &mut conrod_core::UiCell) -> Vec<Event<S>> {
220        // Clear
221        let slot_ids = core::mem::take(&mut self.slot_ids);
222        let slots = core::mem::take(&mut self.slots);
223
224        // Detect drops by of selected item by clicking in empty space
225        if let ManagerState::Selected(_, slot) = self.state
226            && ui.widget_input(ui.window).clicks().left().next().is_some()
227        {
228            self.state = ManagerState::Idle;
229            self.events.push(Event::Dropped(slot));
230        }
231
232        let input = &ui.global_input().current;
233        self.mouse_over_slot = input
234            .widget_under_mouse
235            .and_then(|x| slot_ids.iter().position(|slot_id| *slot_id == x))
236            .map(|x| slots[x]);
237
238        // If dragging and mouse is released check if there is a slot widget under the
239        // mouse
240        if let ManagerState::Dragging(_, slot, content_img, drag_amount) = &self.state {
241            let content_img = *content_img;
242            let drag_amount = *drag_amount;
243
244            let dragged_size = if let Some(dragged_size) = slot.drag_size() {
245                dragged_size
246            } else {
247                self.drag_img_size.map(|e| e as f64).into_array()
248            };
249
250            // If we are dragging and we right click, drop half the stack
251            // on the ground or into the slot under the cursor. This only
252            // works with open slots or slots containing the same kind of
253            // item.
254
255            if drag_amount.is_some()
256                && let Some(id) = input.widget_under_mouse
257                && ui.widget_input(id).clicks().right().next().is_some()
258            {
259                if id == ui.window {
260                    let temp_slot = *slot;
261                    self.events.push(Event::SplitDropped(temp_slot));
262                } else if let Some(idx) = slot_ids.iter().position(|slot_id| *slot_id == id) {
263                    let (from, to) = (*slot, slots[idx]);
264                    if from != to {
265                        self.events.push(Event::SplitDragged(from, to));
266                    }
267                }
268            }
269
270            if let mouse::ButtonPosition::Up = input.mouse.buttons.left() {
271                // Get widget under the mouse
272                if let Some(id) = input.widget_under_mouse {
273                    // If over the window widget drop the contents
274                    if id == ui.window {
275                        self.events.push(Event::Dropped(*slot));
276                    } else if let Some(idx) = slot_ids.iter().position(|slot_id| *slot_id == id) {
277                        // If widget is a slot widget swap with it
278                        let (from, to) = (*slot, slots[idx]);
279                        // Don't drag if it is the same slot
280                        if from != to {
281                            self.events.push(Event::Dragged(from, to));
282                        }
283                    }
284                }
285                // Mouse released stop dragging
286                self.state = ManagerState::Idle;
287            }
288
289            // Draw image of contents being dragged
290            let [mouse_x, mouse_y] = input.mouse.xy;
291            super::ghost_image::GhostImage::new(content_img)
292                .wh(dragged_size)
293                .no_parent()
294                .xy([mouse_x, mouse_y])
295                .set(self.drag_id, ui);
296
297            // TODO(heyzoos) Will be useful for whoever works on rendering the
298            // number of items "in hand".
299            //
300            // if let Some(drag_amount) = drag_amount {
301            //     Text::new(format!("{}", drag_amount).as_str())
302            //         .parent(self.drag_id)
303            //         .font_id(self.amount_font)
304            //         .font_size(self.amount_font_size)
305            //         .bottom_right_with_margins_on(
306            //             self.drag_id,
307            //             self.amount_margins.x as f64,
308            //             self.amount_margins.y as f64,
309            //         )
310            //         .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
311            //         .set(self.drag_amount_shadow_id, ui);
312            //     Text::new(format!("{}", drag_amount).as_str())
313            //         .parent(self.drag_id)
314            //         .font_id(self.amount_font)
315            //         .font_size(self.amount_font_size)
316            //         .bottom_right_with_margins_on(
317            //             self.drag_id,
318            //             self.amount_margins.x as f64,
319            //             self.amount_margins.y as f64,
320            //         )
321            //         .color(self.amount_text_color)
322            //         .set(self.drag_amount_id, ui);
323            // }
324        }
325
326        core::mem::take(&mut self.events)
327    }
328
329    pub fn set_use_prefixes(&mut self, use_prefixes: bool) { self.use_prefixes = use_prefixes; }
330
331    pub fn set_prefix_switch_point(&mut self, prefix_switch_point: u32) {
332        self.prefix_switch_point = prefix_switch_point;
333    }
334
335    fn update(
336        &mut self,
337        widget: widget::Id,
338        slot: S,
339        ui: &conrod_core::Ui,
340        content_img: Option<Vec<image::Id>>,
341        drag_amount: Option<u32>,
342    ) -> Interaction {
343        // Add to list of slots
344        self.slot_ids.push(widget);
345        self.slots.push(slot);
346
347        let filled = content_img.is_some();
348        // If the slot is no longer filled deselect it or cancel dragging
349        match &self.state {
350            ManagerState::Selected(id, _) | ManagerState::Dragging(id, _, _, _)
351                if *id == widget && !filled =>
352            {
353                self.state = ManagerState::Idle;
354            },
355            _ => (),
356        }
357
358        // If this is the selected/dragged widget make sure the slot value is up to date
359        match &mut self.state {
360            ManagerState::Selected(id, stored_slot)
361            | ManagerState::Dragging(id, stored_slot, _, _)
362                if *id == widget =>
363            {
364                *stored_slot = slot
365            },
366            _ => (),
367        }
368
369        let input = ui.widget_input(widget);
370        let click_count = input.clicks().left().count();
371        if click_count > 0 {
372            self.state = if let ManagerState::Selected(id, other_slot) = self.state {
373                if id != widget {
374                    // Swap
375                    if slot != other_slot {
376                        self.events.push(Event::Dragged(other_slot, slot));
377                    }
378                    if click_count == 1 {
379                        ManagerState::Idle
380                    } else {
381                        ManagerState::Selected(widget, slot)
382                    }
383                } else {
384                    // Clicked widget was already selected; deselect widget
385                    ManagerState::Idle
386                }
387            } else {
388                // No widgets were selected
389                if filled {
390                    ManagerState::Selected(widget, slot)
391                } else {
392                    // Selected and then deselected with one or more clicks
393                    ManagerState::Idle
394                }
395            };
396        }
397
398        // Translate ctrl-clicks to stack-requests and shift-clicks to
399        // individual-requests
400        if let Some(click) = input.clicks().left().next()
401            && !matches!(self.state, ManagerState::Dragging(_, _, _, _))
402        {
403            match click.modifiers {
404                ModifierKey::CTRL => {
405                    self.events.push(Event::Request {
406                        slot,
407                        auto_quantity: true,
408                    });
409                    self.state = ManagerState::Idle;
410                },
411                ModifierKey::SHIFT => {
412                    self.events.push(Event::Request {
413                        slot,
414                        auto_quantity: false,
415                    });
416                    self.state = ManagerState::Idle;
417                },
418                _ => {},
419            }
420        }
421
422        // Use on right click if not dragging
423        if input.clicks().right().next().is_some() {
424            match self.state {
425                ManagerState::Selected(_, _) | ManagerState::Idle => {
426                    self.events.push(Event::Used(slot));
427                    // If something is selected, deselect
428                    self.state = ManagerState::Idle;
429                },
430                ManagerState::Dragging(_, _, _, _) => {},
431            }
432        }
433
434        // If not dragging and there is a drag event on this slot start dragging
435        if input.drags().left().next().is_some()
436            && !matches!(self.state, ManagerState::Dragging(_, _, _, _))
437        {
438            // Start dragging if widget is filled
439            if let Some(images) = content_img
440                && !images.is_empty()
441            {
442                self.state = ManagerState::Dragging(widget, slot, images[0], drag_amount);
443            }
444        }
445
446        // Determine whether this slot is being interacted with
447        match self.state {
448            ManagerState::Selected(id, _) if id == widget => Interaction::Selected,
449            ManagerState::Dragging(id, _, _, _) if id == widget => Interaction::Dragging,
450            _ => Interaction::None,
451        }
452    }
453
454    /// Emit a Used event and deselects the selected slot
455    pub fn use_selected(&mut self) {
456        if let ManagerState::Selected(_, slot) = self.state {
457            self.events.push(Event::Used(slot));
458            self.state = ManagerState::Idle;
459        }
460    }
461
462    /// Emit a Dropped event and deselects the selected slot
463    pub fn dropped_selected(&mut self) {
464        if let ManagerState::Selected(_, slot) = self.state {
465            self.events.push(Event::Dropped(slot));
466            self.state = ManagerState::Idle;
467        }
468    }
469
470    /// Returns Some(slot) if a slot is selected
471    pub fn selected(&self) -> Option<S> {
472        if let ManagerState::Selected(_, s) = self.state {
473            Some(s)
474        } else {
475            None
476        }
477    }
478
479    /// Selects a specified slot
480    pub fn select(&mut self, widget: widget::Id, slot: S) {
481        // Sets the slot to selected; if it has no content, it will be deselected on the
482        // next fn update call
483        self.state = ManagerState::Selected(widget, slot);
484    }
485
486    /// Sets the SlotManager into an idle state
487    pub fn idle(&mut self) { self.state = ManagerState::Idle; }
488}
489
490#[derive(WidgetCommon)]
491pub struct Slot<'a, K: SlotKey<C, I> + Into<S>, C, I, S: SumSlot> {
492    slot_key: K,
493
494    // Images for slot background and frame
495    empty_slot: image::Id,
496    hovered_slot: image::Id,
497    selected_slot: image::Id,
498    background_color: Option<Color>,
499
500    // Size of content image
501    content_size: Vec2<f32>,
502    selected_content_scale: f32,
503
504    icon: Option<(image::Id, Vec2<f32>, Option<Color>)>,
505
506    // Amount styling
507    amount_font: font::Id,
508    amount_font_size: u32,
509    amount_margins: Vec2<f32>,
510    amount_text_color: Color,
511
512    slot_manager: Option<&'a mut SlotManager<S>>,
513    filled_slot: image::Id,
514    // Should we just pass in the ImageKey?
515    content_source: &'a C,
516    image_source: &'a I,
517
518    // Menu button navigation
519    menu_hover: bool,
520    menu_click: bool,
521
522    last_input: &'a LastInput,
523
524    pulse: f32,
525
526    #[conrod(common_builder)]
527    common: widget::CommonBuilder,
528}
529
530widget_ids! {
531    struct Ids {
532        background,
533        icon,
534        amount,
535        amount_bg,
536        content,
537        slot_highlight,
538    }
539}
540
541/// Represents the state of the Slot widget.
542pub struct State<K> {
543    ids: Ids,
544    cached_images: Option<(K, Vec<image::Id>)>,
545}
546
547impl<'a, K, C, I, S> Slot<'a, K, C, I, S>
548where
549    K: SlotKey<C, I> + Into<S>,
550    S: SumSlot,
551{
552    builder_methods! {
553        pub with_background_color { background_color = Some(Color) }
554    }
555
556    #[must_use]
557    pub fn with_manager(mut self, slot_manager: &'a mut SlotManager<S>) -> Self {
558        self.slot_manager = Some(slot_manager);
559        self
560    }
561
562    #[must_use]
563    pub fn filled_slot(mut self, img: image::Id) -> Self {
564        self.filled_slot = img;
565        self
566    }
567
568    #[must_use]
569    pub fn with_icon(mut self, img: image::Id, size: Vec2<f32>, color: Option<Color>) -> Self {
570        self.icon = Some((img, size, color));
571        self
572    }
573
574    #[expect(clippy::too_many_arguments)]
575    fn new(
576        slot_key: K,
577        empty_slot: image::Id,
578        hovered_slot: image::Id,
579        filled_slot: image::Id,
580        selected_slot: image::Id,
581        content_size: Vec2<f32>,
582        selected_content_scale: f32,
583        amount_font: font::Id,
584        amount_font_size: u32,
585        amount_margins: Vec2<f32>,
586        amount_text_color: Color,
587        content_source: &'a C,
588        image_source: &'a I,
589        menu_hover: bool,
590        menu_click: bool,
591        last_input: &'a LastInput,
592        pulse: f32,
593    ) -> Self {
594        Self {
595            slot_key,
596            empty_slot,
597            hovered_slot,
598            filled_slot,
599            selected_slot,
600            background_color: None,
601            content_size,
602            selected_content_scale,
603            icon: None,
604            amount_font,
605            amount_font_size,
606            amount_margins,
607            amount_text_color,
608            slot_manager: None,
609            content_source,
610            image_source,
611            menu_hover,
612            menu_click,
613            last_input,
614            pulse,
615            common: widget::CommonBuilder::default(),
616        }
617    }
618}
619
620impl<K, C, I, S> Widget for Slot<'_, K, C, I, S>
621where
622    K: SlotKey<C, I> + Into<S>,
623    S: SumSlot,
624{
625    type Event = ();
626    type State = State<K::ImageKey>;
627    type Style = ();
628
629    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
630        State {
631            ids: Ids::new(id_gen),
632            cached_images: None,
633        }
634    }
635
636    fn style(&self) -> Self::Style {}
637
638    /// Update the state of the Slot.
639    fn update(mut self, args: widget::UpdateArgs<Self>) -> Self::Event {
640        let widget::UpdateArgs {
641            id,
642            state,
643            rect,
644            ui,
645            ..
646        } = args;
647
648        let Slot {
649            slot_key,
650            empty_slot,
651            selected_slot,
652            background_color,
653            content_size,
654            selected_content_scale,
655            icon,
656            amount_font,
657            amount_font_size,
658            amount_margins,
659            amount_text_color,
660            content_source,
661            image_source,
662            ..
663        } = self;
664
665        // If the key changed update the cached image id
666        let (image_key, content_color) = slot_key
667            .image_key(content_source)
668            .map_or((None, None), |(i, c)| (Some(i), c));
669        if state.cached_images.as_ref().map(|c| &c.0) != image_key.as_ref() {
670            state.update(|state| {
671                state.cached_images = image_key.map(|key| {
672                    let image_ids = K::image_ids(&key, image_source);
673                    (key, image_ids)
674                });
675            });
676        }
677
678        // Get image ids
679        let content_images = state.cached_images.as_ref().map(|c| c.1.clone());
680
681        // Check menu navigation selection events
682        if self.menu_click && self.menu_hover {
683            self.slot_manager
684                .as_mut()
685                .map(|m| m.select(id, slot_key.into()));
686        }
687
688        // Get whether this slot is selected
689        let interaction = self.slot_manager.as_mut().map_or(Interaction::None, |m| {
690            m.update(
691                id,
692                slot_key.into(),
693                ui,
694                content_images.clone(),
695                slot_key.amount(content_source),
696            )
697        });
698        // No content if it is being dragged
699        let content_images = if let Interaction::Dragging = interaction {
700            None
701        } else {
702            content_images
703        };
704        // Go back to getting image ids
705        let slot_image = if let Interaction::Selected = interaction {
706            selected_slot
707        } else if content_images.is_some() {
708            self.filled_slot
709        } else {
710            empty_slot
711        };
712
713        // Get amount (None => no amount text)
714        let amount = if let Interaction::Dragging = interaction {
715            None // Don't show amount if being dragged
716        } else {
717            slot_key.amount(content_source)
718        };
719
720        // Get slot widget dimensions and position
721        let (x, y, w, h) = rect.x_y_w_h();
722
723        // Draw slot frame/background
724        Image::new(slot_image)
725            .x_y(x, y)
726            .w_h(w, h)
727            .parent(id)
728            .graphics_for(id)
729            .color(background_color)
730            .set(state.ids.background, ui);
731
732        // Draw icon (only when there is not content)
733        // Note: this could potentially be done by the user instead
734        if let (Some((icon_image, size, color)), true) = (icon, content_images.is_none()) {
735            let wh = size.map(|e| e as f64).into_array();
736            Image::new(icon_image)
737                .x_y(x, y)
738                .wh(wh)
739                .parent(id)
740                .graphics_for(id)
741                .color(color)
742                .set(state.ids.icon, ui);
743        }
744
745        // Draw contents
746        if let Some(content_images) = content_images {
747            Image::new(animate_by_pulse(&content_images, self.pulse))
748                .x_y(x, y)
749                .wh((content_size
750                    * if let Interaction::Selected = interaction {
751                        selected_content_scale
752                    } else {
753                        1.0
754                    })
755                .map(|e| e as f64)
756                .into_array())
757                .color(content_color)
758                .parent(id)
759                .graphics_for(id)
760                .set(state.ids.content, ui);
761        }
762
763        // Draw on-hover highlight - let text overlap if the slot is small
764        let is_highlighted = self
765            .slot_manager
766            .as_ref()
767            .map_or(false, |sm| sm.mouse_over_slot == Some(slot_key.into()))
768            && *self.last_input == LastInput::Mouse;
769
770        // Determine if menu highligh should be shown (for keyboard and controller
771        // inputs)
772        let is_highlighted_menu = self.menu_hover
773            && (*self.last_input == LastInput::Keyboard
774                || *self.last_input == LastInput::Controller);
775
776        if is_highlighted || is_highlighted_menu {
777            Image::new(self.hovered_slot)
778                .x_y(x, y)
779                .w_h(w, h)
780                .parent(id)
781                .graphics_for(id)
782                .set(state.ids.slot_highlight, ui);
783        }
784
785        // Draw amount
786        if let Some(amount) = amount {
787            let amount = match self.slot_manager.as_ref().is_none_or(|sm| sm.use_prefixes) {
788                true => {
789                    let threshold = amount
790                        / (u32::pow(
791                            10,
792                            self.slot_manager
793                                .map_or(4, |sm| sm.prefix_switch_point)
794                                .saturating_sub(4),
795                        ));
796                    match amount {
797                        amount if threshold >= 1_000_000_000 => {
798                            format!("{}G", amount / 1_000_000_000)
799                        },
800                        amount if threshold >= 1_000_000 => format!("{}M", amount / 1_000_000),
801                        amount if threshold >= 1_000 => format!("{}K", amount / 1_000),
802                        amount => format!("{}", amount),
803                    }
804                },
805                false => format!("{}", amount),
806            };
807            // Text shadow
808            Text::new(&amount)
809                .font_id(amount_font)
810                .font_size(amount_font_size)
811                .bottom_right_with_margins_on(
812                    state.ids.content,
813                    amount_margins.x as f64,
814                    amount_margins.y as f64,
815                )
816                .parent(id)
817                .graphics_for(id)
818                .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
819                .set(state.ids.amount_bg, ui);
820            Text::new(&amount)
821                .parent(id)
822                .graphics_for(id)
823                .bottom_left_with_margins_on(
824                    state.ids.amount_bg,
825                    AMOUNT_SHADOW_OFFSET[0],
826                    AMOUNT_SHADOW_OFFSET[1],
827                )
828                .font_id(amount_font)
829                .font_size(amount_font_size)
830                .color(amount_text_color)
831                .set(state.ids.amount, ui);
832        }
833    }
834}