Skip to content

Commit 91e3bfe

Browse files
committed
update state management
1 parent d1b8209 commit 91e3bfe

5 files changed

Lines changed: 714 additions & 210 deletions

File tree

docs/State-Management.html

Lines changed: 237 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -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() -&gt; Self {
253+
Self { value: 0 }
254+
}
255+
256+
fn value(&amp;self) -&gt; i32 {
257+
self.value
258+
}
259+
260+
fn increment(&amp;mut self, _b: &amp;Button) {
261+
self.value += 1;
262+
}
263+
264+
fn update_label(&amp;self, b: &amp;mut Button) {
265+
b.set_label(&amp;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+
253284
struct Counter {
254285
value: i32,
255286
}
256287

257-
#[derive(Debug, Clone, Copy)]
258-
enum Message {
259-
IncrementPressed,
260-
DecrementPressed,
288+
impl Counter {
289+
fn new() -&gt; Self {
290+
Self { value: 0 }
291+
}
292+
fn increment(&amp;mut self) {
293+
self.value += 1;
294+
}
295+
fn decrement(&amp;mut self) {
296+
self.value -= 1;
297+
}
298+
fn get_value(&amp;self) -&gt; i32 {
299+
self.value
300+
}
301+
}
302+
303+
struct CounterViewModel {
304+
model: Counter,
305+
}
306+
307+
impl CounterViewModel {
308+
fn new() -&gt; Self {
309+
Self {
310+
model: Counter::new(),
311+
}
312+
}
313+
314+
fn increment(&amp;mut self, _btn: &amp;Button) {
315+
self.model.increment();
316+
}
317+
318+
fn decrement(&amp;mut self, _btn: &amp;Button) {
319+
self.model.decrement();
320+
}
321+
322+
fn update_display(&amp;self, frame: &amp;mut Frame) {
323+
frame.set_label(&amp;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() -&gt; 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() -&gt; 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(&amp;self) -&gt; String {
271-
String::from("Counter - fltk-rs")
372+
fn run(&amp;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(&amp;mut self, message: Message) {
275407
match message {
276-
Message::IncrementPressed =&gt; {
408+
Message::Increment =&gt; {
277409
self.value += 1;
278410
}
279-
Message::DecrementPressed =&gt; {
411+
Message::Decrement =&gt; {
280412
self.value -= 1;
281413
}
282414
}
283415
}
284416

285-
fn view(&amp;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(&amp;self.value.to_string());
291-
Button::default()
292-
.with_label("Decrement")
293-
.on_event(Message::DecrementPressed);
294-
col.end();
417+
fn view(&amp;self) -&gt; View&lt;Message&gt; {
418+
Column::new(&amp;[
419+
Button::new("+", Message::Increment).view(),
420+
Frame::new(&amp;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};
299428
use 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&lt;_&gt; = Button::default().with_label("+").into();
314-
let mut frame = Frame::default();
315-
let but_dec: Listener&lt;_&gt; = Button::default().with_label("-").into();
316-
flex.end();
317-
wind.end();
318-
wind.show();
319-
320-
let mut val = 0;
321-
frame.set_label(&amp;val.to_string());
322-
323-
while a.wait() {
324-
if but_inc.triggered() {
325-
val += 1;
326-
frame.set_label(&amp;val.to_string());
327-
}
430+
struct Counter {
431+
value: i32,
432+
}
433+
434+
impl Counter {
435+
fn new() -&gt; Self {
436+
Self { value: 0 }
437+
}
438+
fn increment(&amp;mut self) {
439+
self.value += 1;
440+
}
441+
fn decrement(&amp;mut self) {
442+
self.value -= 1;
443+
}
444+
fn value(&amp;self) -&gt; i32 {
445+
self.value
446+
}
447+
}
328448

329-
if but_dec.triggered() {
330-
val -= 1;
331-
frame.set_label(&amp;val.to_string());
449+
struct CounterApp {
450+
counter: Counter,
451+
a: app::App,
452+
but_inc: Listener&lt;Button&gt;,
453+
frame: Frame,
454+
but_dec: Listener&lt;Button&gt;,
455+
}
456+
457+
impl CounterApp {
458+
pub fn new(counter: Counter) -&gt; 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&lt;_&gt; = Button::default().with_label("+").into();
470+
let frame = Frame::default().with_label(&amp;counter.value().to_string());
471+
let but_dec: Listener&lt;_&gt; = 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(&amp;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(&amp;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

Comments
 (0)