fltk_accesskit/
fltk_adapter.rs

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