Skip to main content

fui/controls/internal/
dropdown_option_row_presenter.rs

1use crate::controls::{DropdownColors, DropdownSizing};
2use crate::ffi::{AlignItems, TextVerticalAlign};
3use crate::node::{flex_box, text, FlexBox, TextNode};
4use crate::theme::Theme;
5use std::rc::Rc;
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct DropdownOptionRowMetrics {
9    pub height: f32,
10    pub padding_left: f32,
11    pub padding_right: f32,
12    pub font_size: f32,
13}
14
15impl DropdownOptionRowMetrics {
16    pub const fn new(height: f32, padding_left: f32, padding_right: f32, font_size: f32) -> Self {
17        Self {
18            height,
19            padding_left,
20            padding_right,
21            font_size,
22        }
23    }
24}
25
26pub const DEFAULT_DROPDOWN_OPTION_ROW_METRICS: DropdownOptionRowMetrics =
27    DropdownOptionRowMetrics::new(34.0, 10.0, 10.0, 16.0);
28
29fn resolve_option_row_metrics(sizing: Option<DropdownSizing>) -> DropdownOptionRowMetrics {
30    let Some(sizing) = sizing else {
31        return DEFAULT_DROPDOWN_OPTION_ROW_METRICS;
32    };
33    if !sizing.has_option_height() && !sizing.has_option_font_size() {
34        return DEFAULT_DROPDOWN_OPTION_ROW_METRICS;
35    }
36    let font_size = if sizing.has_option_font_size() {
37        sizing.option_font_size_px()
38    } else {
39        DEFAULT_DROPDOWN_OPTION_ROW_METRICS.font_size
40    };
41    let height = if sizing.has_option_height() {
42        sizing.option_height_px()
43    } else {
44        DEFAULT_DROPDOWN_OPTION_ROW_METRICS.height
45    };
46    DropdownOptionRowMetrics::new(
47        height,
48        DEFAULT_DROPDOWN_OPTION_ROW_METRICS.padding_left,
49        DEFAULT_DROPDOWN_OPTION_ROW_METRICS.padding_right,
50        font_size,
51    )
52}
53
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
55pub struct DropdownOptionRowVisualState {
56    pub highlighted: bool,
57    pub selected: bool,
58    pub enabled: bool,
59}
60
61impl DropdownOptionRowVisualState {
62    pub const fn new(highlighted: bool, selected: bool, enabled: bool) -> Self {
63        Self {
64            highlighted,
65            selected,
66            enabled,
67        }
68    }
69}
70
71pub trait DropdownOptionRowPresenter {
72    fn root(&self) -> FlexBox;
73    fn label_node(&self) -> TextNode;
74    fn metrics(&self) -> DropdownOptionRowMetrics;
75    fn apply(
76        &self,
77        theme: Theme,
78        state: DropdownOptionRowVisualState,
79        colors: Option<DropdownColors>,
80    );
81}
82
83pub trait DropdownOptionRowTemplate {
84    fn create(&self, sizing: Option<DropdownSizing>) -> Rc<dyn DropdownOptionRowPresenter>;
85}
86
87#[derive(Clone)]
88pub struct DefaultDropdownOptionRowPresenter {
89    root: FlexBox,
90    label_node: TextNode,
91    metrics: DropdownOptionRowMetrics,
92}
93
94impl DefaultDropdownOptionRowPresenter {
95    pub fn new(metrics: DropdownOptionRowMetrics) -> Self {
96        let label_node = text("");
97        label_node
98            .selectable(false)
99            .fill_size()
100            .text_limits(0, 1)
101            .wrapping(false);
102        label_node
103            .text_overflow_fade(true, false)
104            .text_vertical_align(TextVerticalAlign::Center);
105        let root = flex_box();
106        root.fill_size()
107            .align_items(AlignItems::Center)
108            .child(&label_node);
109        Self {
110            root,
111            label_node: label_node.clone(),
112            metrics,
113        }
114    }
115}
116
117impl DropdownOptionRowPresenter for DefaultDropdownOptionRowPresenter {
118    fn root(&self) -> FlexBox {
119        self.root.clone()
120    }
121
122    fn label_node(&self) -> TextNode {
123        self.label_node.clone()
124    }
125
126    fn metrics(&self) -> DropdownOptionRowMetrics {
127        self.metrics
128    }
129
130    fn apply(
131        &self,
132        theme: Theme,
133        state: DropdownOptionRowVisualState,
134        colors: Option<DropdownColors>,
135    ) {
136        let metrics = self.metrics;
137        self.root
138            .padding(metrics.padding_left, 0.0, metrics.padding_right, 0.0)
139            .corner_radius(theme.spacing.xs)
140            .bg_color(if state.highlighted {
141                theme.context_menu.item.hover_background
142            } else {
143                0x00000000
144            });
145        let label_color = if !state.enabled {
146            theme.colors.text_muted
147        } else if state.selected {
148            colors
149                .filter(|colors| colors.has_accent())
150                .map(|colors| colors.accent_color())
151                .unwrap_or(theme.colors.accent)
152        } else {
153            colors
154                .filter(|colors| colors.has_text_primary())
155                .map(|colors| colors.text_primary_color())
156                .unwrap_or(theme.colors.text_primary)
157        };
158        self.label_node
159            .font_family(theme.fonts.body_family.clone())
160            .font_size(metrics.font_size)
161            .text_color(label_color);
162    }
163}
164
165#[derive(Clone, Copy, Debug, Default)]
166pub struct DefaultDropdownOptionRowTemplate;
167
168impl DropdownOptionRowTemplate for DefaultDropdownOptionRowTemplate {
169    fn create(&self, sizing: Option<DropdownSizing>) -> Rc<dyn DropdownOptionRowPresenter> {
170        create_default_dropdown_option_row_presenter(sizing)
171    }
172}
173
174pub const DEFAULT_DROPDOWN_OPTION_ROW_TEMPLATE: DefaultDropdownOptionRowTemplate =
175    DefaultDropdownOptionRowTemplate;
176
177pub fn create_default_dropdown_option_row_presenter(
178    sizing: Option<DropdownSizing>,
179) -> Rc<dyn DropdownOptionRowPresenter> {
180    Rc::new(DefaultDropdownOptionRowPresenter::new(
181        resolve_option_row_metrics(sizing),
182    ))
183}