fission_core/input/
mod.rs1use crate::env::{
2 Clipboard, ContextMenuState, InteractionStateMap, ScrollStateMap, SelectableTextStateMap,
3 TextEditStateMap,
4};
5use crate::event::InputEvent;
6use crate::{ActionEnvelope, ActionId, ActionInput, UpdateTextInput};
7use fission_ir::semantics::ActionTrigger;
8use fission_ir::{CoreIR, Op, Semantics, WidgetId};
9use fission_layout::{LayoutSnapshot, TextMeasurer};
10use std::sync::Arc;
11
12pub mod gesture;
13pub mod hover;
14pub mod selectable_text;
15pub mod slider;
16pub mod text;
17
18pub struct ControllerContext<'a> {
19 pub ir: &'a CoreIR,
20 pub layout: &'a LayoutSnapshot,
21 pub text_edit: &'a mut TextEditStateMap,
22 pub selectable_text: &'a mut SelectableTextStateMap,
23 pub context_menu: &'a mut ContextMenuState,
24 pub interaction: &'a mut InteractionStateMap,
25 pub scroll: &'a mut ScrollStateMap,
26 pub gesture: &'a mut crate::env::GestureState,
27 pub clipboard: Option<&'a Arc<dyn Clipboard>>,
28 pub measurer: Option<&'a Arc<dyn TextMeasurer>>,
29 pub dispatched_actions: Vec<(WidgetId, ActionEnvelope, ActionInput)>,
31}
32
33pub trait InputController {
34 fn handle_event(&mut self, ctx: &mut ControllerContext, event: &InputEvent) -> bool;
35}
36
37#[doc(hidden)]
44pub fn prepare_text_input_change(
45 semantics: &Semantics,
46 node_id: WidgetId,
47 new_text: String,
48 new_caret: usize,
49 new_anchor: usize,
50) -> Option<(ActionEnvelope, ActionInput)> {
51 let entry = semantics
52 .actions
53 .entries
54 .iter()
55 .find(|entry| entry.trigger == ActionTrigger::TextChanged)?;
56
57 let payload = entry.payload_data.clone().unwrap_or_default();
61 let input = ActionInput::TextChanged(UpdateTextInput {
62 node_id,
63 new_text,
64 new_caret,
65 new_anchor,
66 });
67
68 Some((
69 ActionEnvelope {
70 id: ActionId::from_u128(entry.action_id),
71 payload,
72 },
73 input,
74 ))
75}
76
77#[doc(hidden)]
83pub fn prepare_scoped_text_input_change(
84 ir: &CoreIR,
85 semantics: &Semantics,
86 node_id: WidgetId,
87 new_text: String,
88 new_caret: usize,
89 new_anchor: usize,
90) -> Option<(ActionEnvelope, ActionInput)> {
91 let (envelope, input) =
92 prepare_text_input_change(semantics, node_id, new_text, new_caret, new_anchor)?;
93 Some((envelope, scoped_action_input(ir, node_id, input)))
94}
95
96pub(crate) fn action_scope_for_node(ir: &CoreIR, node_id: WidgetId) -> Option<u128> {
97 let mut current_id = Some(node_id);
98 while let Some(id) = current_id {
99 let Some(node) = ir.nodes.get(&id) else {
100 break;
101 };
102 if let Op::Semantics(semantics) = &node.op {
103 if let Some(scope_id) = semantics.action_scope_id {
104 return Some(scope_id);
105 }
106 }
107 current_id = node.parent;
108 }
109 None
110}
111
112pub(crate) fn scoped_action_input(
113 ir: &CoreIR,
114 target: WidgetId,
115 input: ActionInput,
116) -> ActionInput {
117 if let Some(scope_id) = action_scope_for_node(ir, target) {
118 ActionInput::scoped_raw(scope_id, target, input)
119 } else {
120 input
121 }
122}