fltk_accesskit/
fltk_adapter.rs

1#![allow(unused_imports)]
2#![allow(unused_variables)]
3use accesskit::{
4    Action, ActionData, ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler,
5    Node, NodeId, Point, Rect, Size, Tree, TreeUpdate,
6};
7use fltk::{
8    button, enums::*, input, misc, prelude::*, text, utils, valuator, widget, *,
9};
10use std::cell::RefCell;
11use std::rc::Rc;
12
13use crate::platform_adapter;
14
15pub(crate) struct FltkActivationHandler {
16    pub wids: Vec<(NodeId, Node)>,
17    pub win_id: NodeId,
18}
19
20impl ActivationHandler for FltkActivationHandler {
21    fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
22        Some(TreeUpdate {
23            nodes: self.wids.clone(),
24            tree: Some(Tree::new(self.win_id)),
25            focus: if let Some(focused) = app::focus() {
26                let focused = focused.as_widget_ptr() as usize as u64;
27                NodeId(focused)
28            } else {
29                self.win_id
30            },
31        })
32    }
33}
34
35pub(crate) struct FltkActionHandler {
36    tx: app::Sender<ActionRequest>,
37}
38
39impl ActionHandler for FltkActionHandler {
40    fn do_action(&mut self, request: ActionRequest) {
41        self.tx.send(request);
42        app::awake();
43    }
44}
45
46pub(crate) struct FltkDeactivationHandler {}
47
48impl DeactivationHandler for FltkDeactivationHandler {
49    fn deactivate_accessibility(&mut self) {}
50}
51
52#[derive(Clone)]
53pub struct Adapter {
54    adapter: Rc<RefCell<platform_adapter::Adapter>>,
55}
56
57impl Adapter {
58    pub fn new(window: &window::Window, source: impl 'static + ActivationHandler + Send) -> Self {
59        let (tx, rx) = app::channel::<ActionRequest>();
60        let action_handler = FltkActionHandler { tx };
61        let this = Self::with_action_handler(window, source, action_handler);
62        // Drain action requests on the UI thread when awakened.
63        let rx = Rc::new(RefCell::new(rx));
64        app::awake_callback({
65            let rx = rx.clone();
66            move || {
67                while let Some(req) = rx.borrow_mut().recv() {
68                    unsafe {
69                        let mut w = widget::Widget::from_widget_ptr(req.target.0 as _);
70                        match req.action {
71                            Action::Click => {
72                                w.do_callback();
73                            }
74                            Action::Focus => {
75                                let _ = w.take_focus();
76                            }
77                            Action::ReplaceSelectedText => {
78                                if let Some(ActionData::Value(s)) = req.data.clone() {
79                                    // TextEditor: operate on buffer
80                                    if utils::is_ptr_of::<text::TextEditor>(w.as_widget_ptr()) {
81                                        let mut e =
82                                            text::TextEditor::from_widget_ptr(w.as_widget_ptr() as _);
83                                        if let Some(mut buf) = e.buffer() {
84                                            if let Some((start, end)) = buf.selection_position() {
85                                                if start != end {
86                                                    buf.replace(start, end, &s);
87                                                    e.set_insert_position(start + s.len() as i32);
88                                                } else {
89                                                    let pos = e.insert_position();
90                                                    buf.insert(pos, &s);
91                                                    e.set_insert_position(pos + s.len() as i32);
92                                                }
93                                            } else {
94                                                let pos = e.insert_position();
95                                                buf.insert(pos, &s);
96                                                e.set_insert_position(pos + s.len() as i32);
97                                            }
98                                        }
99                                    // Input family
100                                    } else if utils::is_ptr_of::<input::Input>(w.as_widget_ptr())
101                                        || utils::is_ptr_of::<input::IntInput>(w.as_widget_ptr())
102                                        || utils::is_ptr_of::<input::FloatInput>(w.as_widget_ptr())
103                                        || utils::is_ptr_of::<input::MultilineInput>(w.as_widget_ptr())
104                                    {
105                                        let mut i = input::Input::from_widget_ptr(w.as_widget_ptr() as _);
106                                        let start = i.position();
107                                        let end = i.mark();
108                                        if start != end {
109                                            let _ = i.replace(start, end, &s);
110                                            let _ = i.set_position(start + s.len() as i32);
111                                            let _ = i.set_mark(start + s.len() as i32);
112                                        } else {
113                                            let _ = i.insert(&s);
114                                            let new_pos = start + s.len() as i32;
115                                            let _ = i.set_position(new_pos);
116                                            let _ = i.set_mark(new_pos);
117                                        }
118                                    }
119                                }
120                            }
121                            Action::ScrollIntoView => {
122                                // Best effort: for TextEditor, ensure caret is visible
123                                if utils::is_ptr_of::<text::TextEditor>(w.as_widget_ptr()) {
124                                    let mut e = text::TextEditor::from_widget_ptr(w.as_widget_ptr() as _);
125                                    e.show_insert_position();
126                                }
127                            }
128                            Action::ScrollToPoint => {
129                                // No robust XY->position mapping for editors; best effort noop.
130                                // Could be extended for specific widgets/containers.
131                            }
132                            Action::SetTextSelection => {
133                                if let Some(ActionData::SetTextSelection(sel)) = req.data.clone() {
134                                    // Only apply when selection nodes target this widget
135                                    if sel.anchor.node == req.target && sel.focus.node == req.target {
136                                        // TextEditor path
137                                        if utils::is_ptr_of::<text::TextEditor>(w.as_widget_ptr()) {
138                                            let mut e =
139                                                text::TextEditor::from_widget_ptr(w.as_widget_ptr() as _);
140                                            let mut buf = if let Some(b) = e.buffer() {
141                                                b
142                                            } else {
143                                                let b = text::TextBuffer::default();
144                                                e.set_buffer(Some(b));
145                                                e.buffer().unwrap()
146                                            };
147                                            let len = buf.length();
148                                            let mut a = sel.anchor.character_index as i32;
149                                            let mut f = sel.focus.character_index as i32;
150                                            a = a.clamp(0, len);
151                                            f = f.clamp(0, len);
152                                            if a == f {
153                                                // Caret move
154                                                buf.unselect();
155                                                e.set_insert_position(a);
156                                            } else {
157                                                let (start, end) = if a <= f { (a, f) } else { (f, a) };
158                                                buf.select(start, end);
159                                                e.set_insert_position(end);
160                                            }
161                                        // Input family path
162                                        } else if utils::is_ptr_of::<input::Input>(w.as_widget_ptr())
163                                            || utils::is_ptr_of::<input::IntInput>(w.as_widget_ptr())
164                                            || utils::is_ptr_of::<input::FloatInput>(w.as_widget_ptr())
165                                            || utils::is_ptr_of::<input::MultilineInput>(w.as_widget_ptr())
166                                        {
167                                            let mut i = input::Input::from_widget_ptr(w.as_widget_ptr() as _);
168                                            let len = i.value().len() as i32;
169                                            let mut a = sel.anchor.character_index as i32;
170                                            let mut f = sel.focus.character_index as i32;
171                                            a = a.clamp(0, len);
172                                            f = f.clamp(0, len);
173                                            let (start, end) = if a <= f { (a, f) } else { (f, a) };
174                                            // Set selection; on collapse, mark==position
175                                            let _ = i.set_position(start);
176                                            let _ = i.set_mark(end);
177                                        }
178                                    }
179                                }
180                            }
181                            Action::Expand => {
182                                // Expand menu-like controls
183                                if utils::is_ptr_of::<menu::MenuButton>(w.as_widget_ptr()) {
184                                    let mb = menu::MenuButton::from_widget_ptr(w.as_widget_ptr() as _);
185                                    // Show popup; user may select an item, which will close automatically
186                                    let _ = mb.popup();
187                                }
188                                // Choice and others: no standard programmatic popup; noop.
189                            }
190                            Action::Collapse => {
191                                // No-op: popups close automatically after selection
192                            }
193                            Action::SetValue => {
194                                if let Some(data) = req.data {
195                                    match data {
196                                        ActionData::Value(s) => {
197                                            // Choice (by label)
198                                            if utils::is_ptr_of::<menu::Choice>(w.as_widget_ptr()) {
199                                                let mut c = menu::Choice::from_widget_ptr(w.as_widget_ptr() as _);
200                                                let idx = c.find_index(&s);
201                                                if idx >= 0 {
202                                                    c.set_value(idx);
203                                                }
204                                            }
205                                            // Text-capable inputs
206                                            if utils::is_ptr_of::<input::IntInput>(w.as_widget_ptr()) {
207                                                let mut i = input::IntInput::from_widget_ptr(w.as_widget_ptr() as _);
208                                                i.set_value(&s);
209                                            } else if utils::is_ptr_of::<input::FloatInput>(w.as_widget_ptr()) {
210                                                let mut i = input::FloatInput::from_widget_ptr(w.as_widget_ptr() as _);
211                                                i.set_value(&s);
212                                            } else if utils::is_ptr_of::<input::MultilineInput>(w.as_widget_ptr()) {
213                                                let mut i =
214                                                    input::MultilineInput::from_widget_ptr(w.as_widget_ptr() as _);
215                                                i.set_value(&s);
216                                            } else if utils::is_ptr_of::<input::Input>(w.as_widget_ptr()) {
217                                                let mut i = input::Input::from_widget_ptr(w.as_widget_ptr() as _);
218                                                i.set_value(&s);
219                                            } else if utils::is_ptr_of::<text::TextEditor>(w.as_widget_ptr()) {
220                                                let mut e = text::TextEditor::from_widget_ptr(w.as_widget_ptr() as _);
221                                                if let Some(mut buf) = e.buffer() {
222                                                    buf.set_text(&s);
223                                                } else {
224                                                    let mut buf = text::TextBuffer::default();
225                                                    buf.set_text(&s);
226                                                    e.set_buffer(Some(buf));
227                                                }
228                                            // Toggle/Check buttons (boolean from string)
229                                            } else if utils::is_ptr_of::<button::CheckButton>(w.as_widget_ptr()) {
230                                                let mut b = button::CheckButton::from_widget_ptr(
231                                                    w.as_widget_ptr() as _,
232                                                );
233                                                let on = matches!(
234                                                    s.to_ascii_lowercase().as_str(),
235                                                    "1" | "true" | "on" | "yes"
236                                                );
237                                                b.set_value(on);
238                                            } else if utils::is_ptr_of::<button::ToggleButton>(w.as_widget_ptr()) {
239                                                let mut b = button::ToggleButton::from_widget_ptr(
240                                                    w.as_widget_ptr() as _,
241                                                );
242                                                let on = matches!(
243                                                    s.to_ascii_lowercase().as_str(),
244                                                    "1" | "true" | "on" | "yes"
245                                                );
246                                                b.set_value(on);
247                                            // Valuators (parse string -> f64)
248                                            } else if let Ok(n) = s.parse::<f64>() {
249                                                macro_rules! set_val {
250                                                    ($t:ty) => {{
251                                                        if utils::is_ptr_of::<$t>(w.as_widget_ptr()) {
252                                                            let mut v = <$t>::from_widget_ptr(w.as_widget_ptr() as _);
253                                                            v.set_value(n);
254                                                            true
255                                                        } else {
256                                                            false
257                                                        }
258                                                    }};
259                                                }
260                                                let _handled =
261                                                    set_val!(valuator::Slider)
262                                                        || set_val!(valuator::NiceSlider)
263                                                        || set_val!(valuator::Dial)
264                                                        || set_val!(valuator::LineDial)
265                                                        || set_val!(valuator::Counter)
266                                                        || set_val!(valuator::Scrollbar)
267                                                        || set_val!(valuator::ValueInput)
268                                                        || set_val!(valuator::ValueOutput)
269                                                        || set_val!(valuator::ValueSlider)
270                                                        || set_val!(valuator::HorValueSlider)
271                                                        || set_val!(valuator::HorSlider)
272                                                        || set_val!(valuator::HorNiceSlider)
273                                                        || set_val!(valuator::FillSlider)
274                                                        || set_val!(valuator::HorFillSlider)
275                                                        || set_val!(misc::Spinner)
276                                                        || set_val!(misc::Progress);
277                                                // else: fallback noop
278                                            }
279                                        }
280                                        ActionData::NumericValue(n) => {
281                                            // Choice (by index)
282                                            if utils::is_ptr_of::<menu::Choice>(w.as_widget_ptr()) {
283                                                let mut c = menu::Choice::from_widget_ptr(w.as_widget_ptr() as _);
284                                                let total = c.size();
285                                                let mut idx = n.round() as i32;
286                                                if idx < 0 { idx = 0; }
287                                                if idx >= total { idx = total - 1; }
288                                                if total > 0 {
289                                                    c.set_value(idx);
290                                                }
291                                            }
292                                            // Inputs (apply rounding for IntInput)
293                                            if utils::is_ptr_of::<input::IntInput>(w.as_widget_ptr()) {
294                                                let mut i = input::IntInput::from_widget_ptr(w.as_widget_ptr() as _);
295                                                i.set_value(&format!("{}", n.round() as i64));
296                                            } else if utils::is_ptr_of::<input::FloatInput>(w.as_widget_ptr()) {
297                                                let mut i = input::FloatInput::from_widget_ptr(w.as_widget_ptr() as _);
298                                                i.set_value(&format!("{}", n));
299                                            } else if utils::is_ptr_of::<input::MultilineInput>(w.as_widget_ptr()) {
300                                                let mut i =
301                                                    input::MultilineInput::from_widget_ptr(w.as_widget_ptr() as _);
302                                                i.set_value(&format!("{}", n));
303                                            } else if utils::is_ptr_of::<input::Input>(w.as_widget_ptr()) {
304                                                let mut i = input::Input::from_widget_ptr(w.as_widget_ptr() as _);
305                                                i.set_value(&format!("{}", n));
306                                            } else if utils::is_ptr_of::<text::TextEditor>(w.as_widget_ptr()) {
307                                                let mut e = text::TextEditor::from_widget_ptr(w.as_widget_ptr() as _);
308                                                let s = format!("{}", n);
309                                                if let Some(mut buf) = e.buffer() {
310                                                    buf.set_text(&s);
311                                                } else {
312                                                    let mut buf = text::TextBuffer::default();
313                                                    buf.set_text(&s);
314                                                    e.set_buffer(Some(buf));
315                                                }
316                                            // Toggle/Check buttons (numeric → bool)
317                                            } else if utils::is_ptr_of::<button::CheckButton>(w.as_widget_ptr()) {
318                                                let mut b = button::CheckButton::from_widget_ptr(
319                                                    w.as_widget_ptr() as _,
320                                                );
321                                                b.set_value(n != 0.0);
322                                            } else if utils::is_ptr_of::<button::ToggleButton>(w.as_widget_ptr()) {
323                                                let mut b = button::ToggleButton::from_widget_ptr(
324                                                    w.as_widget_ptr() as _,
325                                                );
326                                                b.set_value(n != 0.0);
327                                            // Valuators
328                                            } else {
329                                                macro_rules! set_val {
330                                                    ($t:ty) => {{
331                                                        if utils::is_ptr_of::<$t>(w.as_widget_ptr()) {
332                                                            let mut v = <$t>::from_widget_ptr(w.as_widget_ptr() as _);
333                                                            v.set_value(n);
334                                                            true
335                                                        } else {
336                                                            false
337                                                        }
338                                                    }};
339                                                }
340                                                let _handled =
341                                                    set_val!(valuator::Slider)
342                                                        || set_val!(valuator::NiceSlider)
343                                                        || set_val!(valuator::Dial)
344                                                        || set_val!(valuator::LineDial)
345                                                        || set_val!(valuator::Counter)
346                                                        || set_val!(valuator::Scrollbar)
347                                                        || set_val!(valuator::ValueInput)
348                                                        || set_val!(valuator::ValueOutput)
349                                                        || set_val!(valuator::ValueSlider)
350                                                        || set_val!(valuator::HorValueSlider)
351                                                        || set_val!(valuator::HorSlider)
352                                                        || set_val!(valuator::HorNiceSlider)
353                                                        || set_val!(valuator::FillSlider)
354                                                        || set_val!(valuator::HorFillSlider)
355                                                        || set_val!(misc::Spinner)
356                                                        || set_val!(misc::Progress);
357                                                // else: fallback noop
358                                            }
359                                        }
360                                        _ => {}
361                                    }
362                                }
363                            }
364                            _ => {}
365                        }
366                    }
367                }
368            }
369        });
370        this
371    }
372
373    pub fn with_action_handler(
374        window: &window::Window,
375        source: impl 'static + ActivationHandler + Send,
376        action_handler: impl 'static + ActionHandler + Send,
377    ) -> Self {
378        let deactivation_handler = FltkDeactivationHandler {};
379        let adapter =
380            platform_adapter::Adapter::new(window, source, action_handler, deactivation_handler);
381        window.clone().resize_callback({
382            let adapter = adapter.clone();
383            move |win, _x, _y, w, h| {
384                #[cfg(not(any(target_os = "windows", target_os = "macos")))]
385                {
386                    let outer_origin = Point {
387                        x: win.x_root() as _,
388                        y: win.y_root() as _,
389                    };
390                    // Client-area origin (top-left) in root/screen coordinates
391                    let inner_origin = Point {
392                        x: win.x() as _,
393                        y: win.y() as _,
394                    };
395                    let outer_size = Size {
396                        width: win.decorated_w() as _,
397                        height: win.decorated_h() as _,
398                    };
399                    let inner_size = Size {
400                        width: w as _,
401                        height: h as _,
402                    };
403                    adapter.borrow_mut().set_root_window_bounds(
404                        Rect::from_origin_size(outer_origin, outer_size),
405                        Rect::from_origin_size(inner_origin, inner_size),
406                    );
407                }
408            }
409        });
410        Self { adapter }
411    }
412
413    #[cfg(all(
414        not(target_os = "linux"),
415        not(target_os = "dragonfly"),
416        not(target_os = "freebsd"),
417        not(target_os = "netbsd"),
418        not(target_os = "openbsd")
419    ))]
420    #[must_use]
421    pub fn on_event(&self, window: &window::Window, event: &Event) -> bool {
422        unsafe { app::handle_raw(*event, window.as_widget_ptr() as _) }
423    }
424    #[cfg(any(
425        target_os = "linux",
426        target_os = "dragonfly",
427        target_os = "freebsd",
428        target_os = "netbsd",
429        target_os = "openbsd"
430    ))]
431    #[must_use]
432    pub fn on_event(&self, window: &mut window::Window, event: &Event) -> bool {
433        unsafe { app::handle_raw(*event, window.as_widget_ptr() as _) }
434    }
435
436    // pub fn update_window_focus_state(&mut self, is_focused: bool) {
437    //     self.adapter.borrow_mut().update_window_focus_state(is_focused)
438    // }
439
440    pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) {
441        self.adapter.borrow_mut().update_if_active(updater)
442    }
443}