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) }