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