From e84661f0ac94c4cab8e7c2f265e2e26c952b5664 Mon Sep 17 00:00:00 2001 From: "Artem V. Ageev" Date: Tue, 16 Jul 2024 13:19:19 +0300 Subject: [PATCH 01/26] add cairo's live-demo --- .github/workflows/rust.yml | 14 ++ README.md | 4 +- demos/Cargo.toml | 1 + demos/cairo/Cargo.toml | 4 +- demos/cairo/README.md | 5 + demos/cairo/assets/flcairo.png | Bin 5271 -> 0 bytes demos/cairo/src/main.rs | 220 +++++--------------- demos/cairo/src/model/mod.rs | 14 +- demos/cairo_shadow_button/Cargo.toml | 10 + demos/cairo_shadow_button/README.md | 5 + demos/cairo_shadow_button/src/main.rs | 223 +++++++++++++++++++++ demos/cairo_shadow_button/src/model/mod.rs | 18 ++ demos/csv/README.md | 6 + demos/csv/assets/flcsv.png | Bin 4721 -> 0 bytes demos/csv/assets/flerrands.gif | Bin 15610 -> 0 bytes 15 files changed, 337 insertions(+), 187 deletions(-) create mode 100644 demos/cairo/README.md delete mode 100644 demos/cairo/assets/flcairo.png create mode 100644 demos/cairo_shadow_button/Cargo.toml create mode 100644 demos/cairo_shadow_button/README.md create mode 100644 demos/cairo_shadow_button/src/main.rs create mode 100644 demos/cairo_shadow_button/src/model/mod.rs create mode 100644 demos/csv/README.md delete mode 100644 demos/csv/assets/flcsv.png delete mode 100644 demos/csv/assets/flerrands.gif diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a201d23..6b2725a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -56,3 +56,17 @@ jobs: if [[ ${RUNNER_OS} == "Linux" ]]; then cargo build --quiet || cargo build --verbose fi + - name: Build cairo_shadow_button + working-directory: demos/cairo_shadow_button + shell: bash + run: | + if [[ ${RUNNER_OS} == "Linux" ]]; then + cargo build --quiet || cargo build --verbose + fi + - name: Build csv + working-directory: demos/csv + shell: bash + run: | + if [[ ${RUNNER_OS} == "Linux" ]]; then + cargo build --quiet || cargo build --verbose + fi diff --git a/README.md b/README.md index 9558916..819505e 100644 --- a/README.md +++ b/README.md @@ -130,11 +130,11 @@ cargo run --example flresters ### [FlCSV](/demos/csv) -![FlCSV](/demos/csv/assets/flcsv.png) +![FlCSV](https://github.com/fltk-rs/demos/blob/master/csv/assets/csv.gif) ### [FlCairo](/demos/cairo) -![FlCairo](/demos/cairo/assets/flcairo.png) +![FlCairo](https://github.com/fltk-rs/demos/blob/master/cairo/assets/scrot.png) ### [Flightbooker](/demos/flightbooker) diff --git a/demos/Cargo.toml b/demos/Cargo.toml index ab0855b..0ea5367 100644 --- a/demos/Cargo.toml +++ b/demos/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "cairo", + "cairo_shadow_button", "csv", "fltodo", "flightbooker", diff --git a/demos/cairo/Cargo.toml b/demos/cairo/Cargo.toml index 92fb339..1c3ddf2 100644 --- a/demos/cairo/Cargo.toml +++ b/demos/cairo/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cairo_button" +name = "cairo_frame" version = "0.1.0" edition = "2021" @@ -7,5 +7,5 @@ edition = "2021" [dependencies] flemish = { path = "../../" } +fltk = { version = "^1.4", features = ["use-ninja", "cairoext"] } cairo-rs = "0.18" -cairo-blur = "^0.1" diff --git a/demos/cairo/README.md b/demos/cairo/README.md new file mode 100644 index 0000000..0009e0e --- /dev/null +++ b/demos/cairo/README.md @@ -0,0 +1,5 @@ +# Cairo demo + +Use Cairo for custom drawing. + +![img](https://github.com/fltk-rs/demos/blob/master/cairo/assets/scrot.png) diff --git a/demos/cairo/assets/flcairo.png b/demos/cairo/assets/flcairo.png deleted file mode 100644 index 9c245719c320d530efc012701d54a7af3dbae4f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5271 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 diff --git a/demos/cairo/src/main.rs b/demos/cairo/src/main.rs index 84a73ce..a200cb8 100644 --- a/demos/cairo/src/main.rs +++ b/demos/cairo/src/main.rs @@ -1,38 +1,29 @@ -#![forbid(unsafe_code)] - mod model; use { cairo::{Context, Format, ImageSurface}, flemish::{ app, - button::Button, - color_themes, draw, - enums::Event, - enums::{Align, Color, ColorDepth, Font, Shortcut}, frame::Frame, - group::Flex, - image::RgbImage, - menu::{MenuButton, MenuButtonType, MenuFlag}, + color_themes, + enums::{Color, FrameType}, prelude::*, - OnEvent, OnMenuEvent, Sandbox, Settings, + OnEvent, Sandbox, Settings, }, model::Model, }; #[derive(Clone, Copy)] pub enum Message { - Inc, - Dec, - Quit, + Change(usize), } fn main() { Model::new().run(Settings { - size: (640, 360), + size: (260, 260), ignore_esc_close: true, resizable: false, - background: Some(Color::from_u32(0xfdf6e3)), + background: Some(Color::White), color_map: Some(color_themes::TAN_THEME), scheme: Some(app::Scheme::Base), ..Default::default() @@ -47,177 +38,60 @@ impl Sandbox for Model { } fn title(&self) -> String { - format!("{} - FlCairo", self.value()) + format!("{} - FlCairo", self.state[0]) } fn view(&mut self) { - let mut page = Flex::default() - .with_size(600, 200) - .center_of_parent() - .column(); - - let hero = Flex::default(); //HERO - crate::cairobutton() - .with_label("@#<") - .on_event(move |_| Message::Dec); - crate::frame(&self.value()).handle(crate::popup); - crate::cairobutton() - .with_label("@#>") - .on_event(move |_| Message::Inc); - hero.end(); - - page.end(); - page.set_pad(0); - page.set_margin(0); + fltk::app::cairo::set_autolink_context(true); + let mut frame = cairowidget(5, 5, 100, 100, "Box1"); + frame.set_color(match self.state[0] { + true => Color::Red, + false => Color::DarkRed, + }); + frame.on_event(move |_| Message::Change(0)); + let mut frame = cairowidget(80, 80, 100, 100, "Box2"); + frame.set_color(match self.state[1] { + true => Color::Yellow, + false => Color::DarkYellow, + }); + frame.on_event(move |_| Message::Change(1)); + let mut frame = cairowidget(155, 155, 100, 100, "Box3"); + frame.set_color(match self.state[2] { + true => Color::Green, + false => Color::DarkGreen, + }); + frame.clone().on_event(move |_| Message::Change(2)); } fn update(&mut self, message: Message) { match message { - Message::Inc => self.inc(), - Message::Dec => self.dec(), - Message::Quit => app::quit(), + Message::Change(idx) => self.change(idx), } } } -fn menu() -> MenuButton { - MenuButton::default() - .with_type(MenuButtonType::Popup3) - .with_label("@#menu") - .on_item_event( - "@#+ &Increment", - Shortcut::Ctrl | 'i', - MenuFlag::Normal, - move |_| Message::Inc, - ) - .on_item_event( - "@#- &Decrement", - Shortcut::Ctrl | 'd', - MenuFlag::Normal, - move |_| Message::Dec, - ) - .on_item_event( - "@#1+ Quit", - Shortcut::Ctrl | 'q', - MenuFlag::Normal, - move |_| Message::Quit, - ) -} - -fn popup(_: &mut Frame, event: Event) -> bool { - match event { - Event::Push => match app::event_mouse_button() { - app::MouseButton::Right => { - crate::menu().popup(); - true - } - _ => false, - }, - _ => false, - } -} - -fn frame(value: &str) -> Frame { - let mut element = Frame::default().with_label(value); - element.set_label_size(60); - element -} - -fn cairobutton() -> Button { - let mut element = Button::default(); - element.super_draw(false); - element.draw(|button| { - draw::draw_rect_fill( - button.x(), - button.y(), - button.w(), - button.h(), - Color::from_u32(0xfdf6e3), - ); - let mut surface = ImageSurface::create(Format::ARgb32, button.w(), button.h()) - .expect("Couldn’t create surface"); - crate::draw_surface(&mut surface, button.w(), button.h()); - if !button.value() { - cairo_blur::blur_image_surface(&mut surface, 20); - } - surface - .with_data(|surface| { - RgbImage::new(surface, button.w(), button.h(), ColorDepth::Rgba8) - .unwrap() - .draw(button.x(), button.y(), button.w(), button.h()); - }) - .unwrap(); - draw::set_draw_color(Color::Black); - draw::set_font(Font::Helvetica, app::font_size()); - if !button.value() { - draw::draw_rbox( - button.x() + 1, - button.y() + 1, - button.w() - 6, - button.h() - 6, - 15, - true, - Color::White, - ); - draw::draw_text2( - &button.label(), - button.x() + 1, - button.y() + 1, - button.w() - 6, - button.h() - 6, - Align::Center, - ); - } else { - draw::draw_rbox( - button.x() + 1, - button.y() + 1, - button.w() - 4, - button.h() - 4, - 15, - true, - Color::White, - ); - draw::draw_text2( - &button.label(), - button.x() + 1, - button.y() + 1, - button.w() - 4, - button.h() - 4, - Align::Center, - ); - } - }); - element -} - -fn draw_surface(surface: &mut ImageSurface, w: i32, h: i32) { - let ctx = Context::new(surface).unwrap(); +fn draw_box_with_alpha(rect: &mut Frame) { + let ctx = unsafe { Context::from_raw_none(fltk::app::cairo::cc() as _) }; + let (r, g, b) = rect.color().to_rgb(); ctx.save().unwrap(); - let corner_radius = h as f64 / 10.0; - let radius = corner_radius / 1.0; - let degrees = std::f64::consts::PI / 180.0; - - ctx.new_sub_path(); - ctx.arc(w as f64 - radius, radius, radius, -90. * degrees, 0.0); - ctx.arc( - w as f64 - radius, - h as f64 - radius, - radius, - 0.0, - 90. * degrees, - ); - ctx.arc( - radius, - h as f64 - radius, - radius, - 90. * degrees, - 180. * degrees, - ); - ctx.arc(radius, radius, radius, 180. * degrees, 270. * degrees); + ctx.move_to(rect.x() as f64, rect.y() as f64); + ctx.line_to((rect.x() + rect.w()) as f64, rect.y() as f64); + ctx.line_to((rect.x() + rect.w()) as f64, (rect.y() + rect.h()) as f64); + ctx.line_to(rect.x() as f64, (rect.y() + rect.h()) as f64); ctx.close_path(); - - ctx.set_source_rgba(150.0 / 255.0, 150.0 / 255.0, 150.0 / 255.0, 40.0 / 255.0); - ctx.set_line_width(4.); + ctx.set_source_rgba( + r as f64 / 255.0, + g as f64 / 255.0, + b as f64 / 255.0, + 100.0 / 255.0, + ); ctx.fill().unwrap(); ctx.restore().unwrap(); } + +pub fn cairowidget(x: i32, y: i32, w: i32, h: i32, label: &str) -> Frame { + let mut element = Frame::new(x, y, w, h, None).with_label(label); + element.super_draw_first(false); // required for windows + element.draw(draw_box_with_alpha); + element +} diff --git a/demos/cairo/src/model/mod.rs b/demos/cairo/src/model/mod.rs index 0f65d50..e3716d1 100644 --- a/demos/cairo/src/model/mod.rs +++ b/demos/cairo/src/model/mod.rs @@ -1,18 +1,12 @@ pub struct Model { - value: u8, + pub state: [bool; 3], } impl Model { pub fn default() -> Self { - Self { value: 0u8 } + Self { state: [true; 3] } } - pub fn inc(&mut self) { - self.value = self.value.saturating_add(1); - } - pub fn dec(&mut self) { - self.value = self.value.saturating_sub(1); - } - pub fn value(&self) -> String { - self.value.to_string() + pub fn change(&mut self, idx: usize) { + self.state[idx] = !self.state[idx]; } } diff --git a/demos/cairo_shadow_button/Cargo.toml b/demos/cairo_shadow_button/Cargo.toml new file mode 100644 index 0000000..adc0aa9 --- /dev/null +++ b/demos/cairo_shadow_button/Cargo.toml @@ -0,0 +1,10 @@ +[package] +authors = ["Mohammed Alyousef "] +name = "cairo_button" +version = "0.1.0" +edition = "2021" + +[dependencies] +flemish = { path = "../../" } +cairo-rs = "0.18" +cairo-blur = "^0.1" diff --git a/demos/cairo_shadow_button/README.md b/demos/cairo_shadow_button/README.md new file mode 100644 index 0000000..0754df8 --- /dev/null +++ b/demos/cairo_shadow_button/README.md @@ -0,0 +1,5 @@ +# cairo shadow button + +This shows how to create a rounded button with a blur effect using cairo. + +![img](https://github.com/fltk-rs/demos/blob/master/cairo_shadow_button/assets/scrot.png) diff --git a/demos/cairo_shadow_button/src/main.rs b/demos/cairo_shadow_button/src/main.rs new file mode 100644 index 0000000..9b7b33b --- /dev/null +++ b/demos/cairo_shadow_button/src/main.rs @@ -0,0 +1,223 @@ +#![forbid(unsafe_code)] + +mod model; + +use { + cairo::{Context, Format, ImageSurface}, + flemish::{ + app, + button::Button, + color_themes, + draw, + enums::{Event, Align, Color, ColorDepth, Font, Shortcut}, + frame::Frame, + group::Flex, + image::RgbImage, + menu::{MenuButton, MenuButtonType, MenuFlag}, + prelude::*, + OnEvent, OnMenuEvent, Sandbox, Settings, + }, + model::Model, +}; + +#[derive(Clone, Copy)] +pub enum Message { + Inc, + Dec, + Quit, +} + +fn main() { + Model::new().run(Settings { + size: (640, 360), + ignore_esc_close: true, + resizable: false, + background: Some(Color::from_u32(0xfdf6e3)), + color_map: Some(color_themes::TAN_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + format!("{} - FlCairoButton", self.value()) + } + + fn view(&mut self) { + let mut page = Flex::default() + .with_size(600, 200) + .center_of_parent() + .column(); + + let hero = Flex::default(); //HERO + crate::cairobutton() + .with_label("@#<") + .on_event(move |_| Message::Dec); + crate::frame().with_label(&self.value()).handle(crate::popup); + crate::cairobutton() + .with_label("@#>") + .on_event(move |_| Message::Inc); + hero.end(); + + page.end(); + page.set_pad(0); + page.set_margin(0); + } + + fn update(&mut self, message: Message) { + match message { + Message::Inc => self.inc(), + Message::Dec => self.dec(), + Message::Quit => app::quit(), + } + } +} + +fn menu() -> MenuButton { + MenuButton::default() + .with_type(MenuButtonType::Popup3) + .with_label("@#menu") + .on_item_event( + "@#+ &Increment", + Shortcut::Ctrl | 'i', + MenuFlag::Normal, + move |_| Message::Inc, + ) + .on_item_event( + "@#- &Decrement", + Shortcut::Ctrl | 'd', + MenuFlag::Normal, + move |_| Message::Dec, + ) + .on_item_event( + "@#1+ Quit", + Shortcut::Ctrl | 'q', + MenuFlag::Normal, + move |_| Message::Quit, + ) +} + +fn popup(_: &mut Frame, event: Event) -> bool { + match event { + Event::Push => match app::event_mouse_button() { + app::MouseButton::Right => { + crate::menu().popup(); + true + } + _ => false, + }, + _ => false, + } +} + +fn frame() -> Frame { + let mut element = Frame::default(); + element.set_label_size(60); + element +} + +fn cairobutton() -> Button { + let mut element = Button::default(); + element.super_draw(false); + element.draw(|button| { + draw::draw_rect_fill( + button.x(), + button.y(), + button.w(), + button.h(), + Color::from_u32(0xfdf6e3), + ); + let mut surface = ImageSurface::create(Format::ARgb32, button.w(), button.h()) + .expect("Couldn’t create surface"); + crate::draw_surface(&mut surface, button.w(), button.h()); + if !button.value() { + cairo_blur::blur_image_surface(&mut surface, 20); + } + surface + .with_data(|surface| { + RgbImage::new(surface, button.w(), button.h(), ColorDepth::Rgba8) + .unwrap() + .draw(button.x(), button.y(), button.w(), button.h()); + }) + .unwrap(); + draw::set_draw_color(Color::Black); + draw::set_font(Font::Helvetica, app::font_size()); + if !button.value() { + draw::draw_rbox( + button.x() + 1, + button.y() + 1, + button.w() - 6, + button.h() - 6, + 15, + true, + Color::White, + ); + draw::draw_text2( + &button.label(), + button.x() + 1, + button.y() + 1, + button.w() - 6, + button.h() - 6, + Align::Center, + ); + } else { + draw::draw_rbox( + button.x() + 1, + button.y() + 1, + button.w() - 4, + button.h() - 4, + 15, + true, + Color::White, + ); + draw::draw_text2( + &button.label(), + button.x() + 1, + button.y() + 1, + button.w() - 4, + button.h() - 4, + Align::Center, + ); + } + }); + element +} + +fn draw_surface(surface: &mut ImageSurface, w: i32, h: i32) { + let ctx = Context::new(surface).unwrap(); + ctx.save().unwrap(); + let corner_radius = h as f64 / 10.0; + let radius = corner_radius / 1.0; + let degrees = std::f64::consts::PI / 180.0; + + ctx.new_sub_path(); + ctx.arc(w as f64 - radius, radius, radius, -90. * degrees, 0.0); + ctx.arc( + w as f64 - radius, + h as f64 - radius, + radius, + 0.0, + 90. * degrees, + ); + ctx.arc( + radius, + h as f64 - radius, + radius, + 90. * degrees, + 180. * degrees, + ); + ctx.arc(radius, radius, radius, 180. * degrees, 270. * degrees); + ctx.close_path(); + + ctx.set_source_rgba(150.0 / 255.0, 150.0 / 255.0, 150.0 / 255.0, 40.0 / 255.0); + ctx.set_line_width(4.); + ctx.fill().unwrap(); + ctx.restore().unwrap(); +} diff --git a/demos/cairo_shadow_button/src/model/mod.rs b/demos/cairo_shadow_button/src/model/mod.rs new file mode 100644 index 0000000..0f65d50 --- /dev/null +++ b/demos/cairo_shadow_button/src/model/mod.rs @@ -0,0 +1,18 @@ +pub struct Model { + value: u8, +} + +impl Model { + pub fn default() -> Self { + Self { value: 0u8 } + } + pub fn inc(&mut self) { + self.value = self.value.saturating_add(1); + } + pub fn dec(&mut self) { + self.value = self.value.saturating_sub(1); + } + pub fn value(&self) -> String { + self.value.to_string() + } +} diff --git a/demos/csv/README.md b/demos/csv/README.md new file mode 100644 index 0000000..8a1eae3 --- /dev/null +++ b/demos/csv/README.md @@ -0,0 +1,6 @@ +# CSV + +Custom drawing of CSV data. Uses CSV and Serde. + +![img](https://github.com/fltk-rs/demos/blob/master/csv/assets/csv.gif) + diff --git a/demos/csv/assets/flcsv.png b/demos/csv/assets/flcsv.png deleted file mode 100644 index 7b715172f36f6f460d74093115178d2e7d90e7ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4721 zcmeHLXH=6}w?0W|3JfYLs36QJ0t$kHC_RW}1O=q16d@u_DN!IKgcineu+bDjq^kj? z_ZCVV6i`rVKnM{0q)4xUG{|?DZ)V+lXRYu4`tHBG*Sl8Ub@HC~oc-**pLaj!zJ=LY z@y)w80|3O&{d~$203-{>cV(olu^b7^@f0`$e6;nZjEOIV|RV>PrUeD@V=De>rDrCe~YBdo2cD8-0^Js zA0Ek6xn|m$Y=(LKd$O3ckkZqOjng_xs@GMU9==q(YD)JbuJeXgliL(c z1Bjf|u5rzlx{%aWr)zUVwVQxPzH0{xkOKBo!4@&lh z!2pr=qWKCnhsyTcSeL<-89F1BJ0qwyPeG=8b=*|hHXPU4qE_$F`{QV5s(#SHzSg|gpumpo!_=J1 z1$DloChV5ZZ^t})G%*;^{xMs0K3>&6O3rY7ojaFM7YsMrTi{fGla}51NF3N*!UYEeEsB5Kr~00dYB{#Os{X+4UpC$${(YmqU!4Epkv9} zewkNPhJ2IY63nSyIenr(z1;iIrQ8!CoaOoXTWovi!CcYxrgERb{=vbJbspO)%Z$cT z2J-F@j$+rA(D^%Z{y1EUUf^Qv*}aK@*x^JXkr*)3U*@d-@%sS>+4(0%X2QeqO@H= zniK$^FL0{QVzAMn0`eo@-YH9&$86R%@b$gaHdv zMX@jUO2oFD%qYZ7^>luNae=;GtsxYZXCTp2uV>|R8zLkOIp1Fzayp{TQgmo^x~`6n z4kI4At zl7IuzgTJ`XEFDMuQ8XGy#^|8Gbd@1l9Ez~9Zm$Tcoki7 zD7#66ArBb+_!MqbP2;#@k@Nd+(T1Tg$r7AGwaM1~USmR7$RB+r?w(wE5Tj@#OrT}k zFnD9f=j!?Eu=*WpiMTZmlY*aH|AY-)YTSeM4+sbyJ>+ywxptv(&uEYwIj-EBzA~y7 z#va5oR0S(bO8i!52Zbf})`YHc_(B~WF?Wg(@+H}aoM2LZ#@Ceuvlm0SE0NLB327=$ zt$`5i>An)(faw#rhS6*rUHmL8iIEiikoHoZBjM{^K5yKR$r1rEhO`;f2I`HGd(uKy z`g}v&2N@MFUw#{Ydk4-|GVG#nSP+l3pd&5~?2I6NK_-ZN^eAp_n&M3_pPilUBo~<6 zSKYq3pnjdxTengMi6kyyYIdM%sX2BZj8w_cO&COoA-SobcBQ}d1q$7g<0Di_x9Dg)!Om2u+XKkPcV0(>jV0$0~%9#p?ofb zE-Z$oXZr{RkF_Sk`hm5eTGLm*&Ma71Scrhh+i_MdCCC&5(yIY~DUDwPVI8Usa=#Eb zt`p3fhhg3zQW=Bb%JQv9v}m@^CgRPTlb7o`G<-dGFl}wxUC}IAT25|mdU^nY`o=2D z%{%aWd&VFI55bvQlD!JQH2I5xROpy4BsEJzBp5NLU8YvTh0Gz_aAzrV@T&V3rF-Vg znIB|ztaXM1ot>Te>ucna$B?JORw<091y~&9K0`jcjod{3@BhB6{{LK>(6$doDW$l6eG=4f`ISgh21{)e!nG=Ki;z>%XY|KmGve;4+hW zS|a20e=oU>&H;UP8Gz=9@XmT6VJnlhjypFHZdA~kbGu2eEo3}Ex6Y1)czsUcYjV{m zvDP2h`);K#Oyv#ORlxgR!`fJ+q(j;<)yeFgqovwk0nGd z#hzcu>I>8C#wF*)xn%u_aB2QYaeg_gJB_o#RJA*%h;&T@C=2&IO>=J7e^;ZqIqK7f%xrs9$fqOYQ%NUFkL8-Y;RF`$R%aV#b2;H~jj@SG#{FtB-wb z!5Whg#o76avZD&7Gw?n;zU&$8ntGyMLuJ_}b+l=bQi>E(%)-5Fzr`_n1VD9X{_>vw z<&c*9tUy*~%UIemGh^hHQ(ETE8HVKbxD_UIbh%)&28Q(8y%gLh-bBTfZCILdzAv*% zKssH^Z;tObbs?K^D4G4Mg{Wyeut}nKc+I*dD|7dG9Y>0lmQ``_Nu^DAd2+ZWs~cmt z{2;be$+R84$ChCD`sLjl2M|JbJwI_B#vywwE2tc;6`59x%9O;A*rSuL`wy13yR)0q zj#Y6SAQ;}N(GgY`DJscyRVw;n^Y47i4Bzone1rUrICF^Y5AFN~}T+;3o0(sId^%(7^G8k8m z=@3L24Ky(FbeY^!509e$Xj`kk+BLPC2uS;Sxjhzk<5{tDise1Og^z8)G{1Ipdvn_` zXA-~n=Nt0k2FEH{jIA+#NHJ}T=e-8UPE`JS&?bp$=hpd@r?PZa>GpQb5%dIlm^c_Z zywAP;63;**j6>x!nvFgdx*)|6K3hxToSn;Gwdf4}L*f_hQ@eK_pl->1{OoD4%N^~n zxxA2_q2qQ#?wQ0|iPD3J-=^OXYzeXDw5fa}X0`Olz-l7*(TAhn#-wne=enN4CNhHJ zstN(My*D+iKZ{B4&YxVPFfAJ%b+zu;eSwtmdFKaj1+J^0L_Y}_@1sqrzrHkzKek&iUO5+ zwA7IYD}`FDEsAg;+Tq?lzvq4n5#ez{mc*O4Zefa4r*IW=i2~2gk^C?>dL!&uHfzev ztkQx4Y|l~em4dbXBWTY;ffQ34wecR$w%7W(0QVF1tsS$bvruBR4GGsc;T})uZ_`UE zci|#@jYuW?QKB`tO1;xub-K_^d8)SG!>-VSsdGIuO(+YEvqs}2Z6);75!C0$55^N7 zi^brP$zkz#5x&ja{H$p@`<^<2u|ffHfM_M{X~>l#3H~d2hflbD0N$+|#qf7V{f~3^|1a@(Rr`PNnUsC# zQ{>*3n)wK7spg*$Lvy{*J zPN7}mX5YWRsl*Q~DoG+7t9(bRaZ|If$v>HKi@i7+XPIH~v+|CI@7a#UQ1C!;qo>D% zlF0rJqB$j3fR#^~t)8pn9#B-=5GAnnhQGfxX=B1P z$`ST@U51$e}rpGF;~%#_o6a&4bj5?2TDRwm@8 zCGWn03@Qw01_VAf+*!}W#ejZ=t7 zxvfC+R5?=b*fHEhC)uQ&_od0;02PcRD0uvvMyYt)@Sfv7nlm8N?{v~G~2hl6A(R-1g>f#p) r9=j|J(r3O1|LvURui*cW;$Ihah$JjMkJl&z(9WGUJC%PD^UFU0m5gty diff --git a/demos/csv/assets/flerrands.gif b/demos/csv/assets/flerrands.gif deleted file mode 100644 index cc09ebc44861944d8f96f317340fa703cf097c19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15610 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 Date: Tue, 16 Jul 2024 13:23:18 +0300 Subject: [PATCH 02/26] add cairo's live-demo --- demos/cairo/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demos/cairo/src/main.rs b/demos/cairo/src/main.rs index a200cb8..3eaca9d 100644 --- a/demos/cairo/src/main.rs +++ b/demos/cairo/src/main.rs @@ -1,12 +1,12 @@ mod model; use { - cairo::{Context, Format, ImageSurface}, + cairo::Context, flemish::{ app, frame::Frame, color_themes, - enums::{Color, FrameType}, + enums::Color, prelude::*, OnEvent, Sandbox, Settings, }, From 6c90f9c4e90f4706fffc6331c3350c338e921363 Mon Sep 17 00:00:00 2001 From: "Artem V. Ageev" Date: Wed, 17 Jul 2024 15:47:22 +0300 Subject: [PATCH 03/26] add calculator' live-demo --- README.md | 7 +- demos/Cargo.toml | 1 + demos/calculator/Cargo.toml | 11 ++ demos/calculator/README.md | 5 + .../calculator/src/main.rs | 138 +++--------------- demos/calculator/src/model/mod.rs | 98 +++++++++++++ demos/fltodo/src/main.rs | 5 +- 7 files changed, 141 insertions(+), 124 deletions(-) create mode 100644 demos/calculator/Cargo.toml create mode 100644 demos/calculator/README.md rename examples/flcalculator.rs => demos/calculator/src/main.rs (61%) create mode 100644 demos/calculator/src/model/mod.rs diff --git a/README.md b/README.md index 819505e..605795a 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ To run the [examples:](/examples) cargo run --example counter cargo run --example temperature cargo run --example crud -cargo run --example flcalculator cargo run --example fldialect cargo run --example flglyph cargo run --example flnetport @@ -88,7 +87,7 @@ cargo run --example flresters ### [FlCounter](/examples/counter.rs) -![FlCalculator](/assets/counter.png) +![FlCounter](/assets/counter.png) ### [FlTemperature](/examples/temperature.rs) @@ -98,9 +97,9 @@ cargo run --example flresters ![FlCRUD](/assets/crud.png) -### [FlCalculator](/examples/flcalculator.rs) +### [FlCalculator](/examples/calculator.rs) -![FlCalculator](/assets/flcalculator.gif) +![FlCalculator](https://github.com/fltk-rs/demos/tree/master/flcalculator/assets/flcalculator.gif) ### [FlDialect](/examples/fldialect.rs) diff --git a/demos/Cargo.toml b/demos/Cargo.toml index 0ea5367..0ba8891 100644 --- a/demos/Cargo.toml +++ b/demos/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ "cairo", "cairo_shadow_button", + "calculator", "csv", "fltodo", "flightbooker", diff --git a/demos/calculator/Cargo.toml b/demos/calculator/Cargo.toml new file mode 100644 index 0000000..812d753 --- /dev/null +++ b/demos/calculator/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "calculate" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +flemish = { path = "../../" } +serde = { version="1.0", features = ["derive"] } +rmp-serde = { version="1.1" } diff --git a/demos/calculator/README.md b/demos/calculator/README.md new file mode 100644 index 0000000..d10e225 --- /dev/null +++ b/demos/calculator/README.md @@ -0,0 +1,5 @@ +# Calculator demo + +It's just calculator. + +![img](https://github.com/fltk-rs/demos/tree/master/flcalculator/assets/flcalculator.gif) diff --git a/examples/flcalculator.rs b/demos/calculator/src/main.rs similarity index 61% rename from examples/flcalculator.rs rename to demos/calculator/src/main.rs index 43eaf2c..b891e03 100644 --- a/examples/flcalculator.rs +++ b/demos/calculator/src/main.rs @@ -1,4 +1,5 @@ #![forbid(unsafe_code)] +mod model; use { flemish::{ @@ -13,11 +14,11 @@ use { text::{TextBuffer, TextDisplay, WrapMode}, OnEvent, OnMenuEvent, Sandbox, Settings, }, - std::{env, fs, path::Path}, + model::Model, }; -pub fn main() { - app::GlobalState::::new(env::var("HOME").unwrap() + PATH + NAME); +fn main() { + app::GlobalState::::new(std::env::var("HOME").unwrap() + PATH + NAME); Model::new().run(Settings { size: (360, 640), resizable: false, @@ -29,70 +30,12 @@ pub fn main() { } #[derive(PartialEq, Clone)] -enum Message { +pub enum Message { Click(String), Theme, Quit, } -#[derive(Clone)] -struct Model { - prev: String, - operation: String, - current: String, - output: String, - theme: bool, -} - -impl Model { - fn theme(&mut self) { - self.theme = !self.theme; - } - fn click(&mut self, value: String) { - match value.as_str() { - "/" | "x" | "+" | "-" | "%" => { - if self.operation.is_empty() { - self.operation.push_str(&value); - self.prev = self.current.clone(); - } else { - self.equil(); - self.operation = String::from("="); - } - self.output - .push_str(&format!("{} {}", self.prev, self.operation)); - self.current = String::from("0"); - } - "=" => self.equil(), - "CE" => { - self.output.clear(); - self.operation.clear(); - self.current = String::from("0"); - self.prev = String::from("0"); - } - "@<-" => { - let label = self.current.clone(); - self.current = if label.len() > 1 { - String::from(&label[..label.len() - 1]) - } else { - String::from("0") - }; - } - "C" => self.current = String::from("0"), - "." => { - if !self.current.contains('.') { - self.current.push('.'); - } - } - _ => { - if self.current == "0" { - self.current.clear(); - } - self.current = self.current.clone() + &value; - } - }; - } -} - impl Sandbox for Model { type Message = Message; @@ -101,18 +44,8 @@ impl Sandbox for Model { } fn new() -> Self { - let file = app::GlobalState::::get().with(move |model| model.clone()); - let theme: bool = match Path::new(&file).exists() { - true => fs::read(&file).unwrap()[0], - false => 0, - } != 0; - Self { - prev: String::from("0"), - operation: String::new(), - current: String::from("0"), - output: String::new(), - theme, - } + let file = app::GlobalState::::get().with(move |file| file.clone()); + Model::default(&file) } fn view(&mut self) { @@ -121,12 +54,12 @@ impl Sandbox for Model { crate::display("Output", &self.output, self.theme as usize); let mut row = Flex::default(); row.fixed( - &crate::output("Operation", &self.operation, self.theme as usize), + &crate::output("Operation", self.theme as usize).with_label(&self.operation), 30, ); let mut col = Flex::default().column(); - crate::output("Previous", &self.prev, self.theme as usize); - crate::output("Current", &self.current, self.theme as usize); + crate::output("Previous", self.theme as usize).with_label(&self.prev.to_string()); + crate::output("Current", self.theme as usize).with_label(&self.current); col.end(); row.end(); let mut buttons = Flex::default_fill().column(); @@ -171,45 +104,17 @@ impl Sandbox for Model { fn update(&mut self, message: Message) { match message { - Message::Quit => self.quit(), - Message::Theme => self.theme(), + Message::Quit => { + let file = app::GlobalState::::get().with(move |file| file.clone()); + self.save(&file); + app::quit(); + } + Message::Theme => self.theme = !self.theme, Message::Click(value) => self.click(value), }; } } -impl Model { - fn quit(&self) { - let file = app::GlobalState::::get().with(move |model| model.clone()); - fs::write(file, [self.theme as u8]).unwrap(); - app::quit(); - } - fn equil(&mut self) { - if !self.operation.is_empty() { - let left: f64 = self.prev.parse().unwrap(); - let right: f64 = self.current.parse().unwrap(); - let temp = match self.operation.as_str() { - "/" => left / right, - "x" => left * right, - "+" => left + right, - "-" => left - right, - _ => left / 100.0 * right, - }; - self.output.push_str(&format!( - " {right}\n{} = {temp}\n", - (0..=left.to_string().len()) - .map(|_| ' ') - .collect::(), - )); - self.prev = temp.to_string(); - } else { - self.prev = self.current.clone(); - } - self.operation.clear(); - self.current = String::from("0"); - } -} - fn display(tooltip: &str, value: &str, theme: usize) { let mut element = TextDisplay::default(); element.set_tooltip(tooltip); @@ -219,22 +124,21 @@ fn display(tooltip: &str, value: &str, theme: usize) { element.set_scrollbar_size(3); element.set_frame(FrameType::FlatBox); element.wrap_mode(WrapMode::AtBounds, 0); - element.set_color(COLORS[theme as usize][0]); - element.set_text_color(COLORS[theme as usize][1]); + element.set_color(COLORS[theme][0]); + element.set_text_color(COLORS[theme][1]); element.scroll( element.buffer().unwrap().text().split_whitespace().count() as i32, 0, ); } -fn output(tooltip: &str, value: &str, theme: usize) -> Frame { +fn output(tooltip: &str, theme: usize) -> Frame { let mut element = Frame::default().with_align(Align::Right | Align::Inside); element.set_tooltip(tooltip); element.set_label_size(HEIGHT); - element.set_label(value); element.set_frame(FrameType::FlatBox); - element.set_color(COLORS[theme as usize][0]); - element.set_label_color(COLORS[theme as usize][1]); + element.set_color(COLORS[theme][0]); + element.set_label_color(COLORS[theme][1]); element } diff --git a/demos/calculator/src/model/mod.rs b/demos/calculator/src/model/mod.rs new file mode 100644 index 0000000..bb2219a --- /dev/null +++ b/demos/calculator/src/model/mod.rs @@ -0,0 +1,98 @@ +use { + serde::{Deserialize, Serialize}, + std::fs, +}; + +#[derive(Deserialize, Serialize, Clone)] +pub struct Model { + pub prev: f64, + pub operation: String, + pub current: String, + pub output: String, + pub theme: bool, +} + +impl Model { + pub fn click(&mut self, value: String) { + match value.as_str() { + "/" | "x" | "+" | "-" | "%" => { + if self.current != "0" { + if self.operation.is_empty() { + self.prev = self.current.parse().unwrap(); + } else { + self.equil(); + } + self.output.push_str(&format!("{} {}", self.prev, value)); + self.operation = value; + self.current = String::from("0"); + } + } + "=" => { + if !self.operation.is_empty() { + self.equil(); + self.operation.clear(); + } + } + "CE" => { + self.output.clear(); + self.operation.clear(); + self.current = String::from("0"); + self.prev = 0f64; + } + "@<-" => { + let label = self.current.clone(); + self.current = if label.len() > 1 { + String::from(&label[..label.len() - 1]) + } else { + String::from("0") + }; + } + "C" => self.current = String::from("0"), + "." => { + if !self.current.contains('.') { + self.current.push('.'); + } + } + _ => { + if self.current == "0" { + self.current.clear(); + } + self.current = self.current.clone() + &value; + } + }; + } + fn equil(&mut self) { + self.output.push_str(&format!(" {}\n", self.current)); + let current: f64 = self.current.parse().unwrap(); + self.prev = match self.operation.as_str() { + "/" => self.prev / current, + "x" => self.prev * current, + "+" => self.prev + current, + "-" => self.prev - current, + _ => self.prev / 100.0 * current, + }; + self.output.push_str(&format!(" = {}\n", self.prev)); + self.current = String::from("0"); + } + pub fn default(file: &str) -> Self { + let default = Self { + prev: 0f64, + operation: String::new(), + current: String::from("0"), + output: String::new(), + theme: false, + }; + if let Ok(value) = fs::read(file) { + if let Ok(value) = rmp_serde::from_slice(&value) { + value + } else { + default + } + } else { + default + } + } + pub fn save(&mut self, file: &str) { + fs::write(file, rmp_serde::to_vec(&self).unwrap()).unwrap(); + } +} diff --git a/demos/fltodo/src/main.rs b/demos/fltodo/src/main.rs index 81c378b..c88e4de 100644 --- a/demos/fltodo/src/main.rs +++ b/demos/fltodo/src/main.rs @@ -52,7 +52,7 @@ impl Sandbox for Model { } fn new() -> Self { - let file = app::GlobalState::::get().with(move |model| model.clone()); + let file = app::GlobalState::::get().with(move |file| file.clone()); let default = Self { tasks: Vec::new() }; if let Ok(value) = fs::read(file) { if let Ok(value) = rmp_serde::from_slice(&value) { @@ -124,8 +124,7 @@ impl Sandbox for Model { fn update(&mut self, message: Message) { match message { Message::Quit => { - let file = app::GlobalState::::get().with(move |model| model.clone()); - self.save(file); + self.save(app::GlobalState::::get().with(move |file| file.clone())); app::quit(); } Message::Delete(idx) => { From 74f7754d51f6ad8757dd996853726efa4b8b35ac Mon Sep 17 00:00:00 2001 From: "Artem V. Ageev" Date: Thu, 18 Jul 2024 12:10:58 +0300 Subject: [PATCH 04/26] add json-tools to resters --- .github/workflows/rust.yml | 7 + README.md | 31 +- demos/Cargo.toml | 1 + demos/calculator/src/main.rs | 83 +-- demos/calculator/src/model/mod.rs | 50 +- demos/resters/Cargo.toml | 14 + demos/resters/README.md | 5 + .../flresters.rs => demos/resters/src/main.rs | 105 ++-- demos/resters/src/model/mod.rs | 40 ++ examples/fldialect.rs | 473 ------------------ 10 files changed, 216 insertions(+), 593 deletions(-) create mode 100644 demos/resters/Cargo.toml create mode 100644 demos/resters/README.md rename examples/flresters.rs => demos/resters/src/main.rs (74%) create mode 100644 demos/resters/src/model/mod.rs delete mode 100644 examples/fldialect.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6b2725a..dc9fca4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -63,6 +63,13 @@ jobs: if [[ ${RUNNER_OS} == "Linux" ]]; then cargo build --quiet || cargo build --verbose fi + - name: Build calculator + working-directory: demos/calculator + shell: bash + run: | + if [[ ${RUNNER_OS} == "Linux" ]]; then + cargo build --quiet || cargo build --verbose + fi - name: Build csv working-directory: demos/csv shell: bash diff --git a/README.md b/README.md index 605795a..fb394bf 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ To run the [examples:](/examples) cargo run --example counter cargo run --example temperature cargo run --example crud -cargo run --example fldialect cargo run --example flglyph cargo run --example flnetport cargo run --example flpicture @@ -97,14 +96,6 @@ cargo run --example flresters ![FlCRUD](/assets/crud.png) -### [FlCalculator](/examples/calculator.rs) - -![FlCalculator](https://github.com/fltk-rs/demos/tree/master/flcalculator/assets/flcalculator.gif) - -### [FlDialect](/examples/fldialect.rs) - -![FlDialect](/assets/fldialect.gif) - ### [FlGlyph](/examples/flglyph.rs) ![FlGlyph](/assets/flglyph.png) @@ -117,23 +108,31 @@ cargo run --example flresters ![FlPicture](/assets/flpicture.gif) -### [FlResters](/examples/flresters.rs) +## Demos -![FlResters](/assets/flresters.png) +### [FlCairo](/demos/cairo) -## Demos +![FlCairo](https://github.com/fltk-rs/demos/blob/master/cairo/assets/scrot.png) -### [FlTodo](/demos/fltodo) +### [FlCalculator](/demos/calculator) -![FlTodo](/demos/fltodo/assets/fltodo.gif) +![FlCalculator](https://github.com/fltk-rs/demos/tree/master/flcalculator/assets/flcalculator.gif) ### [FlCSV](/demos/csv) ![FlCSV](https://github.com/fltk-rs/demos/blob/master/csv/assets/csv.gif) -### [FlCairo](/demos/cairo) +### [FlDialect](/demos/dialect) -![FlCairo](https://github.com/fltk-rs/demos/blob/master/cairo/assets/scrot.png) +![FlDialect](https://github.com/fltk-rs/demos/tree/master/fldialect/assets/fldialect.gif) + +### [FlResters](/demos/resters) + +![FlResters](https://github.com/fltk-rs/demos/tree/master/flresters/assets/flresters.gif) + +### [FlTodo](/demos/fltodo) + +![FlTodo](/demos/fltodo/assets/fltodo.gif) ### [Flightbooker](/demos/flightbooker) diff --git a/demos/Cargo.toml b/demos/Cargo.toml index 0ba8891..ba6a694 100644 --- a/demos/Cargo.toml +++ b/demos/Cargo.toml @@ -8,6 +8,7 @@ members = [ "csv", "fltodo", "flightbooker", + "resters", ] [profile.release] diff --git a/demos/calculator/src/main.rs b/demos/calculator/src/main.rs index b891e03..864e4c7 100644 --- a/demos/calculator/src/main.rs +++ b/demos/calculator/src/main.rs @@ -6,7 +6,7 @@ use { app, button::Button, color_themes, - enums::{Align, Color, Event, Font, FrameType, Key, Shortcut}, + enums::{Align, Color, Cursor, Event, Font, FrameType, Key, Shortcut}, frame::Frame, group::Flex, menu::{MenuButton, MenuButtonType, MenuFlag}, @@ -17,8 +17,31 @@ use { model::Model, }; +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const EQUAL: &str = "="; +const COLORS: [[Color; 6]; 2] = [ + [ + Color::from_hex(0xfdf6e3), + Color::from_hex(0x586e75), + Color::from_hex(0xb58900), + Color::from_hex(0xeee8d5), + Color::from_hex(0xcb4b16), + Color::from_hex(0xdc322f), + ], + [ + Color::from_hex(0x002b36), + Color::from_hex(0x93a1a1), + Color::from_hex(0x268bd2), + Color::from_hex(0x073642), + Color::from_hex(0x6c71c4), + Color::from_hex(0xd33682), + ], +]; +const NAME: &str = "FlCalculator"; + fn main() { - app::GlobalState::::new(std::env::var("HOME").unwrap() + PATH + NAME); + app::GlobalState::::new(std::env::var("HOME").unwrap() + "/.config/" + NAME); Model::new().run(Settings { size: (360, 640), resizable: false, @@ -63,7 +86,13 @@ impl Sandbox for Model { col.end(); row.end(); let mut buttons = Flex::default_fill().column(); - for line in BUTTONS { + for line in [ + ["CE", "C", "%", "/"], + ["7", "8", "9", "x"], + ["4", "5", "6", "-"], + ["1", "2", "3", "+"], + ["0", ".", "@<-", crate::EQUAL], + ] { let mut row = Flex::default(); for label in line { crate::button(label, self.theme as usize) @@ -81,7 +110,7 @@ impl Sandbox for Model { row.set_margin(0); buttons.set_pad(PAD); buttons.set_margin(0); - buttons.handle(move |_, event| match event { + buttons.handle(move |flex, event| match event { Event::Push => match app::event_mouse_button() { app::MouseButton::Right => { menu.popup(); @@ -89,6 +118,14 @@ impl Sandbox for Model { } _ => false, }, + Event::Enter => { + flex.window().unwrap().set_cursor(Cursor::Hand); + true + } + Event::Leave => { + flex.window().unwrap().set_cursor(Cursor::Arrow); + true + } _ => false, }); page.set_margin(PAD); @@ -110,7 +147,7 @@ impl Sandbox for Model { app::quit(); } Message::Theme => self.theme = !self.theme, - Message::Click(value) => self.click(value), + Message::Click(value) => self.click(&value), }; } } @@ -149,7 +186,7 @@ fn button(label: &'static str, theme: usize) -> Button { match label { "@<-" => element.set_shortcut(Shortcut::None | Key::BackSpace), "CE" => element.set_shortcut(Shortcut::None | Key::Delete), - "=" => element.set_shortcut(Shortcut::None | Key::Enter), + crate::EQUAL => element.set_shortcut(Shortcut::None | Key::Enter), "x" => element.set_shortcut(Shortcut::None | '*'), _ => element.set_shortcut(Shortcut::None | label.chars().next().unwrap()), } @@ -162,7 +199,7 @@ fn button(label: &'static str, theme: usize) -> Button { element.set_color(COLORS[theme][4]); element.set_label_color(COLORS[theme][0]); } - "=" => { + crate::EQUAL => { element.set_color(COLORS[theme][5]); element.set_label_color(COLORS[theme][0]); } @@ -197,37 +234,7 @@ pub fn menu(theme: usize) -> MenuButton { move |_| Message::Quit, ); if theme != 0 { - element.at(1).unwrap().set(); + element.at(0).unwrap().set(); }; element } - -const COLORS: [[Color; 6]; 2] = [ - [ - Color::from_hex(0xfdf6e3), - Color::from_hex(0x586e75), - Color::from_hex(0xb58900), - Color::from_hex(0xeee8d5), - Color::from_hex(0xcb4b16), - Color::from_hex(0xdc322f), - ], - [ - Color::from_hex(0x002b36), - Color::from_hex(0x93a1a1), - Color::from_hex(0x268bd2), - Color::from_hex(0x073642), - Color::from_hex(0x6c71c4), - Color::from_hex(0xd33682), - ], -]; -const BUTTONS: [[&str; 4]; 5] = [ - ["CE", "C", "%", "/"], - ["7", "8", "9", "x"], - ["4", "5", "6", "-"], - ["1", "2", "3", "+"], - ["0", ".", "@<-", "="], -]; -const PAD: i32 = 10; -const HEIGHT: i32 = PAD * 3; -const NAME: &str = "FlCalculator"; -const PATH: &str = "/.config"; diff --git a/demos/calculator/src/model/mod.rs b/demos/calculator/src/model/mod.rs index bb2219a..dbb725f 100644 --- a/demos/calculator/src/model/mod.rs +++ b/demos/calculator/src/model/mod.rs @@ -13,8 +13,29 @@ pub struct Model { } impl Model { - pub fn click(&mut self, value: String) { - match value.as_str() { + pub fn default(file: &str) -> Self { + let default = Self { + prev: 0f64, + operation: String::new(), + current: String::from("0"), + output: String::new(), + theme: false, + }; + if let Ok(value) = fs::read(file) { + if let Ok(value) = rmp_serde::from_slice(&value) { + value + } else { + default + } + } else { + default + } + } + pub fn save(&mut self, file: &str) { + fs::write(file, rmp_serde::to_vec(&self).unwrap()).unwrap(); + } + pub fn click(&mut self, value: &str) { + match value { "/" | "x" | "+" | "-" | "%" => { if self.current != "0" { if self.operation.is_empty() { @@ -23,7 +44,7 @@ impl Model { self.equil(); } self.output.push_str(&format!("{} {}", self.prev, value)); - self.operation = value; + self.operation = value.to_string(); self.current = String::from("0"); } } @@ -57,7 +78,7 @@ impl Model { if self.current == "0" { self.current.clear(); } - self.current = self.current.clone() + &value; + self.current = self.current.clone() + value; } }; } @@ -74,25 +95,4 @@ impl Model { self.output.push_str(&format!(" = {}\n", self.prev)); self.current = String::from("0"); } - pub fn default(file: &str) -> Self { - let default = Self { - prev: 0f64, - operation: String::new(), - current: String::from("0"), - output: String::new(), - theme: false, - }; - if let Ok(value) = fs::read(file) { - if let Ok(value) = rmp_serde::from_slice(&value) { - value - } else { - default - } - } else { - default - } - } - pub fn save(&mut self, file: &str) { - fs::write(file, rmp_serde::to_vec(&self).unwrap()).unwrap(); - } } diff --git a/demos/resters/Cargo.toml b/demos/resters/Cargo.toml new file mode 100644 index 0000000..1b79f1b --- /dev/null +++ b/demos/resters/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "flresters" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +flemish = { path = "../../" } +serde = { version="1.0", features = ["derive"] } +rmp-serde = { version="1.1" } +ureq = { version = "2.9", features = ["json"] } +serde_json = "1" +json-tools = "1.1" diff --git a/demos/resters/README.md b/demos/resters/README.md new file mode 100644 index 0000000..ebf33e0 --- /dev/null +++ b/demos/resters/README.md @@ -0,0 +1,5 @@ +# Rester demo + +It's just resters. + +![img](https://github.com/fltk-rs/demos/tree/master/flresters/assets/flresters.gif) diff --git a/examples/flresters.rs b/demos/resters/src/main.rs similarity index 74% rename from examples/flresters.rs rename to demos/resters/src/main.rs index 3ac7b5f..6024c0c 100644 --- a/examples/flresters.rs +++ b/demos/resters/src/main.rs @@ -1,11 +1,13 @@ #![forbid(unsafe_code)] +mod model; + use { flemish::{ app, button::Button, color_themes, - enums::{Align, Color, Font, FrameType}, + enums::{Align, Color, Event, Font, FrameType}, frame::Frame, misc::InputChoice, group::Flex, @@ -13,11 +15,15 @@ use { prelude::*, text::{StyleTableEntry, TextBuffer, TextDisplay, WrapMode}, OnEvent, Sandbox, Settings, + valuator::Dial, }, std::{process::Command, thread}, + model::Model, }; -pub fn main() { +const SPINNER: Event = Event::from_i32(405); + +fn main() { Model::new().run(Settings { size: (640, 360), resizable: false, @@ -29,30 +35,17 @@ pub fn main() { } #[derive(Clone)] -struct Model { - method: u8, - url: String, - responce: String, - status: String, -} - -#[derive(Clone)] -enum Message { +pub enum Message { Method(u8), Url(String), - Request, + Thread, } impl Sandbox for Model { type Message = Message; fn new() -> Self { - Self { - method: 0, - url: String::new(), - responce: String::new(), - status: String::new(), - } + Model::default() } fn title(&self) -> String { @@ -67,7 +60,7 @@ impl Sandbox for Model { .on_event(move |choice| Message::Method(choice.value() as u8)); header.fixed(&Frame::default(), WIDTH); crate::input(&self.url).on_event(move |input| Message::Url(input.value().unwrap())); - crate::button(&mut header).on_event(move |_| Message::Request); + crate::button(&mut header).on_event(move |_| Message::Thread); header.end(); crate::text(&self.responce); let mut footer = Flex::default(); @@ -79,6 +72,7 @@ impl Sandbox for Model { .with_label(&self.status), WIDTH, ); + footer.fixed(&crate::dial(), HEIGHT); footer.end(); page.end(); { @@ -95,12 +89,14 @@ impl Sandbox for Model { match message { Message::Method(value) => self.method = value, Message::Url(value) => self.url = value, - Message::Request => { - let url = match self.url.starts_with("https://") { - true => self.url.clone(), - false => String::from("https://") + &self.url, - }; - let handler = thread::spawn(move || -> (bool, String) { crate::curl(url) }); + Message::Thread => { + let clone = self.clone(); + let handler = thread::spawn(move || -> (bool, String) { crate::curl(clone) }); + while !handler.is_finished() { + app::wait(); + app::handle_main(SPINNER).unwrap(); + app::sleep(0.02); + } if let Ok((status, check)) = handler.join() { self.status = match status { true => "OK", @@ -114,6 +110,46 @@ impl Sandbox for Model { } } +fn curl(model: Model) -> (bool, String) { + let url = match model.url.starts_with("https://") { + true => model.url.clone(), + false => String::from("https://") + &model.url, + }; + let run = Command::new("curl") + .args(["-s", &url]) + .output() + .expect("failed to execute bash"); + ( + run.status.success(), + String::from_utf8_lossy(match run.status.success() { + true => &run.stdout, + false => &run.stderr, + }) + .to_string(), + ) +} + +fn dial() -> Dial { + const MAX: u8 = 120; + let mut element = Dial::default(); + // element.deactivate(); + element.set_maximum((MAX / 4 * 3) as f64); + element.set_value(element.minimum()); + element.handle(move |dial, event| { + if event == crate::SPINNER { + dial.set_value(if dial.value() == (MAX - 1) as f64 { + dial.minimum() + } else { + dial.value() + 1f64 + }); + true + } else { + false + } + }); + element +} + fn choice(value: i32, flex: &mut Flex) -> Choice { let mut element = Choice::default(); element.add_choice("GET|POST"); @@ -123,6 +159,8 @@ fn choice(value: i32, flex: &mut Flex) -> Choice { } fn text(value: &str) { + let mut buffer = TextBuffer::default(); + buffer.set_text(&model::fill_style_buffer(value)); let styles: Vec = [0xdc322f, 0x268bd2, 0x859900] .into_iter() .map(|color| StyleTableEntry { @@ -135,7 +173,7 @@ fn text(value: &str) { element.wrap_mode(WrapMode::AtBounds, 0); element.set_buffer(TextBuffer::default()); element.set_color(Color::from_hex(0x002b36)); - element.set_highlight_data(TextBuffer::default(), styles); + element.set_highlight_data(buffer, styles); element.buffer().unwrap().set_text(value); } @@ -157,21 +195,6 @@ fn input(value: &str) -> InputChoice { element } -fn curl(url: String) -> (bool, String) { - let run = Command::new("curl") - .args(["-s", &url]) - .output() - .expect("failed to execute bash"); - ( - run.status.success(), - String::from_utf8_lossy(match run.status.success() { - true => &run.stdout, - false => &run.stderr, - }) - .to_string(), - ) -} - const PAD: i32 = 10; const HEIGHT: i32 = PAD * 3; const WIDTH: i32 = HEIGHT * 3; diff --git a/demos/resters/src/model/mod.rs b/demos/resters/src/model/mod.rs new file mode 100644 index 0000000..2c5a286 --- /dev/null +++ b/demos/resters/src/model/mod.rs @@ -0,0 +1,40 @@ +use json_tools::{TokenType,Span,BufferType,Buffer,Lexer}; + +#[derive(Clone)] +pub struct Model { + pub method: u8, + pub url: String, + pub responce: String, + pub status: String, +} + +impl Model { + pub fn default() -> Self { + Self { + method: 0, + url: String::new(), + responce: String::new(), + status: String::new(), + } + } +} + +pub fn fill_style_buffer(s: &str) -> String { + let mut buffer = vec![b'A'; s.len()]; + for token in Lexer::new(s.bytes(), BufferType::Span) { + let c = match token.kind { + TokenType::CurlyOpen | TokenType::CurlyClose | TokenType::BracketOpen | TokenType::BracketClose | TokenType::Colon | TokenType::Comma | TokenType::Invalid => { + 'A' + } + TokenType::String => 'B', + TokenType::BooleanTrue | TokenType::BooleanFalse | TokenType::Null => 'C', + TokenType::Number => 'D', + }; + if let Buffer::Span(Span { first, end }) = token.buf { + let start = first as _; + let last = end as _; + buffer[start..last].copy_from_slice(c.to_string().repeat(last - start).as_bytes()); + } + } + String::from_utf8_lossy(&buffer).to_string() +} diff --git a/examples/fldialect.rs b/examples/fldialect.rs deleted file mode 100644 index 403d28e..0000000 --- a/examples/fldialect.rs +++ /dev/null @@ -1,473 +0,0 @@ -#![forbid(unsafe_code)] - -use { - flemish::{ - app, - button::{Button, ButtonType}, - color_themes, - dialog::{alert_default, FileChooser, FileChooserType, HelpDialog}, - enums::{Color, Font, FrameType, Shortcut}, - frame::Frame, - group::Flex, - menu::{Choice, MenuButton, MenuFlag}, - prelude::*, - text::{TextBuffer, TextEditor, WrapMode}, - valuator::{Counter, CounterType, Dial}, - OnEvent, OnMenuEvent, Sandbox, Settings, - }, - std::{env, fs, path::Path, process::Command, thread}, -}; - -fn main() { - if crate::once() { - app::GlobalState::::new(env::var("HOME").unwrap() + PATH + NAME); - let mut app = Model::new(); - app.run(Settings { - size: (app.width, app.height), - pos: (app.vertical, app.horizontal), - ignore_esc_close: true, - resizable: false, - color_map: Some(color_themes::DARK_THEME), - scheme: Some(app::Scheme::Base), - ..Default::default() - }); - } -} - -struct Model { - width: i32, - height: i32, - vertical: i32, - horizontal: i32, - from: u8, - to: u8, - speak: bool, - font: u8, - size: u8, - source: String, - target: String, - lang: Vec, -} - -#[derive(Clone)] -enum Message { - Switch, - From(u8), - To(u8), - Speak(bool), - Source(String), - Size(u8), - Font(u8), - Info, - Translate, - Open, - Save, - Quit, -} - -impl Sandbox for Model { - type Message = Message; - - fn title(&self) -> String { - String::from(NAME) - } - - fn new() -> Self { - let file = app::GlobalState::::get().with(move |model| model.clone()); - let params: Vec = if Path::new(&file).exists() { - if let Ok(value) = fs::read(&file) { - if value.len() == DEFAULT.len() { - value - } else { - fs::remove_file(&file).unwrap(); - Vec::from(DEFAULT) - } - } else { - Vec::from(DEFAULT) - } - } else { - Vec::from(DEFAULT) - }; - let (w, h) = app::screen_size(); - let width = params[0] as i32 * U8 + params[1] as i32; - let height = params[2] as i32 * U8 + params[3] as i32; - let mut lang = crate::list(); - lang.sort(); - Self { - width, - height, - from: params[4], - to: params[5], - font: params[6], - size: params[7], - vertical: ((w - width as f64) / 2_f64) as i32, - horizontal: ((h - height as f64) / 2_f64) as i32, - speak: false, - source: String::new(), - target: String::new(), - lang, - } - } - - fn view(&mut self) { - let mut page = Flex::default_fill().column(); - { - let mut header = Flex::default(); - crate::menu(&mut header); - Frame::default(); - crate::choice("From", &self.lang.join("|"), self.from, &mut header) - .on_event(move |choice| Message::From(choice.value() as u8)); - crate::button("Switch", "@#refresh", &mut header).on_event(move |_| Message::Switch); - crate::choice("To", &self.lang.join("|"), self.to, &mut header) - .on_event(move |choice| Message::To(choice.value() as u8)); - Frame::default(); - let mut button = - crate::button("Speak", "@#<", &mut header).with_type(ButtonType::Toggle); - button.set(self.speak); - button.on_event(move |button| Message::Speak(button.value())); - header.end(); - header.set_pad(0); - page.fixed(&header, HEIGHT); - } - { - let mut hero = Flex::default_fill().column().with_id("HERO"); - crate::text("Source", &self.source, self.font, self.size) - .on_event(move |text| Message::Source(text.buffer().unwrap().text())); - hero.fixed(&Frame::default(), PAD); - crate::text("Target", &self.target, self.font, self.size); - hero.end(); - hero.set_pad(0); - hero.fixed(&hero.child(1).unwrap(), PAD); - } - { - let mut footer = Flex::default(); //FOOTER - crate::button("Open...", "@#fileopen", &mut footer).on_event(move |_| Message::Open); - Frame::default(); - crate::choice("Font", &app::fonts().join("|"), self.font, &mut footer) - .on_event(move |choice| Message::Font(choice.value() as u8)); - crate::button("Translate", "@#circle", &mut footer) - .on_event(move |_| Message::Translate); - crate::counter("Size", self.size as f64, &mut footer) - .with_type(CounterType::Simple) - .on_event(move |counter| Message::Size(counter.value() as u8)); - crate::dial(&mut footer); - Frame::default(); - crate::button("Save as...", "@#filesaveas", &mut footer) - .on_event(move |_| Message::Save); - footer.end(); - footer.set_pad(0); - page.fixed(&footer, HEIGHT); - } - page.end(); - { - page.set_margin(PAD); - page.set_pad(PAD); - page.set_frame(FrameType::FlatBox); - let mut window = page.window().unwrap(); - window.set_xclass(NAME); - window.set_label(&format!( - "Translate from {} to {} - {NAME}", - self.lang[self.from as usize], self.lang[self.to as usize] - )); - } - } - - fn update(&mut self, message: Message) { - match message { - Message::Speak(value) => self.speak = value, - Message::From(value) => self.from = value, - Message::To(value) => self.to = value, - Message::Source(value) => self.source = value, - Message::Switch => { - let temp = self.from; - self.from = self.to; - self.to = temp; - } - Message::Font(value) => self.font = value, - Message::Size(value) => self.size = value, - Message::Open => self.open(), - Message::Save => self.save(), - Message::Translate => self.translate(), - Message::Info => crate::info(), - Message::Quit => self.quit(), - } - } -} - -impl Model { - fn open(&mut self) { - let mut dialog = FileChooser::new( - env::var("HOME").unwrap(), - "*.{txt,md}", - FileChooserType::Single, - "Open ...", - ); - dialog.show(); - while dialog.shown() { - app::wait(); - } - if dialog.count() > 0 { - if let Some(file) = dialog.value(1) { - self.source = fs::read_to_string(Path::new(&file)).unwrap(); - }; - }; - } - fn save(&self) { - if !self.target.is_empty() { - let mut dialog = FileChooser::new( - std::env::var("HOME").unwrap(), - "*.{txt,md}", - FileChooserType::Create, - "Save ...", - ); - dialog.show(); - while dialog.shown() { - app::wait(); - } - if dialog.count() > 0 { - if let Some(file) = dialog.value(1) { - fs::write(file, self.target.as_bytes()).unwrap(); - }; - }; - } else { - alert_default("Target is empty."); - }; - } - fn translate(&mut self) { - let mut button = app::widget_from_id::