fission_core/input/
slider.rs1use super::{ControllerContext, InputController};
2use crate::event::{InputEvent, PointerEvent};
3use crate::{ActionEnvelope, ActionId};
4use fission_ir::{op::Op, semantics::Role, WidgetId};
5use serde_json;
6
7pub struct SliderController;
8
9impl InputController for SliderController {
10 fn handle_event(&mut self, ctx: &mut ControllerContext, event: &InputEvent) -> bool {
11 match event {
12 InputEvent::Pointer(PointerEvent::Down { point, .. }) => {
13 if let Some(hit_id) =
14 crate::hit_test::hit_test_with_scroll(ctx.ir, ctx.layout, ctx.scroll, *point)
15 {
16 let mut current_id = Some(hit_id);
17 while let Some(node_id) = current_id {
18 if let Some(node) = ctx.ir.nodes.get(&node_id) {
19 if let Op::Semantics(sem) = &node.op {
20 if sem.role == Role::Slider {
21 ctx.interaction.set_focused(Some(node_id));
22 ctx.interaction.set_pressed(node_id, true);
23
24 self.update_value(ctx, node_id, point.x);
25 return true;
26 }
27 }
28 current_id = node.parent;
29 } else {
30 break;
31 }
32 }
33 }
34 }
35 InputEvent::Pointer(PointerEvent::Move { point, .. }) => {
36 if let Some(focused_id) = ctx.interaction.focused {
37 if ctx.interaction.is_pressed(focused_id) {
38 if let Some(node) = ctx.ir.nodes.get(&focused_id) {
39 if let Op::Semantics(sem) = &node.op {
40 if sem.role == Role::Slider {
41 self.update_value(ctx, focused_id, point.x);
42 return true;
43 }
44 }
45 }
46 }
47 }
48 }
49 _ => {}
50 }
51 false
52 }
53}
54
55impl SliderController {
56 fn update_value(&self, ctx: &mut ControllerContext, node_id: WidgetId, point_x: f32) {
57 if let Some(geom) = ctx.layout.get_node_geometry(node_id) {
58 if let Some(node) = ctx.ir.nodes.get(&node_id) {
59 if let Op::Semantics(sem) = &node.op {
60 let min = sem.min_value.unwrap_or(0.0);
61 let max = sem.max_value.unwrap_or(1.0);
62
63 let width = geom.rect.width();
68 if width > 0.0 {
69 let local_x = point_x - geom.rect.x();
70 let t = (local_x / width).clamp(0.0, 1.0);
71 let new_val = min + t * (max - min);
72
73 if let Some(entry) = sem.actions.entries.first() {
74 let payload = slider_payload(entry.payload_data.as_deref(), new_val);
75 let envelope = ActionEnvelope {
76 id: ActionId::from_u128(entry.action_id),
77 payload,
78 };
79 let input = crate::input::scoped_action_input(
80 ctx.ir,
81 node_id,
82 crate::ActionInput::None,
83 );
84 ctx.dispatched_actions.push((node_id, envelope, input));
85 }
86 }
87 }
88 }
89 }
90 }
91}
92
93fn slider_payload(template: Option<&[u8]>, new_value: f32) -> Vec<u8> {
94 let Some(template) = template else {
95 return serde_json::to_vec(&new_value).expect("slider value serialization failed");
96 };
97 let Ok(mut value) = serde_json::from_slice::<serde_json::Value>(template) else {
98 return serde_json::to_vec(&new_value).expect("slider value serialization failed");
99 };
100 let Some(number) = serde_json::Number::from_f64(new_value as f64) else {
101 return serde_json::to_vec(&new_value).expect("slider value serialization failed");
102 };
103
104 if replace_numeric_payload(&mut value, number) {
105 serde_json::to_vec(&value).expect("slider action serialization failed")
106 } else {
107 serde_json::to_vec(&new_value).expect("slider value serialization failed")
108 }
109}
110
111fn replace_numeric_payload(value: &mut serde_json::Value, number: serde_json::Number) -> bool {
112 match value {
113 serde_json::Value::Number(slot) => {
114 *slot = number;
115 true
116 }
117 serde_json::Value::Array(items) if items.len() == 1 && items[0].is_number() => {
118 items[0] = serde_json::Value::Number(number);
119 true
120 }
121 serde_json::Value::Object(fields) => {
122 if let Some(slot) = fields.get_mut("value").filter(|slot| slot.is_number()) {
123 *slot = serde_json::Value::Number(number);
124 return true;
125 }
126 let mut numeric_keys = fields
127 .iter()
128 .filter_map(|(key, field)| field.is_number().then_some(key.clone()))
129 .collect::<Vec<_>>();
130 if numeric_keys.len() == 1 {
131 let key = numeric_keys.pop().expect("one numeric key");
132 if let Some(slot) = fields.get_mut(&key) {
133 *slot = serde_json::Value::Number(number);
134 return true;
135 }
136 }
137 false
138 }
139 _ => false,
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::slider_payload;
146 use serde::Deserialize;
147
148 #[derive(Debug, Deserialize, PartialEq)]
149 #[serde(transparent)]
150 struct TransparentSliderValue(f32);
151
152 #[derive(Debug, Deserialize, PartialEq)]
153 struct NamedSliderValue {
154 value: f32,
155 }
156
157 #[test]
158 fn slider_payload_preserves_transparent_action_shape() {
159 let payload = slider_payload(Some(b"0.0"), 75.0);
160 let action: TransparentSliderValue = serde_json::from_slice(&payload).unwrap();
161
162 assert_eq!(action, TransparentSliderValue(75.0));
163 }
164
165 #[test]
166 fn slider_payload_preserves_named_value_action_shape() {
167 let payload = slider_payload(Some(br#"{"value":0.0}"#), 25.0);
168 let action: NamedSliderValue = serde_json::from_slice(&payload).unwrap();
169
170 assert_eq!(action, NamedSliderValue { value: 25.0 });
171 }
172}