1use super::{ControllerContext, InputController};
2use crate::event::{InputEvent, PointerEvent};
3use crate::{ActionEnvelope, ActionId, ActionInput};
4use fission_ir::{NodeId, Op, semantics::ActionTrigger};
5use fission_layout::LayoutPoint;
6
7pub struct GestureController;
8
9impl InputController for GestureController {
10 fn handle_event(&mut self, ctx: &mut ControllerContext, event: &InputEvent) -> bool {
11 match event {
12 InputEvent::Pointer(pe) => {
13 match pe {
14 PointerEvent::Down { point, button } => {
15 ctx.gesture.start_point = Some(*point);
16 ctx.gesture.last_point = Some(*point);
17 ctx.gesture.is_panning = false;
18 ctx.gesture.pressed_button = Some(button.clone());
19
20 if let Some(hit) = crate::hit_test::hit_test_with_scroll(ctx.ir, ctx.layout, ctx.scroll, *point) {
21 ctx.gesture.target_node = Some(hit);
22 ctx.gesture.dragging_payload = self.find_drag_payload(ctx, hit);
23 } else {
24 ctx.gesture.target_node = None;
25 ctx.gesture.dragging_payload = None;
26 }
27 }
28 PointerEvent::Move { point } => {
29 if let Some(start) = ctx.gesture.start_point {
30 let dx = point.x - start.x;
31 let dy = point.y - start.y;
32 let dist_sq = dx*dx + dy*dy;
33 let threshold = 5.0 * 5.0;
34
35 if !ctx.gesture.is_panning && dist_sq > threshold {
36 ctx.gesture.is_panning = true;
37 if let Some(target) = ctx.gesture.target_node {
39 self.dispatch_trigger(ctx, target, ActionTrigger::DragStart, *point, None);
40 }
41 }
42
43 if ctx.gesture.is_panning {
44 let last = ctx.gesture.last_point.unwrap_or(start);
45 let delta = LayoutPoint { x: point.x - last.x, y: point.y - last.y };
46 ctx.gesture.last_point = Some(*point);
47
48 let dispatched = if let Some(target) = ctx.gesture.target_node {
50 self.dispatch_trigger(ctx, target, ActionTrigger::DragUpdate, *point, Some(delta))
51 } else { false };
52
53 if dispatched {
54 return true;
55 }
56
57 if self.handle_pan_update(ctx, delta) {
59 return true;
60 }
61 }
62 }
63 }
64 PointerEvent::Up { point, .. } => {
65 let mut handled = false;
66 let was_secondary = matches!(
67 ctx.gesture.pressed_button,
68 Some(crate::event::PointerButton::Secondary)
69 );
70 if ctx.gesture.is_panning {
71 if let Some(payload) = ctx.gesture.dragging_payload.take() {
73 if let Some(up_hit) = crate::hit_test::hit_test_with_scroll(ctx.ir, ctx.layout, ctx.scroll, *point) {
74 if self.dispatch_internal_drop(ctx, up_hit, payload, *point) {
75 handled = true;
76 }
77 }
78 }
79
80 if let Some(target) = ctx.gesture.target_node {
81 self.dispatch_trigger(ctx, target, ActionTrigger::DragEnd, *point, None);
82 }
83 handled = true;
84 } else if was_secondary {
85 if let Some(target) = ctx.gesture.target_node {
87 if let Some(up_hit) = crate::hit_test::hit_test_with_scroll(ctx.ir, ctx.layout, ctx.scroll, *point) {
88 if up_hit == target || self.is_descendant(ctx, up_hit, target) || self.is_descendant(ctx, target, up_hit) {
89 if self.dispatch_trigger(ctx, target, ActionTrigger::SecondaryClick, *point, None) {
90 handled = true;
91 }
92 }
93 }
94 }
95 } else {
96 if let Some(target) = ctx.gesture.target_node {
98 if let Some(up_hit) = crate::hit_test::hit_test_with_scroll(ctx.ir, ctx.layout, ctx.scroll, *point) {
99 if up_hit == target || self.is_descendant(ctx, up_hit, target) || self.is_descendant(ctx, target, up_hit) {
100 if self.dispatch_trigger(ctx, target, ActionTrigger::Default, *point, None) {
101 handled = true;
102 }
103 }
104 }
105 }
106 }
107
108 ctx.gesture.start_point = None;
109 ctx.gesture.is_panning = false;
110 ctx.gesture.dragging_payload = None;
111 ctx.gesture.pressed_button = None;
112 return handled;
113 }
114 _ => {}
115 }
116 }
117 _ => {}
118 }
119 false
120 }
121}
122
123impl GestureController {
124 fn is_descendant(&self, ctx: &ControllerContext, child: NodeId, ancestor: NodeId) -> bool {
125 let mut curr = Some(child);
126 while let Some(id) = curr {
127 if id == ancestor { return true; }
128 if let Some(node) = ctx.ir.nodes.get(&id) {
129 curr = node.parent;
130 } else { break; }
131 }
132 false
133 }
134
135 fn find_drag_payload(&self, ctx: &ControllerContext, start_node: NodeId) -> Option<Vec<u8>> {
136 let mut current_id = Some(start_node);
137 while let Some(node_id) = current_id {
138 if let Some(node) = ctx.ir.nodes.get(&node_id) {
139 if let Op::Semantics(sem) = &node.op {
140 if let Some(p) = &sem.drag_payload {
141 return Some(p.clone());
142 }
143 }
144 current_id = node.parent;
145 } else { break; }
146 }
147 None
148 }
149
150 fn dispatch_internal_drop(&self, ctx: &mut ControllerContext, target_node: NodeId, payload: Vec<u8>, point: LayoutPoint) -> bool {
151 let mut current_id = Some(target_node);
152 while let Some(node_id) = current_id {
153 if let Some(node) = ctx.ir.nodes.get(&node_id) {
154 if let Op::Semantics(sem) = &node.op {
155 for entry in &sem.actions.entries {
156 if entry.trigger == ActionTrigger::Drop {
157 let envelope = ActionEnvelope {
158 id: ActionId::from_u128(entry.action_id),
159 payload: entry.payload_data.clone().unwrap_or_default(),
160 };
161
162 let input = ActionInput::InternalDrop {
163 payload: payload.clone(),
164 x: point.x,
165 y: point.y,
166 };
167
168 ctx.dispatched_actions.push((node_id, envelope, input));
169 return true;
170 }
171 }
172 }
173 current_id = node.parent;
174 } else { break; }
175 }
176 false
177 }
178
179 fn dispatch_trigger(&self, ctx: &mut ControllerContext, start_node: NodeId, trigger: ActionTrigger, point: LayoutPoint, delta: Option<LayoutPoint>) -> bool {
180 let mut current_id = Some(start_node);
181 while let Some(node_id) = current_id {
182 if let Some(node) = ctx.ir.nodes.get(&node_id) {
183 if let Op::Semantics(sem) = &node.op {
184 for entry in &sem.actions.entries {
185 if entry.trigger == trigger {
186 let envelope = ActionEnvelope {
187 id: ActionId::from_u128(entry.action_id),
188 payload: entry.payload_data.clone().unwrap_or_default(),
189 };
190
191 let input = ActionInput::Pointer {
192 x: point.x,
193 y: point.y,
194 delta_x: delta.map(|d| d.x).unwrap_or(0.0),
195 delta_y: delta.map(|d| d.y).unwrap_or(0.0),
196 };
197
198 ctx.dispatched_actions.push((node_id, envelope, input));
199 return true;
200 }
201 }
202 }
203 current_id = node.parent;
204 } else { break; }
205 }
206 false
207 }
208
209 fn handle_pan_update(&self, ctx: &mut ControllerContext, delta: LayoutPoint) -> bool {
210 if let Some(target) = ctx.gesture.target_node {
211 let mut current = Some(target);
212 while let Some(id) = current {
213 if let Some(node) = ctx.ir.nodes.get(&id) {
214 if let fission_ir::Op::Semantics(sem) = &node.op {
215 if sem.draggable {
216 return false;
217 }
218 }
219 if let fission_ir::Op::Layout(fission_ir::op::LayoutOp::Scroll { direction, .. }) = &node.op {
220 let current_offset = ctx.scroll.get_offset(id);
221 let move_val = match direction {
222 fission_ir::op::FlexDirection::Row => -delta.x,
223 fission_ir::op::FlexDirection::Column => -delta.y,
224 };
225
226 let mut new_offset = current_offset + move_val;
227
228 if let Some(geom) = ctx.layout.get_node_geometry(id) {
229 let max_offset = if matches!(direction, fission_ir::op::FlexDirection::Row) {
230 (geom.content_size.width - geom.rect.width()).max(0.0)
231 } else {
232 (geom.content_size.height - geom.rect.height()).max(0.0)
233 };
234 new_offset = new_offset.clamp(0.0, max_offset);
235 }
236
237 ctx.scroll.set_offset(id, new_offset);
238 return true;
239 }
240 current = node.parent;
241 } else { break; }
242 }
243 }
244 false
245 }
246}