Skip to main content

fui/controls/internal/
dropdown_chevron_presenter.rs

1use crate::controls::DropdownSizing;
2use crate::ffi::{AlignItems, JustifyContent, Unit};
3use crate::node::{flex_box, svg, FlexBox, SvgNode};
4use crate::theme::Theme;
5use std::rc::Rc;
6
7const DROPDOWN_CHEVRON_COLLAPSED_SVG: &str = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M3 4.5 6 7.5 9 4.5' fill='none' stroke='%23000000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>";
8const DROPDOWN_CHEVRON_EXPANDED_SVG: &str = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M3 7.5 6 4.5 9 7.5' fill='none' stroke='%23000000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>";
9const DEFAULT_DROPDOWN_CHEVRON_ICON_SIZE: f32 = 12.0;
10
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct DropdownChevronMetrics {
13    pub icon_size: f32,
14}
15
16impl DropdownChevronMetrics {
17    pub const fn new(icon_size: f32) -> Self {
18        Self { icon_size }
19    }
20}
21
22pub const DEFAULT_DROPDOWN_CHEVRON_METRICS: DropdownChevronMetrics =
23    DropdownChevronMetrics::new(DEFAULT_DROPDOWN_CHEVRON_ICON_SIZE);
24
25fn resolve_chevron_metrics(sizing: Option<DropdownSizing>) -> DropdownChevronMetrics {
26    let Some(sizing) = sizing else {
27        return DEFAULT_DROPDOWN_CHEVRON_METRICS;
28    };
29    if !sizing.has_chevron_icon_size() {
30        return DEFAULT_DROPDOWN_CHEVRON_METRICS;
31    }
32    DropdownChevronMetrics::new(sizing.chevron_icon_size_px())
33}
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
36pub struct DropdownChevronVisualState {
37    pub open: bool,
38    pub hovered: bool,
39    pub enabled: bool,
40}
41
42impl DropdownChevronVisualState {
43    pub const fn new(open: bool, hovered: bool, enabled: bool) -> Self {
44        Self {
45            open,
46            hovered,
47            enabled,
48        }
49    }
50}
51
52pub trait DropdownChevronPresenter {
53    fn root(&self) -> FlexBox;
54    fn apply(&self, theme: Theme, state: DropdownChevronVisualState);
55}
56
57pub trait DropdownChevronTemplate {
58    fn create(&self, sizing: Option<DropdownSizing>) -> Rc<dyn DropdownChevronPresenter>;
59}
60
61#[derive(Clone)]
62pub struct DefaultDropdownChevronPresenter {
63    root: FlexBox,
64    metrics: DropdownChevronMetrics,
65    icon_node: SvgNode,
66}
67
68impl DefaultDropdownChevronPresenter {
69    pub fn new(metrics: DropdownChevronMetrics) -> Self {
70        let root = flex_box();
71        root.fill_size()
72            .align_items(AlignItems::Center)
73            .justify_content(JustifyContent::Center);
74        let icon_node = svg(0);
75        icon_node
76            .width(metrics.icon_size, Unit::Pixel)
77            .height(metrics.icon_size, Unit::Pixel);
78        root.child(&icon_node);
79        Self {
80            root,
81            metrics,
82            icon_node: icon_node.clone(),
83        }
84    }
85}
86
87impl DropdownChevronPresenter for DefaultDropdownChevronPresenter {
88    fn root(&self) -> FlexBox {
89        self.root.clone()
90    }
91
92    fn apply(&self, theme: Theme, state: DropdownChevronVisualState) {
93        let metrics = self.metrics;
94        self.root
95            .fill_size()
96            .align_items(AlignItems::Center)
97            .justify_content(JustifyContent::Center);
98        self.icon_node
99            .width(metrics.icon_size, Unit::Pixel)
100            .height(metrics.icon_size, Unit::Pixel)
101            .source(if state.open {
102                DROPDOWN_CHEVRON_EXPANDED_SVG
103            } else {
104                DROPDOWN_CHEVRON_COLLAPSED_SVG
105            })
106            .tint(if !state.enabled {
107                theme.colors.text_muted
108            } else if state.hovered {
109                theme.colors.text_primary
110            } else {
111                theme.colors.text_muted
112            });
113    }
114}
115
116#[derive(Clone, Copy, Debug, Default)]
117pub struct DefaultDropdownChevronTemplate;
118
119impl DropdownChevronTemplate for DefaultDropdownChevronTemplate {
120    fn create(&self, sizing: Option<DropdownSizing>) -> Rc<dyn DropdownChevronPresenter> {
121        create_default_dropdown_chevron_presenter(sizing)
122    }
123}
124
125pub const DEFAULT_DROPDOWN_CHEVRON_TEMPLATE: DefaultDropdownChevronTemplate =
126    DefaultDropdownChevronTemplate;
127
128pub fn create_default_dropdown_chevron_presenter(
129    sizing: Option<DropdownSizing>,
130) -> Rc<dyn DropdownChevronPresenter> {
131    Rc::new(DefaultDropdownChevronPresenter::new(
132        resolve_chevron_metrics(sizing),
133    ))
134}