@@ -223,114 +223,281 @@ <h1 id="state-management"><a class="header" href="#state-management">State manag
223223 a.run();
224224}</ code > </ pre > </ pre >
225225< h2 id ="helper-crates "> < a class ="header " href ="#helper-crates "> Helper crates</ a > </ h2 >
226- < p > The crates ecosystem offers many crates which provide state management. Also there are 2 crates under the fltk-rs org which offer means of architecting your app and managing its state:</ p >
226+ < p > The crates ecosystem offers many crates which provide state management. Also there are 3 crates under the fltk-rs org which offer means of architecting your app and managing its state:</ p >
227227< ul >
228- < li > < a href ="https://github.com/fltk-rs/flemish "> flemish</ a > :</ li >
228+ < li >
229+ < p > < a href ="https://github.com/fltk-rs/fltk-observe "> fltk-observe</ a >
230+ Provides an observer-like mechanism on top of fltk-rs. With it, you can implement patterns such as MVC, MVP, or MVVM. The crate helps decouple your data (Model) from your UI (View) by automatically triggering updates in the UI when state changes, making your code cleaner and easier to maintain.</ p >
231+ </ li >
232+ < li >
233+ < p > < a href ="https://github.com/fltk-rs/flemish "> flemish</ a > :</ p >
234+ </ li >
229235</ ul >
230- < p > Offers an Elm like SVU architecture. This is reactive, immutable in essence, and tears down the view which each Message .</ p >
236+ < p > Implements an Elm- like architecture, sometimes referred to as SVU (State-View-Update). It uses an immutable, reactive approach: each time you send a Message, Flemish compares the difference in the virtual dom and updates the View according to the updated state. This can simplify logic by making view rendering a pure function of your state .</ p >
231237< ul >
232238< li > < a href ="https://github.com/fltk-rs/fltk-evented "> fltk-evented</ a > :</ li >
233239</ ul >
234- < p > This resembles immediate-mode guis in that all events are handled in the event loop. In reality it's also reactive but mutable and stateless. This doesn't cause a redraw with triggers.</ p >
235- < p > Both crates avoid the use of callbacks since these can be a pain in Rust in terms of lifetimes and borrowing. You need to use shared smart pointers with interior mutability to be able to borrow into a callback.</ p >
236- < p > You can take a look at both crates for inspiration.</ p >
237- < p > A sample counter in both:</ p >
238- < h2 id ="flemish "> < a class ="header " href ="#flemish "> Flemish</ a > </ h2 >
239- < pre > < pre class ="playground "> < code class ="language-rust edition2021 "> use flemish::{
240- button::Button, color_themes, frame::Frame, group::Flex, prelude::*, OnEvent, Sandbox, Settings,
241- };
240+ < p > Adopts a more immediate-mode paradigm, where all events are handled centrally in the event loop. Despite being described as “reactive” and “stateless,” it still processes user interactions in a single pass, updating state as needed without forcing redraws unless explicitly triggered. This avoids needing multiple callbacks scattered throughout your code.</ p >
241+ < p > All three crates aim to sidestep heavy use of callbacks, which can introduce borrowing complexities in Rust. Instead, they lean on shared smart pointers, internal mutability, or fully reactive state flows, so you don’t have to juggle lifetimes and manual callback wiring. If you’re looking for alternative approaches to managing state and events in your fltk-rs application, check out each crate for inspiration on different ways to structure your GUI logic.</ p >
242+ < p > A sample counter in the above mentioned crates:</ p >
243+ < h2 id ="fltk-observe "> < a class ="header " href ="#fltk-observe "> fltk-observe</ a > </ h2 >
244+ < pre > < pre class ="playground "> < code class ="language-rust edition2021 "> use fltk::{app, button::Button, prelude::*, window::Window};
245+ use fltk_observe::{Runner, WidgetObserver};
242246
243- pub fn main() {
244- Counter::new().run(Settings {
245- size: (300, 100),
246- resizable: true,
247- color_map: Some(color_themes::BLACK_THEME),
248- ..Default::default()
249- })
247+ struct Counter {
248+ value: i32,
250249}
251250
252- #[derive(Default)]
251+ impl Counter {
252+ fn new() -> Self {
253+ Self { value: 0 }
254+ }
255+
256+ fn value(&self) -> i32 {
257+ self.value
258+ }
259+
260+ fn increment(&mut self, _b: &Button) {
261+ self.value += 1;
262+ }
263+
264+ fn update_label(&self, b: &mut Button) {
265+ b.set_label(&self.value().to_string());
266+ }
267+ }
268+
269+ fn main() {
270+ let a = app::App::default().use_state(Counter::new).unwrap();
271+
272+ let mut window = Window::default().with_size(200, 200).with_label("Add data");
273+ let mut inc = Button::default_fill();
274+ inc.set_action(Counter::increment);
275+ inc.set_view(Counter::update_label);
276+ window.end();
277+ window.show();
278+
279+ a.run().unwrap();
280+ }</ code > </ pre > </ pre >
281+ < p > An example of an MVVM architecture on top of fltk-observe:</ p >
282+ < pre > < pre class ="playground "> < code class ="language-rust edition2021 "> use fltk::{app, button::Button, frame::Frame, group::Flex, prelude::*, window::Window};
283+
253284struct Counter {
254285 value: i32,
255286}
256287
257- #[derive(Debug, Clone, Copy)]
258- enum Message {
259- IncrementPressed,
260- DecrementPressed,
288+ impl Counter {
289+ fn new() -> Self {
290+ Self { value: 0 }
291+ }
292+ fn increment(&mut self) {
293+ self.value += 1;
294+ }
295+ fn decrement(&mut self) {
296+ self.value -= 1;
297+ }
298+ fn get_value(&self) -> i32 {
299+ self.value
300+ }
301+ }
302+
303+ struct CounterViewModel {
304+ model: Counter,
305+ }
306+
307+ impl CounterViewModel {
308+ fn new() -> Self {
309+ Self {
310+ model: Counter::new(),
311+ }
312+ }
313+
314+ fn increment(&mut self, _btn: &Button) {
315+ self.model.increment();
316+ }
317+
318+ fn decrement(&mut self, _btn: &Button) {
319+ self.model.decrement();
320+ }
321+
322+ fn update_display(&self, frame: &mut Frame) {
323+ frame.set_label(&self.model.get_value().to_string());
324+ }
325+ }
326+
327+ struct CounterView {
328+ inc_btn: Button,
329+ dec_btn: Button,
330+ display: Frame,
331+ }
332+
333+ impl CounterView {
334+ fn new() -> Self {
335+ let mut window = Window::default()
336+ .with_size(300, 160)
337+ .with_label("MVVM (fltk-observe)");
338+ let flex = Flex::default_fill().column();
339+ let inc_btn = Button::default().with_label("Increment");
340+ let display = Frame::default();
341+ let dec_btn = Button::default().with_label("Decrement");
342+ flex.end();
343+ window.end();
344+ window.show();
345+ Self {
346+ inc_btn,
347+ dec_btn,
348+ display,
349+ }
350+ }
261351}
262352
263- impl Sandbox for Counter {
264- type Message = Message;
353+ struct CounterApp {
354+ app: app::App,
355+ }
265356
357+ impl CounterApp {
266358 fn new() -> Self {
267- Self::default()
359+ use fltk_observe::{Runner, WidgetObserver};
360+ let app = app::App::default()
361+ .use_state(CounterViewModel::new)
362+ .unwrap();
363+
364+ let mut view = CounterView::new();
365+ view.inc_btn.set_action(CounterViewModel::increment);
366+ view.dec_btn.set_action(CounterViewModel::decrement);
367+ view.display.set_view(CounterViewModel::update_display);
368+
369+ Self { app }
268370 }
269371
270- fn title (&self) -> String {
271- String::from("Counter - fltk-rs")
372+ fn run (&self) {
373+ self.app.run().unwrap();
272374 }
375+ }
376+
377+ fn main() {
378+ let app = CounterApp::new();
379+ app.run();
380+ }</ code > </ pre > </ pre >
381+ < h2 id ="flemish "> < a class ="header " href ="#flemish "> Flemish</ a > </ h2 >
382+ < pre > < pre class ="playground "> < code class ="language-rust edition2021 "> use flemish::{view::*, Settings};
383+
384+ pub fn main() {
385+ flemish::application("counter", Counter::update, Counter::view)
386+ .settings(Settings {
387+ size: (300, 100),
388+ resizable: true,
389+ ..Default::default()
390+ })
391+ .run();
392+ }
393+
394+ #[derive(Default)]
395+ struct Counter {
396+ value: i32,
397+ }
398+
399+ #[derive(Debug, Clone, Copy)]
400+ enum Message {
401+ Increment,
402+ Decrement,
403+ }
273404
405+ impl Counter {
274406 fn update(&mut self, message: Message) {
275407 match message {
276- Message::IncrementPressed => {
408+ Message::Increment => {
277409 self.value += 1;
278410 }
279- Message::DecrementPressed => {
411+ Message::Decrement => {
280412 self.value -= 1;
281413 }
282414 }
283415 }
284416
285- fn view(&mut self) {
286- let col = Flex::default_fill().column();
287- Button::default()
288- .with_label("Increment")
289- .on_event(Message::IncrementPressed);
290- Frame::default().with_label(&self.value.to_string());
291- Button::default()
292- .with_label("Decrement")
293- .on_event(Message::DecrementPressed);
294- col.end();
417+ fn view(&self) -> View<Message> {
418+ Column::new(&[
419+ Button::new("+", Message::Increment).view(),
420+ Frame::new(&self.value.to_string()).view(),
421+ Button::new("-", Message::Decrement).view(),
422+ ])
423+ .view()
295424 }
296425}</ code > </ pre > </ pre >
297426< h2 id ="fltk-evented "> < a class ="header " href ="#fltk-evented "> fltk-evented</ a > </ h2 >
298427< pre > < pre class ="playground "> < code class ="language-rust edition2021 "> use fltk::{app, button::Button, frame::Frame, group::Flex, prelude::*, window::Window};
299428use fltk_evented::Listener;
300429
301- fn main() {
302- let a = app::App::default().with_scheme(app::Scheme::Gtk);
303- app::set_font_size(20);
304-
305- let mut wind = Window::default()
306- .with_size(160, 200)
307- .center_screen()
308- .with_label("Counter");
309- let flex = Flex::default()
310- .with_size(120, 160)
311- .center_of_parent()
312- .column();
313- let but_inc: Listener<_> = Button::default().with_label("+").into();
314- let mut frame = Frame::default();
315- let but_dec: Listener<_> = Button::default().with_label("-").into();
316- flex.end();
317- wind.end();
318- wind.show();
319-
320- let mut val = 0;
321- frame.set_label(&val.to_string());
322-
323- while a.wait() {
324- if but_inc.triggered() {
325- val += 1;
326- frame.set_label(&val.to_string());
327- }
430+ struct Counter {
431+ value: i32,
432+ }
433+
434+ impl Counter {
435+ fn new() -> Self {
436+ Self { value: 0 }
437+ }
438+ fn increment(&mut self) {
439+ self.value += 1;
440+ }
441+ fn decrement(&mut self) {
442+ self.value -= 1;
443+ }
444+ fn value(&self) -> i32 {
445+ self.value
446+ }
447+ }
328448
329- if but_dec.triggered() {
330- val -= 1;
331- frame.set_label(&val.to_string());
449+ struct CounterApp {
450+ counter: Counter,
451+ a: app::App,
452+ but_inc: Listener<Button>,
453+ frame: Frame,
454+ but_dec: Listener<Button>,
455+ }
456+
457+ impl CounterApp {
458+ pub fn new(counter: Counter) -> Self {
459+ let a = app::App::default().with_scheme(app::Scheme::Gtk);
460+ app::set_font_size(20);
461+
462+ let mut wind = Window::default()
463+ .with_size(160, 200)
464+ .with_label("Counter");
465+ let flex = Flex::default()
466+ .with_size(120, 160)
467+ .center_of_parent()
468+ .column();
469+ let but_inc: Listener<_> = Button::default().with_label("+").into();
470+ let frame = Frame::default().with_label(&counter.value().to_string());
471+ let but_dec: Listener<_> = Button::default().with_label("-").into();
472+ flex.end();
473+ wind.end();
474+ wind.show();
475+ Self {
476+ counter,
477+ a,
478+ but_inc,
479+ frame,
480+ but_dec,
481+ }
482+ }
483+ pub fn run(&mut self) {
484+ while self.a.wait() {
485+ if fltk_evented::event() {
486+ if self.but_inc.triggered() {
487+ self.counter.increment();
488+ }
489+ if self.but_dec.triggered() {
490+ self.counter.decrement();
491+ }
492+ self.frame.set_label(&self.counter.value().to_string());
493+ }
332494 }
333495 }
496+ }
497+
498+ fn main() {
499+ let mut app = CounterApp::new(Counter::new());
500+ app.run();
334501}</ code > </ pre > </ pre >
335502
336503 </ main >
0 commit comments