fui/controls/internal/
dropdown_option_row_presenter.rs1use 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}