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#[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 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 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 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 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 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}