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}