diff --git a/README.md b/README.md index 4648d98..9558916 100644 --- a/README.md +++ b/README.md @@ -74,13 +74,30 @@ impl Sandbox for Counter { To run the [examples:](/examples) ```bash +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 cargo run --example flpicture +cargo run --example flresters ... ``` +### [FlCounter](/examples/counter.rs) + +![FlCalculator](/assets/counter.png) + +### [FlTemperature](/examples/temperature.rs) + +![FlTemperature](/assets/temperature.png) + +### [FlCRUD](/examples/crud.rs) + +![FlCRUD](/assets/crud.png) + ### [FlCalculator](/examples/flcalculator.rs) ![FlCalculator](/assets/flcalculator.gif) @@ -89,16 +106,36 @@ cargo run --example flpicture ![FlDialect](/assets/fldialect.gif) +### [FlGlyph](/examples/flglyph.rs) + +![FlGlyph](/assets/flglyph.png) + ### [FlNetPort](/examples/flnetport.rs) -![FlNetPort](/assets/flnetport.gif) +![FlNetPort](/assets/flnetport.png) ### [FlPicture](/examples/flpicture.rs) ![FlPicture](/assets/flpicture.gif) -## Demo +### [FlResters](/examples/flresters.rs) + +![FlResters](/assets/flresters.png) + +## Demos + +### [FlTodo](/demos/fltodo) + +![FlTodo](/demos/fltodo/assets/fltodo.gif) + +### [FlCSV](/demos/csv) + +![FlCSV](/demos/csv/assets/flcsv.png) + +### [FlCairo](/demos/cairo) + +![FlCairo](/demos/cairo/assets/flcairo.png) -### [FlErrands](/demos/flerrands) +### [Flightbooker](/demos/flightbooker) -![FlPicture](/demos/flerrands/assets/flerrands.gif) +![Flightbooker](/demos/flightbooker/assets/flightbooker.png) diff --git a/assets/counter.png b/assets/counter.png new file mode 100644 index 0000000..b7f79ce Binary files /dev/null and b/assets/counter.png differ diff --git a/assets/crud.png b/assets/crud.png new file mode 100644 index 0000000..66daa4e Binary files /dev/null and b/assets/crud.png differ diff --git a/assets/flglyph.png b/assets/flglyph.png new file mode 100644 index 0000000..6509b73 Binary files /dev/null and b/assets/flglyph.png differ diff --git a/assets/flnetport.gif b/assets/flnetport.gif deleted file mode 100644 index be6359a..0000000 Binary files a/assets/flnetport.gif and /dev/null differ diff --git a/assets/flnetport.png b/assets/flnetport.png new file mode 100644 index 0000000..b53b0df Binary files /dev/null and b/assets/flnetport.png differ diff --git a/assets/flresters.png b/assets/flresters.png new file mode 100644 index 0000000..9421149 Binary files /dev/null and b/assets/flresters.png differ diff --git a/assets/temperature.png b/assets/temperature.png new file mode 100644 index 0000000..ec4bbf7 Binary files /dev/null and b/assets/temperature.png differ diff --git a/demos/Cargo.toml b/demos/Cargo.toml index be42420..ab0855b 100644 --- a/demos/Cargo.toml +++ b/demos/Cargo.toml @@ -3,7 +3,9 @@ resolver = "2" members = [ "cairo", - "flerrands", + "csv", + "fltodo", + "flightbooker", ] [profile.release] diff --git a/demos/cairo/assets/flcairo.png b/demos/cairo/assets/flcairo.png new file mode 100644 index 0000000..9c24571 Binary files /dev/null and b/demos/cairo/assets/flcairo.png differ diff --git a/demos/cairo/src/main.rs b/demos/cairo/src/main.rs index 1bc0e06..84a73ce 100644 --- a/demos/cairo/src/main.rs +++ b/demos/cairo/src/main.rs @@ -1,164 +1,188 @@ #![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, MenuFlag}, + menu::{MenuButton, MenuButtonType, MenuFlag}, prelude::*, OnEvent, OnMenuEvent, Sandbox, Settings, }, + model::Model, }; -pub fn main() { - Counter::new().run(Settings { +#[derive(Clone, Copy)] +pub enum Message { + Inc, + Dec, + Quit, +} + +fn main() { + Model::new().run(Settings { size: (640, 360), ignore_esc_close: true, - background: Some(Color::White), + resizable: false, + background: Some(Color::from_u32(0xfdf6e3)), color_map: Some(color_themes::TAN_THEME), - scheme: Some(app::Scheme::Plastic), + scheme: Some(app::Scheme::Base), ..Default::default() }) } -#[derive(Default)] -struct Counter { - value: u8, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - Inc, - Dec, - Quit, -} - -impl Sandbox for Counter { +impl Sandbox for Model { type Message = Message; - fn title(&self) -> String { - String::from("Cairo Buttons") + fn new() -> Self { + Self::default() } - fn new() -> Self { - Self { value: 0u8 } + fn title(&self) -> String { + format!("{} - FlCairo", self.value()) } fn view(&mut self) { - let mut page = Flex::default_fill().column(); - let mut header = Flex::default(); - let menu = MenuButton::default() - .with_label("@#menu") - .on_item_event( - "Command/Increment", - Shortcut::None, - MenuFlag::Normal, - |_| Message::Inc, - ) - .on_item_event( - "Command/Decrement", - Shortcut::None, - MenuFlag::Normal, - |_| Message::Dec, - ) - .on_item_event( - "Quit", - Shortcut::Ctrl | 'q', - MenuFlag::Normal, - |_| Message::Quit, - ); - header.end(); - header.fixed(&menu, 50); - page.set_pad(10); - page.set_margin(10); - page.fixed(&header, 30); - let hero = Flex::default(); + let mut page = Flex::default() + .with_size(600, 200) + .center_of_parent() + .column(); + + let hero = Flex::default(); //HERO crate::cairobutton() .with_label("@#<") - .on_event(|_| Message::Dec); - let mut frame = Frame::default().with_label(&self.value.to_string()); - frame.set_label_size(60); + .on_event(move |_| Message::Dec); + crate::frame(&self.value()).handle(crate::popup); crate::cairobutton() .with_label("@#>") - .on_event(|_| Message::Inc); + .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.value = self.value.saturating_add(1); - } - Message::Dec => { - self.value = self.value.saturating_sub(1); - } - Message::Quit => { - app::quit(); - } + 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(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(|w| { - draw::draw_rect_fill(w.x(), w.y(), w.w(), w.h(), Color::White); - let mut surface = - ImageSurface::create(Format::ARgb32, w.w(), w.h()).expect("Couldn’t create surface"); - crate::draw_surface(&mut surface, w.w(), w.h()); - if !w.value() { + 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(|s| { - let mut img = RgbImage::new(s, w.w(), w.h(), ColorDepth::Rgba8).unwrap(); - img.draw(w.x(), w.y(), w.w(), w.h()); + .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 !w.value() { + if !button.value() { draw::draw_rbox( - w.x() + 1, - w.y() + 1, - w.w() - 6, - w.h() - 6, + button.x() + 1, + button.y() + 1, + button.w() - 6, + button.h() - 6, 15, true, Color::White, ); draw::draw_text2( - &w.label(), - w.x() + 1, - w.y() + 1, - w.w() - 6, - w.h() - 6, + &button.label(), + button.x() + 1, + button.y() + 1, + button.w() - 6, + button.h() - 6, Align::Center, ); } else { draw::draw_rbox( - w.x() + 1, - w.y() + 1, - w.w() - 4, - w.h() - 4, + button.x() + 1, + button.y() + 1, + button.w() - 4, + button.h() - 4, 15, true, Color::White, ); draw::draw_text2( - &w.label(), - w.x() + 1, - w.y() + 1, - w.w() - 4, - w.h() - 4, + &button.label(), + button.x() + 1, + button.y() + 1, + button.w() - 4, + button.h() - 4, Align::Center, ); } diff --git a/demos/cairo/src/model/mod.rs b/demos/cairo/src/model/mod.rs new file mode 100644 index 0000000..0f65d50 --- /dev/null +++ b/demos/cairo/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/Cargo.toml b/demos/csv/Cargo.toml new file mode 100644 index 0000000..5c385fa --- /dev/null +++ b/demos/csv/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "flcsv" +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 = "../../" } +csv = "^1.3" +serde = { version = "^1.0", features = ["derive"] } +lazy_static = "^1.4" +image = { version = "^0.25", default-features = false, features = ["jpeg"] } diff --git a/demos/csv/assets/flcsv.png b/demos/csv/assets/flcsv.png new file mode 100644 index 0000000..7b71517 Binary files /dev/null and b/demos/csv/assets/flcsv.png differ diff --git a/demos/flerrands/assets/flerrands.gif b/demos/csv/assets/flerrands.gif similarity index 100% rename from demos/flerrands/assets/flerrands.gif rename to demos/csv/assets/flerrands.gif diff --git a/demos/csv/assets/historical_data/GME.csv b/demos/csv/assets/historical_data/GME.csv new file mode 100644 index 0000000..a0925eb --- /dev/null +++ b/demos/csv/assets/historical_data/GME.csv @@ -0,0 +1,152 @@ +Date,Open,High,Low,Close,Adj Close,Volume +2020-03-24,3.950000,4.210000,3.890000,4.160000,4.160000,6805600 +2020-03-25,4.150000,4.490000,4.040000,4.170000,4.170000,3592100 +2020-03-26,4.240000,4.710000,4.240000,4.410000,4.410000,6185700 +2020-03-27,4.940000,5.090000,4.150000,4.220000,4.220000,7024800 +2020-03-30,4.220000,4.270000,3.650000,3.650000,3.650000,3350600 +2020-03-31,3.630000,3.800000,3.500000,3.500000,3.500000,2300900 +2020-04-01,3.450000,3.490000,3.120000,3.250000,3.250000,4568700 +2020-04-02,3.260000,3.350000,2.850000,2.850000,2.850000,4064300 +2020-04-03,2.850000,2.940000,2.570000,2.800000,2.800000,3830400 +2020-04-06,2.860000,3.200000,2.830000,3.090000,3.090000,3340500 +2020-04-07,3.390000,3.440000,3.100000,3.270000,3.270000,2836900 +2020-04-08,3.230000,3.670000,3.200000,3.410000,3.410000,2884500 +2020-04-09,3.600000,4.250000,3.490000,3.890000,3.890000,5908600 +2020-04-13,4.250000,4.760000,4.160000,4.740000,4.740000,6844500 +2020-04-14,5.210000,6.470000,5.140000,5.950000,5.950000,13506600 +2020-04-15,5.660000,5.670000,4.900000,5.270000,5.270000,7499900 +2020-04-16,5.120000,5.440000,4.880000,5.030000,5.030000,3371900 +2020-04-17,5.220000,5.280000,4.430000,4.880000,4.880000,5653200 +2020-04-20,4.850000,5.900000,4.780000,5.610000,5.610000,6085000 +2020-04-21,5.230000,5.300000,4.760000,4.780000,4.780000,4142100 +2020-04-22,5.140000,5.170000,4.830000,4.890000,4.890000,2677800 +2020-04-23,4.750000,4.890000,4.580000,4.700000,4.700000,2265900 +2020-04-24,4.780000,4.850000,4.660000,4.770000,4.770000,2236200 +2020-04-27,4.850000,5.990000,4.810000,5.820000,5.820000,7275100 +2020-04-28,5.920000,6.040000,5.060000,5.640000,5.640000,5200200 +2020-04-29,5.830000,6.090000,5.450000,6.040000,6.040000,3369600 +2020-04-30,5.870000,5.980000,5.640000,5.730000,5.730000,2173300 +2020-05-01,5.650000,6.180000,5.450000,6.050000,6.050000,4005600 +2020-05-04,5.840000,5.930000,5.400000,5.480000,5.480000,4068100 +2020-05-05,5.550000,5.630000,5.350000,5.390000,5.390000,2105900 +2020-05-06,5.400000,5.400000,4.900000,4.930000,4.930000,3369600 +2020-05-07,4.830000,4.930000,4.650000,4.870000,4.870000,2500800 +2020-05-08,4.880000,5.190000,4.820000,4.980000,4.980000,2353700 +2020-05-11,4.920000,4.960000,4.750000,4.760000,4.760000,1699200 +2020-05-12,4.780000,4.920000,4.460000,4.540000,4.540000,2639200 +2020-05-13,4.540000,4.540000,4.070000,4.210000,4.210000,2882900 +2020-05-14,4.150000,4.190000,3.960000,4.130000,4.130000,2004900 +2020-05-15,4.050000,4.450000,4.040000,4.220000,4.220000,1940600 +2020-05-18,4.400000,4.630000,4.360000,4.580000,4.580000,2371700 +2020-05-19,4.580000,4.750000,4.370000,4.440000,4.440000,1840700 +2020-05-20,4.500000,4.690000,4.340000,4.430000,4.430000,2543600 +2020-05-21,4.450000,4.650000,4.400000,4.440000,4.440000,1971900 +2020-05-22,4.460000,4.490000,4.130000,4.180000,4.180000,2379900 +2020-05-26,4.340000,4.610000,4.330000,4.420000,4.420000,3545700 +2020-05-27,4.570000,4.710000,4.360000,4.690000,4.690000,3146600 +2020-05-28,4.750000,4.760000,4.300000,4.330000,4.330000,2183900 +2020-05-29,4.330000,4.420000,4.050000,4.060000,4.060000,3973500 +2020-06-01,4.120000,4.360000,4.020000,4.130000,4.130000,2611600 +2020-06-02,4.270000,4.310000,4.060000,4.180000,4.180000,2369400 +2020-06-03,4.240000,4.520000,4.200000,4.440000,4.440000,3037400 +2020-06-04,4.440000,4.720000,4.370000,4.470000,4.470000,3514300 +2020-06-05,4.290000,4.410000,4.090000,4.140000,4.140000,6274400 +2020-06-08,4.280000,5.140000,4.270000,5.010000,5.010000,10133700 +2020-06-09,5.000000,5.000000,4.550000,4.960000,4.960000,8073400 +2020-06-10,4.780000,5.530000,4.680000,5.070000,5.070000,10606400 +2020-06-11,4.640000,4.900000,4.180000,4.370000,4.370000,6236000 +2020-06-12,4.600000,4.780000,4.390000,4.720000,4.720000,4378200 +2020-06-15,4.500000,4.770000,4.420000,4.690000,4.690000,3909000 +2020-06-16,4.950000,4.950000,4.510000,4.640000,4.640000,3121300 +2020-06-17,4.540000,4.830000,4.530000,4.760000,4.760000,3593500 +2020-06-18,4.670000,4.950000,4.650000,4.950000,4.950000,3423800 +2020-06-19,4.950000,5.080000,4.690000,4.880000,4.880000,7366600 +2020-06-22,4.790000,4.950000,4.720000,4.870000,4.870000,3178900 +2020-06-23,4.950000,4.990000,4.800000,4.830000,4.830000,3205400 +2020-06-24,4.830000,4.840000,4.380000,4.410000,4.410000,2976200 +2020-06-25,4.330000,4.540000,4.300000,4.460000,4.460000,2450800 +2020-06-26,4.480000,4.500000,4.270000,4.350000,4.350000,3801200 +2020-06-29,4.350000,4.520000,4.300000,4.380000,4.380000,2131200 +2020-06-30,4.330000,4.510000,4.200000,4.340000,4.340000,3889000 +2020-07-01,4.310000,4.500000,4.310000,4.440000,4.440000,2303700 +2020-07-02,4.490000,4.510000,4.290000,4.290000,4.290000,1887600 +2020-07-06,4.310000,4.340000,4.190000,4.240000,4.240000,2140900 +2020-07-07,4.200000,4.250000,4.060000,4.090000,4.090000,2456600 +2020-07-08,4.100000,4.290000,4.030000,4.260000,4.260000,2052800 +2020-07-09,4.270000,4.320000,4.130000,4.210000,4.210000,1992600 +2020-07-10,4.200000,4.380000,4.180000,4.340000,4.340000,1410800 +2020-07-13,4.350000,4.550000,4.260000,4.260000,4.260000,4216200 +2020-07-14,4.220000,4.310000,4.070000,4.080000,4.080000,2261600 +2020-07-15,4.130000,4.290000,4.130000,4.190000,4.190000,1474100 +2020-07-16,4.190000,4.200000,4.090000,4.170000,4.170000,1330100 +2020-07-17,4.160000,4.230000,3.940000,3.960000,3.960000,3065900 +2020-07-20,3.950000,4.060000,3.770000,3.850000,3.850000,3401100 +2020-07-21,3.900000,4.090000,3.880000,4.010000,4.010000,3341000 +2020-07-22,4.020000,4.120000,3.920000,4.110000,4.110000,2523500 +2020-07-23,4.090000,4.310000,4.060000,4.110000,4.110000,3237200 +2020-07-24,4.060000,4.230000,4.010000,4.030000,4.030000,2215900 +2020-07-27,4.020000,4.120000,3.950000,4.010000,4.010000,2472700 +2020-07-28,3.960000,4.050000,3.920000,3.940000,3.940000,4555400 +2020-07-29,3.940000,4.180000,3.920000,4.060000,4.060000,2879600 +2020-07-30,4.000000,4.230000,3.970000,4.100000,4.100000,2398500 +2020-07-31,4.060000,4.160000,3.990000,4.010000,4.010000,1879400 +2020-08-03,4.030000,4.250000,4.000000,4.150000,4.150000,2517600 +2020-08-04,4.130000,4.740000,4.130000,4.430000,4.430000,10361400 +2020-08-05,4.500000,4.760000,4.250000,4.630000,4.630000,4925700 +2020-08-06,4.600000,4.660000,4.380000,4.430000,4.430000,1901200 +2020-08-07,4.390000,4.400000,4.060000,4.160000,4.160000,3341100 +2020-08-10,4.200000,4.570000,4.180000,4.330000,4.330000,4561800 +2020-08-11,4.430000,4.570000,4.340000,4.350000,4.350000,3138800 +2020-08-12,4.400000,4.630000,4.360000,4.520000,4.520000,3057600 +2020-08-13,4.520000,4.710000,4.500000,4.640000,4.640000,2128300 +2020-08-14,4.600000,4.830000,4.550000,4.750000,4.750000,3474400 +2020-08-17,4.780000,4.780000,4.560000,4.630000,4.630000,2371000 +2020-08-18,4.610000,4.870000,4.430000,4.810000,4.810000,3834400 +2020-08-19,4.800000,4.840000,4.640000,4.720000,4.720000,2612600 +2020-08-20,4.620000,4.680000,4.510000,4.610000,4.610000,2441200 +2020-08-21,4.600000,5.600000,4.600000,5.030000,5.030000,10642600 +2020-08-24,5.100000,5.130000,4.560000,4.870000,4.870000,4585400 +2020-08-25,4.880000,5.250000,4.880000,4.980000,4.980000,2998700 +2020-08-26,4.970000,5.220000,4.920000,5.110000,5.110000,2779700 +2020-08-27,5.110000,5.380000,5.020000,5.250000,5.250000,3384400 +2020-08-28,5.300000,5.570000,5.220000,5.390000,5.390000,4236900 +2020-08-31,5.770000,7.150000,5.690000,6.680000,6.680000,37976000 +2020-09-01,7.300000,7.820000,6.770000,7.650000,7.650000,23211000 +2020-09-02,7.800000,8.050000,7.110000,7.710000,7.710000,13011100 +2020-09-03,7.880000,8.450000,7.240000,7.820000,7.820000,14344500 +2020-09-04,7.780000,7.920000,7.170000,7.650000,7.650000,7662000 +2020-09-08,7.550000,8.280000,7.480000,7.700000,7.700000,9816600 +2020-09-09,7.960000,7.990000,7.310000,7.350000,7.350000,9068100 +2020-09-10,6.650000,6.950000,6.190000,6.230000,6.230000,15558300 +2020-09-11,6.260000,6.330000,5.870000,6.090000,6.090000,6061200 +2020-09-14,6.800000,7.000000,6.430000,6.910000,6.910000,10119000 +2020-09-15,6.860000,7.260000,6.690000,7.090000,7.090000,5743500 +2020-09-16,7.030000,9.040000,7.030000,8.680000,8.680000,19256300 +2020-09-17,8.570000,9.770000,8.410000,9.200000,9.200000,17026700 +2020-09-18,9.200000,9.770000,8.910000,9.470000,9.470000,17407500 +2020-09-21,9.350000,9.600000,8.380000,8.750000,8.750000,7639800 +2020-09-22,10.450000,11.170000,9.900000,10.560000,10.560000,34752500 +2020-09-23,10.600000,10.860000,9.920000,10.040000,10.040000,10651200 +2020-09-24,9.710000,9.810000,9.010000,9.140000,9.140000,7938800 +2020-09-25,9.190000,10.180000,9.100000,10.020000,10.020000,7515200 +2020-09-28,10.160000,10.260000,9.550000,10.090000,10.090000,6764300 +2020-09-29,10.000000,10.650000,9.930000,10.350000,10.350000,5237600 +2020-09-30,10.250000,10.760000,10.060000,10.200000,10.200000,6079000 +2020-10-01,10.090000,10.250000,9.690000,9.770000,9.770000,4554100 +2020-10-02,9.380000,9.780000,9.300000,9.390000,9.390000,4340500 +2020-10-05,9.440000,9.590000,9.250000,9.460000,9.460000,2805000 +2020-10-06,9.560000,9.840000,9.100000,9.130000,9.130000,4535400 +2020-10-07,9.230000,9.560000,9.170000,9.360000,9.360000,3308600 +2020-10-08,9.540000,13.640000,9.190000,13.490000,13.490000,76453600 +2020-10-09,12.830000,14.800000,11.900000,12.020000,12.020000,77152800 +2020-10-12,11.660000,12.770000,11.400000,11.800000,11.800000,23655700 +2020-10-13,11.730000,12.420000,11.650000,11.880000,11.880000,10179700 +2020-10-14,12.670000,12.680000,12.050000,12.250000,12.250000,10776800 +2020-10-15,11.990000,15.100000,11.990000,13.830000,13.830000,39894800 +2020-10-16,13.770000,13.900000,13.080000,13.310000,13.310000,11651600 +2020-10-19,13.440000,14.500000,13.380000,13.910000,13.910000,13169100 +2020-10-20,14.030000,14.140000,13.670000,13.860000,13.860000,6604000 +2020-10-21,13.900000,14.420000,13.800000,14.100000,14.100000,5361900 +2020-10-22,14.200000,15.870000,14.190000,14.910000,14.910000,16212200 +2020-10-23,15.050000,15.380000,14.550000,15.000000,15.000000,6507300 +2020-10-26,14.930000,15.450000,13.260000,13.450000,13.450000,13376300 diff --git a/demos/csv/assets/historical_data/dlpn.csv b/demos/csv/assets/historical_data/dlpn.csv new file mode 100644 index 0000000..d3802f0 --- /dev/null +++ b/demos/csv/assets/historical_data/dlpn.csv @@ -0,0 +1,55 @@ +Date,Open,High,Low,Close,Adj Close,Volume +2021-01-04,3.480000,3.480000,3.300000,3.390000,3.390000,55400 +2021-01-05,3.360000,3.690000,3.360000,3.610000,3.610000,136500 +2021-01-06,3.590000,3.660000,3.450000,3.450000,3.450000,39700 +2021-01-07,3.490000,3.860000,3.470000,3.580000,3.580000,105200 +2021-01-08,3.590000,3.750000,3.520000,3.690000,3.690000,82000 +2021-01-11,3.710000,3.780000,3.510000,3.630000,3.630000,68100 +2021-01-12,3.720000,5.500000,3.630000,4.610000,4.610000,9075100 +2021-01-13,4.220000,4.400000,4.020000,4.070000,4.070000,748800 +2021-01-14,4.140000,4.280000,4.000000,4.040000,4.040000,681200 +2021-01-15,4.030000,4.050000,3.860000,3.990000,3.990000,596600 +2021-01-19,3.990000,3.990000,3.760000,3.940000,3.940000,363300 +2021-01-20,3.950000,3.980000,3.720000,3.870000,3.870000,273500 +2021-01-21,3.940000,3.980000,3.760000,3.980000,3.980000,232500 +2021-01-22,3.950000,4.250000,3.810000,4.120000,4.120000,521700 +2021-01-25,4.150000,4.290000,3.830000,4.060000,4.060000,309300 +2021-01-26,4.100000,4.100000,3.970000,4.050000,4.050000,283300 +2021-01-27,4.240000,4.980000,4.050000,4.830000,4.830000,2167600 +2021-01-28,4.440000,4.460000,4.050000,4.100000,4.100000,496200 +2021-01-29,4.050000,4.080000,3.800000,3.880000,3.880000,320400 +2021-02-01,3.880000,3.940000,3.820000,3.830000,3.830000,151000 +2021-02-02,3.830000,4.000000,3.830000,3.880000,3.880000,139500 +2021-02-03,3.890000,4.100000,3.890000,4.000000,4.000000,153600 +2021-02-04,4.050000,4.230000,4.010000,4.190000,4.190000,200400 +2021-02-05,4.200000,4.540000,3.960000,4.470000,4.470000,596100 +2021-02-08,4.550000,4.710000,4.500000,4.650000,4.650000,339200 +2021-02-09,4.590000,5.100000,4.590000,5.030000,5.030000,335200 +2021-02-10,5.030000,5.140000,4.620000,4.860000,4.860000,443700 +2021-02-11,4.910000,7.500000,4.610000,5.930000,5.930000,10768000 +2021-02-12,5.500000,5.600000,4.880000,5.200000,5.200000,1079700 +2021-02-16,5.010000,5.180000,4.680000,4.820000,4.820000,697900 +2021-02-17,4.840000,5.260000,4.750000,5.160000,5.160000,583800 +2021-02-18,5.100000,5.690000,5.100000,5.600000,5.600000,548700 +2021-02-19,5.600000,5.960000,5.400000,5.750000,5.750000,369600 +2021-02-22,5.550000,5.890000,5.500000,5.720000,5.720000,139300 +2021-02-23,5.530000,5.580000,4.930000,5.020000,5.020000,221300 +2021-02-24,5.110000,5.400000,5.090000,5.090000,5.090000,64400 +2021-02-25,5.050000,5.150000,4.820000,4.850000,4.850000,86400 +2021-02-26,4.830000,4.980000,4.550000,4.660000,4.660000,74200 +2021-03-01,4.700000,4.980000,4.700000,4.920000,4.920000,80600 +2021-03-02,5.030000,5.030000,4.670000,4.670000,4.670000,46800 +2021-03-03,4.660000,4.770000,4.520000,4.610000,4.610000,67100 +2021-03-04,4.520000,4.580000,4.030000,4.070000,4.070000,134300 +2021-03-05,4.070000,4.140000,3.800000,4.080000,4.080000,84800 +2021-03-08,4.080000,4.640000,3.910000,4.630000,4.630000,80900 +2021-03-09,4.600000,4.660000,4.280000,4.540000,4.540000,66800 +2021-03-10,4.670000,4.760000,4.370000,4.460000,4.460000,38100 +2021-03-11,4.400000,4.900000,4.400000,4.760000,4.760000,127600 +2021-03-12,4.720000,4.800000,4.500000,4.510000,4.510000,24900 +2021-03-15,4.500000,5.190000,4.500000,5.120000,5.120000,118400 +2021-03-16,5.180000,5.710000,4.910000,4.960000,4.960000,185800 +2021-03-17,4.950000,5.400000,4.810000,5.220000,5.220000,53000 +2021-03-18,5.230000,5.500000,5.040000,5.090000,5.090000,183400 +2021-03-19,5.010000,5.690000,5.000000,5.690000,5.690000,264200 +2021-03-22,5.750000,5.750000,5.300000,5.450000,5.450000,134200 \ No newline at end of file diff --git a/demos/csv/assets/historical_data/oil.csv b/demos/csv/assets/historical_data/oil.csv new file mode 100644 index 0000000..66a2e82 --- /dev/null +++ b/demos/csv/assets/historical_data/oil.csv @@ -0,0 +1,82 @@ +Date,Open,High,Low,Close,Adj Close,Volume +2020-11-20,41.700001,42.320000,41.509998,42.150002,42.150002,276340 +2020-11-23,42.459999,43.360001,42.290001,43.060001,43.060001,300717 +2020-11-24,42.840000,45.200001,42.820000,44.910000,44.910000,418527 +2020-11-25,44.820000,46.259998,44.730000,45.709999,45.709999,417182 +2020-11-30,45.340000,45.799999,44.419998,45.340000,45.340000,370755 +2020-12-01,45.080002,45.700001,44.119999,44.549999,44.549999,314919 +2020-12-02,44.380001,45.919998,43.919998,45.279999,45.279999,368854 +2020-12-03,44.990002,45.840000,44.660000,45.639999,45.639999,350768 +2020-12-04,45.639999,46.680000,45.610001,46.259998,46.259998,335453 +2020-12-07,46.150002,46.540001,45.360001,45.759998,45.759998,348871 +2020-12-08,45.660000,45.930000,45.139999,45.599998,45.599998,332641 +2020-12-09,45.599998,46.240002,44.950001,45.520000,45.520000,434976 +2020-12-10,45.689999,47.740002,45.520000,46.779999,46.779999,447529 +2020-12-11,46.970001,47.290001,46.340000,46.570000,46.570000,367305 +2020-12-14,46.730000,47.439999,45.689999,46.990002,46.990002,403543 +2020-12-15,46.990002,47.730000,46.540001,47.619999,47.619999,311169 +2020-12-16,47.599998,47.939999,47.169998,47.820000,47.820000,275331 +2020-12-17,47.849998,48.590000,47.810001,48.360001,48.360001,96536 +2020-12-18,48.430000,49.279999,48.099998,49.099998,49.099998,83711 +2020-12-21,48.540001,48.610001,46.180000,47.740002,47.740002,478098 +2020-12-22,47.930000,47.959999,46.599998,47.020000,47.020000,295737 +2020-12-23,46.790001,48.500000,46.160000,48.119999,48.119999,344306 +2020-12-28,48.230000,48.959999,47.500000,47.619999,47.619999,238462 +2020-12-29,47.720001,48.349998,47.680000,48.000000,48.000000,213778 +2020-12-30,48.130001,48.660000,47.610001,48.400002,48.400002,266957 +2020-12-31,48.349998,48.580002,47.770000,48.520000,48.520000,181894 +2021-01-04,48.400002,49.830002,47.180000,47.619999,47.619999,528525 +2021-01-05,47.380001,50.200001,47.240002,49.930000,49.930000,643191 +2021-01-06,49.820000,50.939999,49.480000,50.630001,50.630001,509365 +2021-01-07,50.529999,51.279999,50.389999,50.830002,50.830002,369292 +2021-01-08,50.930000,52.750000,50.810001,52.240002,52.240002,499416 +2021-01-11,52.580002,52.700001,51.500000,52.250000,52.250000,394822 +2021-01-12,52.180000,53.450001,52.070000,53.209999,53.209999,400200 +2021-01-13,53.310001,53.930000,52.580002,52.910000,52.910000,429898 +2021-01-14,52.840000,53.750000,52.240002,53.570000,53.570000,390087 +2021-01-15,53.799999,53.830002,51.830002,52.360001,52.360001,165668 +2021-01-19,52.000000,53.130001,51.759998,52.980000,52.980000,125964 +2021-01-20,53.130001,53.790001,53.049999,53.240002,53.240002,400301 +2021-01-21,52.930000,53.410000,52.750000,53.130001,53.130001,352397 +2021-01-22,53.099998,53.160000,51.439999,52.270000,52.270000,437484 +2021-01-25,52.169998,52.950001,51.820000,52.770000,52.770000,349076 +2021-01-26,52.910000,53.250000,52.290001,52.610001,52.610001,314538 +2021-01-27,52.759998,53.299999,51.849998,52.849998,52.849998,442376 +2021-01-28,52.650002,53.580002,52.040001,52.340000,52.340000,411406 +2021-01-29,52.150002,53.250000,51.930000,52.200001,52.200001,410914 +2021-02-01,51.990002,53.740002,51.639999,53.549999,53.549999,416067 +2021-02-02,53.480000,55.259998,53.450001,54.759998,54.759998,463718 +2021-02-03,55.049999,56.330002,54.810001,55.689999,55.689999,448693 +2021-02-04,55.959999,56.580002,55.299999,56.230000,56.230000,381404 +2021-02-05,56.459999,57.290001,56.430000,56.849998,56.849998,400833 +2021-02-08,57.060001,58.139999,57.000000,57.970001,57.970001,399724 +2021-02-09,58.110001,58.619999,57.270000,58.360001,58.360001,514698 +2021-02-10,58.450001,58.910000,58.080002,58.680000,58.680000,457535 +2021-02-11,58.400002,58.709999,57.840000,58.240002,58.240002,380445 +2021-02-12,57.939999,59.820000,57.410000,59.470001,59.470001,480822 +2021-02-16,59.980000,60.950001,59.330002,60.049999,60.049999,602332 +2021-02-17,60.240002,61.730000,59.430000,61.139999,61.139999,335419 +2021-02-18,61.680000,62.259998,59.790001,60.520000,60.520000,132035 +2021-02-19,60.200001,60.290001,58.590000,59.240002,59.240002,102427 +2021-02-22,58.880001,61.840000,58.820000,61.490002,61.490002,505773 +2021-02-23,62.160000,63.000000,60.669998,61.669998,61.669998,570677 +2021-02-24,61.290001,63.509998,60.970001,63.220001,63.220001,489933 +2021-02-25,63.389999,63.810001,62.650002,63.529999,63.529999,510357 +2021-02-26,63.459999,63.570000,61.340000,61.500000,61.500000,472723 +2021-03-01,61.950001,62.919998,59.959999,60.639999,60.639999,456679 +2021-03-02,60.230000,61.209999,59.380001,59.750000,59.750000,464133 +2021-03-03,59.549999,61.990002,59.240002,61.279999,61.279999,465311 +2021-03-04,61.080002,64.860001,60.520000,63.830002,63.830002,694628 +2021-03-05,64.160004,66.419998,63.820000,66.089996,66.089996,573168 +2021-03-08,66.680000,67.980003,64.570000,65.050003,65.050003,533376 +2021-03-09,64.730003,65.980003,63.630001,64.010002,64.010002,554564 +2021-03-10,63.840000,64.959999,63.130001,64.440002,64.440002,498488 +2021-03-11,64.699997,66.209999,64.540001,66.019997,66.019997,441063 +2021-03-12,65.959999,66.239998,65.410004,65.610001,65.610001,321714 +2021-03-15,65.559998,66.400002,64.129997,65.389999,65.389999,378009 +2021-03-16,65.330002,65.430000,63.799999,64.800003,64.800003,317537 +2021-03-17,64.750000,65.339996,63.599998,64.599998,64.599998,254724 +2021-03-18,64.419998,64.820000,58.200001,60.000000,60.000000,143597 +2021-03-19,59.560001,61.720001,58.939999,61.419998,61.419998,84171 +2021-03-22,61.549999,61.900002,60.389999,61.549999,61.549999,84171 +2021-03-23,57.400002,57.650002,57.290001,57.450001,57.450001,3606 \ No newline at end of file diff --git a/demos/csv/src/main.rs b/demos/csv/src/main.rs new file mode 100644 index 0000000..b8d331c --- /dev/null +++ b/demos/csv/src/main.rs @@ -0,0 +1,138 @@ +#![forbid(unsafe_code)] + +mod model; + +use { + flemish::{ + app, + browser::{Browser, BrowserType}, + button::Button, + color_themes, draw, + enums::{Color, FrameType}, + frame::Frame, + group::Flex, + prelude::*, + OnEvent, Sandbox, Settings, + }, + model::Model, +}; + +const NAME: &str = "FlCSV"; +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const WIDTH: i32 = HEIGHT * 3; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +#[derive(Clone)] +pub enum Message { + Choice(usize), + Save, +} + +impl Sandbox for Model { + type Message = Message; + + fn title(&self) -> String { + String::from(NAME) + } + + fn new() -> Self { + Self::default() + } + + fn view(&mut self) { + let mut page = Flex::default_fill(); + { + let mut left = Flex::default_fill().column(); + crate::browser("Browser", self.clone()).on_event(move |browser| { + Message::Choice((browser.value() as usize).saturating_sub(1)) + }); + crate::button("Load image", &mut left).on_event(move |_| Message::Save); + left.end(); + left.set_pad(PAD); + page.fixed(&left, WIDTH); + } + crate::frame("Canvas", self.clone()); + page.end(); + page.set_pad(PAD); + page.set_margin(PAD); + } + + fn update(&mut self, message: Message) { + match message { + Message::Save => self.init(), + Message::Choice(value) => self.choice(value), + } + } +} + +fn browser(tooltip: &str, value: Model) -> Browser { + let mut element = Browser::default().with_type(BrowserType::Hold); + element.set_tooltip(tooltip); + if !value.temp.is_empty() { + for item in value.temp { + element.add(&item); + } + element.select(value.curr as i32 + 1); + } + element +} + +fn button(tooltip: &str, flex: &mut Flex) -> Button { + let mut element = Button::default().with_label("@#refresh"); + element.set_tooltip(tooltip); + flex.fixed(&element, HEIGHT); + element +} + +fn frame(tooltip: &str, value: Model) { + let mut element = Frame::default(); + element.set_frame(FrameType::DownBox); + element.set_tooltip(tooltip); + element.set_color(Color::Black); + element.draw(move |frame| { + if !value.temp.is_empty() { + if let Some(data) = value.cash.get(&value.temp[value.curr]) { + let mut highest = data + .iter() + .map(|elem| elem.low) + .collect::>() + .iter() + .cloned() + .fold(f64::NAN, f64::max); + highest += (highest.to_string().len() * 10) as f64 / 3.; + let factor = frame.h() as f64 / highest; + if !data.is_empty() { + let step = frame.w() / data.len() as i32; + let mut idx = frame.x() + step; + for elem in data { + let open = frame.h() - (elem.open * factor) as i32; + let high = frame.h() - (elem.high * factor) as i32; + let low = frame.h() - (elem.low * factor) as i32; + let close = frame.h() - (elem.close * factor) as i32; + draw::draw_line(idx, high, idx, low); + let col = if close > open { + Color::Red + } else { + Color::Green + }; + draw::set_draw_color(col); + draw::draw_rectf(idx - 2, open, 4, i32::abs(close - open)); + draw::set_draw_color(Color::White); + idx += step; + } + }; + } + } + }); +} diff --git a/demos/csv/src/model/mod.rs b/demos/csv/src/model/mod.rs new file mode 100644 index 0000000..36b7982 --- /dev/null +++ b/demos/csv/src/model/mod.rs @@ -0,0 +1,63 @@ +use { + csv::Reader, + serde::Deserialize, + std::{collections::HashMap, fs}, +}; + +#[derive(Debug, Deserialize, Clone)] +pub struct Price { + #[serde(rename = "Date")] + _date: String, + #[serde(rename = "Open")] + pub open: f64, + #[serde(rename = "High")] + pub high: f64, + #[serde(rename = "Low")] + pub low: f64, + #[serde(rename = "Close")] + pub close: f64, + #[serde(rename = "Volume")] + _volume: usize, +} + +#[derive(Debug, Clone)] +pub struct Model { + pub cash: HashMap>, + pub temp: Vec, + pub curr: usize, + pub save: bool, +} + +impl Model { + pub fn default() -> Self { + Self { + cash: HashMap::new(), + temp: Vec::new(), + save: false, + curr: 0, + } + } + pub fn init(&mut self) { + for file in std::fs::read_dir("assets/historical_data").unwrap() { + let entry = file.unwrap().file_name().into_string().unwrap(); + if entry.ends_with(".csv") { + self.temp + .push(entry.strip_suffix(".csv").unwrap().to_string()); + } + self.choice(0); + } + } + pub fn choice(&mut self, curr: usize) { + if self.cash.contains_key(&self.temp[curr]) { + self.curr = curr; + } else if let Ok(data) = fs::read(format!("assets/historical_data/{}.csv", self.temp[curr])) + { + let mut prices: Vec = Vec::new(); + for result in Reader::from_reader(data.as_slice()).deserialize() { + prices.push(result.unwrap()); + } + self.cash.insert(self.temp[curr].clone(), prices); + self.curr = curr; + } + } +} diff --git a/demos/flightbooker/Cargo.toml b/demos/flightbooker/Cargo.toml new file mode 100644 index 0000000..bf974ab --- /dev/null +++ b/demos/flightbooker/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "flightbooker" +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 = "../../" } +chrono = "^0.4" diff --git a/demos/flightbooker/assets/flightbooker.png b/demos/flightbooker/assets/flightbooker.png new file mode 100644 index 0000000..c4a6a97 Binary files /dev/null and b/demos/flightbooker/assets/flightbooker.png differ diff --git a/demos/flightbooker/src/main.rs b/demos/flightbooker/src/main.rs new file mode 100644 index 0000000..b1fec32 --- /dev/null +++ b/demos/flightbooker/src/main.rs @@ -0,0 +1,126 @@ +#![forbid(unsafe_code)] + +mod model; + +use { + flemish::{ + app, button::Button, color_themes, dialog::alert_default, enums::FrameType, frame::Frame, + group::Flex, input::Input, menu::Choice, prelude::*, OnEvent, Sandbox, Settings, + }, + model::Model, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const NAME: &str = "7GUI: Flightbooker"; +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const WIDTH: i32 = PAD * 12; + +#[derive(Clone)] +pub enum Message { + Direct(i32), + Start(String), + Back(String), + Book, +} + +impl Sandbox for Model { + type Message = Message; + + fn title(&self) -> String { + String::from(NAME) + } + + fn new() -> Self { + Self::default() + } + + fn view(&mut self) { + let mut page = Flex::default() + .with_size(PAD * 26, PAD * 17) + .center_of_parent(); + { + page.fixed(&Frame::default(), WIDTH); + let mut right = Flex::default().column(); + crate::choice(self.direct, &mut right) + .with_label("Direct") + .on_event(move |choice| Message::Direct(choice.value())); + crate::input(&self.start, self.start_active) + .with_label("Start") + .on_event(move |input| Message::Start(input.value())); + crate::input(&self.back, self.back_active) + .with_label("Back") + .on_event(move |input| Message::Back(input.value())); + crate::button(self.book_active, &mut right) + .with_label("Book") + .clone() + .on_event(move |_| Message::Book); + right.end(); + right.set_pad(PAD); + page.end(); + page.set_pad(0); + page.set_margin(PAD); + page.set_frame(FrameType::UpBox); + } + page.set_margin(PAD); + page.set_pad(PAD); + } + + fn update(&mut self, message: Message) { + match message { + Message::Direct(value) => self.direct(value), + Message::Start(value) => self.start(value), + Message::Back(value) => self.back(value), + Message::Book => { + alert_default(&if self.direct == 0 { + format!("You have booked a one-way flight for {}.", self.start) + } else { + format!( + "You have booked a return flight from {} to {}", + self.start, self.back + ) + }); + } + } + } +} + +fn input(value: &str, active: bool) -> Input { + let mut element = Input::default(); + element.set_value(value); + if active { + element.activate(); + } else { + element.deactivate(); + }; + element +} + +fn choice(value: i32, flex: &mut Flex) -> Choice { + let mut element = Choice::default(); + element.add_choice("one-way flight|return flight"); + element.set_value(value); + flex.fixed(&element, HEIGHT); + element +} + +fn button(active: bool, flex: &mut Flex) -> Button { + let mut element = Button::default(); + if active { + element.activate(); + } else { + element.deactivate(); + }; + flex.fixed(&element, HEIGHT); + element +} diff --git a/demos/flightbooker/src/model/mod.rs b/demos/flightbooker/src/model/mod.rs new file mode 100644 index 0000000..45efd5f --- /dev/null +++ b/demos/flightbooker/src/model/mod.rs @@ -0,0 +1,57 @@ +use chrono::{offset::Local, NaiveDate}; + +pub struct Model { + pub direct: i32, + pub start: String, + pub back: String, + pub start_active: bool, + pub back_active: bool, + pub book_active: bool, +} + +impl Model { + pub fn direct(&mut self, value: i32) { + self.direct = value; + self.refresh(); + } + pub fn start(&mut self, value: String) { + self.start = value; + self.refresh(); + } + pub fn back(&mut self, value: String) { + self.back = value; + self.refresh(); + } + pub fn default() -> Self { + let current = Local::now() + .naive_local() + .date() + .format("%Y-%m-%d") + .to_string(); + Self { + direct: 0, + start: current.clone(), + start_active: true, + back: current, + back_active: false, + book_active: false, + } + } + pub fn refresh(&mut self) { + if self.direct == 0 { + self.back_active = false; + self.book_active = get_date(&self.start).is_ok(); + } else { + self.back_active = true; + let start_date = get_date(&self.start); + let back_date = get_date(&self.back); + self.book_active = start_date.is_ok() + && back_date.is_ok() + && start_date.unwrap() <= back_date.unwrap(); + } + } +} + +fn get_date(value: &str) -> Result { + NaiveDate::parse_from_str(value, "%Y-%m-%d") +} diff --git a/demos/flerrands/Cargo.toml b/demos/fltodo/Cargo.toml similarity index 100% rename from demos/flerrands/Cargo.toml rename to demos/fltodo/Cargo.toml diff --git a/demos/fltodo/assets/fltodo.gif b/demos/fltodo/assets/fltodo.gif new file mode 100644 index 0000000..cc09ebc Binary files /dev/null and b/demos/fltodo/assets/fltodo.gif differ diff --git a/demos/flerrands/src/main.rs b/demos/fltodo/src/main.rs similarity index 69% rename from demos/flerrands/src/main.rs rename to demos/fltodo/src/main.rs index c09287d..81c378b 100644 --- a/demos/flerrands/src/main.rs +++ b/demos/fltodo/src/main.rs @@ -1,5 +1,7 @@ #![forbid(unsafe_code)] +mod model; + use { flemish::{ app, @@ -7,21 +9,21 @@ use { color_themes, enums::{FrameType, Shortcut}, frame::Frame, - group::{Flex,Scroll}, + group::{Flex, Scroll, ScrollType}, input::Input, menu::{MenuButton, MenuFlag}, prelude::*, OnEvent, OnMenuEvent, Sandbox, Settings, }, - serde::{Deserialize, Serialize}, + model::Model, std::{env, fs}, }; pub fn main() { - let mut app = Model::new(); - app.run(Settings { - size: app.size, - resizable: true, + app::GlobalState::::new(env::var("HOME").unwrap() + "/.config/" + NAME); + Model::new().run(Settings { + size: (360, 640), + resizable: false, ignore_esc_close: true, color_map: Some(color_themes::DARK_THEME), scheme: Some(app::Scheme::Base), @@ -29,27 +31,12 @@ pub fn main() { }) } -const NAME: &str = "FlErrands"; -const PATH: &str = "/.config/"; +const NAME: &str = "FlTodo"; const PAD: i32 = 10; const HEIGHT: i32 = PAD * 3; -const WINDOW_WIDTH: i32 = 360; -const WINDOW_HEIGHT: i32 = 640; - -#[derive(Deserialize, Serialize)] -struct Task { - status: bool, - description: String, -} - -#[derive(Deserialize, Serialize)] -struct Model { - size: (i32, i32), - tasks: Vec, -} #[derive(Clone)] -enum Message { +pub enum Message { New(String), Delete(usize), Check(usize), @@ -65,11 +52,8 @@ impl Sandbox for Model { } fn new() -> Self { - let file = env::var("HOME").unwrap() + PATH + NAME; - let default = Self { - size: (WINDOW_WIDTH, WINDOW_HEIGHT), - tasks: Vec::new(), - }; + let file = app::GlobalState::::get().with(move |model| model.clone()); + let default = Self { tasks: Vec::new() }; if let Ok(value) = fs::read(file) { if let Ok(value) = rmp_serde::from_slice(&value) { value @@ -86,11 +70,17 @@ impl Sandbox for Model { let mut header = Flex::default(); // HEADER header.fixed(&crate::menu(), 50); let description = Input::default(); - let add = Button::default().with_label("@#+"); - header.fixed(&add, HEIGHT); - add.on_event(move |_| Message::New(description.value())); + header.fixed( + &Button::default() + .with_label("@#+") + .clone() + .on_event(move |_| Message::New(description.value())), + HEIGHT, + ); header.end(); - let scroll = Scroll::default().with_size(324, 600); + let scroll = Scroll::default() + .with_size(324, 600) + .with_type(ScrollType::Vertical); let mut hero = Flex::default_fill().column(); // HERO for (idx, task) in self.tasks.iter().enumerate() { let mut row = Flex::default(); @@ -133,30 +123,21 @@ impl Sandbox for Model { fn update(&mut self, message: Message) { match message { - Message::Quit => self.quit(), + Message::Quit => { + let file = app::GlobalState::::get().with(move |model| model.clone()); + self.save(file); + app::quit(); + } Message::Delete(idx) => { self.tasks.remove(idx); } Message::Change((idx, value)) => self.tasks[idx].description = value, - Message::Check(idx) => self.tasks[idx].status = !self.tasks[idx].status, - Message::New(description) => self.tasks.push(Task { - status: false, - description, - }), + Message::Check(idx) => self.check(idx), + Message::New(description) => self.add(description), } } } -impl Model { - fn quit(&mut self) { - let file = env::var("HOME").unwrap() + PATH + NAME; - let window = app::first_window().unwrap(); - self.size = (window.width(), window.height()); - fs::write(file, rmp_serde::to_vec(&self).unwrap()).unwrap(); - app::quit(); - } -} - fn menu() -> MenuButton { let mut element = MenuButton::default().with_label("@#menu").on_item_event( "@#1+ &Quit", diff --git a/demos/fltodo/src/model/mod.rs b/demos/fltodo/src/model/mod.rs new file mode 100644 index 0000000..da32807 --- /dev/null +++ b/demos/fltodo/src/model/mod.rs @@ -0,0 +1,30 @@ +use { + serde::{Deserialize, Serialize}, + std::fs, +}; + +#[derive(Deserialize, Serialize)] +pub struct Task { + pub status: bool, + pub description: String, +} + +#[derive(Deserialize, Serialize)] +pub struct Model { + pub tasks: Vec, +} + +impl Model { + pub fn check(&mut self, idx: usize) { + self.tasks[idx].status = !self.tasks[idx].status + } + pub fn add(&mut self, description: String) { + self.tasks.push(Task { + status: false, + description, + }) + } + pub fn save(&mut self, file: String) { + fs::write(file, rmp_serde::to_vec(&self).unwrap()).unwrap(); + } +} diff --git a/examples/checkbutton.rs b/examples/checkbutton.rs new file mode 100644 index 0000000..a04ee71 --- /dev/null +++ b/examples/checkbutton.rs @@ -0,0 +1,84 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, button::CheckButton, color_themes, enums::FrameType, group::Flex, prelude::*, OnEvent, + Sandbox, Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; + +struct Model { + default: bool, + styled: bool, + custom: bool, +} + +#[derive(Clone, Copy)] +enum Message { + DefaultToggled(bool), + CustomToggled(bool), + StyledToggled(bool), +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Self { + default: true, + styled: false, + custom: false, + } + } + + fn title(&self) -> String { + String::from("CheckButton - Flemish") + } + + fn view(&mut self) { + let mut page = Flex::default() + .with_size(300, 150) + .center_of_parent() + .column(); + { + crate::check(self.default) + .on_event(move |check| Message::DefaultToggled(check.value())); + crate::check(self.styled).on_event(move |check| Message::StyledToggled(check.value())); + crate::check(self.custom).on_event(move |check| Message::CustomToggled(check.value())); + } + page.end(); + page.set_pad(PAD); + page.set_frame(FrameType::UpBox); + } + + fn update(&mut self, message: Message) { + match message { + Message::DefaultToggled(value) => { + self.default = value; + } + Message::StyledToggled(value) => { + self.styled = value; + } + Message::CustomToggled(value) => { + self.custom = value; + } + } + } +} + +fn check(value: bool) -> CheckButton { + let mut element = CheckButton::default(); + element.set_value(value); + element +} diff --git a/examples/counter.rs b/examples/counter.rs index 424a5d7..9f4c55e 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -1,13 +1,13 @@ #![forbid(unsafe_code)] use flemish::{ - app, button::Button, color_themes, frame::Frame, group::Flex, prelude::*, valuator::Dial, + app, button::Button, color_themes, enums::FrameType, frame::Frame, group::Flex, prelude::*, OnEvent, Sandbox, Settings, }; pub fn main() { - Counter::new().run(Settings { - size: (100, 300), + Model::new().run(Settings { + size: (640, 360), resizable: false, ignore_esc_close: true, color_map: Some(color_themes::DARK_THEME), @@ -16,63 +16,64 @@ pub fn main() { }) } -const PAD: i32 = 10; -const DIAL: u8 = 120; +#[derive(Debug, Clone, Copy)] +enum Message { + Inc, + Dec, +} -struct Counter { +struct Model { value: u8, } -#[derive(Debug, Clone, Copy)] -enum Message { - IncrementPressed, - DecrementPressed, +impl Model { + fn default() -> Self { + Self { value: 0u8 } + } + fn inc(&mut self) { + self.value = self.value.saturating_add(1); + } + fn dec(&mut self) { + self.value = self.value.saturating_sub(1); + } + fn value(&self) -> String { + self.value.to_string() + } } -impl Sandbox for Counter { +impl Sandbox for Model { type Message = Message; fn new() -> Self { - Self { value: 0 } + Self::default() } fn title(&self) -> String { - String::from("Counter - fltk-rs") + format!("{} - 7GUI: Counter", self.value()) } - fn update(&mut self, message: Message) { - match message { - Message::IncrementPressed => { - if self.value == DIAL - 1 { - self.value = 0; - } else { - self.value += 1; - } - } - Message::DecrementPressed => { - if self.value == 0 { - self.value = DIAL - 1; - } else { - self.value -= 1; - } - } + fn view(&mut self) { + let mut page = Flex::default().with_size(300, 100).center_of_parent(); + { + Button::default() + .with_label("@#<") + .on_event(|_| Message::Dec); + Frame::default() + .with_label(&self.value.to_string()) + .set_frame(FrameType::UpBox); + Button::default() + .with_label("@#>") + .on_event(|_| Message::Inc); } + page.end(); + page.set_pad(0); + page.set_margin(10); } - fn view(&mut self) { - let mut page = Flex::default_fill().column(); - Button::default() - .with_label("Increment") - .on_event(|_| Message::IncrementPressed); - Frame::default().with_label(&self.value.to_string()); - Button::default() - .with_label("Decrement") - .on_event(|_| Message::DecrementPressed); - let mut dial = Dial::default(); - dial.set_maximum((DIAL / 4 * 3) as f64); - dial.set_value(self.value as f64); - dial.deactivate(); - page.end(); - page.set_margin(PAD); + fn update(&mut self, message: Message) { + match message { + Message::Inc => self.inc(), + Message::Dec => self.dec(), + } } } diff --git a/examples/crud.rs b/examples/crud.rs new file mode 100644 index 0000000..7187a5e --- /dev/null +++ b/examples/crud.rs @@ -0,0 +1,236 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, + browser::{Browser, BrowserType}, + button::Button, + color_themes, + frame::Frame, + group::Flex, + input::Input, + prelude::*, + OnEvent, Sandbox, Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const WIDTH: i32 = HEIGHT * 3; + +#[derive(Debug, Clone)] +enum Message { + Inc, + Dec, + Upd, + Choice(usize), + Filter(String), + Name(String), + Surname(String), + Secretname(String), +} + +#[derive(Debug, Clone)] +struct Person { + name: String, + surname: String, + secretname: String, +} + +impl Person { + fn default() -> Self { + Self { + name: String::new(), + surname: String::new(), + secretname: String::new(), + } + } +} + +#[derive(Debug, Clone)] +struct Model { + filter: String, + inc: bool, + dec: bool, + upd: bool, + list: Vec, + temp: Person, + curr: usize, +} + +impl Model { + fn default() -> Self { + Self { + filter: String::new(), + inc: true, + dec: false, + upd: false, + list: Vec::from([Person::default()]), + temp: Person::default(), + curr: 0, + } + } + fn inc(&mut self) { + self.list.push(self.temp.clone()); + self.curr = self.curr.saturating_add(1); + self.temp = Person::default(); + } + fn dec(&mut self) { + self.list.remove(self.curr); + self.curr = self.curr.saturating_sub(1); + self.temp = self.list[self.curr].clone(); + } + fn upd(&mut self) { + self.list[self.curr] = self.temp.clone(); + } + fn name(&mut self, value: String) { + self.temp.name = value; + } + fn sur(&mut self, value: String) { + self.temp.surname = value; + } + fn sec(&mut self, value: String) { + self.temp.secretname = value; + } +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("7GUI: CRUD") + } + + fn view(&mut self) { + let mut page = Flex::default_fill(); + { + let mut left = Flex::default_fill().column(); + + let mut row = Flex::default(); + row.fixed(&Frame::default(), HEIGHT); + crate::input(&self.filter) + .with_label("@#search") + .on_event(move |input| Message::Filter(input.value())); + row.end(); + left.fixed(&row, HEIGHT); + + crate::browser("Browser", self).on_event(move |browser| { + Message::Choice((browser.value() as usize).saturating_sub(1)) + }); + + let mut row = Flex::default(); + crate::button(self.inc) + .with_label("@#+ Add") + .on_event(move |_| Message::Inc); + crate::button(self.upd) + .with_label("@#refresh Update") + .on_event(move |_| Message::Upd); + crate::button(self.dec) + .with_label("@#1+ Delete") + .on_event(move |_| Message::Dec); + row.end(); + row.set_pad(PAD); + left.fixed(&row, HEIGHT); + + left.end(); + left.set_pad(PAD); + page.fixed(&Frame::default(), WIDTH); + + let mut right = Flex::default_fill().column(); + right.fixed( + &crate::input(&self.temp.name) + .with_label("Name:") + .clone() + .on_event(move |input| Message::Name(input.value())), + HEIGHT, + ); + right.fixed( + &crate::input(&self.temp.surname) + .with_label("Surname:") + .clone() + .on_event(move |input| Message::Surname(input.value())), + HEIGHT, + ); + right.fixed( + &crate::input(&self.temp.secretname) + .with_label("Secretname:") + .clone() + .on_event(move |input| Message::Secretname(input.value())), + HEIGHT, + ); + right.end(); + } + page.end(); + page.set_pad(0); + page.set_margin(10); + } + + fn update(&mut self, message: Message) { + match message { + Message::Choice(value) => { + self.curr = value; + self.upd = true; + if self.list.len() > 1 { + self.dec = true; + } + } + Message::Filter(value) => self.filter = value, + Message::Name(value) => self.name(value), + Message::Surname(value) => self.sur(value), + Message::Secretname(value) => self.sec(value), + Message::Inc => self.inc(), + Message::Upd => self.upd(), + Message::Dec => self.dec(), + } + } +} + +fn button(value: bool) -> Button { + let mut element = Button::default(); + if value { + element.activate(); + } else { + element.deactivate(); + } + element +} + +fn browser(tooltip: &str, value: &Model) -> Browser { + let mut element = Browser::default().with_type(BrowserType::Hold); + element.set_tooltip(tooltip); + if !value.list.is_empty() { + for item in &value.list { + if item + .name + .to_lowercase() + .starts_with(&value.filter.to_lowercase()) + { + element.add(&format!( + "{} {} {}", + item.name, item.surname, item.secretname + )); + } + } + element.select(value.curr as i32 + 1); + } + element +} + +fn input(value: &str) -> Input { + let mut element = Input::default(); + element.set_value(value); + element +} diff --git a/examples/flcalculator.rs b/examples/flcalculator.rs index 166af7c..43eaf2c 100644 --- a/examples/flcalculator.rs +++ b/examples/flcalculator.rs @@ -17,6 +17,7 @@ use { }; pub fn main() { + app::GlobalState::::new(env::var("HOME").unwrap() + PATH + NAME); Model::new().run(Settings { size: (360, 640), resizable: false, @@ -27,6 +28,13 @@ pub fn main() { }) } +#[derive(PartialEq, Clone)] +enum Message { + Click(String), + Theme, + Quit, +} + #[derive(Clone)] struct Model { prev: String, @@ -36,18 +44,64 @@ struct Model { theme: bool, } -#[derive(PartialEq, Clone)] -enum Message { - Click(String), - Theme, - Quit, +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; + fn title(&self) -> String { + String::from(NAME) + } + fn new() -> Self { - let file = env::var("HOME").unwrap() + PATH + NAME; + 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, @@ -61,10 +115,6 @@ impl Sandbox for Model { } } - fn title(&self) -> String { - String::from(NAME) - } - fn view(&mut self) { let menu = crate::menu(self.theme as usize); let mut page = Flex::default_fill().column(); @@ -122,57 +172,15 @@ impl Sandbox for Model { fn update(&mut self, message: Message) { match message { Message::Quit => self.quit(), - Message::Theme => { - self.theme = !self.theme; - } - Message::Click(value) => 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; - } - }, + Message::Theme => self.theme(), + Message::Click(value) => self.click(value), }; } } impl Model { fn quit(&self) { - let file = env::var("HOME").unwrap() + PATH + NAME; + let file = app::GlobalState::::get().with(move |model| model.clone()); fs::write(file, [self.theme as u8]).unwrap(); app::quit(); } diff --git a/examples/fldialect.rs b/examples/fldialect.rs index f9c672e..403d28e 100644 --- a/examples/fldialect.rs +++ b/examples/fldialect.rs @@ -6,9 +6,9 @@ use { button::{Button, ButtonType}, color_themes, dialog::{alert_default, FileChooser, FileChooserType, HelpDialog}, - enums::{Color, Cursor, Event, Font, FrameType, Shortcut}, + enums::{Color, Font, FrameType, Shortcut}, frame::Frame, - group::{Flex, FlexType}, + group::Flex, menu::{Choice, MenuButton, MenuFlag}, prelude::*, text::{TextBuffer, TextEditor, WrapMode}, @@ -20,12 +20,13 @@ use { 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: true, + resizable: false, color_map: Some(color_themes::DARK_THEME), scheme: Some(app::Scheme::Base), ..Default::default() @@ -43,7 +44,6 @@ struct Model { speak: bool, font: u8, size: u8, - spinner: u8, source: String, target: String, lang: Vec, @@ -73,7 +73,7 @@ impl Sandbox for Model { } fn new() -> Self { - let file = env::var("HOME").unwrap() + PATH + NAME; + 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() { @@ -100,9 +100,8 @@ impl Sandbox for Model { to: params[5], font: params[6], size: params[7], - vertical: ((w + width as f64) / 4_f64) as i32, - horizontal: ((h + height as f64) / 4_f64) as i32, - spinner: 0, + 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(), @@ -112,105 +111,55 @@ impl Sandbox for Model { fn view(&mut self) { let mut page = Flex::default_fill().column(); - - let mut header = Flex::default(); - header.fixed(&crate::menu(), HEIGHT); - 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(); - - let mut hero = Flex::default().column().with_id("HERO"); - crate::text("Source", &self.source, self.font, self.size) - .on_event(move |text| Message::Source(text.buffer().unwrap().text())); - Frame::default().with_id("Handle").handle(move |frame, event| { - let mut flex = app::widget_from_id::("HERO").unwrap(); - match event { - Event::Push => true, - Event::Drag => { - let child = flex.child(0).unwrap(); - match flex.get_type() { - FlexType::Column => { - if (flex.y()..=flex.height() + flex.y() - frame.height()) - .contains(&app::event_y()) - { - flex.fixed(&child, app::event_y() - flex.y()); - } - } - FlexType::Row => { - if (flex.x()..=flex.width() + flex.x() - frame.width()) - .contains(&app::event_x()) - { - flex.fixed(&child, app::event_x() - flex.x()); - } - } - } - app::redraw(); - true - } - Event::Enter => { - frame.window().unwrap().set_cursor( - match flex.get_type() { - FlexType::Column => Cursor::NS, - FlexType::Row => Cursor::WE, - } - ); - true - } - Event::Leave => { - frame.window().unwrap().set_cursor(Cursor::Arrow); - true - } - _ => false, - } - }); - crate::text("Target", &self.target, self.font, self.size); - hero.end(); - - 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)); - footer.fixed(&crate::dial(self.spinner as f64), HEIGHT); - Frame::default(); - crate::button("Save as...", "@#filesaveas", &mut footer).on_event(move |_| Message::Save); - footer.end(); - - page.end(); { + 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); - hero.handle(move |flex, event| { - if event == Event::Resize { - flex.set_type( - match flex.width() < flex.height() { - true => FlexType::Column, - false => FlexType::Row, - } - ); - flex.fixed(&flex.child(0).unwrap(), 0); - flex.fixed(&flex.child(1).unwrap(), PAD); - true - } else { - false - } - }); + } + { + 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(&header, HEIGHT); page.fixed(&footer, HEIGHT); + } + page.end(); + { page.set_margin(PAD); page.set_pad(PAD); page.set_frame(FrameType::FlatBox); @@ -220,12 +169,6 @@ impl Sandbox for Model { "Translate from {} to {} - {NAME}", self.lang[self.from as usize], self.lang[self.to as usize] )); - window.size_range( - DEFAULT[0] as i32 * U8 + DEFAULT[1] as i32, - DEFAULT[0] as i32 * U8 + DEFAULT[1] as i32, - 0, - 0, - ); } } @@ -313,19 +256,19 @@ impl Model { button.activate(); } fn quit(&self) { - let file = env::var("HOME").unwrap() + PATH + NAME; + let file = app::GlobalState::::get().with(move |model| model.clone()); let window = app::first_window().unwrap(); fs::write( file, [ - (window.width() / U8) as u8, - (window.width() % U8) as u8, - (window.height() / U8) as u8, - (window.height() % U8) as u8, - self.from, - self.to, - self.font, - self.size, + (window.width() / U8) as u8, //[0] + (window.width() % U8) as u8, //[1] + (window.height() / U8) as u8, //[2] + (window.height() % U8) as u8, //[3] + self.from, //[4] + self.to, //[5] + self.font, //[6] + self.size, //[7] ], ) .unwrap(); @@ -351,11 +294,12 @@ fn counter(tooltip: &str, value: f64, flex: &mut Flex) -> Counter { element } -fn dial(value: f64) -> Dial { +fn dial(flex: &mut Flex) { + const DIAL: u8 = 120; let mut element = Dial::default().with_id("SPINNER"); element.deactivate(); element.set_maximum((DIAL / 4 * 3) as f64); - element.set_value(value); + element.set_value(element.minimum()); element.set_callback(move |dial| { dial.set_value(if dial.value() == (DIAL - 1) as f64 { dial.minimum() @@ -363,7 +307,7 @@ fn dial(value: f64) -> Dial { dial.value() + 1f64 }) }); - element + flex.fixed(&element, HEIGHT); } fn choice(tooltip: &str, choice: &str, value: u8, flex: &mut Flex) -> Choice { @@ -389,9 +333,10 @@ fn text(tooltip: &str, value: &str, font: u8, size: u8) -> TextEditor { element } -fn menu() -> MenuButton { +fn menu(flex: &mut Flex) { let element = MenuButton::default(); - element.clone() + element + .clone() .on_item_event( "@#circle T&ranslate", Shortcut::Ctrl | 'r', @@ -410,7 +355,7 @@ fn menu() -> MenuButton { MenuFlag::Normal, move |_| Message::Quit, ); - element + flex.fixed(&element, HEIGHT); } fn info() { @@ -511,10 +456,9 @@ pub fn once() -> bool { const NAME: &str = "FlDialect"; const PATH: &str = "/.config"; -const DIAL: u8 = 120; const PAD: i32 = 10; const HEIGHT: i32 = PAD * 3; -const WIDTH: i32 = 125; +const WIDTH: i32 = 125; const U8: i32 = 255; const DEFAULT: [u8; 9] = [ 1, // [0] window_width * U8 + diff --git a/examples/flglyph.rs b/examples/flglyph.rs new file mode 100644 index 0000000..f586252 --- /dev/null +++ b/examples/flglyph.rs @@ -0,0 +1,139 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, + button::Button, + color_themes, + enums::{FrameType, Font}, + frame::Frame, + browser::{Browser, BrowserType}, + group::Flex, + prelude::*, + OnEvent, Sandbox, Settings, +}; + +#[derive(Debug, Clone)] +struct Model { + list: Vec, + curr: usize, +} + +impl Model { + fn init() -> Self { + Self { + list: (0x2700..=0x27BF).map(|x| char::from_u32(x).unwrap()).collect(), + curr: 0, + } + } + fn choice(&mut self, curr: usize) { + self.curr = curr; + } + pub fn inc(&mut self) { + if !self.list.is_empty() { + match self.curr < self.list.len() - 1 { + true => self.curr += 1, + false => self.curr = 0, + }; + } + } + pub fn dec(&mut self) { + if !self.list.is_empty() { + match self.curr > 0 { + true => self.curr -= 1, + false => self.curr = self.list.len() - 1, + }; + } + } +} + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; +const HEIGHT: i32 = PAD * 3; +const WIDTH: i32 = HEIGHT * 3; + +#[derive(Clone, Copy)] +enum Message { + Inc, + Dec, + Choice(usize) +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Model::init() + } + + fn title(&self) -> String { + String::from("FlPictures") + } + + fn view(&mut self) { + let mut page = Flex::default_fill(); + { + let mut left = Flex::default_fill().column(); + crate::browser("List", self.clone()).on_event(move |browser| { + Message::Choice((browser.value() as usize).saturating_sub(1)) + }); + let mut buttons = Flex::default(); + Button::default().with_label("@#<").on_event(move |_| Message::Dec); + Button::default().with_label("@#>").on_event(move |_| Message::Inc); + buttons.end(); + buttons.set_pad(0); + left.end(); + left.set_pad(PAD); + left.fixed(&buttons, HEIGHT); + page.fixed(&left, WIDTH); + crate::frame("Canvas", self.clone()); + } + page.end(); + page.set_pad(PAD); + page.set_margin(PAD); + page.set_frame(FrameType::FlatBox); + } + + fn update(&mut self, message: Message) { + match message { + Message::Choice(value) => self.choice(value), + Message::Dec => self.dec(), + Message::Inc => self.inc(), + } + } +} + +fn browser(tooltip: &str, value: Model) -> Browser { + let mut element = Browser::default().with_type(BrowserType::Hold); + element.set_tooltip(tooltip); + element.set_label_font(Font::Zapfdingbats); + element.set_text_size(16); + if !value.list.is_empty() { + for item in value.list { + element.add(&item.to_string()); + } + element.select(value.curr as i32 + 1); + } + element +} + +fn frame(tooltip: &str, value: Model) -> Frame { + let mut element = Frame::default(); + element.set_frame(FrameType::DownBox); + element.set_label_font(Font::Zapfdingbats); + element.set_label_size(250); + element.set_tooltip(tooltip); + if !value.list.is_empty() { + element.set_label(&value.list[value.curr].to_string()); + }; + element +} diff --git a/examples/flnetport.rs b/examples/flnetport.rs index 2761590..081538e 100644 --- a/examples/flnetport.rs +++ b/examples/flnetport.rs @@ -8,7 +8,7 @@ use { enums::FrameType, frame::Frame, group::Flex, - input::IntInput, + input::{Input, InputType}, prelude::*, valuator::{Counter, CounterType}, OnEvent, Sandbox, Settings, @@ -32,13 +32,6 @@ pub fn main() { }) } -#[derive(Clone)] -struct Model { - status: String, - address: [u8; 4], - port: u32, -} - #[derive(Clone, Copy)] enum Message { Octet(usize, u8), @@ -46,6 +39,13 @@ enum Message { Check, } +#[derive(Clone)] +struct Model { + status: String, + address: [u8; 4], + port: u32, +} + impl Sandbox for Model { type Message = Message; @@ -58,47 +58,43 @@ impl Sandbox for Model { } fn title(&self) -> String { - String::from("FlNetPort") + format!( + "[{}] {:?}:{} - FlNetPort", + self.status, self.address, self.port + ) } fn view(&mut self) { let mut page = Flex::default_fill().column(); - let mut header = Flex::default(); - header.fixed(&Frame::default().with_label("IP address:"), WIDTH); - for idx in 0..4 { - let mut octet = crate::counter(&mut header); - octet.set_value(self.address[idx] as f64); - octet - .clone() - .on_event(move |_| Message::Octet(idx, octet.value() as u8)); - } - Frame::default(); - let mut port = IntInput::default().with_label("Port:"); - port.set_value(&self.port.to_string()); - header.fixed(&port, WIDTH); - port.clone() - .on_event(move |_| Message::Port(port.value().parse::().unwrap())); - header.end(); - let mut hero = Flex::default(); - Frame::default().with_label(&self.status); - hero.end(); - let mut footer = Flex::default(); - Button::default() - .with_label("Check") - .on_event(move |_| Message::Check); - footer.end(); - page.end(); { - header.set_frame(FrameType::DownFrame); + let mut header = Flex::default(); + header.fixed(&Frame::default().with_label("IP address:"), WIDTH); + for idx in 0..4 { + crate::counter(self.address[idx] as f64, &mut header) + .on_event(move |octet| Message::Octet(idx, octet.value() as u8)); + } + Frame::default(); + crate::input(&self.port.to_string(), &mut header) + .with_label("Port:") + .on_event(move |input| Message::Port(input.value().parse::().unwrap())); + header.fixed( + &Button::default() + .with_label("@#->") + .clone() + .on_event(move |_| Message::Check), + HEIGHT, + ); + header.end(); header.set_pad(0); - hero.set_frame(FrameType::DownFrame); - footer.set_frame(FrameType::DownFrame); - page.set_pad(PAD); - page.set_margin(PAD); - page.set_frame(FrameType::FlatBox); page.fixed(&header, HEIGHT); - page.fixed(&footer, HEIGHT); } + Frame::default() + .with_label(&self.status) + .set_frame(FrameType::DownFrame); + page.end(); + page.set_pad(PAD); + page.set_margin(PAD); + page.set_frame(FrameType::FlatBox); } fn update(&mut self, message: Message) { @@ -144,10 +140,18 @@ impl Sandbox for Model { } } -fn counter(flex: &mut Flex) -> Counter { +fn counter(value: f64, flex: &mut Flex) -> Counter { let mut element = Counter::default().with_type(CounterType::Simple); - element.set_range(0_f64, 254_f64); + element.set_maximum(254_f64); element.set_precision(0); + element.set_value(value); + flex.fixed(&element, WIDTH); + element +} + +fn input(value: &str, flex: &mut Flex) -> Input { + let mut element = Input::default().with_type(InputType::Int); + element.set_value(value); flex.fixed(&element, WIDTH); element } diff --git a/examples/flpicture.rs b/examples/flpicture.rs index 65b5653..21c4f01 100644 --- a/examples/flpicture.rs +++ b/examples/flpicture.rs @@ -11,15 +11,14 @@ use flemish::{ image::SharedImage, menu::{MenuButton, MenuFlag}, prelude::*, - valuator::{Slider, SliderType}, OnEvent, OnMenuEvent, Sandbox, Settings, }; use std::fs; pub fn main() { Model::new().run(Settings { - size: (640, 480), - resizable: true, + size: (640, 360), + resizable: false, ignore_esc_close: true, color_map: Some(color_themes::DARK_THEME), scheme: Some(app::Scheme::Base), @@ -30,15 +29,16 @@ pub fn main() { const PAD: i32 = 10; const HEIGHT: i32 = PAD * 3; +#[derive(Debug, Clone)] struct Image { file: String, image: SharedImage, } +#[derive(Debug, Clone)] struct Model { list: Vec, - size: f64, - current: usize, + curr: usize, } #[derive(Clone, Copy)] @@ -56,8 +56,7 @@ impl Sandbox for Model { fn new() -> Self { Self { list: Vec::new(), - size: 1f64, - current: 0, + curr: 0, } } @@ -67,45 +66,24 @@ impl Sandbox for Model { fn view(&mut self) { let mut page = Flex::default_fill().column(); - let mut header = Flex::default(); - crate::menu(&mut header); - crate::button("Open", "@#fileopen", &mut header).on_event(|_| Message::Open); - crate::button("Prev", "@#|<", &mut header).on_event(|_| Message::Prev); - let mut size = crate::slider("Size").with_type(SliderType::Horizontal); - crate::button("Next", "@#>|", &mut header).on_event(|_| Message::Next); - crate::button("Remove", "@#1+", &mut header).on_event(|_| Message::Remove); - header.end(); - let mut hero = Flex::default_fill(); - let mut frame = crate::frame("Image").with_id("image-frame"); - hero.end(); - page.end(); { + let mut header = Flex::default(); + crate::menu(&mut header); + crate::button("Open", "@#fileopen", &mut header).on_event(|_| Message::Open); + crate::button("Prev", "@#|<", &mut header).on_event(|_| Message::Prev); + Frame::default(); + crate::button("Next", "@#>|", &mut header).on_event(|_| Message::Next); + crate::button("Remove", "@#1+", &mut header).on_event(|_| Message::Remove); + header.end(); header.set_pad(0); header.set_margin(0); - header.set_frame(FrameType::DownBox); - hero.set_margin(0); - hero.set_frame(FrameType::DownBox); - page.set_frame(FrameType::FlatBox); - page.set_pad(PAD); - page.set_margin(PAD); page.fixed(&header, HEIGHT); + crate::frame("Image", self.clone()); } - - let image = if self.list.is_empty() { - None:: - } else { - let mut image = self.list[self.current].image.clone(); - image.scale( - (frame.w() as f64 * self.size) as i32, - (frame.h() as f64 * self.size) as i32, - true, - true, - ); - Some(image) - }; - - frame.set_image(image.clone()); - size.set_callback(move |s| slider_cb(s, image.clone())); + page.end(); + page.set_pad(PAD); + page.set_margin(PAD); + page.set_frame(FrameType::FlatBox); } fn update(&mut self, message: Message) { @@ -139,14 +117,14 @@ impl Model { }; }; } - self.current = 0; + self.curr = 0; }; } fn prev(&mut self) { if !self.list.is_empty() { - self.current = match self.current > 0 { - true => self.current.saturating_sub(1), + self.curr = match self.curr > 0 { + true => self.curr.saturating_sub(1), false => self.list.len() - 1, }; } @@ -154,8 +132,8 @@ impl Model { fn next(&mut self) { if !self.list.is_empty() { - self.current = match self.current < self.list.len() - 1 { - true => self.current.saturating_add(1), + self.curr = match self.curr < self.list.len() - 1 { + true => self.curr.saturating_add(1), false => 0, }; } @@ -165,11 +143,11 @@ impl Model { if !self.list.is_empty() { match choice2_default("Remove ...?", "Remove", "Cancel", "Permanent") { Some(0) => { - self.list.remove(self.current); + self.list.remove(self.curr); } Some(2) => { - if fs::remove_file(self.list[self.current].file.clone()).is_ok() { - self.list.remove(self.current); + if fs::remove_file(self.list[self.curr].file.clone()).is_ok() { + self.list.remove(self.curr); } } _ => {} @@ -186,10 +164,14 @@ fn button(tooltip: &str, label: &str, flex: &mut Flex) -> Button { element } -fn frame(tooltip: &str) -> Frame { - let mut element = Frame::default_fill(); +fn frame(tooltip: &str, value: Model) -> Frame { + let mut element = Frame::default(); element.set_tooltip(tooltip); - element.set_image(None::); + element.set_image(match value.list.is_empty() { + true => None::, + false => Some(value.list[value.curr].image.clone()), + }); + element.set_frame(FrameType::DownBox); element } @@ -229,24 +211,3 @@ fn menu(flex: &mut Flex) { |_| Message::Quit, ); } - -fn slider(tooltip: &str) -> Slider { - let mut element = Slider::default(); - element.set_tooltip(tooltip); - element.set_value(element.maximum()); - element -} - -fn slider_cb(s: &mut Slider, image: Option) { - let mut frame: Frame = app::widget_from_id("image-frame").unwrap(); - if let Some(mut image) = image.clone() { - image.scale( - (frame.width() as f64 * s.value()) as i32, - (frame.height() as f64 * s.value()) as i32, - true, - true, - ); - frame.set_image(Some(image)); - app::redraw(); - } -} diff --git a/examples/flresters.rs b/examples/flresters.rs index 5719c1a..3ac7b5f 100644 --- a/examples/flresters.rs +++ b/examples/flresters.rs @@ -7,8 +7,8 @@ use { color_themes, enums::{Align, Color, Font, FrameType}, frame::Frame, + misc::InputChoice, group::Flex, - input::Input, menu::Choice, prelude::*, text::{StyleTableEntry, TextBuffer, TextDisplay, WrapMode}, @@ -62,10 +62,11 @@ impl Sandbox for Model { fn view(&mut self) { let mut page = Flex::default_fill().column(); let mut header = Flex::default(); - crate::choice(self.method as i32, &mut header) + header.fixed(&Frame::default(), WIDTH); + crate::choice(self.method as i32, &mut header).with_label("Method: ") .on_event(move |choice| Message::Method(choice.value() as u8)); - header.fixed(&Frame::default().with_label("https://"), WIDTH); - crate::input(&self.url).on_event(move |input| Message::Url(input.value())); + 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); header.end(); crate::text(&self.responce); @@ -145,8 +146,13 @@ fn button(flex: &mut Flex) -> Button { element } -fn input(value: &str) -> Input { - let mut element = Input::default(); +fn input(value: &str) -> InputChoice { + let mut element = InputChoice::default().with_label("URL: "); + for item in ["users", "posts", "albums", "todos", "comments", "posts"] { + element.add(&(format!(r#"https:\/\/jsonplaceholder.typicode.com\/{item}"#))); + } + element.add(r#"https:\/\/lingva.ml\/api\/v1\/languages"#); + element.add(r#"https:\/\/ipinfo.io\/json"#); element.set_value(value); element } diff --git a/examples/input.rs b/examples/input.rs deleted file mode 100644 index f0aaded..0000000 --- a/examples/input.rs +++ /dev/null @@ -1,56 +0,0 @@ -use flemish::{ - app, button::Button, color_themes, frame::Frame, group::Flex, input::Input, prelude::*, - OnEvent, Sandbox, Settings, -}; - -pub fn main() { - State::new().run(Settings { - size: (300, 100), - resizable: true, - color_map: Some(color_themes::DARK_THEME), - scheme: Some(app::Scheme::Base), - ..Default::default() - }) -} - -#[derive(Default)] -struct State { - text: String, -} - -#[derive(Debug, Clone)] -enum Message { - Submit(String), -} - -impl Sandbox for State { - type Message = Message; - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("State - fltk-rs") - } - - fn update(&mut self, message: Message) { - match message { - Message::Submit(value) => { - self.text = value.clone(); - println!("Hello {}", value); - } - } - } - - fn view(&mut self) { - let col = Flex::default_fill().column(); - Frame::default().with_label("Enter name:"); - let mut name = Input::default(); - name.set_value(&self.text); - Button::default() - .with_label("Submit") - .on_event(move |_| Message::Submit(name.value())); - col.end(); - } -} diff --git a/examples/inputchoice.rs b/examples/inputchoice.rs new file mode 100644 index 0000000..98befdb --- /dev/null +++ b/examples/inputchoice.rs @@ -0,0 +1,111 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, color_themes, frame::Frame, group::Flex, misc::InputChoice, prelude::*, OnEvent, Sandbox, + Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (360, 640), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; + +#[derive(Clone)] +enum Message { + Selected(String), +} + +struct Model { + language: String, + text: String, +} + +impl Sandbox for Model { + fn new() -> Self { + Self { + language: String::new(), + text: String::new(), + } + } + + fn view(&mut self) { + let mut page = Flex::default_fill().column(); + { + Frame::default().with_label("What is your language?"); + crate::input(&self.language); + Frame::default(); + Frame::default().with_label(&self.text); + } + page.end(); + page.fixed(&page.child(0).unwrap(), 30); + page.fixed(&page.child(1).unwrap(), 30); + page.fixed(&page.child(3).unwrap(), 30); + page.set_margin(PAD); + } + + type Message = Message; + fn update(&mut self, message: Message) { + match message { + Message::Selected(value) => { + self.language = value.clone(); + self.text = crate::hello(&value).to_string(); + } + } + } + + fn title(&self) -> String { + format!("InputChoice -{}- Flemish", self.language) + } +} + +const LANGUAGES: [&str; 8] = [ + "Danish", + "English", + "French", + "German", + "Italian", + "Portuguese", + "Spanish", + "Other", +]; + +fn hello(label: &str) -> &str { + match label { + "Danish" => "Halloy!", + "English" => "Hello!", + "French" => "Salut!", + "German" => "Hallo!", + "Italian" => "Ciao!", + "Portuguese" => "Olá!", + "Spanish" => "¡Hola!", + _ => "... hello?", + } +} + +fn input(value: &str) { + let mut element = InputChoice::default(); + element.input().set_value(value); + let mut choice = element.clone(); + element.input().set_callback(move |input| { + choice.clear(); + for lang in crate::LANGUAGES { + if lang + .to_lowercase() + .starts_with(&input.value().to_lowercase()) + { + choice.add(lang); + } + } + }); + element.input().do_callback(); + element.set_value_index(0); + element.on_event(move |choice| Message::Selected(choice.value().unwrap())); +} diff --git a/examples/menu.rs b/examples/menu.rs index c770fb0..ceaf7c1 100644 --- a/examples/menu.rs +++ b/examples/menu.rs @@ -9,8 +9,8 @@ use flemish::{ }; pub fn main() { - MenuApp::new().run(Settings { - size: (300, 300), + Model::new().run(Settings { + size: (640, 360), resizable: true, ignore_esc_close: true, color_map: Some(color_themes::DARK_THEME), @@ -20,7 +20,7 @@ pub fn main() { } #[derive(Default)] -struct MenuApp { +struct Model { value: i32, } @@ -30,7 +30,7 @@ enum Message { DecrementPressed, } -impl Sandbox for MenuApp { +impl Sandbox for Model { type Message = Message; fn new() -> Self { diff --git a/examples/progress.rs b/examples/progress.rs new file mode 100644 index 0000000..80761a3 --- /dev/null +++ b/examples/progress.rs @@ -0,0 +1,79 @@ +#![forbid(unsafe_code)] + +use flemish::{ + app, color_themes, + group::Flex, + misc::Progress, + prelude::*, + valuator::{Slider, SliderType}, + OnEvent, Sandbox, Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +const PAD: i32 = 10; + +#[derive(Clone)] +enum Message { + Slider(f64), +} + +struct Model { + value: f64, +} + +impl Sandbox for Model { + type Message = Message; + + fn title(&self) -> String { + String::from("Progress - Flemish") + } + + fn new() -> Self { + Self { value: 0f64 } + } + + fn view(&mut self) { + let mut page = Flex::default_fill() + .column() + .with_size(560, 70) + .center_of_parent(); + { + crate::progress(self.value); + crate::slider(self.value).on_event(move |slider| Message::Slider(slider.value())); + } + page.end(); + page.set_pad(PAD); + } + + fn update(&mut self, message: Message) { + match message { + Message::SliderChanged(value) => self.value = value, + } + } +} + +const MAX: f64 = 100f64; + +fn progress(value: f64) { + let mut element = Progress::default(); + element.set_maximum(MAX); + element.set_value(value); +} + +fn slider(value: f64) -> Slider { + let mut element = Slider::default().with_type(SliderType::Horizontal); + element.set_maximum(MAX); + element.set_precision(0); + element.set_value(value); + element +} diff --git a/examples/temperature.rs b/examples/temperature.rs new file mode 100644 index 0000000..cb56d54 --- /dev/null +++ b/examples/temperature.rs @@ -0,0 +1,94 @@ +use flemish::{ + app, color_themes, + enums::{CallbackTrigger, FrameType}, + frame::Frame, + group::Flex, + input::{Input, InputType}, + prelude::*, + OnEvent, Sandbox, Settings, +}; + +pub fn main() { + Model::new().run(Settings { + size: (640, 360), + resizable: false, + ignore_esc_close: true, + color_map: Some(color_themes::DARK_THEME), + scheme: Some(app::Scheme::Base), + ..Default::default() + }) +} + +#[derive(Debug, Clone)] +enum Message { + Celsius(i32), + Fahrenheit(i32), +} + +const WIDTH: i32 = 90; + +struct Model { + celsius: i32, + fahrenheit: i32, +} + +impl Model { + fn celsius(&mut self, value: i32) { + self.fahrenheit = value; + self.celsius = ((value as f64 - 32f64) * (5f64 / 9f64)).round() as i32 + } + fn fahrenheit(&mut self, value: i32) { + self.celsius = value; + self.fahrenheit = (value as f64 * (9f64 / 5f64) + 32f64).round() as i32 + } +} + +impl Sandbox for Model { + type Message = Message; + + fn new() -> Self { + Self { + celsius: 0, + fahrenheit: 32, + } + } + + fn title(&self) -> String { + format!("{} : {} - 7GUI: Temperature", self.celsius, self.fahrenheit) + } + + fn view(&mut self) { + let mut page = Flex::default() + .with_size(WIDTH * 3, WIDTH) + .center_of_parent(); + { + Frame::default(); + let mut right = Flex::default().column(); + crate::input(self.celsius) + .with_label("Celsius: ") + .on_event(move |input| Message::Celsius(input.value().parse::().unwrap())); + crate::input(self.fahrenheit) + .with_label("Fahrenheit: ") + .on_event(move |input| Message::Fahrenheit(input.value().parse::().unwrap())); + right.end(); + right.set_pad(10); + } + page.end(); + page.set_pad(0); + page.set_margin(10); + page.set_frame(FrameType::UpBox); + } + + fn update(&mut self, message: Message) { + match message { + Message::Celsius(value) => self.fahrenheit(value), + Message::Fahrenheit(value) => self.celsius(value), + } + } +} + +fn input(value: i32) -> Input { + let mut element = Input::default().with_type(InputType::Int); + element.set_value(&value.to_string()); + element +}