fltk/app/
event.rs

1use crate::app::widget::first_window;
2use crate::enums::{CallbackReason, Event, Key, Shortcut};
3use crate::prelude::*;
4use crate::utils::FlString;
5use fltk_sys::fl;
6use std::{
7    cmp,
8    ffi::{CStr, CString},
9    mem,
10    os::raw,
11    panic,
12};
13
14/// Alias Window ptr
15pub type WindowPtr = *mut fltk_sys::window::Fl_Window;
16
17/// Returns the latest captured event
18pub fn event() -> Event {
19    unsafe { mem::transmute(fl::Fl_event()) }
20}
21
22/// Returns the pressed key
23pub fn event_key() -> Key {
24    unsafe { mem::transmute(fl::Fl_event_key()) }
25}
26
27/// Returns the original key
28pub fn event_original_key() -> Key {
29    unsafe { mem::transmute(fl::Fl_event_original_key()) }
30}
31
32/// Returns whether the  key is pressed or held down during the last event
33pub fn event_key_down(key: Key) -> bool {
34    unsafe { fl::Fl_event_key_down(mem::transmute(key)) != 0 }
35}
36
37/// Returns a textual representation of the latest event
38pub fn event_text() -> Option<String> {
39    unsafe {
40        let text = fl::Fl_event_text();
41        if text.is_null() {
42            None
43        } else {
44            Some(
45                CStr::from_ptr(text as *mut raw::c_char)
46                    .to_string_lossy()
47                    .to_string(),
48            )
49        }
50    }
51}
52
53/// Returns the captured button event.
54/// 1 for left key, 2 for middle, 3 for right
55pub fn event_button() -> i32 {
56    unsafe { fl::Fl_event_button() }
57}
58
59/// Defines Mouse buttons
60#[repr(i32)]
61#[derive(Debug, Copy, Clone, PartialEq, Eq)]
62#[non_exhaustive]
63pub enum MouseButton {
64    /// Left mouse button
65    Left = 1,
66    /// Middle mouse button
67    Middle = 2,
68    /// Right mouse button
69    Right = 3,
70    /// Back mouse button
71    Back = 4,
72    /// Forward mouse button
73    Forward = 5,
74}
75
76/// Returns the captured button event
77pub fn event_mouse_button() -> MouseButton {
78    unsafe { mem::transmute(fl::Fl_event_button()) }
79}
80
81/// Returns the number of clicks - 1
82pub fn event_clicks() -> i32 {
83    unsafe { fl::Fl_event_clicks() }
84}
85
86/// Gets the x coordinate of the mouse in the window
87pub fn event_x() -> i32 {
88    unsafe { fl::Fl_event_x() }
89}
90
91/// Gets the y coordinate of the mouse in the window
92pub fn event_y() -> i32 {
93    unsafe { fl::Fl_event_y() }
94}
95
96/// Gets the x coordinate of the mouse in the screen
97pub fn event_x_root() -> i32 {
98    unsafe { fl::Fl_event_x_root() }
99}
100
101/// Gets the y coordinate of the mouse in the screen
102pub fn event_y_root() -> i32 {
103    unsafe { fl::Fl_event_y_root() }
104}
105
106/// Event direction with Mousewheel event
107#[derive(Debug, Copy, Clone, PartialEq, Eq)]
108pub enum MouseWheel {
109    /// No movement
110    None,
111    /// Right movement
112    Right,
113    /// Left movement
114    Left,
115    /// Up movement
116    Up,
117    /// Down movement
118    Down,
119}
120
121/// Returns the current horizontal mouse scrolling associated with the Mousewheel event.
122/// Returns [`MouseWheel::None`], `Right` or `Left`
123pub fn event_dx() -> MouseWheel {
124    let t = unsafe { fl::Fl_event_dx() };
125    match 0.cmp(&t) {
126        cmp::Ordering::Greater => MouseWheel::Left,
127        cmp::Ordering::Equal => MouseWheel::None,
128        cmp::Ordering::Less => MouseWheel::Right,
129    }
130}
131
132/// Returns the current vertical mouse scrolling associated with the Mousewheel event.
133/// Returns [`MouseWheel::None`], `Up` or `Down`.
134pub fn event_dy() -> MouseWheel {
135    let t = unsafe { fl::Fl_event_dy() };
136    match 0.cmp(&t) {
137        cmp::Ordering::Greater => MouseWheel::Up,
138        cmp::Ordering::Equal => MouseWheel::None,
139        cmp::Ordering::Less => MouseWheel::Down,
140    }
141}
142
143/// Returns the current horizontal mouse scrolling value associated with the Mousewheel event.
144/// Right is positive
145pub fn event_dx_value() -> i32 {
146    unsafe { fl::Fl_event_dx() }
147}
148
149/// Returns the current vertical mouse scrolling value associated with the Mousewheel event.
150/// Down is positive
151pub fn event_dy_value() -> i32 {
152    unsafe { fl::Fl_event_dy() }
153}
154
155/// Returns the x and y coordinates of the captured event
156pub fn event_coords() -> (i32, i32) {
157    unsafe { (fl::Fl_event_x(), fl::Fl_event_y()) }
158}
159
160/// Determines whether an event was a click
161pub fn event_is_click() -> bool {
162    unsafe { fl::Fl_event_is_click() != 0 }
163}
164
165/// Returns the duration of an event
166pub fn event_length() -> i32 {
167    unsafe { fl::Fl_event_length() }
168}
169
170/// Returns the state of the event
171pub fn event_state() -> Shortcut {
172    unsafe { mem::transmute(fl::Fl_event_state()) }
173}
174
175/// Returns whether an event occurred within a widget
176pub fn event_inside_widget<Wid: WidgetExt>(wid: &Wid) -> bool {
177    let x = wid.x();
178    let y = wid.y();
179    let w = wid.w();
180    let h = wid.h();
181    unsafe { fl::Fl_event_inside(x, y, w, h) != 0 }
182}
183
184/// Returns whether an event occurred within a region
185pub fn event_inside(x: i32, y: i32, w: i32, h: i32) -> bool {
186    unsafe { fl::Fl_event_inside(x, y, w, h) != 0 }
187}
188
189/**
190    Gets the widget that is below the mouse cursor.
191    This returns an `Option<impl WidgetExt>` which can be specified in the function call
192    ```rust,no_run
193    use fltk::app;
194    use fltk::widget;
195    let w = app::belowmouse(); // or by specifying a more concrete type
196    ```
197*/
198pub fn belowmouse() -> Option<crate::widget::Widget> {
199    unsafe {
200        let x = fl::Fl_belowmouse();
201        if x.is_null() {
202            None
203        } else {
204            crate::widget::Widget::from_dyn_widget_ptr(x as _)
205        }
206    }
207}
208
209/// Sets the widget that's below the mouse
210pub fn set_belowmouse<Wid: WidgetExt>(w: &Wid) {
211    unsafe {
212        fl::Fl_set_belowmouse(w.as_widget_ptr() as _);
213    }
214}
215
216/// Returns whether the event is a shift press
217pub fn is_event_shift() -> bool {
218    unsafe { fl::Fl_event_shift() != 0 }
219}
220
221/// Returns whether the event is a control key press
222pub fn is_event_ctrl() -> bool {
223    unsafe { fl::Fl_event_ctrl() != 0 }
224}
225
226/// Returns whether the event is a command key press
227pub fn is_event_command() -> bool {
228    unsafe { fl::Fl_event_command() != 0 }
229}
230
231/// Returns whether the event is a alt key press
232pub fn is_event_alt() -> bool {
233    unsafe { fl::Fl_event_alt() != 0 }
234}
235
236/// Initiate dnd action
237pub fn dnd() {
238    unsafe {
239        fl::Fl_dnd();
240    }
241}
242
243/// Get the clipboard content if it's an image
244pub fn event_clipboard_image() -> Option<crate::image::RgbImage> {
245    unsafe {
246        let image = fl::Fl_event_clipboard();
247        if image.is_null() {
248            None
249        } else {
250            Some(crate::image::RgbImage::from_image_ptr(image as _))
251        }
252    }
253}
254
255/// The event clipboard type
256#[derive(Debug, Clone)]
257pub enum ClipboardEvent {
258    /// Text paste event
259    Text(String),
260    /// image paste event
261    Image(Option<crate::image::RgbImage>),
262}
263
264/// Get the clipboard content type
265pub fn event_clipboard() -> Option<ClipboardEvent> {
266    unsafe {
267        let txt = fl::Fl_event_clipboard_type();
268        let txt = CStr::from_ptr(txt as _).to_string_lossy().to_string();
269        if txt == "text/plain" {
270            Some(ClipboardEvent::Text(event_text()?))
271        } else if txt == "image" {
272            Some(ClipboardEvent::Image(event_clipboard_image()))
273        } else {
274            None
275        }
276    }
277}
278
279/**
280    Send a signal to a window pointer from `event_dispatch`.
281    Returns true if the event was handled.
282    Returns false if the event was not handled or ignored.
283    ```rust,no_run
284    use fltk::{prelude::*, *};
285    const CHANGE_FRAME: i32 = 100;
286    let mut wind = window::Window::default();
287    let mut but = button::Button::default();
288    let mut frame = frame::Frame::default();
289    wind.end();
290    wind.show();
291    but.set_callback(move |_| {
292        let _ = app::handle_main(CHANGE_FRAME).unwrap();
293    });
294
295    frame.handle(move |f, ev| {
296        if ev == CHANGE_FRAME.into() {
297            f.set_label("Hello world");
298            true
299        } else {
300            false
301        }
302    });
303    unsafe {
304        app::event_dispatch(|ev, winptr| {
305            if ev == CHANGE_FRAME.into() {
306                false // ignore CHANGE_FRAME event
307            } else {
308                app::handle_raw(ev, winptr)
309            }
310        });
311    }
312    ```
313    # Safety
314    The window pointer must be valid
315*/
316pub unsafe fn handle_raw(event: Event, w: WindowPtr) -> bool {
317    unsafe { fl::Fl_handle_(event.bits(), w as _) != 0 }
318}
319
320/**
321    The event dispatch function is called after native events are converted to
322    FLTK events, but before they are handled by FLTK. If the dispatch function
323    handler is set, it is up to the dispatch function to call
324    [`app::handle_raw(Event, WindowPtr)`](crate::app::handle_raw) or to ignore the event.
325
326    The dispatch function itself must return false if it ignored the event,
327    or true if it used the event. If you call [`app::handle_raw()`](crate::app::handle_raw),
328    then this will return the correct value.
329    # Safety
330    The window pointer must not be invalidated
331*/
332pub unsafe fn event_dispatch(f: fn(Event, WindowPtr) -> bool) {
333    unsafe {
334        fl::Fl_event_dispatch(mem::transmute(f));
335    }
336}
337
338/// Gets the mouse coordinates relative to the screen
339pub fn get_mouse() -> (i32, i32) {
340    unsafe {
341        let mut x: i32 = 0;
342        let mut y: i32 = 0;
343        fl::Fl_get_mouse(&mut x, &mut y);
344        (x, y)
345    }
346}
347
348/// Text editing widget should call this for each `FL_KEYBOARD` event.
349pub fn compose() -> Option<i32> {
350    unsafe {
351        let mut del = 0;
352        if fl::Fl_compose(&mut del) != 0 {
353            Some(del)
354        } else {
355            None
356        }
357    }
358}
359
360/// Reset the length of bytes of `app::compose()`
361pub fn compose_reset() {
362    unsafe {
363        fl::Fl_compose_reset();
364    }
365}
366
367/// Return the length of bytes written in `app::compose()`
368pub fn compose_state() -> i32 {
369    unsafe { fl::Fl_compose_state() }
370}
371
372/// Copy text to the clipboard
373pub fn copy(stuff: &str, source: CopyPasteLocation) {
374    unsafe {
375        fl::Fl_open_display();
376        let len = stuff.len();
377        let stuff = CString::safe_new(stuff);
378        fl::Fl_copy(stuff.as_ptr() as _, len as _, source as i32);
379    }
380}
381
382/// Types of Clipboard contents
383#[derive(Debug, Clone, Copy)]
384pub enum ClipboardContent {
385    /// Textual content
386    Text,
387    /// Image content
388    Image,
389}
390
391/// Check the contents of the clipboard
392pub fn clipboard_contains(content: ClipboardContent) -> bool {
393    use ClipboardContent::{Image, Text};
394    let txt = match content {
395        Text => "text/plain",
396        Image => "image",
397    };
398    let txt = CString::new(txt).unwrap();
399    unsafe { fl::Fl_clipboard_contains(txt.as_ptr() as _) != 0 }
400}
401
402/// Pastes content from the clipboard
403pub fn paste<T>(widget: &T)
404where
405    T: WidgetExt,
406{
407    if clipboard_contains(ClipboardContent::Text) {
408        paste_text(widget, CopyPasteLocation::Clipboard);
409    } else if clipboard_contains(ClipboardContent::Image) {
410        paste_image(widget, CopyPasteLocation::Clipboard);
411    } else {
412        // Do nothing!
413    }
414}
415
416/// Source of text to be pasted
417#[repr(i32)]
418#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
419pub enum CopyPasteLocation {
420    /// Selection buffer when supported
421    SelectionBuffer,
422    /// Clipboard
423    Clipboard,
424}
425
426/// Pastes textual content from the clipboard
427pub fn paste_text<T>(widget: &T, source: CopyPasteLocation)
428where
429    T: WidgetExt,
430{
431    unsafe {
432        fl::Fl_paste_text(
433            widget.as_widget_ptr() as *mut fltk_sys::fl::Fl_Widget,
434            source as i32,
435        );
436    }
437}
438
439/// Pastes image content from the clipboard
440pub fn paste_image<T>(widget: &T, source: CopyPasteLocation)
441where
442    T: WidgetExt,
443{
444    unsafe {
445        fl::Fl_paste_image(
446            widget.as_widget_ptr() as *mut fltk_sys::fl::Fl_Widget,
447            source as i32,
448        );
449    }
450}
451
452/// Adds a custom handler for unhandled events
453pub fn add_handler(cb: fn(Event) -> bool) {
454    unsafe {
455        let callback: Option<unsafe extern "C" fn(ev: raw::c_int) -> raw::c_int> =
456            Some(mem::transmute(move |ev| {
457                let _ = panic::catch_unwind(panic::AssertUnwindSafe(|| i32::from(cb(ev))));
458            }));
459        fl::Fl_add_handler(callback);
460    }
461}
462
463/**
464    Send a signal to a window.
465    Integral values from 0 to 30 are reserved.
466    Returns Ok(true) if the event was handled.
467    Returns Ok(false) if the event was not handled.
468    Returns Err on error or in use of one of the reserved values.
469    ```rust,no_run
470    use fltk::{prelude::*, *};
471    const CHANGE_FRAME: i32 = 100;
472    let mut wind = window::Window::default();
473    let mut but = button::Button::default();
474    let mut frame = frame::Frame::default();
475    but.set_callback(move |_| {
476        let _ = app::handle(CHANGE_FRAME, &wind).unwrap();
477    });
478    frame.handle(move |f, ev| {
479        if ev == CHANGE_FRAME.into() {
480            f.set_label("Hello world");
481            true
482        } else {
483            false
484        }
485    });
486    ```
487    # Errors
488    Returns Err on error or in use of one of the reserved values.
489*/
490pub fn handle<I: Into<Event> + Copy + PartialEq + PartialOrd, W: WindowExt>(
491    msg: I,
492    w: &W,
493) -> Result<bool, FltkError> {
494    let val = msg.into();
495    let ret = unsafe { fl::Fl_handle(val.bits(), w.as_widget_ptr() as _) != 0 };
496    Ok(ret)
497}
498
499/**
500    Send a signal to the main window.
501    Integral values from 0 to 30 are reserved.
502    Returns Ok(true) if the event was handled.
503    Returns Ok(false) if the event was not handled.
504    ```rust,no_run
505    use fltk::{prelude::*, *};
506    const CHANGE_FRAME: i32 = 100;
507    let mut wind = window::Window::default();
508    let mut but = button::Button::default();
509    let mut frame = frame::Frame::default();
510    but.set_callback(move |_| {
511        let _ = app::handle_main(CHANGE_FRAME).unwrap();
512    });
513    frame.handle(move |f, ev| {
514        if ev == CHANGE_FRAME.into() {
515            f.set_label("Hello world");
516            true
517        } else {
518            false
519        }
520    });
521    ```
522    # Errors
523    Returns Err on error or in use of one of the reserved values.
524*/
525pub fn handle_main<I: Into<Event> + Copy + PartialEq + PartialOrd>(
526    msg: I,
527) -> Result<bool, FltkError> {
528    let val = msg.into();
529    first_window().map_or(
530        Err(FltkError::Internal(FltkErrorKind::FailedOperation)),
531        |win| {
532            let ret = unsafe { fl::Fl_handle(val.bits(), win.as_widget_ptr() as _) != 0 };
533            Ok(ret)
534        },
535    )
536}
537
538#[cfg(target_os = "macos")]
539/// Register a function called for each file dropped onto an application icon.
540/// This function is effective only on the Mac OS X platform.
541/// cb will be called with a single Unix-style file name and path.
542/// If multiple files were dropped, cb will be called multiple times.
543/// ```rust,no_run
544/// use fltk::{app, dialog};
545/// app::raw_open_callback(Some(|s| {
546///    let name = unsafe { std::ffi::CStr::from_ptr(s).to_string_lossy().to_string() };
547///    dialog::message(&format!("You dropped {}", name));
548/// }));
549/// ```
550pub fn raw_open_callback(cb: Option<fn(*const raw::c_char)>) {
551    unsafe {
552        if let Some(cb) = cb {
553            fl::Fl_open_callback(Some(mem::transmute(cb)))
554        } else {
555            fl::Fl_open_callback(None)
556        }
557    }
558}
559
560/// Get the callback reason
561pub fn callback_reason() -> CallbackReason {
562    unsafe { mem::transmute(fl::Fl_callback_reason()) }
563}
564
565#[cfg(target_os = "windows")]
566/// Get the fl_msg win32 MSG object, see this [discussion](https://github.com/fltk-rs/fltk-rs/discussions/1417)
567pub fn fl_msg() -> *mut raw::c_void {
568    unsafe { fl::Fl_get_fl_msg() }
569}