fltk_decl/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use fltk::{prelude::*, *};
4use notify::{
5    event::{AccessKind, AccessMode, DataChange, EventKind, ModifyKind},
6    Event, RecursiveMode, Watcher,
7};
8use serde_derive::{Deserialize, Serialize};
9use std::{
10    path::PathBuf,
11    sync::{
12        atomic::{AtomicBool, Ordering},
13        Arc,
14    },
15};
16
17mod frames;
18mod utils;
19
20#[derive(Clone, Debug, Serialize, Deserialize)]
21pub struct Widget {
22    widget: String,
23    label: Option<String>,
24    id: Option<String>,
25    fixed: Option<i32>,
26    color: Option<String>,
27    labelcolor: Option<String>,
28    children: Option<Vec<Widget>>,
29    hide: Option<bool>,
30    deactivate: Option<bool>,
31    visible: Option<bool>,
32    resizable: Option<bool>,
33    selectioncolor: Option<String>,
34    tooltip: Option<String>,
35    image: Option<String>,
36    deimage: Option<String>,
37    labelfont: Option<u32>,
38    labelsize: Option<i32>,
39    align: Option<i32>,
40    when: Option<i32>,
41    frame: Option<String>,
42    downframe: Option<String>,
43    shortcut: Option<String>,
44    pad: Option<i32>,
45    minimum: Option<f64>,
46    maximum: Option<f64>,
47    step: Option<f64>,
48    slidersize: Option<f64>,
49    textfont: Option<i32>,
50    textsize: Option<i32>,
51    textcolor: Option<String>,
52    x: Option<i32>,
53    y: Option<i32>,
54    w: Option<i32>,
55    h: Option<i32>,
56    margin: Option<i32>,
57    left: Option<i32>,
58    top: Option<i32>,
59    right: Option<i32>,
60    bottom: Option<i32>,
61}
62
63/// Entry point for your declarative app
64#[derive(Debug, Clone)]
65pub struct DeclarativeApp {
66    a: app::App,
67    w: i32,
68    h: i32,
69    label: String,
70    #[allow(dead_code)]
71    path: Option<&'static str>,
72    widget: Option<Widget>,
73    load_fn: fn(&'static str) -> Option<Widget>,
74}
75
76impl DeclarativeApp {
77    /// Instantiate a new declarative app
78    pub fn new(
79        w: i32,
80        h: i32,
81        label: &str,
82        path: &'static str,
83        load_fn: fn(&'static str) -> Option<Widget>,
84    ) -> Self {
85        let widget = load_fn(path);
86        let a = app::App::default().with_scheme(app::Scheme::Gtk);
87        Self {
88            a,
89            w,
90            h,
91            label: label.to_string(),
92            path: Some(path),
93            widget,
94            load_fn,
95        }
96    }
97
98    #[cfg(feature = "json")]
99    pub fn new_json(w: i32, h: i32, label: &str, path: &'static str) -> Self {
100        fn load_fn(path: &'static str) -> Option<Widget> {
101            let s = std::fs::read_to_string(path).ok()?;
102            serde_json::from_str(&s).map_err(|e| eprintln!("{e}")).ok()
103        }
104        Self::new(w, h, label, path, load_fn)
105    }
106
107    #[cfg(feature = "json5")]
108    pub fn new_json5(w: i32, h: i32, label: &str, path: &'static str) -> Self {
109        fn load_fn(path: &'static str) -> Option<Widget> {
110            let s = std::fs::read_to_string(path).ok()?;
111            serde_json5::from_str(&s).map_err(|e| eprintln!("{e}")).ok()
112        }
113        Self::new(w, h, label, path, load_fn)
114    }
115
116    #[cfg(feature = "xml")]
117    pub fn new_xml(w: i32, h: i32, label: &str, path: &'static str) -> Self {
118        fn load_fn(path: &'static str) -> Option<Widget> {
119            let s = std::fs::read_to_string(path).ok()?;
120            serde_xml_rs::from_str(&s)
121                .map_err(|e| eprintln!("{e}"))
122                .ok()
123        }
124        Self::new(w, h, label, path, load_fn)
125    }
126
127    #[cfg(feature = "yaml")]
128    pub fn new_yaml(w: i32, h: i32, label: &str, path: &'static str) -> Self {
129        fn load_fn(path: &'static str) -> Option<Widget> {
130            let s = std::fs::read_to_string(path).ok()?;
131            serde_yaml::from_str(&s).map_err(|e| eprintln!("{e}")).ok()
132        }
133        Self::new(w, h, label, path, load_fn)
134    }
135
136    /// Instantiate a new declarative app
137    pub fn new_inline(w: i32, h: i32, label: &str, widget: Option<Widget>) -> Self {
138        let a = app::App::default().with_scheme(app::Scheme::Gtk);
139        Self {
140            a,
141            w,
142            h,
143            label: label.to_string(),
144            path: None,
145            widget,
146            load_fn: |_| None,
147        }
148    }
149
150    /// Run your declarative app.
151    /// The callback exposes the app's main window
152    pub fn run<F: FnMut(&mut window::Window) + 'static>(
153        &self,
154        mut run_cb: F,
155    ) -> Result<(), Box<dyn std::error::Error>> {
156        if let Some(path) = &self.path {
157            let mut win = window::Window::default()
158                .with_size(self.w, self.h)
159                .with_label(&self.label);
160            if let Some(widget) = &self.widget {
161                utils::transform(widget);
162            }
163            win.end();
164            win.show();
165
166            if let Some(mut frst) = win.child(0) {
167                frst.resize(0, 0, win.w(), win.h());
168                win.resizable(&frst);
169            }
170
171            run_cb(&mut win);
172
173            let flag = Arc::new(AtomicBool::new(false));
174            app::add_timeout3(0.1, {
175                let flag = flag.clone();
176                let mut win = win.clone();
177                move |_t| {
178                    if flag.load(Ordering::Relaxed) {
179                        run_cb(&mut win);
180                        flag.store(false, Ordering::Relaxed);
181                    }
182                    app::repeat_timeout3(0.1, _t);
183                }
184            });
185
186            let load_fn = self.load_fn;
187            let mut watcher = notify::recommended_watcher({
188                let path = <&str>::clone(path);
189                move |res: Result<Event, notify::Error>| match res {
190                    Ok(event) => {
191                        let mut needs_update = false;
192                        match event.kind {
193                            EventKind::Access(AccessKind::Close(mode)) => {
194                                if mode == AccessMode::Write {
195                                    needs_update = true;
196                                }
197                            }
198                            EventKind::Modify(ModifyKind::Data(DataChange::Content)) => {
199                                needs_update = true;
200                            }
201                            _ => (),
202                        }
203                        if needs_update {
204                            if let Some(wid) = (load_fn)(path) {
205                                win.clear();
206                                win.begin();
207                                utils::transform(&wid);
208                                win.end();
209                                if let Some(mut frst) = win.child(0) {
210                                    frst.resize(0, 0, win.w(), win.h());
211                                    win.resizable(&frst);
212                                }
213                                app::redraw();
214                                flag.store(true, Ordering::Relaxed);
215                            }
216                        }
217                    }
218                    Err(e) => eprintln!("{}", e),
219                }
220            })?;
221            watcher.watch(&PathBuf::from(path), RecursiveMode::NonRecursive)?;
222
223            self.a.run()?;
224        } else {
225            self.run_once(run_cb)?;
226        }
227        Ok(())
228    }
229
230    /// Run the app without hot-reloading!
231    pub fn run_once<F: FnMut(&mut window::Window) + 'static>(
232        &self,
233        mut run_cb: F,
234    ) -> Result<(), Box<dyn std::error::Error>> {
235        let mut win = window::Window::default()
236            .with_size(self.w, self.h)
237            .with_label(&self.label);
238        if let Some(widget) = &self.widget {
239            utils::transform(widget);
240        }
241        win.end();
242        win.show();
243
244        if let Some(mut frst) = win.child(0) {
245            frst.resize(0, 0, win.w(), win.h());
246            win.resizable(&frst);
247        }
248
249        run_cb(&mut win);
250
251        self.a.run()?;
252        Ok(())
253    }
254
255    /// Just load the image of the window
256    pub fn dump_image(&self) {
257        let mut win = window::Window::default()
258            .with_size(self.w, self.h)
259            .with_label(&self.label);
260        if let Some(widget) = &self.widget {
261            utils::transform(widget);
262        }
263        win.end();
264        win.show();
265
266        if let Some(mut frst) = win.child(0) {
267            frst.resize(0, 0, win.w(), win.h());
268            win.resizable(&frst);
269        }
270        let sur = surface::SvgFileSurface::new(win.w(), win.h(), "temp.svg");
271        surface::SvgFileSurface::push_current(&sur);
272        draw::set_draw_color(enums::Color::White);
273        draw::draw_rectf(0, 0, win.w(), win.h());
274        sur.draw(&win, 0, 0);
275        surface::SvgFileSurface::pop_current();
276    }
277}