diff --git a/.gitmodules b/.gitmodules index 88ada86..0332445 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "database"] path = database url = ./database/ +[submodule "backend"] + path = backend + url = ./backend/ diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml new file mode 100644 index 0000000..72d0600 --- /dev/null +++ b/frontend/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "advotracker_frontend" +version = "0.1.0" +authors = ["Ralf Zerres "] +description = "Frontend component that supports lawyers to capture relevant data encountered during an online legal advice\n" +readme = "README.md" +license = "(0BSD OR MIT)" +edition = "2018" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["winuser"] } +#diesel = { version = "1.4", features = [ "postgres", "sqlite" ] } + +[dependencies] +#orbtk = "0.3.1-alpha3" +orbtk = { git = "https://github.com/redox-os/orbtk.git", branch = "develop" } + +[dev-dependencies] +#euc = "0.4.2" +#vek = "0.9.10" + +[features] +# no features by default +default = [] +light-theme = [] \ No newline at end of file diff --git a/frontend/advotracker.conf b/frontend/advotracker.conf new file mode 100644 index 0000000..9f76eca --- /dev/null +++ b/frontend/advotracker.conf @@ -0,0 +1,5 @@ +[advotracker.conf] + +mode = interative + + diff --git a/frontend/examples/README.md b/frontend/examples/README.md new file mode 100644 index 0000000..3313ff5 --- /dev/null +++ b/frontend/examples/README.md @@ -0,0 +1,20 @@ +# OrbTk examples + +[![Build and test](https://github.com/redox-os/orbtk/workflows/build/badge.svg)](https://github.com/redox-os/orbtk/actions) +![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg) + +* calculator: calculator example +* canvas: use third party render library in canvas +* clear: interaction between widgets +* grid: grid layout example +* image: image widget +* minimal: minimal example +* multi_window: multi window example +* overlay: draw widgets on the top +* settings: use registry and settings service (load / save) +* stack: stack layout example +* widgets: widget overview + +## License + +Licensed under MIT license ([LICENSE](../LICENSE)). \ No newline at end of file diff --git a/frontend/examples/advowidgets.rs b/frontend/examples/advowidgets.rs new file mode 100644 index 0000000..4bff27d --- /dev/null +++ b/frontend/examples/advowidgets.rs @@ -0,0 +1,240 @@ +use std::{cell::Cell, collections::HashSet}; + +use orbtk::prelude::*; +use orbtk::theme::DEFAULT_THEME_CSS; +use orbtk::*; + +static DARK_EXT: &'static str = include_str!("../resources/stylesheets/advotracker-dark.css"); + +//#[cfg(feature = "light-theme")] +static LIGHT_EXT: &'static str = include_str!("../resources/stylesheets/advotracker-light.css"); + +static CSS_EXT: &'static str = include_str!("../resources/stylesheets/grid.css"); + +//#[cfg(not(feature = "light-theme"))] +//fn get_theme() -> ThemeValue { +// ThemeValue::create_from_css(DEFAULT_THEME_CSS) +// .extension_css(CSS_EXT) +// .build() +//} + +fn get_theme() -> ThemeValue { + ThemeValue::create() + //.extension_css(DARK_EXT) + .extension_css(LIGHT_EXT) + //.extension_css(CSS_EXT) + .build() +} + +#[derive(Debug, Copy, Clone)] +enum Action { + IncrementCounter, +} + +pub struct MainViewState { + counter: Cell, + action: Cell>, +} + +impl MainViewState { + fn action(&self, action: impl Into>) { + self.action.set(action.into()); + } +} + +impl Default for MainViewState { + fn default() -> Self { + MainViewState { + counter: Cell::new(0), + action: Cell::new(None), + } + } +} + +impl State for MainViewState { + fn update(&self, ctx: &mut Context<'_>) { + if let Some(action) = self.action.get() { + match action { + Action::IncrementCounter => { + self.counter.set(self.counter.get() + 1); + ctx.widget().set( + "result", + String16::from(format!("Button count: {}", self.counter.get())), + ); + } + } + + self.action.set(None); + } + } +} + +fn create_header(ctx: &mut BuildContext, text: &str) -> Entity { + TextBlock::create() + .text(text) + .selector(Selector::new().with("text-block").class("h1")) + .build(ctx) +} + +widget!( + MainView { + selected_indices: SelectedIndices, + text_harm: String16, + result: String16 + } +); + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + let state = self.clone_state(); + + self.name("MainView").child( + //.result("Button count: 0") + //.selected_indices(HashSet::new()) + /* + Grid::create() + .margin(8.0) + .rows( + Rows::create() + // define two rows + .row("*") + .row("*") + .build(), + ) + .child( + // Container 0 + Container::create() + .padding(8.0) + .selector(Selector::from("container").class("header")) + .attach(Grid::row(0)) + .child( + */ + Grid::create() + .selector("lynch") + .margin(8.0) + .columns( + Columns::create() + // define three columns + .column("auto") + .column(24.0) + .column("auto") + .build(), + ) + .child( + Stack::create() + // Column 0 + .attach(Grid::column(0)) + .child(create_header(ctx, "Buttons")) + .child( + Button::create() + .text("Button") + .selector(Selector::new().with("button").class("primary")) + .margin((0.0, 8.0, 0.0, 0.0)) + .icon(material_font_icons::CHECK_FONT_ICON) + .attach(Grid::column(0)) + .attach(Grid::row(1)) + .on_mouse_move(move |_| { + println!("Mouse moved over Button!"); + true + }) + .on_click(move |_| { + println!("Mouse clicked Buttonmoved over Button!"); + state.action(Action::IncrementCounter); + true + }) + .build(ctx), + ) + .child( + Switch::create() + .selector("bluebayoux") + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(0)) + .attach(Grid::row(2)) + .build(ctx), + ) + .build(ctx), + ) + .child( + Stack::create() + // Column 2 + .attach(Grid::column(2)) + .child(create_header(ctx, "Text")) + .child( + TextBlock::create() + .selector(Selector::new().class("body")) + .text(("result", id)) + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(2)) + .attach(Grid::row(1)) + .build(ctx), + ) + .child( + TextBox::create() + .water_mark("Harm...") + .text(("text_harm", id)) + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(2)) + .attach(Grid::row(2)) + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + /*.child( + // Container 1 + Container::create() + .selector(Selector::from("container").class("content")) + .padding(8.0) + .attach(Grid::row(1)) + .child( + ImageWidget::create() + .image("resources/images/orbtk-space.png") + .visibility("Hidden") + .margin(8.0) + .vertical_alignment("Center") + .horizontal_alignment("Center") + .size(600.0, 600.0) + .clip(true) + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + */ + } +} + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + let mut application = Application::default(); + application + .create_window() + .bounds((100.0, 100.0, 420.0, 730.0)) + .title("OrbTk - advotracker test GUI") + .resizeable(true) + .root(MainView::create()) + .build(); + application.run(); + + /* + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - advotracker test GUI") + .position((100.0, 100.0)) + //.size(600.0, 800.0) + .size(640.0, 480.0) + .resizeable(true) + //.theme(get_theme()) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); + */ +} diff --git a/frontend/examples/calculator-dark.css b/frontend/examples/calculator-dark.css new file mode 100644 index 0000000..437e2e3 --- /dev/null +++ b/frontend/examples/calculator-dark.css @@ -0,0 +1,32 @@ +.header { + background: #444e55; +} + +.content { + background: #3b434a; +} + +text_box { + background: transparent; + border-width: 0; + color: #9dafbf; + font-size: 16; +} + +#input { + font-size: 16; +} + +text-block { + font-size: 42; + color: #dfebf5; +} + +#input { + background: transparent; +} + +button { + border-radius: 1; + font-size: 20; +} \ No newline at end of file diff --git a/frontend/examples/calculator-light.css b/frontend/examples/calculator-light.css new file mode 100644 index 0000000..f807825 --- /dev/null +++ b/frontend/examples/calculator-light.css @@ -0,0 +1,15 @@ +.header { + background: #ffffff; +} + +.content { + background: #fafafa; +} + +text_box { + color: #4d4c4c; +} + +text-block { + color: #4d4c4c; +} \ No newline at end of file diff --git a/frontend/examples/calculator.rs b/frontend/examples/calculator.rs new file mode 100644 index 0000000..0ebd831 --- /dev/null +++ b/frontend/examples/calculator.rs @@ -0,0 +1,317 @@ +use orbtk::prelude::*; +use orbtk::theme::DEFAULT_THEME_CSS; + +static DARK_EXT: &'static str = include_str!("../resources/calculator-dark.css"); + +#[cfg(feature = "light-theme")] +static LIGHT_EXT: &'static str = include_str!("../resources/calculator-light.css"); + +#[cfg(not(feature = "light-theme"))] +fn get_theme() -> ThemeValue { + ThemeValue::create_from_css(DEFAULT_THEME_CSS) + .extension_css(DARK_EXT) + .build() +} + +#[cfg(feature = "light-theme")] +fn get_theme() -> ThemeValue { + ThemeValue::create() + .extension_css(DARK_EXT) + .extension_css(LIGHT_EXT) + .build() +} + +#[derive(Debug, Copy, Clone)] +enum Action { + Digit(char), + Operator(char), +} + +#[derive(Default, AsAny)] +pub struct MainViewState { + input: String, + operator: Option, + left_side: Option, + right_side: Option, + action: Option, +} + +impl MainViewState { + fn action(&mut self, action: impl Into>) { + self.action = action.into(); + } + + fn calculate(&mut self, ctx: &mut Context) { + let mut result = 0.0; + if let Some(operator) = self.operator { + if let Some(left_side) = self.left_side { + if let Some(right_side) = self.right_side { + match operator { + '+' => { + result = left_side + right_side; + } + '-' => { + result = left_side - right_side; + } + '*' => { + result = left_side * right_side; + } + '/' => { + result = left_side / right_side; + } + _ => {} + } + } + } + } + + if result % 1.0 == 0.0 { + ctx.widget() + .set("text", String16::from(format!("{}", result))); + } else { + ctx.widget() + .set("text", String16::from(format!("{:.8}", result))); + } + + self.left_side = Some(result); + self.right_side = None; + } +} + +impl State for MainViewState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context) { + if let Some(action) = self.action { + match action { + Action::Digit(digit) => { + self.input.push(digit); + ctx.child("input").get_mut::("text").push(digit); + } + Action::Operator(operator) => match operator { + 'C' => { + self.input.clear(); + self.left_side = None; + self.operator = None; + self.right_side = None; + ctx.widget().get_mut::("text").clear(); + ctx.child("input").get_mut::("text").clear() + } + '=' => { + self.right_side = Some(self.input.parse().unwrap_or(0.0)); + self.calculate(ctx); + self.input.clear(); + self.left_side = None; + self.operator = None; + self.right_side = None; + ctx.child("input").get_mut::("text").clear() + } + _ => { + if self.input.is_empty() { + return; + } + if self.left_side.is_none() { + self.left_side = Some(self.input.parse().unwrap_or(0.0)); + } else { + self.right_side = Some(self.input.parse().unwrap_or(0.0)); + self.calculate(ctx); + } + + ctx.child("input") + .get_mut::("text") + .push(operator); + self.input.clear(); + self.operator = Some(operator); + } + }, + } + + self.action = None; + } + } +} + +fn generate_digit_button( + ctx: &mut BuildContext, + id: Entity, + sight: char, + primary: bool, + column: usize, + column_span: usize, + row: usize, +) -> Entity { + let mut button = Button::create() + .class("single_content") + .min_size(48.0, 48.0) + .text(sight.to_string()) + .on_click(move |states, _| -> bool { + state(id, states).action(Action::Digit(sight)); + true + }) + .attach(Grid::column(column)) + .attach(Grid::row(row)) + .attach(Grid::column_span(column_span)); + + if primary { + button = button.class("primary"); + } + + button.build(ctx) +} + +fn generate_operation_button( + ctx: &mut BuildContext, + id: Entity, + sight: char, + primary: bool, + column: usize, + column_span: usize, + row: usize, +) -> Entity { + let mut button = Button::create() + .class("single_content") + .min_size(48.0, 48.0) + .text(sight.to_string()) + .class("square") + .on_click(move |states, _| -> bool { + state(id, states).action(Action::Operator(sight)); + true + }) + .attach(Grid::column(column)) + .attach(Grid::column_span(column_span)) + .attach(Grid::row(row)); + + if primary { + button = button.class("primary"); + } + + button.build(ctx) +} + +widget!(MainView { + text: String16 +}); + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView") + .width(212.0) + .height(336.0) + .text("") + .child( + Grid::create() + .rows(Rows::create().row(72.0).row("*").build()) + .child( + Container::create() + .padding(8.0) + .element("container") + .class("header") + .attach(Grid::row(0)) + .child( + Grid::create() + .child( + ScrollViewer::create() + .scroll_viewer_mode(("custom", "disabled")) + .child( + TextBlock::create() + .width(0.0) + .height(14.0) + .text("") + .element("text-block") + .id("input") + .vertical_alignment("start") + .build(ctx), + ) + .build(ctx), + ) + .child( + TextBlock::create() + .element("text-block") + .text(id) + .vertical_alignment("end") + .horizontal_alignment("end") + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + .child( + Container::create() + .element("container") + .class("content") + .padding(8.0) + .attach(Grid::row(1)) + .child( + Grid::create() + .columns( + Columns::create() + .column(48.0) + .column(4.0) + .column(48.0) + .column(4.0) + .column(48.0) + .column(4.0) + .column(48.0) + .build(), + ) + .rows( + Rows::create() + .row(48.0) + .row(4.0) + .row(48.0) + .row(4.0) + .row(48.0) + .row(4.0) + .row(48.0) + .row(4.0) + .row(48.0) + .build(), + ) + // row 0 + .child(generate_operation_button(ctx, id, 'C', false, 0, 5, 0)) + .child(generate_operation_button(ctx, id, '/', true, 6, 3, 0)) + // row 2 + .child(generate_digit_button(ctx, id, '7', false, 0, 1, 2)) + .child(generate_digit_button(ctx, id, '8', false, 2, 1, 2)) + .child(generate_digit_button(ctx, id, '9', false, 4, 1, 2)) + .child(generate_operation_button(ctx, id, '*', true, 6, 1, 2)) + // row 4 + .child(generate_digit_button(ctx, id, '4', false, 0, 1, 4)) + .child(generate_digit_button(ctx, id, '5', false, 2, 1, 4)) + .child(generate_digit_button(ctx, id, '6', false, 4, 1, 4)) + .child(generate_operation_button(ctx, id, '-', true, 6, 1, 4)) + // row 6 + .child(generate_digit_button(ctx, id, '1', false, 0, 1, 6)) + .child(generate_digit_button(ctx, id, '2', false, 2, 1, 6)) + .child(generate_digit_button(ctx, id, '3', false, 4, 1, 6)) + .child(generate_operation_button(ctx, id, '+', true, 6, 1, 6)) + // row 8 + .child(generate_digit_button(ctx, id, '0', false, 0, 3, 8)) + .child(generate_digit_button(ctx, id, '.', false, 4, 1, 8)) + .child(generate_operation_button(ctx, id, '=', true, 6, 1, 8)) + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - Calculator example") + .position((100.0, 100.0)) + .size(212.0, 336.0) + .theme(get_theme()) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} + +// helper to request MainViewState +fn state<'a>(id: Entity, states: &'a mut StatesContext) -> &'a mut MainViewState { + states.get_mut(id) +} diff --git a/frontend/examples/canvas.rs b/frontend/examples/canvas.rs new file mode 100644 index 0000000..eba6274 --- /dev/null +++ b/frontend/examples/canvas.rs @@ -0,0 +1,279 @@ +use orbtk::{prelude::*, render::platform::RenderContext2D, utils}; +use std::cell::Cell; + +use euc::{buffer::Buffer2d, rasterizer, Pipeline}; +use vek::*; + +// Cube is copied from euc spinning_cube example +// https://github.com/zesterer/euc/blob/master/examples/spinning_cube.rs +struct Cube<'a> { + mvp: Mat4, + positions: &'a [Vec4], +} + +impl<'a> Pipeline for Cube<'a> { + type Vertex = (usize, Rgba); + type VsOut = Rgba; + type Pixel = u32; + + #[inline(always)] + fn vert(&self, (v_index, v_color): &Self::Vertex) -> ([f32; 4], Self::VsOut) { + ((self.mvp * self.positions[*v_index]).into_array(), *v_color) + } + + #[inline(always)] + fn frag(&self, v_color: &Self::VsOut) -> Self::Pixel { + let bytes = v_color.map(|e| (e * 255.0) as u8).into_array(); + (bytes[2] as u32) << 0 + | (bytes[1] as u32) << 8 + | (bytes[0] as u32) << 16 + | (bytes[3] as u32) << 24 + } +} + +#[derive(Clone, Default, PartialEq, Pipeline)] +struct CubePipeline { + spin: Cell, +} + +impl render::RenderPipeline for CubePipeline { + fn draw(&self, render_target: &mut render::RenderTarget) { + let mut color = Buffer2d::new( + [ + render_target.width() as usize, + render_target.height() as usize, + ], + 0, + ); + let mut depth = Buffer2d::new( + [ + render_target.width() as usize, + render_target.height() as usize, + ], + 1.0, + ); + + let mvp = Mat4::perspective_fov_rh_no( + 1.3, + render_target.width() as f32, + render_target.height() as f32, + 0.01, + 100.0, + ) * Mat4::translation_3d(Vec3::new(0.0, 0.0, -2.0)) + * Mat4::::scaling_3d(0.4) + * Mat4::rotation_x((self.spin.get() * 0.002) * 8.0) + * Mat4::rotation_y((self.spin.get() as f32 * 0.004).cos() * 4.0) + * Mat4::rotation_z((self.spin.get() as f32 * 0.008).sin() * 2.0); + + Cube { + mvp, + positions: &[ + Vec4::new(-1.0, -1.0, -1.0, 1.0), // 0 + Vec4::new(-1.0, -1.0, 1.0, 1.0), // 1 + Vec4::new(-1.0, 1.0, -1.0, 1.0), // 2 + Vec4::new(-1.0, 1.0, 1.0, 1.0), // 3 + Vec4::new(1.0, -1.0, -1.0, 1.0), // 4 + Vec4::new(1.0, -1.0, 1.0, 1.0), // 5 + Vec4::new(1.0, 1.0, -1.0, 1.0), // 6 + Vec4::new(1.0, 1.0, 1.0, 1.0), // 7 + ], + } + .draw::, _>( + &[ + // -x + (0, Rgba::green()), + (3, Rgba::blue()), + (2, Rgba::red()), + (0, Rgba::green()), + (1, Rgba::red()), + (3, Rgba::blue()), + // +x + (7, Rgba::blue()), + (4, Rgba::green()), + (6, Rgba::red()), + (5, Rgba::red()), + (4, Rgba::green()), + (7, Rgba::blue()), + // -y + (5, Rgba::blue()), + (0, Rgba::red()), + (4, Rgba::green()), + (1, Rgba::green()), + (0, Rgba::red()), + (5, Rgba::blue()), + // +y + (2, Rgba::red()), + (7, Rgba::blue()), + (6, Rgba::green()), + (2, Rgba::red()), + (3, Rgba::green()), + (7, Rgba::blue()), + // -z + (0, Rgba::red()), + (6, Rgba::green()), + (4, Rgba::blue()), + (0, Rgba::red()), + (2, Rgba::blue()), + (6, Rgba::green()), + // +z + (7, Rgba::green()), + (1, Rgba::red()), + (5, Rgba::blue()), + (3, Rgba::blue()), + (1, Rgba::red()), + (7, Rgba::green()), + ], + &mut color, + Some(&mut depth), + ); + + render_target.draw(color.as_ref()); + } +} + +// OrbTk 2D drawing +#[derive(Clone, Default, PartialEq, Pipeline)] +struct Graphic2DPipeline; + +impl render::RenderPipeline for Graphic2DPipeline { + fn draw(&self, render_target: &mut render::RenderTarget) { + let mut render_context = + RenderContext2D::new(render_target.width(), render_target.height()); + + let width = 120.0; + let height = 120.0; + + let x = (render_target.width() - width) / 2.0; + let y = (render_target.height() - height) / 2.0; + // render_context.set_fill_style(utils::Brush::SolidColor(Color::from("#000000"))); + + render_context.set_fill_style(utils::Brush::LinearGradient { + start: Point::new(x, y), + end: Point::new(x + width, y + height), + stops: vec![ + LinearGradientStop { + position: 0.0, + color: Color::from("#0021EB"), + }, + LinearGradientStop { + position: 0.5, + color: Color::from("#CE2F24"), + }, + LinearGradientStop { + position: 1.0, + color: Color::from("#70EF49"), + }, + ], + }); + render_context.fill_rect(x, y, width, height); + // render_target.draw(render_context.data()); + } +} + +#[derive(Default, AsAny)] +pub struct MainViewState { + cube_spin: f32, +} + +impl MainViewState { + fn spin(&mut self) { + self.cube_spin += 32.0; + } +} + +impl State for MainViewState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { + if let Some(cube) = ctx + .widget() + .get_mut::("render_pipeline") + .0 + .as_any() + .downcast_ref::() + { + cube.spin.set(self.cube_spin); + } + } +} + +widget!( + MainView { + render_pipeline: RenderPipeline + } +); + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView") + .render_pipeline(RenderPipeline(Box::new(CubePipeline::default()))) + .child( + Grid::create() + .rows( + Rows::create() + .row("auto") + .row("*") + .row("auto") + .row("*") + .build(), + ) + .child( + TextBlock::create() + .attach(Grid::row(0)) + .text("Canvas (render with euc crate)") + .element("text-block") + .class("h1") + .margin(4.0) + .build(ctx), + ) + .child( + Canvas::create() + .attach(Grid::row(1)) + .render_pipeline(id) + .build(ctx), + ) + .child( + Button::create() + .text("spin cube") + .vertical_alignment("end") + .attach(Grid::row(1)) + .margin(4.0) + .on_click(move |states, _| { + states.get_mut::(id).spin(); + true + }) + .build(ctx), + ) + .child( + TextBlock::create() + .attach(Grid::row(2)) + .text("Canvas (render with OrbTk)") + .element("text-block") + .class("h1") + .margin(4.0) + .build(ctx), + ) + .child( + Canvas::create() + .attach(Grid::row(3)) + .render_pipeline(RenderPipeline(Box::new(Graphic2DPipeline::default()))) + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - canvas example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/clear.rs b/frontend/examples/clear.rs new file mode 100644 index 0000000..02521ca --- /dev/null +++ b/frontend/examples/clear.rs @@ -0,0 +1,67 @@ +use orbtk::prelude::*; + +#[derive(Default, AsAny)] +pub struct MainViewState { + clear: bool, +} + +impl MainViewState { + // Sets an action the state + fn clear(&mut self) { + self.clear = true; + } +} + +impl State for MainViewState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { + if self.clear { + // Clears the text property of MainView and because + // of the sharing also the text of the TextBox. + ctx.widget().set("text", String16::from("")); + self.clear = false; + } + } +} + +widget!(MainView { + text: String16 +}); + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView").child( + Stack::create() + .orientation("horizontal") + // By injecting the id of the parent the text property + // is shared between the MainView and the TextBox. This + // means both references the same String16 object. + .child(TextBox::create().height(32.0).text(id).build(ctx)) + .child( + Button::create() + .margin((8.0, 0.0, 0.0, 0.0)) + // mouse click event handler + .on_click(move |states, _| { + // Calls clear of the state of MainView + states.get_mut::(id).clear(); + true + }) + .text("Clear") + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - minimal example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .child(MainView::create().margin(4.0).build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/counter.rs-0.3.1-alpha1 b/frontend/examples/counter.rs-0.3.1-alpha1 new file mode 100644 index 0000000..7e15032 --- /dev/null +++ b/frontend/examples/counter.rs-0.3.1-alpha1 @@ -0,0 +1,111 @@ +use orbtk::api::widget::StatesContext; +use orbtk::prelude::*; + +#[derive(Debug)] +pub enum Event { + Increment(Entity), +} + +#[derive(AsAny, Default)] +pub struct AppState { + events: VecDeque, + pub notes: Entity, + count: u32, +} + +//rbtk_api::widget::states_context::StatesContext<'_> +#[derive(Debug, Copy, Clone)] +enum Action { + Increment(usize), +} + +#[derive(Default, AsAny)] +pub struct MainViewState { + num: usize, + action: Option, +} + +impl MainViewState { + fn action(&mut self, action: impl Into>) { + self.action = action.into(); + } +} + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView") + .counter(0) + .result("Button count: 0") + .child( + Button::create() + .selector(Selector::from("button").id("btn")) + .min_size(100.0, 50.0) + .text(("result", id)) + .on_click(move |states, _| -> bool { + state(id, states).action(Action::Increment(10)); + true + }) + .build(ctx), + ) + } +} + +impl State for MainViewState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { + if let Some(action) = self.action { + match action { + Action::Increment(digit) => { + *ctx.widget().get_mut::("counter") += digit as usize; + let counter = *ctx.widget().get::("counter"); + ctx.widget().set( + "result", + String16::from(format!("Button count: {}", counter)), + ); + } + _ => {} + } + + // Is it possible to get rid of this line ? + self.action = None; + } + + if let Some(event) = self.events.pop_front() { + match event { + // Create and append the note to the UI. + Event::Increment(entity) => { + if let Some(copy) = Self::fetch_text(ctx, entity) { + self.append_note(ctx, copy); + let count = self.count + 1; + self.count = count; + if count == 1 { + ctx.widget().set::("spacing", 12.0); + } + } + } + } + } + } +} + +widget!(MainView { + age:i32, + counter:usize, + result: String16 +}); + +fn main() { + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - counter example") + .position((100.0, 100.0)) + .size(400.0, 400.0) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} + +fn state<'a>(id: Entity, states: &'a mut StatesContext) -> &'a mut MainViewState { + states.get_mut(id) +} diff --git a/frontend/examples/grid.css b/frontend/examples/grid.css new file mode 100644 index 0000000..a5af45f --- /dev/null +++ b/frontend/examples/grid.css @@ -0,0 +1,25 @@ +* { + font-size: 24; +} + +lynch { + background: #647b91; +} + +bluebayoux { + background: #516475; +} + +linkwater { + background: #dfebf5; + color: #3b434a; +} + +light-text { + color: #dfebf5; +} + +goldendream { + background: #efd035; + color: #3b434a; +} \ No newline at end of file diff --git a/frontend/examples/grid.rs b/frontend/examples/grid.rs new file mode 100644 index 0000000..1acd5b2 --- /dev/null +++ b/frontend/examples/grid.rs @@ -0,0 +1,108 @@ +use orbtk::prelude::*; +use orbtk::theme::DEFAULT_THEME_CSS; + +static CSS_EXT: &'static str = include_str!("../resources/grid.css"); + +fn get_theme() -> ThemeValue { + ThemeValue::create_from_css(DEFAULT_THEME_CSS) + .extension_css(CSS_EXT) + .build() +} + +widget!(MainView); + +impl Template for MainView { + fn template(self, _: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView").child( + Grid::create() + .columns( + Columns::create() + .column("*") + .column("auto") + .column(50.0) + .build(), + ) + .rows(Rows::create().row("*").row("*").build()) + .child( + Grid::create() + .element("lynch") + .margin((10.0, 0.0, 0.0, 4.0)) + .attach(Grid::column(0)) + .child( + TextBlock::create() + .text("(0,0)") + .element("light-text") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .element("bluebayoux") + .margin(10.0) + .constraint(Constraint::create().width(150.0).build()) + .attach(Grid::column(1)) + .child( + TextBlock::create() + .text("(1,0)") + .element("white") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .element("linkwater") + .attach(Grid::column(2)) + .child( + TextBlock::create() + .text("(2,0)") + .element("linkwater") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .element("goldendream") + .attach(Grid::column(0)) + .attach(Grid::row(1)) + .attach(Grid::column_span(3)) + .child( + TextBlock::create() + .text("(0,1) - ColumnSpan 3") + .element("goldendream") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - grid example") + .position((400.0, 400.0)) + .size(420.0, 730.0) + .theme(get_theme()) + .resizeable(true) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/grid.rs-0.3.1-alpha1 b/frontend/examples/grid.rs-0.3.1-alpha1 new file mode 100644 index 0000000..511a3b0 --- /dev/null +++ b/frontend/examples/grid.rs-0.3.1-alpha1 @@ -0,0 +1,154 @@ +use orbtk::prelude::*; +use orbtk::theme::DEFAULT_THEME_CSS; + +static CSS_EXT: &'static str = include_str!("../resources/stylesheets/grid.css"); + +fn get_theme() -> ThemeValue { + ThemeValue::create_from_css(DEFAULT_THEME_CSS) + .extension_css(CSS_EXT) + .build() +} + +widget!(MainView); + +impl Template for MainView { + fn template(self, _: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView").child( + Grid::create() + .columns( + Columns::create() + .column("*") + .column("auto") + .column(90.0) + .build(), + ) + .rows(Rows::create().row("*").row("*").row("*").build()) + .child( + Grid::create() + .selector("hiedemann") + .margin((30.0, 25.0, 0.0, 20.0)) + .constraint(Constraint::create().width(80.0).build()) + .attach(Grid::column(0)) + .child( + TextBlock::create() + .text("Grid\n(0,0)") + .selector("hiedemann") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .selector("bluebayoux") + .margin(10.0) + .constraint(Constraint::create().width(150.0).build()) + .attach(Grid::column(1)) + .child( + TextBlock::create() + .text("Grid\n(1,0)") + .selector("white") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .selector("linkwater") + .attach(Grid::column(2)) + .child( + TextBlock::create() + .text("Grid\n(2,0)") + .selector("linkwater") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .selector("goldendream") + .attach(Grid::column(0)) + .attach(Grid::row(1)) + .attach(Grid::column_span(3)) + .child( + TextBlock::create() + .text("Grid (0,1) - ColumnSpan 3") + .selector("goldendream") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .selector("bluebayoux") + .margin(10.0) + .constraint(Constraint::create().width(200.0).build()) + .attach(Grid::column(0)) + .attach(Grid::row(2)) + .child( + TextBlock::create() + .text("Row 2") + .selector("white") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .child( + TextBlock::create() + .text("(0,1)") + .selector("white") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + /*.child( + Grid::create() + .selector("hiedemann") + .attach(Grid::column(1)) + .attach(Grid::row(2)) + //.attach(Grid::column_span(2)) + .margin((30.0, 25.0, 0.0, 20.0)) + .attach(Grid::column(0)) + .child( + TextBlock::create() + .text("(0,2)") + //.selector("light-text") + .selector("hiedemann") + .horizontal_alignment("center") + .vertical_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + */ + .build(ctx), + ) + } +} + +fn main() { + // use this only if you want to run it as web application. + //orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - grid example") + .position((100.0, 10.0)) + .size(420.0, 530.0) + .theme(get_theme()) + .resizeable(true) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/image.rs b/frontend/examples/image.rs new file mode 100644 index 0000000..53b0335 --- /dev/null +++ b/frontend/examples/image.rs @@ -0,0 +1,21 @@ +use orbtk::prelude::*; + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - image example") + .position((100.0, 100.0)) + .size(800.0, 420.0) + .child( + ImageWidget::create() + .image("res/orbtk-space.png") + .build(ctx), + ) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/image.rs-0.3.1-alpha1 b/frontend/examples/image.rs-0.3.1-alpha1 new file mode 100644 index 0000000..889f62e --- /dev/null +++ b/frontend/examples/image.rs-0.3.1-alpha1 @@ -0,0 +1,27 @@ +use orbtk::prelude::*; + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - image example") + .position((100.0, 100.0)) + .size(610.0, 610.0) + .resizeable(true) + .child( + ImageWidget::create() + .image("resources/images/advotracker.jpg") + .margin(10.0) + .vertical_alignment("Center") + .horizontal_alignment("Center") + .size(600.0, 600.0) + .clip(true) + .build(ctx), + ) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/messages.rs b/frontend/examples/messages.rs new file mode 100644 index 0000000..6ae34b5 --- /dev/null +++ b/frontend/examples/messages.rs @@ -0,0 +1,86 @@ +// // This examples shows how you could send messages between different widgets / views. + +// use orbtk::*; + +// mod sender { +// use orbtk::*; +// use std::{cell::Cell, rc::Rc}; + +// #[derive(Default)] +// struct SenderState { +// send_message: Cell, +// } + +// impl State for SenderState { +// fn update(&mut self, ctx: &mut Context<'_>) { +// if self.send_message.get() { +// ctx.send_message("receiver_view", StringMessage::from("Hello from sender.")); +// } +// } +// } + +// widget!(SenderView); + +// impl Widget for SenderView { +// fn create() -> Self { +// let state = Rc::new(SenderState::default()); +// let send_state = state.clone(); + +// SenderView::new() +// .debug_name("SenderView") +// .child( +// Button::create() +// .text("Send message") +// .on_click(move |_| -> bool { +// send_state.send_message.set(true); +// true +// }), +// ) +// .state(state) +// } +// } +// } + +// mod receiver { +// use super::sender; +// use orbtk::*; +// use std::rc::Rc; + +// struct ReceiverState; + +// impl State for ReceiverState { +// fn receive_messages(&self, _context: &mut Context<'_>, messages: &Vec) { +// for message in messages { +// if let Ok(message) = message.downcast_ref::() { +// println!("Message received: {}", message.0); +// } +// } +// } +// } + +// widget!(ReceiverView); + +// impl Widget for ReceiverView { +// fn create() -> Self { +// ReceiverView::new() +// .debug_name("ReceiverView") +// .child(Container::create().child(sender::SenderView::create())) +// .attach(Selector::default().id("receiver_view")) +// .state(Rc::new(ReceiverState)) +// } +// } +// } + +// fn main() { +// let mut application = Application::default(); +// application +// .create_window() +// .bounds((100.0, 100.0, 420.0, 730.0)) +// .title("OrbTk - minimal example") +// .root(receiver::ReceiverView::create()) +// .debug_flag(true) +// .build(); +// application.run(); +// } + +fn main() {} diff --git a/frontend/examples/minimal.rs b/frontend/examples/minimal.rs new file mode 100644 index 0000000..bd024bc --- /dev/null +++ b/frontend/examples/minimal.rs @@ -0,0 +1,17 @@ +use orbtk::prelude::*; + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - minimal example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .child(TextBlock::create().text("OrbTk").margin(4.0).build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/minimal.rs-0.3.1-alpha1 b/frontend/examples/minimal.rs-0.3.1-alpha1 new file mode 100644 index 0000000..bd024bc --- /dev/null +++ b/frontend/examples/minimal.rs-0.3.1-alpha1 @@ -0,0 +1,17 @@ +use orbtk::prelude::*; + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - minimal example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .child(TextBlock::create().text("OrbTk").margin(4.0).build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/multi_window.rs b/frontend/examples/multi_window.rs new file mode 100644 index 0000000..4a538c1 --- /dev/null +++ b/frontend/examples/multi_window.rs @@ -0,0 +1,87 @@ +/// mutli window will not work properly on web now. But it will be fixed. + +use orbtk::prelude::*; + +#[derive(AsAny, Default)] +struct MainState { + show_window: bool, +} + +impl MainState { + fn show_window(&mut self) { + self.show_window = true; + } +} + +impl State for MainState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context) { + if self.show_window { + ctx.child("button").set("enabled", false); + ctx.show_window(|ctx| { + Window::create() + .title("Dialog") + .position((120.0, 120.0)) + .size(100.0, 75.0) + .child( + Stack::create() + .child(TextBlock::create().text("New window").margin(4.0).build(ctx)) + .build(ctx), + ) + .build(ctx) + }); + self.show_window = false; + } + } +} + +widget!(MainView); + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.child( + Stack::create() + .child(TextBlock::create().text("Window 1").margin(4.0).build(ctx)) + .child( + Button::create() + .id("button") + .on_click(move |states, _| { + states.get_mut::(id).show_window(); + true + }) + .margin(4.0) + .text("Show window") + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - multi window example window 1") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .window(|ctx| { + Window::create() + .title("OrbTk - multi window example window 2") + .position((600.0, 100.0)) + .size(420.0, 730.0) + .child( + Stack::create() + .child(TextBlock::create().text("Window 2").margin(4.0).build(ctx)) + .child(Button::create().margin(4.0).text("Click me").build(ctx)) + .build(ctx), + ) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/numeric_box.rs b/frontend/examples/numeric_box.rs new file mode 100644 index 0000000..2402655 --- /dev/null +++ b/frontend/examples/numeric_box.rs @@ -0,0 +1,43 @@ +use orbtk::prelude::*; + +widget!(MainView); + +impl Template for MainView { + fn template(self, _id: Entity, ctx: &mut BuildContext) -> Self { + self.child( + Stack::create() + .spacing(8.0) + .orientation("vertical") + .horizontal_alignment("center") + .child( + TextBlock::create() + .text("Tyre pressure") + .font_size(20.0) + .build(ctx), + ) + .child( + NumericBox::create() + .max(123.0) + .step(0.123) + .val(0.123) + .build(ctx), + ) + .child(Button::create().text("Blow air").build(ctx)) + .build(ctx), + ) + } +} + +fn main() { + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - NumericBox example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .resizeable(true) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/orbtk-space.png b/frontend/examples/orbtk-space.png new file mode 100644 index 0000000..96b8ceb Binary files /dev/null and b/frontend/examples/orbtk-space.png differ diff --git a/frontend/examples/overlay.rs b/frontend/examples/overlay.rs new file mode 100644 index 0000000..1fc8bab --- /dev/null +++ b/frontend/examples/overlay.rs @@ -0,0 +1,52 @@ +use orbtk::prelude::*; + +widget!(MainView); + +impl Template for MainView { + fn template(self, _: Entity, ctx: &mut BuildContext) -> Self { + let container = Container::create() + .background("#dfebf5") + .width(200.0) + .height(200.0) + .child( + TextBlock::create() + .foreground("#3b434a") + .text("Overlay") + .vertical_alignment("center") + .horizontal_alignment("center") + .build(ctx), + ) + .build(ctx); + + ctx.append_child_to_overlay(container).unwrap(); + self.name("MainView").child( + Container::create() + .background("#e1bc21") + .child( + TextBlock::create() + .text("MainView") + .element("h1") + .vertical_alignment("center") + .horizontal_alignment("center") + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - overlay example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/progress_bar.rs b/frontend/examples/progress_bar.rs new file mode 100644 index 0000000..1d3c267 --- /dev/null +++ b/frontend/examples/progress_bar.rs @@ -0,0 +1,120 @@ +use orbtk::prelude::*; + +#[derive(Copy, Clone)] +enum ProgressEvent { + Advance(f64), + Reset, + SetToFull, +} + +#[derive(Default, AsAny)] +struct MainViewState { + action: Option, +} + +widget!(MainView); + +impl MainViewState { + fn action(&mut self, action: impl Into>) { + self.action = action.into(); + } +} + +impl State for MainViewState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { + if let Some(action) = self.action { + match action { + ProgressEvent::Advance(amount) => { + let old_width = ctx.child("pgbar").clone_or_default::("val"); + let new_width = old_width + amount; + // Set the ProgressBar's val property to the calculated percentage + // (whereas 0.0 means 0 %, and 1.0 means 100 %) to advance the progress + ctx.child("pgbar").set::("val", new_width); + } + ProgressEvent::Reset => { + ctx.child("pgbar").set::("val", 0.0); + } + ProgressEvent::SetToFull => { + ctx.child("pgbar").set::("val", 1.0); + } + } + self.action = None; + } + } +} + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.child( + Stack::create() + .horizontal_alignment("center") + .margin((16.0, 16.0, 16.0, 16.0)) + .spacing(8.0) + .child( + ProgressBar::create() + .id("pgbar") + .val(0.0) + .width(512.0) + .build(ctx), + ) + .child( + Stack::create() + .horizontal_alignment("center") + .spacing(8.0) + .child( + Button::create() + .text("Progress by 25%") + .width(256.0) + .on_click(move |states, _| -> bool { + states + .get_mut::(id) + .action(ProgressEvent::Advance(0.25)); + true + }) + .build(ctx), + ) + .child( + Button::create() + .text("Reset") + .width(256.0) + .on_click(move |states, _| -> bool { + states + .get_mut::(id) + .action(ProgressEvent::Reset); + true + }) + .build(ctx), + ) + .child( + Button::create() + .text("Set to 100%") + .width(256.0) + .on_click(move |states, _| -> bool { + states + .get_mut::(id) + .action(ProgressEvent::SetToFull); + true + }) + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - ProgressBar example") + .position((0.0, 0.0)) + .size(720.0, 576.0) + .borderless(false) + .resizeable(true) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/settings.rs b/frontend/examples/settings.rs new file mode 100644 index 0000000..7122e6a --- /dev/null +++ b/frontend/examples/settings.rs @@ -0,0 +1,162 @@ +use serde_derive::{Deserialize, Serialize}; + +use orbtk::prelude::*; + +#[derive(Copy, Clone, PartialEq)] +enum Action { + Load, + Save, + Clear, +} + +#[derive(Default, Clone, Serialize, Deserialize)] +pub struct Global { + pub label: String, +} + +#[derive(Default, AsAny)] +pub struct MainViewState { + action: Option, +} + +impl MainViewState { + fn action(&mut self, action: Action) { + self.action = Some(action); + } +} + +impl State for MainViewState { + fn update(&mut self, registry: &mut Registry, ctx: &mut Context<'_>) { + if let Some(action) = self.action { + match action { + Action::Load => { + // load label from settings file. + if let Ok(global) = registry + .get::("settings") + .load::("global") + { + ctx.widget().set("text", String16::from(global.label)); + } + + ctx.widget().set( + "info_text", + String16::from("Label loaded from settings file."), + ); + } + Action::Save => { + // save label to settings file. + registry + .get_mut::("settings") + .save( + "global", + &Global { + label: ctx.widget().get::("text").to_string(), + }, + ) + .unwrap(); + ctx.widget() + .set("info_text", String16::from("Label saved to settings file.")); + } + Action::Clear => { + ctx.widget().set("text", String16::default()); + ctx.widget().set("info_text", String16::from("")); + } + } + + self.action = None; + } + } +} + +widget!(MainView { + text: String16, + info_text: String16 +}); + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView").child( + Grid::create() + .rows(Rows::create().row(36.0).row(4.0).row("auto").build()) + .columns( + Columns::create() + .column(160.0) + .column(4.0) + .column("Auto") + .column(4.0) + .column("Auto") + .column(4.0) + .column("Auto") + .build(), + ) + .child( + TextBox::create() + .vertical_alignment("center") + .text(id) + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .attach(Grid::row(0)) + .attach(Grid::column(2)) + .on_click(move |states, _| { + state(id, states).action(Action::Load); + true + }) + .text("Load") + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .attach(Grid::row(0)) + .attach(Grid::column(4)) + .on_click(move |states, _| { + state(id, states).action(Action::Save); + true + }) + .text("Save") + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .attach(Grid::row(0)) + .attach(Grid::column(6)) + .on_click(move |states, _| { + state(id, states).action(Action::Clear); + true + }) + .text("Clear") + .build(ctx), + ) + .child( + TextBlock::create() + .attach(Grid::row(2)) + .attach(Grid::column(0)) + .text(("info_text", id)) + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + Application::from_name("orbtk-settings") + .window(|ctx| { + Window::create() + .title("OrbTk - settings example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .child(MainView::create().margin(4.0).build(ctx)) + .build(ctx) + }) + .run(); +} + +// helper to request MainViewState +fn state<'a>(id: Entity, states: &'a mut StatesContext) -> &'a mut MainViewState { + states.get_mut(id) +} diff --git a/frontend/examples/stack.rs b/frontend/examples/stack.rs new file mode 100644 index 0000000..57cac5f --- /dev/null +++ b/frontend/examples/stack.rs @@ -0,0 +1,112 @@ +use orbtk::prelude::*; + +widget!(MainView); + +impl Template for MainView { + fn template(self, _: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView").child( + Stack::create() + .child( + TextBlock::create() + .margin((0.0, 0.0, 0.0, 8.0)) + .text("Stack vertical") + .element("h1") + .build(ctx), + ) + .child( + Stack::create() + .spacing(4.0) + .child( + Button::create() + .class("single_content") + .text("left") + .horizontal_alignment("start") + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .text("center") + .horizontal_alignment("center") + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .text("stretch") + .horizontal_alignment("stretch") + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .text("right") + .horizontal_alignment("end") + .build(ctx), + ) + .build(ctx), + ) + .child( + TextBlock::create() + .margin((0.0, 0.0, 0.0, 8.0)) + .text("Stack horizontal") + .element("h1") + .build(ctx), + ) + .child( + Stack::create() + .orientation("horizontal") + .spacing(4.0) + .height(100.0) + .child( + Button::create() + .class("single_content") + .text("top") + .vertical_alignment("start") + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .text("center") + .vertical_alignment("center") + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .height(0.0) + .text("stretch") + .vertical_alignment("stretch") + .build(ctx), + ) + .child( + Button::create() + .class("single_content") + .text("bottom") + .vertical_alignment("end") + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - stack example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .resizeable(true) + .child(MainView::create().margin(4.0).build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/todo/main.rs b/frontend/examples/todo/main.rs new file mode 100644 index 0000000..a132056 --- /dev/null +++ b/frontend/examples/todo/main.rs @@ -0,0 +1,68 @@ +mod state; + +use self::state::{AppState, Event}; + +use orbtk::prelude::*; + +widget! { + MainView { + orientation: Orientation, + spacing: f64 + } +} + +impl Template for MainView { + fn template(mut self, id: Entity, ctx: &mut BuildContext) -> Self { + let entry = TextBox::create() + .vertical_alignment(Alignment::End) + .on_activate(move |ctxt, entity| { + ctxt.get_mut::(id).send(Event::Create(entity)); + }) + .build(ctx); + + let notes = Stack::create() + .orientation(Orientation::Vertical) + .spacing(4) + .build(ctx); + + self.state.notes = notes; + + let scroll_viewer = ScrollViewer::create() + .scroll_viewer_mode(("disabled", "auto")) + .child(notes) + .build(ctx); + + self.name("main-view") + .orientation(Orientation::Vertical) + .margin(4) + .child( + Container::create() + .child(scroll_viewer) + .child( + ScrollIndicator::create() + .padding(2.0) + .scroll_offset(scroll_viewer) + .build(ctx), + ) + .build(ctx) + ) + .child(entry) + } + + fn layout(&self) -> Box { + Box::new(StackLayout::new()) + } +} + +fn main() { + Application::new() + .window(|ctx| { + Window::create() + .title("Orbital To Do") + .position((500.0, 500.0)) + .size(400.0, 400.0) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/todo/state.rs b/frontend/examples/todo/state.rs new file mode 100644 index 0000000..44e0419 --- /dev/null +++ b/frontend/examples/todo/state.rs @@ -0,0 +1,92 @@ +use orbtk::prelude::*; + +use std::collections::VecDeque; + +#[derive(Debug)] +pub enum Event { + Create(Entity), + Delete(Entity), +} + +#[derive(AsAny, Default)] +pub struct AppState { + events: VecDeque, + pub notes: Entity, + count: u32, +} + +impl AppState { + fn append_note(&self, ctx: &mut Context, copy: String) { + let id = ctx.entity; + let bctx = &mut ctx.build_context(); + let text = TextBox::create().text(copy); + + let container = Stack::create() + .orientation(Orientation::Horizontal) + .vertical_alignment(Alignment::Start) + .child(text.build(bctx)) + .build(bctx); + + let button = Button::create() + .horizontal_alignment(Alignment::End) + .text("Delete") + .on_click(move |ctxt, _| { + ctxt.get_mut::(id) + .events + .push_back(Event::Delete(container)); + + true + }) + .build(bctx); + + bctx.append_child(container, button); + bctx.append_child(self.notes, container); + } + + fn fetch_text(ctx: &mut Context, entity: Entity) -> Option { + let mut widget = ctx.get_widget(entity); + + let entry = widget.get_mut::("text"); + if entry.is_empty() { + return None; + } + + let copy = entry.to_string(); + entry.clear(); + Some(copy) + } + + pub fn send(&mut self, event: Event) { + self.events.push_back(event); + } +} + +impl State for AppState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context) { + if let Some(event) = self.events.pop_front() { + match event { + // Create and append the note to the UI. + Event::Create(entity) => { + if let Some(copy) = Self::fetch_text(ctx, entity) { + self.append_note(ctx, copy); + let count = self.count + 1; + self.count = count; + if count == 1 { + ctx.widget().set::("spacing", 12.0); + } + } + } + + // Delete the note of the given ID from the UI. + Event::Delete(id) => { + ctx.remove_child_from(id, self.notes); + let count = self.count - 1; + self.count = count; + if count == 0 { + ctx.widget().set::("spacing", 0.0); + } + } + } + } + } +} diff --git a/frontend/examples/widget.rs b/frontend/examples/widget.rs new file mode 100644 index 0000000..8e29646 --- /dev/null +++ b/frontend/examples/widget.rs @@ -0,0 +1,15 @@ +// source: https://gitlab.redox-os.org/redox-os/orbtk/tree/develop +use orbtk::prelude::*; + +fn main() { + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - minimal example") + .position((100.0, 100.0)) + .size(420.0, 730.0) + .child(TextBlock::create().text("OrbTk").build(ctx)) + .build(ctx) + }) + .run(); +} diff --git a/frontend/examples/widgets.rs b/frontend/examples/widgets.rs new file mode 100644 index 0000000..8e1a30a --- /dev/null +++ b/frontend/examples/widgets.rs @@ -0,0 +1,478 @@ +use std::collections::HashSet; + +use orbtk::prelude::*; + +#[derive(Debug, Copy, Clone)] +enum Action { + AddItem, + ClearText, + EntryActivated(Entity), + EntryChanged(Entity), + ValueChanged(Entity), + IncrementCounter, + RemoveItem, +} + +#[derive(AsAny)] +pub struct MainViewState { + action: Option, +} + +impl Default for MainViewState { + fn default() -> Self { + MainViewState { action: None } + } +} + +impl MainViewState { + fn action(&mut self, action: impl Into>) { + self.action = action.into(); + } +} + +impl State for MainViewState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { + if let Some(action) = self.action { + match action { + Action::AddItem => { + let len = ctx.widget().get::("list").len(); + if len < 5 { + ctx.widget() + .get_mut::("list") + .push(format!("Item {}", len + 1)); + ctx.child("items").set("count", len + 1); + ctx.child("remove-item-button").set("enabled", true); + + if len == 4 { + ctx.child("add-item-button").set("enabled", false); + } + } + } + Action::RemoveItem => { + let len = ctx.widget().get::("list").len(); + if len > 0 { + ctx.widget().get_mut::("list").remove(len - 1); + ctx.child("items").set("count", len - 1); + ctx.child("add-item-button").set("enabled", true); + + if len == 1 { + ctx.child("remove-item-button").set("enabled", false); + } + } + } + Action::IncrementCounter => { + *ctx.widget().get_mut::("counter") += 1; + + let counter = *ctx.widget().get::("counter"); + + ctx.widget().set( + "result", + String16::from(format!("Button count: {}", counter)), + ); + } + Action::ClearText => { + ctx.widget().set("text_one", String16::from("")); + ctx.widget().set("text_two", String16::from("")); + } + Action::EntryActivated(entity) => { + let mut widget = ctx.get_widget(entity); + let text = widget.get_mut::("text"); + println!("submitting {}", text); + text.clear(); + } + Action::EntryChanged(entity) => { + let widget = ctx.get_widget(entity); + let text = widget.get::("text"); + println!("entry changed: {}", text); + } + Action::ValueChanged(entity) => { + let value = + ((*ctx.get_widget(entity).get::("value")).floor() as i32).to_string(); + ctx.child("value_text").set("text", String16::from(value)); + } + } + + self.action = None; + } + } + + fn update_post_layout(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { + let mut selection_string = "Selected:".to_string(); + + for index in &ctx.widget().get::("selected_indices").0 { + selection_string = format!("{} {}", selection_string, index); + } + + ctx.child("selection") + .set("text", String16::from(selection_string)); + } +} + +fn create_header(ctx: &mut BuildContext, text: &str) -> Entity { + TextBlock::create() + .text(text) + .element("text-block") + .class("h1") + .build(ctx) +} + +type List = Vec; + +widget!( + MainView { + selected_indices: SelectedIndices, + counter: usize, + list_count: usize, + combo_box_list_count: usize, + list: List, + selection_list: List, + combo_box_list: List, + selection_list_count: usize, + text_one: String16, + text_two: String16, + result: String16 + } +); + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView") + .result("Button count: 0") + .counter(0) + .selected_indices(HashSet::new()) + .list(vec![ + "Item 1".to_string(), + "Item 2".to_string(), + "Item 3".to_string(), + ]) + .list_count(3) + .selection_list(vec![ + "Item 1".to_string(), + "Item 2".to_string(), + "Item 3".to_string(), + "Item 4".to_string(), + "Item 5".to_string(), + "Item 6".to_string(), + "Item 7".to_string(), + "Item 8".to_string(), + "Item 9".to_string(), + "Item 10".to_string(), + ]) + .combo_box_list(vec![ + "CB 1".to_string(), + "CB 2".to_string(), + "CB 3".to_string(), + "CB 4".to_string(), + "CB 5".to_string(), + "CB 6".to_string(), + "CB 7".to_string(), + "CB 8".to_string(), + "CB 9".to_string(), + "CB 10".to_string(), + ]) + .selection_list_count(10) + .combo_box_list_count(10) + .child( + Grid::create() + .margin(8.0) + .columns( + Columns::create() + .column(132.0) + .column(16.0) + .column(132.0) + .column(16.0) + .column(132.0) + .build(), + ) + .child( + Stack::create() + .attach(Grid::column(0)) + // Column 0 + .child(create_header(ctx, "Buttons")) + .child( + Button::create() + .text("Button") + .margin((0.0, 8.0, 0.0, 0.0)) + .icon(material_font_icons::CHECK_FONT_ICON) + .attach(Grid::column(0)) + .attach(Grid::row(1)) + .on_click(move |states, _| { + state(id, states).action(Action::IncrementCounter); + true + }) + .build(ctx), + ) + .child( + Button::create() + .text("Primary") + .element("button") + .class("primary") + .margin((0.0, 8.0, 0.0, 0.0)) + .icon(material_font_icons::CHECK_FONT_ICON) + .attach(Grid::column(0)) + .attach(Grid::row(2)) + .build(ctx), + ) + .child( + ToggleButton::create() + .class("single_content") + .text("ToggleButton") + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(0)) + .attach(Grid::row(3)) + .build(ctx), + ) + .child( + CheckBox::create() + .text("CheckBox") + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(0)) + .attach(Grid::row(4)) + .build(ctx), + ) + .child( + Switch::create() + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(0)) + .attach(Grid::row(5)) + .build(ctx), + ) + .child( + TextBlock::create() + .margin((0.0, 8.0, 0.0, 0.0)) + .element("h1") + .id("value_text") + .text("0") + .horizontal_alignment("center") + .build(ctx), + ) + .child( + Slider::create() + .on_changed(move |states, entity| { + state(id, states).action(Action::ValueChanged(entity)); + }) + .build(ctx), + ) + .build(ctx), + ) + .child( + Stack::create() + .attach(Grid::column(2)) + .child(create_header(ctx, "Text")) + .child( + TextBlock::create() + .class("body") + .text(("result", id)) + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(2)) + .attach(Grid::row(1)) + .build(ctx), + ) + .child( + TextBox::create() + .water_mark("TextBox...") + .text(("text_one", id)) + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(2)) + .attach(Grid::row(2)) + .on_activate(move |states, entity| { + state(id, states).action(Action::EntryActivated(entity)); + }) + .on_changed(move |states, entity| { + state(id, states).action(Action::EntryChanged(entity)); + }) + .build(ctx), + ) + .child( + TextBox::create() + .water_mark("TextBox...") + .text(("text_two", id)) + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(2)) + .attach(Grid::row(2)) + .on_activate(move |states, entity| { + state(id, states).action(Action::EntryActivated(entity)); + }) + .on_changed(move |states, entity| { + state(id, states).action(Action::EntryChanged(entity)); + }) + .build(ctx), + ) + .child( + Button::create() + .margin((0.0, 8.0, 0.0, 0.0)) + .class("single_content") + .text("clear text") + .on_click(move |states, _| { + state(id, states).action(Action::ClearText); + true + }) + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .rows( + Rows::create() + .row("auto") + .row(32.0) + .row(16.0) + .row(204.0) + .row("auto") + .row(192.0) + .row("auto") + .build(), + ) + .columns( + Columns::create() + .column("*") + .column(4.0) + .column("*") + .build(), + ) + .attach(Grid::column(4)) + .child( + TextBlock::create() + .text("Items") + .element("text-block") + .class("h1") + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(0)) + .build(ctx), + ) + .child( + ComboBox::create() + .items_builder(move |bc, index| { + let text = bc + .get_widget(id) + .get::>("combo_box_list")[index] + .clone(); + TextBlock::create() + .margin((0.0, 0.0, 0.0, 2.0)) + .vertical_alignment("center") + .text(text) + .build(bc) + }) + .selected_index(0) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(1)) + .margin((0.0, 8.0, 0.0, 0.0)) + .count(("combo_box_list_count", id)) + .build(ctx), + ) + .child( + ItemsWidget::create() + .element("items-widget") + .id("items") + .padding((4.0, 4.0, 4.0, 2.0)) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(3)) + .margin((0.0, 0.0, 0.0, 8.0)) + .items_builder(move |bc, index| { + let text = bc.get_widget(id).get::>("list") + [index] + .clone(); + + Button::create() + .margin((0.0, 0.0, 0.0, 2.0)) + .text(text) + .build(bc) + }) + .count(("list_count", id)) + .build(ctx), + ) + .child( + Button::create() + .element("button") + .class("single_content") + .id("remove-item-button") + .icon(material_font_icons::MINUS_FONT_ICON) + .on_click(move |states, _| { + state(id, states).action(Action::RemoveItem); + true + }) + .min_width(0.0) + .attach(Grid::column(0)) + .attach(Grid::row(4)) + .build(ctx), + ) + .child( + Button::create() + .element("button") + .class("single_content") + .id("add-item-button") + .icon(material_font_icons::ADD_FONT_ICON) + .on_click(move |states, _| { + state(id, states).action(Action::AddItem); + true + }) + .min_width(0.0) + .attach(Grid::column(2)) + .attach(Grid::row(4)) + .build(ctx), + ) + .child( + ListView::create() + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(5)) + .selected_indices(id) + .margin((0.0, 16.0, 0.0, 8.0)) + .items_builder(move |bc, index| { + let text = bc + .get_widget(id) + .get::>("selection_list")[index] + .clone(); + TextBlock::create() + .margin((0.0, 0.0, 0.0, 2.0)) + .vertical_alignment("center") + .text(text) + .build(bc) + }) + .count(("selection_list_count", id)) + .build(ctx), + ) + .child( + // todo: wrong text width???? + TextBlock::create() + .element("text-block") + .id("selection") + .max_width(120.0) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(6)) + .text("Selected:") + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - widgets example") + .position((100.0, 100.0)) + .size(468.0, 730.0) + .resizeable(true) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} + +// helper to request MainViewState +fn state<'a>(id: Entity, states: &'a mut StatesContext) -> &'a mut MainViewState { + states.get_mut(id) +} diff --git a/frontend/examples/widgets.rs-0.3.1-alpha1 b/frontend/examples/widgets.rs-0.3.1-alpha1 new file mode 100644 index 0000000..55c1e19 --- /dev/null +++ b/frontend/examples/widgets.rs-0.3.1-alpha1 @@ -0,0 +1,415 @@ +use std::{ + collections::HashSet, +}; + +use orbtk::prelude::*; + +#[derive(Debug, Copy, Clone)] +enum Action { + AddItem, + ClearText, + EntryActivated(Entity), + EntryChanged(Entity), + IncrementCounter, + RemoveItem, +} + +#[derive(AsAny)] +pub struct MainViewState { + action: Option, +} + +impl Default for MainViewState { + fn default() -> Self { + MainViewState { + action: None, + } + } +} + +impl MainViewState { + fn action(&mut self, action: impl Into>) { + self.action = action.into(); + } +} + +impl State for MainViewState { + fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { + if let Some(action) = self.action { + match action { + Action::AddItem => { + let len = ctx.widget().get::("list").len(); + if len < 5 { + ctx.widget() + .get_mut::("list") + .push(format!("Item {}", len + 1)); + ctx.child("items").set("count", len + 1); + ctx.child("remove-item-button").set("enabled", true); + + if len == 4 { + ctx.child("add-item-button").set("enabled", false); + } + } + } + Action::RemoveItem => { + let len = ctx.widget().get::("list").len(); + if len > 0 { + ctx.widget().get_mut::("list").remove(len - 1); + ctx.child("items").set("count", len - 1); + //ctx.child("add-item-button").set("enabled", true); + + if len == 1 { + ctx.child("remove-item-button").set("enabled", false); + } + } + } + Action::IncrementCounter => { + *ctx.widget().get_mut::("counter") += 1; + + let counter = *ctx.widget().get::("counter"); + + ctx.widget().set( + "result", + String16::from(format!("Button count: {}", counter)), + ); + } + Action::ClearText => { + ctx.widget().set("text_one", String16::from("")); + ctx.widget().set("text_two", String16::from("")); + } + Action::EntryActivated(entity) => { + let mut widget = ctx.get_widget(entity); + let text = widget.get_mut::("text"); + println!("submitting {}", text); + text.clear(); + } + Action::EntryChanged(entity) => { + let widget = ctx.get_widget(entity); + let text = widget.get::("text"); + println!("entry changed: {}", text); + } + } + + self.action = None; + } + } + + fn update_post_layout(&mut self, _: &mut Registry, ctx: &mut Context<'_>) { + let mut selection_string = "Selected:".to_string(); + + for index in &ctx.widget().get::("selected_indices").0 { + selection_string = format!("{} {}", selection_string, index); + } + + ctx.child("selection") + .set("text", String16::from(selection_string)); + } +} + +fn create_header(ctx: &mut BuildContext, text: &str) -> Entity { + TextBlock::create() + .text(text) + .selector(Selector::new().with("text-block").class("h1")) + .build(ctx) +} + +type List = Vec; + +widget!( + MainView { + selected_indices: SelectedIndices, + counter: usize, + list_count: usize, + list: List, + selection_list: List, + selection_list_count: usize, + text_one: String16, + text_two: String16, + result: String16 + } +); + +impl Template for MainView { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + self.name("MainView") + .result("Button count: 0") + .counter(0) + .selected_indices(HashSet::new()) + .list(vec![ + "Item 1".to_string(), + "Item 2".to_string(), + "Item 3".to_string(), + ]) + .list_count(3) + .selection_list(vec![ + "Item 1".to_string(), + "Item 2".to_string(), + "Item 3".to_string(), + "Item 4".to_string(), + "Item 5".to_string(), + "Item 6".to_string(), + "Item 7".to_string(), + "Item 8".to_string(), + "Item 9".to_string(), + "Item 10".to_string(), + ]) + .selection_list_count(10) + .child( + Grid::create() + .margin(8.0) + .columns( + Columns::create() + .column(132.0) + .column(16.0) + .column(132.0) + .column(16.0) + .column(132.0) + .build(), + ) + .child( + Stack::create() + .attach(Grid::column(0)) + // Column 0 + .child(create_header(ctx, "Buttons")) + .child( + Button::create() + .text("Button") + .margin((0.0, 8.0, 0.0, 0.0)) + .icon(material_font_icons::CHECK_FONT_ICON) + .attach(Grid::column(0)) + .attach(Grid::row(1)) + .on_mouse_move(move |states, _| { + println!("ABc"); + true + }) + .on_click(move |states, _| { + state(id, states).action(Action::IncrementCounter); + true + }) + .build(ctx), + ) + .child( + Button::create() + .text("Primary") + .selector(Selector::new().with("button").class("primary")) + .margin((0.0, 8.0, 0.0, 0.0)) + .icon(material_font_icons::CHECK_FONT_ICON) + .attach(Grid::column(0)) + .attach(Grid::row(2)) + .build(ctx), + ) + .child( + ToggleButton::create() + .text("ToggleButton") + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(0)) + .attach(Grid::row(3)) + .build(ctx), + ) + .child( + CheckBox::create() + .text("CheckBox") + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(0)) + .attach(Grid::row(4)) + .build(ctx), + ) + .child( + Switch::create() + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(0)) + .attach(Grid::row(5)) + .build(ctx), + ) + .build(ctx), + ) + .child( + Stack::create() + .attach(Grid::column(2)) + .child(create_header(ctx, "Text")) + .child( + TextBlock::create() + .selector(Selector::new().class("body")) + .text(("result", id)) + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(2)) + .attach(Grid::row(1)) + .build(ctx), + ) + .child( + TextBox::create() + .water_mark("TextBox...") + .text(("text_one", id)) + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(2)) + .attach(Grid::row(2)) + .on_activate(move |states, entity| { + state(id, states).action(Action::EntryActivated(entity)); + }) + .on_changed(move |states, entity| { + state(id, states).action(Action::EntryChanged(entity)); + }) + .build(ctx), + ) + .child( + TextBox::create() + .water_mark("TextBox...") + .text(("text_two", id)) + .margin((0.0, 8.0, 0.0, 0.0)) + .attach(Grid::column(2)) + .attach(Grid::row(2)) + .on_activate(move |states, entity| { + state(id, states).action(Action::EntryActivated(entity)); + }) + .on_changed(move |states, entity| { + state(id, states).action(Action::EntryChanged(entity)); + }) + .build(ctx), + ) + .child( + Button::create() + .margin((0.0, 8.0, 0.0, 0.0)) + .text("clear text") + .on_click(move |states, _| { + state(id, states).action(Action::ClearText); + true + }) + .build(ctx), + ) + .build(ctx), + ) + .child( + Grid::create() + .rows( + Rows::create() + .row("auto") + .row(192.0) + .row("auto") + .row(192.0) + .row("auto") + .build(), + ) + .columns( + Columns::create() + .column("*") + .column(4.0) + .column("*") + .build(), + ) + .attach(Grid::column(4)) + .child( + TextBlock::create() + .text("Items") + .selector(Selector::new().with("text-block").class("h1")) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(0)) + .build(ctx), + ) + .child( + ItemsWidget::create() + .selector(Selector::from("items-widget").id("items")) + .padding((4.0, 4.0, 4.0, 2.0)) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(1)) + .margin((0.0, 8.0, 0.0, 8.0)) + .items_builder(move |bc, index| { + let text = bc.get_widget(id).get::>("list") + [index] + .clone(); + + Button::create() + .margin((0.0, 0.0, 0.0, 2.0)) + .text(text) + .build(bc) + }) + .count(("list_count", id)) + .build(ctx), + ) + .child( + Button::create() + .selector(Selector::from("button").id("remove-item-button")) + .icon(material_font_icons::MINUS_FONT_ICON) + .on_click(move |states, _| { + state(id, states).action(Action::RemoveItem); + true + }) + .min_width(0.0) + .attach(Grid::column(0)) + .attach(Grid::row(2)) + .build(ctx), + ) + .child( + Button::create() + .selector(Selector::from("button").id("add-item-button")) + .icon(material_font_icons::ADD_FONT_ICON) + .on_click(move |states, _| { + state(id, states).action(Action::AddItem); + true + }) + .min_width(0.0) + .attach(Grid::column(2)) + .attach(Grid::row(2)) + .build(ctx), + ) + .child( + ListView::create() + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(3)) + .selected_indices(id) + .margin((0.0, 16.0, 0.0, 8.0)) + .items_builder(move |bc, index| { + let text = bc + .get_widget(id) + .get::>("selection_list")[index] + .clone(); + TextBlock::create() + .margin((0.0, 0.0, 0.0, 2.0)) + .vertical_alignment("center") + .text(text) + .build(bc) + }) + .count(("selection_list_count", id)) + .build(ctx), + ) + .child( + // todo: wrong text width???? + TextBlock::create() + .selector(Selector::from("text-block").id("selection")) + .max_width(120.0) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .attach(Grid::row(4)) + .text("Selected:") + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + } +} + +fn main() { + // use this only if you want to run it as web application. + orbtk::initialize(); + + Application::new() + .window(|ctx| { + Window::create() + .title("OrbTk - widgets example") + .position((100.0, 100.0)) + .size(468.0, 730.0) + .resizeable(true) + .child(MainView::create().build(ctx)) + .build(ctx) + }) + .run(); +} + +// helper to request MainViewState +fn state<'a>(id: Entity, states: &'a mut StatesContext) -> &'a mut MainViewState { + states.get_mut(id) +} diff --git a/frontend/locales/main.json b/frontend/locales/main.json new file mode 100644 index 0000000..5d50827 --- /dev/null +++ b/frontend/locales/main.json @@ -0,0 +1,73 @@ +{ + "err.lang.not_found": { + "de_DE.UTF-8": "Konnte Sprachkode nicht auslesen", + "de": "Konnte Sprachkode nicht auslesen", + "en": "Couldn't read LANG" + }, + "err.user.not_found": { + "fr": "Utilisateur introuvable: $email, $id", + "de-DE.UTF-8": "Anwender nicht gefunden: $email, $id", + "de": "Anwender nicht gefunden: $email, $id", + "en": "User not found: $email, $id" + }, + "main.started": { + "de_DE.UTF-8": "Programmlogik starten", + "de": "Programmlogik starten", + "en": "Program logic started" + }, + "main.finished": { + "de_DE.UTF-8": "Programmlogik beendet", + "de": "Programmlogik beendet", + "en": "Program logic finished" + }, + "parse.arguments": { + "de_DE.UTF-8": "Programmargumente prüfen", + "de": "Programmargumente prüfen", + "en": "Parsing arguments" + }, + "parse.environment": { + "de_DE.UTF-8": "Umgebungsvariablen prüfen", + "de": "Umgebungsvariablen prüfen", + "en": "Parsing environment" + }, + "config.name": { + "de_DE.UTF-8": "Konfigurationswert für", + "de": "Konfigurationswert für", + "en": "Config Value for" + }, + "config.name.lang": { + "de_DE.UTF-8": "Sprach-Code", + "de": "Sprach-Code", + "en": "Language code" + }, + "config.name.verbositylevel": { + "de_DE.UTF-8": "Ausgabe-Ebene", + "de": "Ausgabe-Ebene", + "en": "verbosity level" + }, + "config.name.environment": { + "de_DE.UTF-8": "Umgebungsvariablen", + "de": "Umgebungsvariablen", + "en": "environment" + }, + "config.name.configfile": { + "de_DE.UTF-8": "Konfigurations-Datei", + "de": "Konfigurations-Datei", + "en": "config file" + }, + "config.name.dbdriver": { + "de_DE.UTF-8": "Datenbank-Treiber", + "de": "Datenbank-Treiber", + "en": "database driver" + }, + "state.started": { + "de_DE.UTF-8": "gestartet", + "de": "gestartet", + "en": "started" + }, + "state.finished": { + "de_DE.UTF-8": "beendet", + "de": "beended", + "en": "finished" + } +} diff --git a/frontend/resources/calculator-dark.css b/frontend/resources/calculator-dark.css new file mode 100644 index 0000000..437e2e3 --- /dev/null +++ b/frontend/resources/calculator-dark.css @@ -0,0 +1,32 @@ +.header { + background: #444e55; +} + +.content { + background: #3b434a; +} + +text_box { + background: transparent; + border-width: 0; + color: #9dafbf; + font-size: 16; +} + +#input { + font-size: 16; +} + +text-block { + font-size: 42; + color: #dfebf5; +} + +#input { + background: transparent; +} + +button { + border-radius: 1; + font-size: 20; +} \ No newline at end of file diff --git a/frontend/resources/calculator-light.css b/frontend/resources/calculator-light.css new file mode 100644 index 0000000..f807825 --- /dev/null +++ b/frontend/resources/calculator-light.css @@ -0,0 +1,15 @@ +.header { + background: #ffffff; +} + +.content { + background: #fafafa; +} + +text_box { + color: #4d4c4c; +} + +text-block { + color: #4d4c4c; +} \ No newline at end of file diff --git a/frontend/resources/fonts/materialdesignicons-webfont.ttf b/frontend/resources/fonts/materialdesignicons-webfont.ttf new file mode 100755 index 0000000..c9a3a30 Binary files /dev/null and b/frontend/resources/fonts/materialdesignicons-webfont.ttf differ diff --git a/frontend/resources/grid.css b/frontend/resources/grid.css new file mode 100644 index 0000000..a5af45f --- /dev/null +++ b/frontend/resources/grid.css @@ -0,0 +1,25 @@ +* { + font-size: 24; +} + +lynch { + background: #647b91; +} + +bluebayoux { + background: #516475; +} + +linkwater { + background: #dfebf5; + color: #3b434a; +} + +light-text { + color: #dfebf5; +} + +goldendream { + background: #efd035; + color: #3b434a; +} \ No newline at end of file diff --git a/frontend/resources/icons/advotracker/20x20/back.png b/frontend/resources/icons/advotracker/20x20/back.png new file mode 100755 index 0000000..db43e27 Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20/back.png differ diff --git a/frontend/resources/icons/advotracker/20x20/drawer.png b/frontend/resources/icons/advotracker/20x20/drawer.png new file mode 100755 index 0000000..1e974ef Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20/drawer.png differ diff --git a/frontend/resources/icons/advotracker/20x20/menu.png b/frontend/resources/icons/advotracker/20x20/menu.png new file mode 100755 index 0000000..a10473d Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20/menu.png differ diff --git a/frontend/resources/icons/advotracker/20x20@2/back.png b/frontend/resources/icons/advotracker/20x20@2/back.png new file mode 100755 index 0000000..c55ab31 Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@2/back.png differ diff --git a/frontend/resources/icons/advotracker/20x20@2/drawer.png b/frontend/resources/icons/advotracker/20x20@2/drawer.png new file mode 100755 index 0000000..eba3b6c Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@2/drawer.png differ diff --git a/frontend/resources/icons/advotracker/20x20@2/menu.png b/frontend/resources/icons/advotracker/20x20@2/menu.png new file mode 100755 index 0000000..649c2a0 Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@2/menu.png differ diff --git a/frontend/resources/icons/advotracker/20x20@3/back.png b/frontend/resources/icons/advotracker/20x20@3/back.png new file mode 100755 index 0000000..b228eb8 Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@3/back.png differ diff --git a/frontend/resources/icons/advotracker/20x20@3/drawer.png b/frontend/resources/icons/advotracker/20x20@3/drawer.png new file mode 100755 index 0000000..3584ed6 Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@3/drawer.png differ diff --git a/frontend/resources/icons/advotracker/20x20@3/menu.png b/frontend/resources/icons/advotracker/20x20@3/menu.png new file mode 100755 index 0000000..9554b69 Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@3/menu.png differ diff --git a/frontend/resources/icons/advotracker/20x20@4/back.png b/frontend/resources/icons/advotracker/20x20@4/back.png new file mode 100755 index 0000000..dd157e7 Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@4/back.png differ diff --git a/frontend/resources/icons/advotracker/20x20@4/drawer.png b/frontend/resources/icons/advotracker/20x20@4/drawer.png new file mode 100755 index 0000000..60d93af Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@4/drawer.png differ diff --git a/frontend/resources/icons/advotracker/20x20@4/menu.png b/frontend/resources/icons/advotracker/20x20@4/menu.png new file mode 100755 index 0000000..187c171 Binary files /dev/null and b/frontend/resources/icons/advotracker/20x20@4/menu.png differ diff --git a/frontend/resources/icons/advotracker/index.theme b/frontend/resources/icons/advotracker/index.theme new file mode 100755 index 0000000..313d6f8 --- /dev/null +++ b/frontend/resources/icons/advotracker/index.theme @@ -0,0 +1,24 @@ +[Icon Theme] +Name=AdvoTracker +Comment=AdvoTracker Icon Theme + +Directories=20x20,20x20@2,20x20@3,20x20@4 + +[20x20] +Size=20 +Type=Fixed + +[20x20@2] +Size=20 +Scale=2 +Type=Fixed + +[20x20@3] +Size=20 +Scale=3 +Type=Fixed + +[20x20@4] +Size=20 +Scale=4 +Type=Fixed diff --git a/frontend/resources/images/advotracker.jpg b/frontend/resources/images/advotracker.jpg new file mode 100755 index 0000000..af1cf5a Binary files /dev/null and b/frontend/resources/images/advotracker.jpg differ diff --git a/frontend/resources/images/arrow.png b/frontend/resources/images/arrow.png new file mode 100755 index 0000000..d81accb Binary files /dev/null and b/frontend/resources/images/arrow.png differ diff --git a/frontend/resources/images/arrow@2x.png b/frontend/resources/images/arrow@2x.png new file mode 100755 index 0000000..309c018 Binary files /dev/null and b/frontend/resources/images/arrow@2x.png differ diff --git a/frontend/resources/images/arrow@3x.png b/frontend/resources/images/arrow@3x.png new file mode 100755 index 0000000..e146ecc Binary files /dev/null and b/frontend/resources/images/arrow@3x.png differ diff --git a/frontend/resources/images/arrow@4x.png b/frontend/resources/images/arrow@4x.png new file mode 100755 index 0000000..b2ad27f Binary files /dev/null and b/frontend/resources/images/arrow@4x.png differ diff --git a/frontend/resources/images/arrows.png b/frontend/resources/images/arrows.png new file mode 100755 index 0000000..d38b94a Binary files /dev/null and b/frontend/resources/images/arrows.png differ diff --git a/frontend/resources/images/arrows@2x.png b/frontend/resources/images/arrows@2x.png new file mode 100755 index 0000000..69b9342 Binary files /dev/null and b/frontend/resources/images/arrows@2x.png differ diff --git a/frontend/resources/images/arrows@3x.png b/frontend/resources/images/arrows@3x.png new file mode 100755 index 0000000..7703252 Binary files /dev/null and b/frontend/resources/images/arrows@3x.png differ diff --git a/frontend/resources/images/arrows@4x.png b/frontend/resources/images/arrows@4x.png new file mode 100755 index 0000000..66c1fac Binary files /dev/null and b/frontend/resources/images/arrows@4x.png differ diff --git a/frontend/resources/images/background.jpg b/frontend/resources/images/background.jpg new file mode 100755 index 0000000..1d203e9 Binary files /dev/null and b/frontend/resources/images/background.jpg differ diff --git a/frontend/resources/images/customer_logo.jpg b/frontend/resources/images/customer_logo.jpg new file mode 100755 index 0000000..af1cf5a Binary files /dev/null and b/frontend/resources/images/customer_logo.jpg differ diff --git a/frontend/resources/images/networkx_logo.jpg b/frontend/resources/images/networkx_logo.jpg new file mode 100755 index 0000000..a5ed7ad Binary files /dev/null and b/frontend/resources/images/networkx_logo.jpg differ diff --git a/frontend/resources/images/nobody.png b/frontend/resources/images/nobody.png new file mode 100755 index 0000000..9a37b52 Binary files /dev/null and b/frontend/resources/images/nobody.png differ diff --git a/frontend/resources/images/orbtk-space.png b/frontend/resources/images/orbtk-space.png new file mode 100644 index 0000000..96b8ceb Binary files /dev/null and b/frontend/resources/images/orbtk-space.png differ diff --git a/frontend/resources/orbtk-space.png b/frontend/resources/orbtk-space.png new file mode 100644 index 0000000..96b8ceb Binary files /dev/null and b/frontend/resources/orbtk-space.png differ diff --git a/frontend/resources/stylesheets/advotracker-dark.css b/frontend/resources/stylesheets/advotracker-dark.css new file mode 100644 index 0000000..8fabd99 --- /dev/null +++ b/frontend/resources/stylesheets/advotracker-dark.css @@ -0,0 +1,32 @@ +.header { + background: #444e55; +} + +.content { + background: #3b434a; +} + +text-box { + background: transparent; + border-width: 0; + color: #9dafbf; + font-size: 16; +} + +#input { + font-size: 16; +} + +text-block { + font-size: 42; + color: #dfebf5; +} + +#input { + background: transparent; +} + +button { + border-radius: 1; + font-size: 20; +} \ No newline at end of file diff --git a/frontend/resources/stylesheets/advotracker-light.css b/frontend/resources/stylesheets/advotracker-light.css new file mode 100644 index 0000000..28c6ed5 --- /dev/null +++ b/frontend/resources/stylesheets/advotracker-light.css @@ -0,0 +1,23 @@ +* { + font-size: 16; +} + +.header { + background: #ffffff; +} + +.content { + background: #fafafa; +} + +text-box { + color: #4d4c4c; +} + +text-block { + color: #4d4c4c; +} + +light-text { + color: #dfebf5; +} diff --git a/frontend/resources/stylesheets/grid.css b/frontend/resources/stylesheets/grid.css new file mode 100644 index 0000000..92ec7e2 --- /dev/null +++ b/frontend/resources/stylesheets/grid.css @@ -0,0 +1,55 @@ +* { + font-size: 16; +} + +lynch { + background: #647b91; +} + +bluebayoux { + background: #516475; +} + +linkwater { + background: #dfebf5; + color: #3b434a; +} + +light-text { + color: #dfebf5; +} + +goldendream { + background: #efd035; + color: #3b434a; +} + +lightgrey { + #f0f0f0; +} + +darkgrey { + #879488; +} + +grey { + #b6c3b7; +} + +Indigo-bases { + #5c6bc0; +} + +hiedemann-blue { + #a5b3bf; +} + +hiedemann-red { + color: #5b0f22; +} + +hiedemann { + background: #5b0f22; + color: #879488; + //color: #3b434a; +} diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs new file mode 100644 index 0000000..31e1bb2 --- /dev/null +++ b/frontend/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}