1use crate::constants::constants::*;
6use crate::font;
7use carnelian::color::Color;
8use carnelian::drawing::measure_text_width;
9use carnelian::input::{self};
10use carnelian::scene::facets::{
11 Facet, FacetId, RiveFacet, SetColorMessage, TextFacetOptions, TextHorizontalAlignment,
12 TextVerticalAlignment,
13};
14use carnelian::scene::layout::{
15 Alignment, CrossAxisAlignment, Flex, FlexOptions, MainAxisAlignment, MainAxisSize, Stack,
16 StackOptions,
17};
18use carnelian::scene::scene::{Scene, SceneBuilder};
19use carnelian::{make_message, Coord, Point, Size, ViewAssistantContext};
20use derivative::Derivative;
21use euclid::{size2, Size2D, UnknownUnit};
22use std::ops::Add;
23use zx::MonotonicInstant;
24
25pub enum ButtonMessages {
28 Pressed(MonotonicInstant, String),
29}
30
31#[derive(Debug, Clone, Copy)]
33#[allow(unused)]
34pub enum ButtonShape {
35 Square = 0,
36 Rounded = 15,
37 Oval = 50,
38}
39
40impl Default for ButtonShape {
41 fn default() -> Self {
42 ButtonShape::Square
43 }
44}
45
46#[derive(Debug, Derivative)]
47#[derivative(Default)]
48pub struct ButtonOptions {
49 #[derivative(Default(value = "SMALL_BUTTON_FONT_SIZE"))]
50 pub font_size: f32,
51 #[derivative(Default(value = "BUTTON_BORDER"))]
52 pub padding: f32,
53 #[derivative(Default(value = "false"))]
55 pub bg_fg_swapped: bool,
56 #[derivative(Default(value = "BUTTON_COLOR"))]
57 pub bg_color: Color,
58 #[derivative(Default(value = "ACTIVE_BUTTON_COLOR"))]
59 pub bg_color_active: Color,
60 #[derivative(Default(value = "ACTIVE_BUTTON_COLOR_SWAPPED"))]
61 pub bg_color_active_if_bg_fg_swapped: Color,
62 #[derivative(Default(value = "BUTTON_DISABLED_COLOR"))]
63 pub bg_color_disabled: Color,
64 #[derivative(Default(value = "BUTTON_TEXT_COLOR"))]
65 pub fg_color: Color,
66 #[derivative(Default(value = "BUTTON_TEXT_COLOR"))]
67 pub fg_color_disabled: Color,
68 #[derivative(Default(value = "BUTTON_BORDER_COLOR"))]
69 pub border_color: Color,
70 #[derivative(Default(value = "false"))]
71 pub draw_border: bool,
72 #[derivative(Default(value = "ButtonShape::Square"))]
73 pub shape: ButtonShape,
74 #[derivative(Default(value = "None"))]
75 pub bg_size: Option<Size2D<f32, UnknownUnit>>,
76 #[derivative(Default(value = "None"))]
77 pub text_alignment: Option<Alignment>,
78 #[derivative(Default(value = "false"))]
79 pub hide_text: bool,
80}
81
82pub struct Button {
83 tracking_pointer: Option<input::pointer::PointerId>,
84 active: bool,
85 focused: bool,
86 label_text: String,
87 background: FacetId,
88 label: FacetId,
89 button_options: ButtonOptions,
90}
91
92impl Button {
93 pub fn new(
94 text: &str,
95 icon: Option<RiveFacet>,
96 mut button_options: ButtonOptions,
97 builder: &mut SceneBuilder,
98 ) -> Button {
99 if button_options.bg_fg_swapped {
100 std::mem::swap(&mut button_options.fg_color, &mut button_options.bg_color);
101 button_options.bg_color_active = button_options.bg_color_active_if_bg_fg_swapped;
102 }
103 let mut alignment = match button_options.bg_size {
104 Some(_) => Alignment::center_left(),
105 _ => Alignment::center(),
106 };
107 alignment = button_options.text_alignment.unwrap_or(alignment);
108 let options = StackOptions { alignment, ..StackOptions::default() };
109
110 builder.start_group("button", Stack::with_options_ptr(options));
111 let font_size = button_options.font_size;
112 let (label_text, mut label_width) = if button_options.hide_text || font_size == 0.0 {
113 ("", 0.0)
114 } else {
115 (text, measure_text_width(font::get_default_font_face(), font_size, text))
116 };
117
118 builder.start_group(
119 &("Button icon text row"),
120 Flex::with_options_ptr(FlexOptions::row(
121 MainAxisSize::Min,
122 MainAxisAlignment::Start,
123 CrossAxisAlignment::Start,
124 )),
125 );
126 if let Some(icon) = icon {
127 label_width += icon.calculate_size(size2(0.0, 0.0)).width;
128 builder.facet(Box::new(icon));
129 }
130 builder.space(size2(5.0, 1.0));
131 let label = builder.text(
132 font::get_default_font_face().clone(),
133 label_text,
134 font_size,
135 Point::zero(),
136 TextFacetOptions {
137 color: Color::blue(),
138 horizontal_alignment: TextHorizontalAlignment::Right,
139 vertical_alignment: TextVerticalAlignment::Center,
140 ..TextFacetOptions::default()
141 },
142 );
143 builder.end_group(); let padding = button_options.padding;
146 let bg_size = button_options
147 .bg_size
148 .unwrap_or_else(|| size2(label_width + padding * 2.0, font_size + padding * 2.0));
149 let corner: Coord =
150 Coord::from(bg_size.height * (button_options.shape.clone() as i32 as f32) / 100.0);
151 let background =
152 builder.rounded_rectangle(bg_size.clone(), corner, button_options.bg_color.clone());
153 if button_options.draw_border {
154 let bg_size = bg_size.add(size2(2.0, 2.0));
155 builder.rounded_rectangle(bg_size, corner, button_options.border_color);
156 }
157 builder.end_group(); Button {
160 button_options,
161 tracking_pointer: None,
162 active: false,
163 focused: false,
164 label_text: text.to_string(),
165 background,
166 label,
167 }
168 }
169
170 #[allow(unused)]
171 pub fn get_size(&self, scene: &mut Scene) -> Size {
172 scene.get_facet_size(&self.background)
173 }
174
175 fn update_button_bg_color(&mut self, scene: &mut Scene) {
176 let (label_color, color) = if self.focused {
177 if self.active {
178 (self.button_options.fg_color, self.button_options.bg_color_active)
179 } else {
180 (self.button_options.fg_color, self.button_options.bg_color)
181 }
182 } else {
183 (self.button_options.fg_color_disabled, self.button_options.bg_color_disabled)
184 };
185 scene.send_message(&self.background, Box::new(SetColorMessage { color }));
186 scene.send_message(&self.label, Box::new(SetColorMessage { color: label_color }));
187 }
188
189 fn set_active(&mut self, scene: &mut Scene, active: bool) {
190 if self.active != active {
191 self.active = active;
192 self.update_button_bg_color(scene);
193 }
194 }
195
196 pub fn set_focused(&mut self, scene: &mut Scene, focused: bool) {
197 if focused != self.focused {
198 self.focused = focused;
199 self.active = false;
200 self.update_button_bg_color(scene);
201 if !focused {
202 self.tracking_pointer = None;
203 }
204 }
205 }
206
207 pub fn handle_pointer_event(
208 &mut self,
209 scene: &mut Scene,
210 context: &mut ViewAssistantContext,
211 pointer_event: &input::pointer::Event,
212 ) {
213 if !self.focused {
214 return;
215 }
216
217 let bounds = scene.get_facet_bounds(&self.background);
218
219 if self.tracking_pointer.is_none() {
220 match pointer_event.phase {
221 input::pointer::Phase::Down(location) => {
222 self.set_active(scene, bounds.contains(location.to_f32()));
223 if self.active {
224 #[cfg(feature = "debug_logging")]
225 println!("====== Button {} is now active", self.label_text);
226 self.tracking_pointer = Some(pointer_event.pointer_id.clone());
227 }
228 }
229 _ => (),
230 }
231 } else {
232 if let Some(tracking_pointer) = self.tracking_pointer.as_ref() {
233 if tracking_pointer == &pointer_event.pointer_id {
234 match pointer_event.phase {
235 input::pointer::Phase::Moved(location) => {
236 self.set_active(scene, bounds.contains(location.to_f32()));
237 }
238 input::pointer::Phase::Up => {
239 if self.active {
240 #[cfg(feature = "debug_logging")]
241 println!("====== Button {} pressed", self.label_text);
242 context.queue_message(make_message(ButtonMessages::Pressed(
243 MonotonicInstant::get(),
244 self.label_text.clone(),
245 )));
246 }
247 self.tracking_pointer = None;
248 self.set_active(scene, false);
249 }
250 input::pointer::Phase::Remove => {
251 self.set_active(scene, false);
252 self.tracking_pointer = None;
253 }
254 input::pointer::Phase::Cancel => {
255 self.set_active(scene, false);
256 self.tracking_pointer = None;
257 }
258 _ => (),
259 }
260 }
261 }
262 }
263 }
264}
265
266pub trait SceneBuilderButtonExt {
267 fn button(
268 &mut self,
269 text: &str,
270 icon: Option<RiveFacet>,
271 button_options: ButtonOptions,
272 ) -> Button;
273}
274
275impl SceneBuilderButtonExt for SceneBuilder {
276 fn button(
277 &mut self,
278 text: &str,
279 icon: Option<RiveFacet>,
280 button_options: ButtonOptions,
281 ) -> Button {
282 Button::new(text, icon, button_options, self)
283 }
284}