flemish 0.7.0

An Elmish architecture for fltk-rs
Documentation
use crate::props::*;
use crate::utils::macros::*;
use crate::vdom::VirtualDom;
use crate::vnode::{VNode, VNodeType, View};
use crate::widgets::WidgetUnion;
use fltk::{prelude::*, *};
use std::rc::Rc;

macro_rules! define_browser {
    ($name: ident) => {
        #[derive(Clone)]
        pub struct $name<Message> {
            node_id: usize,
            typ: VNodeType,
            wprops: WidgetProps,
            items: Vec<String>,
            selection: i32,
            #[allow(clippy::type_complexity)]
            on_change: Option<Rc<Box<dyn Fn(i32) -> Message>>>,
        }

        impl<Message: Clone> $name<Message> {
            pub fn new(items: &[&str], selection: i32) -> Self {
                let items = items.iter().map(|s| s.to_string()).collect();
                Self {
                    node_id: 0,
                    typ: VNodeType::$name,
                    wprops: WidgetProps::default(),
                    items,
                    selection,
                    on_change: None,
                }
            }
            pub fn on_change<F: 'static + Fn(i32) -> Message>(mut self, f: F) -> Self {
                self.on_change = Some(Rc::new(Box::new(f)));
                self
            }
        }

        impl<Message: Clone + 'static + Send + Sync> VNode<Message> for $name<Message> {
            default_impl!();
            fn gprops(&mut self) -> Option<&mut GroupProps<Message>> {
                None
            }
            fn mount(&self, dom: &VirtualDom<Message>) {
                let mut b = browser::$name::default();
                default_mount!(b, self, dom, $name, {
                    let on_change = self.on_change.clone();
                    for item in &self.items {
                        b.add(item);
                    }
                    b.select(self.selection);
                    b.set_callback(move |b| {
                        let v = b.value();
                        if let Some(on_change) = &on_change {
                            app::Sender::<Message>::get().send(on_change(v));
                        }
                    });
                });
            }
            fn patch(&mut self, old: &mut View<Message>, dom: &VirtualDom<Message>) {
                let b;
                default_patch!(b, self, old, dom, $name, {
                    let old: &$name<Message> = old.as_any().downcast_ref().unwrap();
                    if self.items != old.items {
                        b.clear();
                        for item in &self.items {
                            b.add(item);
                        }
                        b.select(self.selection);
                    }
                    if self.selection != old.selection {
                        b.select(self.selection);
                    }
                    let on_change = self.on_change.clone();
                    b.set_callback(move |b| {
                        let v = b.value();
                        if let Some(on_change) = &on_change {
                            app::Sender::<Message>::get().send(on_change(v));
                        }
                    });
                });
            }
        }
    };
}

define_browser!(Browser);
define_browser!(SelectBrowser);
define_browser!(HoldBrowser);
define_browser!(MultiBrowser);
define_browser!(FileBrowser);

#[derive(Clone)]
pub struct CheckBrowser<Message> {
    node_id: usize,
    typ: VNodeType,
    wprops: WidgetProps,
    items: Vec<(String, bool)>,
    #[allow(clippy::type_complexity)]
    on_change: Option<Rc<Box<dyn Fn(Vec<i32>) -> Message>>>,
}

impl<Message: Clone> CheckBrowser<Message> {
    pub fn new(items: &[(&str, bool)]) -> Self {
        Self {
            node_id: 0,
            typ: VNodeType::CheckBrowser,
            wprops: WidgetProps::default(),
            items: items.iter().map(|(s, c)| (s.to_string(), *c)).collect(),
            on_change: None,
        }
    }
    pub fn on_change<F: 'static + Fn(Vec<i32>) -> Message>(mut self, f: F) -> Self {
        self.on_change = Some(Rc::new(Box::new(f)));
        self
    }
}

impl<Message: Clone + 'static + Send + Sync> VNode<Message> for CheckBrowser<Message> {
    default_impl!();
    fn gprops(&mut self) -> Option<&mut GroupProps<Message>> {
        None
    }
    fn mount(&self, dom: &VirtualDom<Message>) {
        let mut b = browser::CheckBrowser::default();
        default_mount!(b, self, dom, CheckBrowser, {
            for (item, checked) in &self.items {
                b.add(item, *checked);
            }
            let on_change = self.on_change.clone();
            b.set_callback(move |b| {
                if let Some(on_change) = &on_change {
                    let mut checked: Vec<i32> = Vec::new();
                    for i in 1..=b.size() {
                        let ii = i as i32;
                        if b.checked(ii) {
                            checked.push(ii);
                        }
                    }
                    app::Sender::<Message>::get().send(on_change(checked));
                }
            });
        });
    }
    fn patch(&mut self, old: &mut View<Message>, dom: &VirtualDom<Message>) {
        let b;
        default_patch!(b, self, old, dom, CheckBrowser, {
            let old: &CheckBrowser<Message> = old.as_any().downcast_ref().unwrap();
            if self.items != old.items {
                b.clear();
                for (item, checked) in &self.items {
                    b.add(item, *checked);
                }
            }
            if self.on_change.is_some() != old.on_change.is_some() {
                let on_change = self.on_change.clone();
                b.set_callback(move |b| {
                    if let Some(on_change) = &on_change {
                        let mut checked: Vec<i32> = Vec::new();
                        for i in 1..=b.size() {
                            let ii = i as i32;
                            if b.checked(ii) {
                                checked.push(ii);
                            }
                        }
                        app::Sender::<Message>::get().send(on_change(checked));
                    }
                });
            }
        });
    }
}