tessera_ui_basic_components/
slider.rs1use std::sync::Arc;
2
3use derive_builder::Builder;
4use parking_lot::Mutex;
5use tessera_ui::{
6 Color, ComputedData, Constraint, CursorEventContent, DimensionValue, Dp, Px, PxPosition,
7 focus_state::Focus, winit::window::CursorIcon,
8};
9use tessera_ui_macros::tessera;
10
11use crate::{
12 shape_def::Shape,
13 surface::{SurfaceArgsBuilder, surface},
14};
15
16pub struct SliderState {
18 pub is_dragging: bool,
20 pub focus: Focus,
22}
23
24impl Default for SliderState {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl SliderState {
31 pub fn new() -> Self {
32 Self {
33 is_dragging: false,
34 focus: Focus::new(),
35 }
36 }
37}
38
39#[derive(Builder, Clone)]
41#[builder(pattern = "owned")]
42pub struct SliderArgs {
43 #[builder(default = "0.0")]
45 pub value: f32,
46
47 #[builder(default = "Arc::new(|_| {})")]
49 pub on_change: Arc<dyn Fn(f32) + Send + Sync>,
50
51 #[builder(default = "Dp(200.0)")]
53 pub width: Dp,
54
55 #[builder(default = "Dp(12.0)")]
57 pub track_height: Dp,
58
59 #[builder(default = "Color::new(0.2, 0.5, 0.8, 1.0)")]
61 pub active_track_color: Color,
62
63 #[builder(default = "Color::new(0.8, 0.8, 0.8, 1.0)")]
65 pub inactive_track_color: Color,
66
67 #[builder(default = "false")]
69 pub disabled: bool,
70}
71
72#[tessera]
73pub fn slider(args: impl Into<SliderArgs>, state: Arc<Mutex<SliderState>>) {
74 let args: SliderArgs = args.into();
75
76 surface(
78 SurfaceArgsBuilder::default()
79 .width(DimensionValue::Fixed(args.width.to_px()))
80 .height(DimensionValue::Fixed(args.track_height.to_px()))
81 .color(args.inactive_track_color)
82 .shape(Shape::RoundedRectangle {
83 corner_radius: args.track_height.to_px().to_f32() / 2.0,
84 g2_k_value: 2.0, })
86 .build()
87 .unwrap(),
88 None,
89 move || {
90 let progress_width = args.width.to_px().to_f32() * args.value;
92 surface(
93 SurfaceArgsBuilder::default()
94 .width(DimensionValue::Fixed(Px(progress_width as i32)))
95 .height(DimensionValue::Fill {
96 min: None,
97 max: None,
98 })
99 .color(args.active_track_color)
100 .shape(Shape::RoundedRectangle {
101 corner_radius: args.track_height.to_px().to_f32() / 2.0,
102 g2_k_value: 2.0, })
104 .build()
105 .unwrap(),
106 None,
107 || {},
108 );
109 },
110 );
111
112 let on_change = args.on_change.clone();
113 let state_handler_state = state.clone();
114 let disabled = args.disabled;
115
116 state_handler(Box::new(move |input| {
117 if disabled {
118 return;
119 }
120 let mut state = state_handler_state.lock();
121
122 let is_in_component = input.cursor_position.is_some_and(|cursor_pos| {
123 cursor_pos.x.0 >= 0
124 && cursor_pos.x.0 < input.computed_data.width.0
125 && cursor_pos.y.0 >= 0
126 && cursor_pos.y.0 < input.computed_data.height.0
127 });
128
129 if is_in_component {
131 input.requests.cursor_icon = CursorIcon::Pointer;
132 }
133
134 if !is_in_component && !state.is_dragging {
135 return;
136 }
137
138 let mut new_value = None;
139
140 for event in input.cursor_events.iter() {
141 match &event.content {
142 CursorEventContent::Pressed(_) => {
143 state.focus.request_focus();
144 state.is_dragging = true;
145
146 if let Some(pos) = input.cursor_position {
147 let v =
148 (pos.x.0 as f32 / input.computed_data.width.0 as f32).clamp(0.0, 1.0);
149 new_value = Some(v);
150 }
151 }
152 CursorEventContent::Released(_) => {
153 state.is_dragging = false;
154 }
155 _ => {}
156 }
157 }
158
159 if state.is_dragging {
160 if let Some(pos) = input.cursor_position {
161 let v = (pos.x.0 as f32 / input.computed_data.width.0 as f32).clamp(0.0, 1.0);
162 new_value = Some(v);
163 }
164 }
165
166 if let Some(v) = new_value {
167 if (v - args.value).abs() > f32::EPSILON {
168 on_change(v);
169 }
170 }
171 }));
172
173 measure(Box::new(move |input| {
174 let self_width = args.width.to_px();
175 let self_height = args.track_height.to_px();
176
177 let track_id = input.children_ids[0];
178
179 let track_constraint = Constraint::new(
181 DimensionValue::Fixed(self_width),
182 DimensionValue::Fixed(self_height),
183 );
184 tessera_ui::measure_node(
185 track_id,
186 &track_constraint,
187 input.tree,
188 input.metadatas,
189 input.compute_resource_manager.clone(),
190 input.gpu,
191 )?;
192 tessera_ui::place_node(track_id, PxPosition::new(Px(0), Px(0)), input.metadatas);
193
194 Ok(ComputedData {
195 width: self_width,
196 height: self_height,
197 })
198 }));
199}