├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── async_examples │ ├── async_std_ex │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ └── tokio_ex │ │ ├── Cargo.toml │ │ └── src │ │ └── main.rs ├── evented.rs ├── on_event.rs └── triggered.rs └── src ├── asynch.rs ├── base.rs ├── blocking.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fltk-evented" 3 | version = "0.5.3" 4 | edition = "2021" 5 | authors = ["MoAlyousef "] 6 | description = "Listener widgets for fltk-rs" 7 | repository = "https://github.com/fltk-rs/fltk-evented" 8 | documentation = "https://docs.rs/fltk-evented" 9 | keywords = ["gui", "widgets", "graphics"] 10 | categories = ["gui"] 11 | readme = "README.md" 12 | license = "MIT" 13 | 14 | [features] 15 | default = [] 16 | 17 | [dependencies] 18 | fltk = "1.4.24" 19 | tokio = { version = "1", features = ["rt"], optional = true } 20 | async-std = { version = "1", optional = true } 21 | 22 | [[example]] 23 | path = "examples/tokio_ex" 24 | name = "tokio_ex" 25 | doc-scrape-examples = true 26 | required-features = ["tokio"] 27 | 28 | [[example]] 29 | path = "examples/async_std_ex" 30 | name = "async_std_ex" 31 | required-features = ["async-std"] 32 | 33 | [package.metadata.docs.rs] 34 | features = ["tokio"] 35 | rustdoc-args = ["--cfg", "docsrs"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fltk-evented 2 | 3 | This crate provides Listener widgets both for sync and async 4 | which can basically wrap any fltk-rs widget (implementing [WidgetBase](fltk::prelude::WidgetBase) and [WidgetExt](fltk::prelude::WidgetExt)) 5 | and provides methods `triggered() -> bool` and `event() -> Event` to handle events in the event loop, without requiring callbacks. 6 | It also provides `on_(callback)` methods which simplify handling events whereas you would've had to use the widget's handle method directly. 7 | 8 | ## Usage 9 | ```toml 10 | [dependencies] 11 | fltk = "1.4" 12 | fltk-evented = "0.5" 13 | ``` 14 | 15 | ## Examples 16 | 17 | ```rust,no_run 18 | use fltk::{app, button::Button, frame::Frame, group::Flex, prelude::*, window::Window}; 19 | use fltk_evented::Listener; 20 | 21 | fn main() { 22 | let a = app::App::default().with_scheme(app::Scheme::Gtk); 23 | app::set_font_size(20); 24 | 25 | let mut count = 0; 26 | 27 | let mut wind = Window::default() 28 | .with_size(160, 200) 29 | .with_label("Counter"); 30 | let flex = Flex::default() 31 | .with_size(120, 160) 32 | .center_of_parent() 33 | .column(); 34 | let but_inc: Listener<_> = Button::default().with_label("+").into(); 35 | let mut frame = Frame::default().with_label(&count.to_string()); 36 | let but_dec: Listener<_> = Button::default().with_label("-").into(); 37 | flex.end(); 38 | wind.end(); 39 | wind.show(); 40 | 41 | while a.wait() { 42 | if fltk_evented::event() { 43 | if but_inc.triggered() { 44 | count += 1; 45 | } 46 | 47 | if but_dec.triggered() { 48 | count -= 1; 49 | } 50 | 51 | frame.set_label(&count.to_string()); 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | ```rust,no_run 58 | use fltk::{ 59 | app, 60 | button::Button, 61 | enums::{Color, Event}, 62 | frame::Frame, 63 | group::Flex, 64 | prelude::*, 65 | window::Window, 66 | }; 67 | use fltk_evented::Listener; 68 | 69 | fn main() { 70 | let a = app::App::default().with_scheme(app::Scheme::Gtk); 71 | app::set_font_size(20); 72 | 73 | let mut count = 0; 74 | 75 | let mut wind = Window::default() 76 | .with_size(160, 200) 77 | .with_label("Counter"); 78 | let flex = Flex::default() 79 | .with_size(120, 160) 80 | .center_of_parent() 81 | .column(); 82 | let mut but_inc: Listener<_> = Button::default().with_label("+").into(); 83 | let mut frame = Frame::default().with_label(&count.to_string()); 84 | let mut but_dec: Listener<_> = Button::default().with_label("-").into(); 85 | flex.end(); 86 | wind.end(); 87 | wind.show(); 88 | 89 | fn button_color(btn: &mut Button, c: Color) { 90 | btn.set_color(c); 91 | btn.redraw(); 92 | } 93 | 94 | while a.wait() { 95 | if fltk_evented::event() { 96 | match but_inc.event() { 97 | Event::Enter | Event::Move => button_color(&mut but_inc, Color::White), 98 | Event::Leave => button_color(&mut but_inc, Color::BackGround), 99 | _ => (), 100 | } 101 | 102 | match but_dec.event() { 103 | Event::Enter | Event::Move => button_color(&mut but_dec, Color::White), 104 | Event::Leave => button_color(&mut but_dec, Color::BackGround), 105 | _ => (), 106 | } 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | Using `on_` methods: 113 | ```rust,no_run 114 | use fltk::{ 115 | app, button, 116 | enums::{Color, FrameType}, 117 | prelude::*, 118 | window, 119 | }; 120 | use fltk_evented::Listener; 121 | 122 | fn main() { 123 | let app = app::App::default(); 124 | let mut wind = window::Window::default().with_size(400, 300); 125 | wind.set_color(Color::White); 126 | let mut but: Listener<_> = button::Button::new(160, 210, 80, 35, "Click me!").into(); 127 | but.set_frame(FrameType::FlatBox); 128 | but.set_color(Color::Cyan); 129 | but.set_selection_color(Color::Cyan.darker()); 130 | but.clear_visible_focus(); 131 | wind.end(); 132 | wind.show(); 133 | 134 | but.on_hover(|b| { 135 | b.set_color(Color::Cyan.lighter().lighter()); 136 | }); 137 | 138 | but.on_leave(|b| { 139 | b.set_color(Color::Cyan); 140 | }); 141 | 142 | but.on_click(|b| { 143 | println!("Clicked"); 144 | b.set_label_color(Color::White); 145 | }); 146 | 147 | but.on_release(move |b| { 148 | wind.set_label("Button Released!"); 149 | b.set_label_color(Color::Black); 150 | }); 151 | 152 | app.run().unwrap(); 153 | } 154 | ``` 155 | 156 | ## Async Examples 157 | fltk-evented can be used with either tokio or async-std to handle non-blocking async calls in the event loop. The following examples shows usage with tokio. Another example using async-std can be found in the examples directory: 158 | ```rust,ignore 159 | use fltk::{prelude::*, *}; 160 | use fltk_evented::AsyncListener; 161 | 162 | #[tokio::main] 163 | async fn main() -> Result<(), reqwest::Error> { 164 | let mut buf = text::TextBuffer::default(); 165 | let a = app::App::default().with_scheme(app::Scheme::Gtk); 166 | app::set_font_size(20); 167 | 168 | let mut wind = window::Window::default() 169 | .with_size(400, 300) 170 | .center_screen() 171 | .with_label("Counter"); 172 | let col = group::Pack::default() 173 | .with_size(400, 300) 174 | .center_of_parent() 175 | .with_type(group::PackType::Vertical); 176 | let mut editor = text::TextEditor::default().with_size(0, 240); 177 | editor.set_buffer(buf.clone()); 178 | let getter: AsyncListener<_> = button::Button::default() 179 | .with_label("Get") 180 | .with_size(0, 60) 181 | .into(); 182 | col.end(); 183 | wind.end(); 184 | wind.show(); 185 | 186 | while a.wait() { 187 | if getter.triggered().await { 188 | let text = reqwest::get("https://www.rust-lang.org") 189 | .await? 190 | .text() 191 | .await?; 192 | buf.set_text(&text); 193 | } 194 | } 195 | Ok(()) 196 | } 197 | ``` -------------------------------------------------------------------------------- /examples/async_examples/async_std_ex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "async_std_ex" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | fltk = "1.4.24" 10 | fltk-evented = { path = "../../..", features=["async-std"] } 11 | async-std = { version = "1", features = ["attributes"] } 12 | surf = "2" -------------------------------------------------------------------------------- /examples/async_examples/async_std_ex/src/main.rs: -------------------------------------------------------------------------------- 1 | use fltk::{prelude::*, *}; 2 | use fltk_evented::AsyncListener; 3 | 4 | #[async_std::main] 5 | async fn main() -> Result<(), surf::Error> { 6 | let mut buf = text::TextBuffer::default(); 7 | let a = app::App::default().with_scheme(app::Scheme::Gtk); 8 | app::set_font_size(20); 9 | 10 | let mut wind = window::Window::default() 11 | .with_size(400, 300) 12 | .center_screen() 13 | .with_label("Counter"); 14 | let col = group::Pack::default() 15 | .with_size(400, 300) 16 | .center_of_parent() 17 | .with_type(group::PackType::Vertical); 18 | let mut editor = text::TextEditor::default().with_size(0, 240); 19 | editor.set_buffer(buf.clone()); 20 | let getter: AsyncListener<_> = button::Button::default() 21 | .with_label("Get") 22 | .with_size(0, 60) 23 | .into(); 24 | col.end(); 25 | wind.end(); 26 | wind.show(); 27 | 28 | while a.wait() { 29 | if getter.triggered().await { 30 | let text = surf::get("https://www.rust-lang.org") 31 | .await? 32 | .body_string() 33 | .await?; 34 | buf.set_text(&text); 35 | } 36 | } 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/async_examples/tokio_ex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tokio_ex" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | fltk = "1.4.24" 10 | fltk-evented = { path = "../../..", features=["tokio"] } 11 | tokio = { version = "1", features = ["macros", "rt-multi-thread"] } 12 | reqwest = "0.11" -------------------------------------------------------------------------------- /examples/async_examples/tokio_ex/src/main.rs: -------------------------------------------------------------------------------- 1 | use fltk::{prelude::*, *}; 2 | use fltk_evented::AsyncListener; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<(), reqwest::Error> { 6 | let mut buf = text::TextBuffer::default(); 7 | let a = app::App::default().with_scheme(app::Scheme::Gtk); 8 | app::set_font_size(20); 9 | 10 | let mut wind = window::Window::default() 11 | .with_size(400, 300) 12 | .center_screen() 13 | .with_label("Counter"); 14 | let col = group::Pack::default() 15 | .with_size(400, 300) 16 | .center_of_parent() 17 | .with_type(group::PackType::Vertical); 18 | let mut editor = text::TextEditor::default().with_size(0, 240); 19 | editor.set_buffer(buf.clone()); 20 | let getter: AsyncListener<_> = button::Button::default() 21 | .with_label("Get") 22 | .with_size(0, 60) 23 | .into(); 24 | col.end(); 25 | wind.end(); 26 | wind.show(); 27 | 28 | while a.wait() { 29 | if getter.triggered().await { 30 | let text = reqwest::get("https://www.rust-lang.org") 31 | .await? 32 | .text() 33 | .await?; 34 | buf.set_text(&text); 35 | } 36 | } 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /examples/evented.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app, 3 | button::Button, 4 | enums::{Color, Event}, 5 | frame::Frame, 6 | group::Flex, 7 | prelude::*, 8 | window::Window, 9 | }; 10 | use fltk_evented::Listener; 11 | 12 | fn main() { 13 | let a = app::App::default().with_scheme(app::Scheme::Gtk); 14 | app::set_font_size(20); 15 | 16 | let mut count = 0; 17 | 18 | let mut wind = Window::default() 19 | .with_size(160, 200) 20 | .with_label("Counter"); 21 | let flex = Flex::default() 22 | .with_size(120, 160) 23 | .center_of_parent() 24 | .column(); 25 | let mut but_inc: Listener<_> = Button::default().with_label("+").into(); 26 | let mut frame = Frame::default().with_label(&count.to_string()); 27 | let mut but_dec: Listener<_> = Button::default().with_label("-").into(); 28 | flex.end(); 29 | wind.end(); 30 | wind.show(); 31 | 32 | fn button_color(btn: &mut Button, c: Color) { 33 | btn.set_color(c); 34 | btn.redraw(); 35 | } 36 | 37 | while a.wait() { 38 | if fltk_evented::event() { 39 | if but_inc.triggered() { 40 | count += 1; 41 | } 42 | 43 | if but_dec.triggered() { 44 | count -= 1; 45 | } 46 | 47 | match but_inc.event() { 48 | Event::Enter | Event::Move => button_color(&mut but_inc, Color::White), 49 | Event::Leave => button_color(&mut but_inc, Color::BackGround), 50 | _ => (), 51 | } 52 | 53 | match but_dec.event() { 54 | Event::Enter | Event::Move => button_color(&mut but_dec, Color::White), 55 | Event::Leave => button_color(&mut but_dec, Color::BackGround), 56 | _ => (), 57 | } 58 | 59 | frame.set_label(&count.to_string()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/on_event.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | app, button, 3 | enums::{Color, FrameType}, 4 | prelude::*, 5 | window, 6 | }; 7 | use fltk_evented::Listener; 8 | 9 | fn main() { 10 | let app = app::App::default(); 11 | let mut wind = window::Window::default().with_size(400, 300); 12 | wind.set_color(Color::White); 13 | let mut but: Listener<_> = button::Button::new(160, 210, 80, 35, "Click me!").into(); 14 | but.set_frame(FrameType::FlatBox); 15 | but.set_color(Color::Cyan); 16 | but.set_selection_color(Color::Cyan.darker()); 17 | but.clear_visible_focus(); 18 | wind.end(); 19 | wind.show(); 20 | 21 | but.on_hover(|b| { 22 | b.set_color(Color::Cyan.lighter().lighter()); 23 | }); 24 | 25 | but.on_leave(|b| { 26 | b.set_color(Color::Cyan); 27 | }); 28 | 29 | but.on_click(|b| { 30 | println!("Clicked"); 31 | b.set_label_color(Color::White); 32 | }); 33 | 34 | but.on_release(move |b| { 35 | wind.set_label("Button Released!"); 36 | b.set_label_color(Color::Black); 37 | }); 38 | 39 | app.run().unwrap(); 40 | } 41 | -------------------------------------------------------------------------------- /examples/triggered.rs: -------------------------------------------------------------------------------- 1 | use fltk::{app, button::Button, frame::Frame, group::Flex, prelude::*, window::Window}; 2 | use fltk_evented::Listener; 3 | 4 | fn main() { 5 | let a = app::App::default().with_scheme(app::Scheme::Gtk); 6 | app::set_font_size(20); 7 | 8 | let mut count = 0; 9 | 10 | let mut wind = Window::default() 11 | .with_size(160, 200) 12 | .with_label("Counter"); 13 | let flex = Flex::default() 14 | .with_size(120, 160) 15 | .center_of_parent() 16 | .column(); 17 | let but_inc: Listener<_> = Button::default().with_label("+").into(); 18 | let mut frame = Frame::default().with_label(&count.to_string()); 19 | let but_dec: Listener<_> = Button::default().with_label("-").into(); 20 | flex.end(); 21 | wind.end(); 22 | wind.show(); 23 | 24 | while a.wait() { 25 | if fltk_evented::event() { 26 | if but_inc.triggered() { 27 | count += 1; 28 | } 29 | if but_dec.triggered() { 30 | count -= 1; 31 | } 32 | frame.set_label(&count.to_string()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/asynch.rs: -------------------------------------------------------------------------------- 1 | use crate::base::BaseListener; 2 | use fltk::{ 3 | app, 4 | enums::Event, 5 | prelude::{WidgetBase, WidgetExt}, 6 | }; 7 | use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; 8 | use std::sync::Arc; 9 | 10 | #[cfg(feature = "tokio")] 11 | use tokio::spawn; 12 | 13 | #[cfg(feature = "async-std")] 14 | use async_std::task::spawn; 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct Trig { 18 | triggered: Arc, 19 | event: Arc, 20 | } 21 | 22 | /// The async widget listener recieves both `triggered: bool` from [`AsyncListener::triggered()`], 23 | /// and [`Event`] from [`AsyncListener::event()`]. 24 | pub type AsyncListener = BaseListener; 25 | 26 | /// core constructor 27 | impl From for AsyncListener { 28 | fn from(mut wid: T) -> Self { 29 | let triggered = Arc::new(AtomicBool::new(false)); 30 | wid.set_callback({ 31 | let triggered = triggered.clone(); 32 | move |_| { 33 | let triggered = triggered.clone(); 34 | spawn(async move { 35 | triggered.store(true, Ordering::Relaxed); 36 | app::awake(); 37 | }); 38 | } 39 | }); 40 | let event = Arc::new(AtomicI32::new(Event::NoEvent.bits())); 41 | wid.handle({ 42 | let event = event.clone(); 43 | move |_, evt| { 44 | let event = event.clone(); 45 | spawn(async move { 46 | event.store(evt.bits(), Ordering::Relaxed); 47 | app::awake(); 48 | }); 49 | false 50 | } 51 | }); 52 | let trig = Trig { triggered, event }; 53 | Self { wid, trig } 54 | } 55 | } 56 | 57 | /// core implementation 58 | impl AsyncListener { 59 | /// Check whether a widget was triggered 60 | pub async fn triggered(&self) -> bool { 61 | self.trig.triggered.swap(false, Ordering::Relaxed) 62 | } 63 | 64 | /// Get an event the widget received, 65 | /// returns [`Event::NoEvent`] if no events received 66 | pub fn event(&self) -> Event { 67 | self.trig 68 | .event 69 | .swap(Event::NoEvent.bits(), Ordering::Relaxed) 70 | .into() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/base.rs: -------------------------------------------------------------------------------- 1 | use fltk::{ 2 | enums::Align, 3 | prelude::{WidgetBase, WidgetExt, WidgetType}, 4 | }; 5 | 6 | /// The base listener widget, wraps a fltk [`WidgetBase`]. 7 | #[derive(Debug, Clone)] 8 | pub struct BaseListener { 9 | #[allow(dead_code)] 10 | pub(crate) wid: T, 11 | pub(crate) trig: TRIG, 12 | } 13 | 14 | /// `#[derive(Default)]` won't register callbacks, so we must impl `Default` manually. 15 | impl>, TRIG> Default 16 | for BaseListener 17 | { 18 | fn default() -> Self { 19 | T::default().into() 20 | } 21 | } 22 | 23 | /// Used to call methods like [`WidgetExt::x`]. 24 | impl std::ops::Deref for BaseListener { 25 | type Target = T; 26 | 27 | fn deref(&self) -> &Self::Target { 28 | &self.wid 29 | } 30 | } 31 | 32 | /// Used to call methods like [`WidgetExt::set_pos`]. 33 | impl std::ops::DerefMut for BaseListener { 34 | fn deref_mut(&mut self) -> &mut Self::Target { 35 | &mut self.wid 36 | } 37 | } 38 | 39 | /// Constructors, depends on `impl From for BaseListener`, see [`crate::blocking::Listener::from`] 40 | impl>, TRIG> BaseListener { 41 | /// Not recommanded, use [`Into`] like `let btn: Listener<_> = btn.into();` 42 | pub fn from_widget(wid: T) -> Self { 43 | wid.into() 44 | } 45 | 46 | /// Creates a new widget, takes an x, y coordinates, as well as a width and height, plus a title. 47 | /// Same as [`WidgetBase::new`] 48 | pub fn new>>(x: i32, y: i32, w: i32, h: i32, label: S) -> Self { 49 | T::new(x, y, w, h, label).into() 50 | } 51 | 52 | /// Construct a widget filling the parent. 53 | /// Same as [`WidgetBase::default_fill`] 54 | pub fn default_fill() -> Self { 55 | T::default_fill().into() 56 | } 57 | } 58 | 59 | /// Builder functions, delegated to [`WidgetBase`] 60 | impl BaseListener { 61 | /// Initialize to position x, y 62 | pub fn with_pos(mut self, x: i32, y: i32) -> Self { 63 | self.wid = self.wid.with_pos(x, y); 64 | self 65 | } 66 | 67 | /// Initialize to size width, height 68 | pub fn with_size(mut self, width: i32, height: i32) -> Self { 69 | self.wid = self.wid.with_size(width, height); 70 | self 71 | } 72 | 73 | /// Initialize with a label 74 | pub fn with_label(mut self, title: &str) -> Self { 75 | self.set_label(title); 76 | self 77 | } 78 | 79 | /// Initialize with alignment 80 | pub fn with_align(mut self, align: Align) -> Self { 81 | self.set_align(align); 82 | self 83 | } 84 | 85 | /// Initialize with type 86 | pub fn with_type(mut self, typ: W) -> Self { 87 | self.wid = self.wid.with_type(typ); 88 | self 89 | } 90 | 91 | /// Initialize at bottom of another widget 92 | pub fn below_of(mut self, wid: &W, padding: i32) -> Self { 93 | self.wid = self.wid.below_of(wid, padding); 94 | self 95 | } 96 | 97 | /// Initialize above of another widget 98 | pub fn above_of(mut self, wid: &W, padding: i32) -> Self { 99 | self.wid = self.wid.above_of(wid, padding); 100 | self 101 | } 102 | 103 | /// Initialize right of another widget 104 | pub fn right_of(mut self, wid: &W, padding: i32) -> Self { 105 | self.wid = self.wid.right_of(wid, padding); 106 | self 107 | } 108 | 109 | /// Initialize left of another widget 110 | pub fn left_of(mut self, wid: &W, padding: i32) -> Self { 111 | self.wid = self.wid.left_of(wid, padding); 112 | self 113 | } 114 | 115 | /// Initialize center of another widget 116 | pub fn center_of(mut self, w: &W) -> Self { 117 | self.wid = self.wid.center_of(w); 118 | self 119 | } 120 | 121 | /// Initialize center of parent 122 | pub fn center_of_parent(mut self) -> Self { 123 | self.wid = self.wid.center_of_parent(); 124 | self 125 | } 126 | 127 | /// Initialize center of another widget on the x axis 128 | pub fn center_x(mut self, w: &W) -> Self { 129 | self.wid = self.wid.center_x(w); 130 | self 131 | } 132 | 133 | /// Initialize center of another widget on the y axis 134 | pub fn center_y(mut self, w: &W) -> Self { 135 | self.wid = self.wid.center_y(w); 136 | self 137 | } 138 | 139 | /// Initialize to the size of another widget 140 | pub fn size_of(mut self, w: &W) -> Self { 141 | self.wid = self.wid.size_of(w); 142 | self 143 | } 144 | 145 | /// Initialize to the size of the parent 146 | pub fn size_of_parent(mut self) -> Self { 147 | self.wid = self.wid.size_of_parent(); 148 | self 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/blocking.rs: -------------------------------------------------------------------------------- 1 | use crate::base::BaseListener; 2 | use fltk::enums::Event; 3 | use fltk::prelude::{WidgetBase, WidgetExt}; 4 | use std::cell::{Cell, RefCell}; 5 | use std::collections::HashMap; 6 | use std::rc::Rc; 7 | 8 | type EventMap = HashMap>>; 9 | 10 | #[derive(Clone)] 11 | pub struct Trig { 12 | triggered: Rc>, 13 | event: Rc>, 14 | events: Rc>>, 15 | } 16 | 17 | /// The blocking widget listener recieves both `triggered: bool` from [`Listener::triggered()`], 18 | /// and [`Event`] from [`Listener::event()`]. 19 | pub type Listener = BaseListener>; 20 | 21 | /// core constructor 22 | impl From for Listener { 23 | fn from(mut wid: T) -> Self { 24 | let triggered = Rc::new(Cell::new(false)); 25 | wid.set_callback({ 26 | let triggered = triggered.clone(); 27 | move |_| { 28 | triggered.set(true); 29 | } 30 | }); 31 | let event = Rc::new(Cell::new(Event::NoEvent)); 32 | let events: EventMap = HashMap::new(); 33 | let events = Rc::from(RefCell::from(events)); 34 | wid.handle({ 35 | let event = event.clone(); 36 | let events = events.clone(); 37 | move |w, evt| { 38 | let ret = if !events.borrow().is_empty() { 39 | if let Some(Some(cb)) = events.borrow_mut().get_mut(&(evt.bits())) { 40 | cb(w); 41 | w.redraw(); 42 | true 43 | } else { 44 | false 45 | } 46 | } else { 47 | event.set(evt); 48 | false 49 | }; 50 | ret 51 | } 52 | }); 53 | let trig = Trig { 54 | triggered, 55 | event, 56 | events, 57 | }; 58 | Self { wid, trig } 59 | } 60 | } 61 | 62 | /// core implementation 63 | impl Listener { 64 | /// Check whether a widget was triggered 65 | pub fn triggered(&self) -> bool { 66 | self.trig.triggered.replace(false) 67 | } 68 | 69 | /// Get an event the widget received, 70 | /// returns [`Event::NoEvent`] if no events received 71 | pub fn event(&self) -> Event { 72 | self.trig.event.replace(Event::NoEvent) 73 | } 74 | 75 | /// What the widget should do on a custom event 76 | pub fn on(&mut self, ev: Event, cb: impl FnMut(&mut T) + 'static) { 77 | self.trig 78 | .events 79 | .borrow_mut() 80 | .insert(ev.bits(), Some(Box::new(cb))); 81 | } 82 | 83 | /// What the widget should do on hover 84 | pub fn on_hover(&mut self, cb: impl FnMut(&mut T) + 'static) { 85 | self.on(Event::Enter, cb); 86 | } 87 | 88 | /// What the widget should do on leave 89 | pub fn on_leave(&mut self, cb: impl FnMut(&mut T) + 'static) { 90 | self.on(Event::Leave, cb); 91 | } 92 | 93 | /// What the widget should do on click 94 | pub fn on_click(&mut self, cb: impl FnMut(&mut T) + 'static) { 95 | self.on(Event::Push, cb); 96 | } 97 | 98 | /// What the widget should do on release 99 | pub fn on_release(&mut self, cb: impl FnMut(&mut T) + 'static) { 100 | self.on(Event::Released, cb); 101 | } 102 | 103 | /// What the widget should do on drag 104 | pub fn on_drag(&mut self, cb: impl FnMut(&mut T) + 'static) { 105 | self.on(Event::Drag, cb); 106 | } 107 | 108 | /// What the widget should do on focus 109 | pub fn on_focus(&mut self, cb: impl FnMut(&mut T) + 'static) { 110 | self.on(Event::Focus, cb); 111 | } 112 | 113 | /// What the widget should do on unfocus 114 | pub fn on_unfocus(&mut self, cb: impl FnMut(&mut T) + 'static) { 115 | self.on(Event::Unfocus, cb); 116 | } 117 | 118 | /// What the widget should do on keydown 119 | pub fn on_keydown(&mut self, cb: impl FnMut(&mut T) + 'static) { 120 | self.on(Event::KeyDown, cb); 121 | } 122 | 123 | /// What the widget should do on keyup 124 | pub fn on_keyup(&mut self, cb: impl FnMut(&mut T) + 'static) { 125 | self.on(Event::KeyUp, cb); 126 | } 127 | 128 | /// What the widget should do on close 129 | pub fn on_close(&mut self, cb: impl FnMut(&mut T) + 'static) { 130 | self.on(Event::Close, cb); 131 | } 132 | 133 | /// What the widget should do on move 134 | pub fn on_move(&mut self, cb: impl FnMut(&mut T) + 'static) { 135 | self.on(Event::Move, cb); 136 | } 137 | 138 | /// What the widget should do on shortcut 139 | pub fn on_shortcut(&mut self, cb: impl FnMut(&mut T) + 'static) { 140 | self.on(Event::Shortcut, cb); 141 | } 142 | 143 | /// What the widget should do on deactivate 144 | pub fn on_deactivate(&mut self, cb: impl FnMut(&mut T) + 'static) { 145 | self.on(Event::Deactivate, cb); 146 | } 147 | 148 | /// What the widget should do on activate 149 | pub fn on_activate(&mut self, cb: impl FnMut(&mut T) + 'static) { 150 | self.on(Event::Activate, cb); 151 | } 152 | 153 | /// What the widget should do on hide 154 | pub fn on_hide(&mut self, cb: impl FnMut(&mut T) + 'static) { 155 | self.on(Event::Hide, cb); 156 | } 157 | 158 | /// What the widget should do on show 159 | pub fn on_show(&mut self, cb: impl FnMut(&mut T) + 'static) { 160 | self.on(Event::Show, cb); 161 | } 162 | 163 | /// What the widget should do on paste 164 | pub fn on_paste(&mut self, cb: impl FnMut(&mut T) + 'static) { 165 | self.on(Event::Paste, cb); 166 | } 167 | 168 | /// What the widget should do on selection_clear 169 | pub fn on_selection_clear(&mut self, cb: impl FnMut(&mut T) + 'static) { 170 | self.on(Event::SelectionClear, cb); 171 | } 172 | 173 | /// What the widget should do on mousewheel 174 | pub fn on_mousewheel(&mut self, cb: impl FnMut(&mut T) + 'static) { 175 | self.on(Event::MouseWheel, cb); 176 | } 177 | 178 | /// What the widget should do on dnd_enter 179 | pub fn on_dnd_enter(&mut self, cb: impl FnMut(&mut T) + 'static) { 180 | self.on(Event::DndEnter, cb); 181 | } 182 | 183 | /// What the widget should do on dnd_drag 184 | pub fn on_dnd_drag(&mut self, cb: impl FnMut(&mut T) + 'static) { 185 | self.on(Event::DndDrag, cb); 186 | } 187 | 188 | /// What the widget should do on dnd_leave 189 | pub fn on_dnd_leave(&mut self, cb: impl FnMut(&mut T) + 'static) { 190 | self.on(Event::DndLeave, cb); 191 | } 192 | 193 | /// What the widget should do on dnd_release 194 | pub fn on_dnd_release(&mut self, cb: impl FnMut(&mut T) + 'static) { 195 | self.on(Event::DndRelease, cb); 196 | } 197 | 198 | /// What the widget should do on screen_config_changed 199 | pub fn on_screen_config_changed(&mut self, cb: impl FnMut(&mut T) + 'static) { 200 | self.on(Event::ScreenConfigChanged, cb); 201 | } 202 | 203 | /// What the widget should do on fullscreen 204 | pub fn on_fullscreen(&mut self, cb: impl FnMut(&mut T) + 'static) { 205 | self.on(Event::Fullscreen, cb); 206 | } 207 | 208 | /// What the widget should do on zoom_gesture 209 | pub fn on_zoom_gesture(&mut self, cb: impl FnMut(&mut T) + 'static) { 210 | self.on(Event::ZoomGesture, cb); 211 | } 212 | 213 | /// What the widget should do on zoom 214 | pub fn on_zoom(&mut self, cb: impl FnMut(&mut T) + 'static) { 215 | self.on(Event::ZoomEvent, cb); 216 | } 217 | 218 | /// What the widget should do on resize 219 | pub fn on_resize(&mut self, cb: impl FnMut(&mut T) + 'static) { 220 | self.on(Event::Resize, cb); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 2 | #![cfg_attr(docsrs, allow(unused_attributes))] 3 | #![doc = include_str!("../README.md")] 4 | #![cfg_attr(feature = "tokio", doc = concat!(r##" 5 | ## Async usage 6 | This crate provides an AsyncListener which can be used in async contexts. This requires enabling either the tokio or async-std features. You can check the examples directory for an example on usage. 7 | 8 | ```rust,ignore 9 | use fltk::{prelude::*, *}; 10 | use fltk_evented::AsyncListener; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<(), reqwest::Error> { 14 | let mut buf = text::TextBuffer::default(); 15 | let a = app::App::default().with_scheme(app::Scheme::Gtk); 16 | app::set_font_size(20); 17 | 18 | let mut wind = window::Window::default() 19 | .with_size(400, 300) 20 | .center_screen() 21 | .with_label("Counter"); 22 | let col = group::Pack::default() 23 | .with_size(400, 300) 24 | .center_of_parent() 25 | .with_type(group::PackType::Vertical); 26 | let mut editor = text::TextEditor::default().with_size(0, 240); 27 | editor.set_buffer(buf.clone()); 28 | let getter: AsyncListener<_> = button::Button::default() 29 | .with_label("Get") 30 | .with_size(0, 60) 31 | .into(); 32 | col.end(); 33 | wind.end(); 34 | wind.show(); 35 | 36 | while a.wait() { 37 | if getter.triggered().await { 38 | let text = reqwest::get("https://www.rust-lang.org") 39 | .await? 40 | .text() 41 | .await?; 42 | buf.set_text(&text); 43 | } 44 | } 45 | Ok(()) 46 | } 47 | ``` 48 | 49 | The repo contains an async-std example as well. 50 | "##))] 51 | #![allow(clippy::needless_doctest_main)] 52 | 53 | mod base; 54 | pub use base::BaseListener; 55 | 56 | mod blocking; 57 | pub use blocking::Listener; 58 | 59 | pub fn event() -> bool { 60 | fltk::app::event() != fltk::enums::Event::NoEvent 61 | } 62 | 63 | #[cfg(all(feature = "tokio", feature = "async-std"))] 64 | compile_error!("Features `tokio` and `async-std` are mutually exclusive."); 65 | 66 | #[cfg(any(feature = "tokio", feature = "async-std"))] 67 | mod asynch; 68 | #[cfg(any(feature = "tokio", feature = "async-std"))] 69 | pub use asynch::AsyncListener; 70 | --------------------------------------------------------------------------------