Skip to main content

fission_core/input/
slider.rs

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