From f3157e59895a50155b2eae38e981d3c7337f0238 Mon Sep 17 00:00:00 2001 From: Ralf Zerres Date: Fri, 19 Jun 2020 17:24:51 +0200 Subject: [PATCH] frontend: policydata: component to manage the policy data elements * policydata_view: GUI the user will interact with (orbtk Widget) will act on policy element lists, that itself will combine policy data (e.g. a vector of policy_code elements, metadata elements) * policydata_state: rust code with helper methods Signed-off-by: Ralf Zerres --- frontend/src/policydata_state.rs | 252 ++++++++++++++++++++++++++++ frontend/src/policydata_view.rs | 280 +++++++++++++++++++++++++++++++ 2 files changed, 532 insertions(+) create mode 100644 frontend/src/policydata_state.rs create mode 100644 frontend/src/policydata_view.rs diff --git a/frontend/src/policydata_state.rs b/frontend/src/policydata_state.rs new file mode 100644 index 0000000..650bc8a --- /dev/null +++ b/frontend/src/policydata_state.rs @@ -0,0 +1,252 @@ +use orbtk::prelude::*; + +use crate::{ + base_state::BaseState, + data::{PolicyData, PolicyList}, + keys::*, +}; +use chrono::{Local, NaiveDateTime}; +//use chrono::{DateTime, Local, TimeZone, NaiveDateTime, Utc}; + +/// Actions that can execute on the task view. +#[derive(Debug, Clone, Copy)] +pub enum Action { + InputTextChanged(Entity), + NavigateBack(), + NewEntry(Entity), + RemoveFocus(Entity), + RemoveEntry(usize), + SetEntry(Entity), + StatusChanged(Entity, usize), + TextChanged(Entity, usize), +} + +/// Handles the requests of the `OverviewView`. +#[derive(Default, AsAny)] +pub struct PolicyDataState { + action:Option, + add_button: Entity, + back_entity: Entity, + last_focused: Option, + pub text_box: Entity, + open: bool, +} + +/// implement of the BaseState trait for our PolicyDataState type +impl BaseState for PolicyDataState {} + +/// implement the PolicyDateState type itself +impl PolicyDataState { + /// Sets a new action. + pub fn action(&mut self, action: Action) { + self.action = action.into(); + } + + /// create a new policy data member + fn new_entry(&self, text: String, registry: &mut Registry, ctx: &mut Context) { + let index = ctx.widget().clone::>("list_index"); + + if let Some(index) = index { + if let Some(policy_list) = ctx + .widget() + .get_mut::("policy_data") + .get_mut(index) + { + policy_list.push(PolicyData { + policy_code, + date_valid_until, + selected: true, + }); + } + + self.adjust_count(ctx); + } + + self.save(registry, ctx); + } + + /// update number of available policy data entries. + fn adjust_count(&self, ctx: &mut Context) { + if let Some(index) = ctx.widget().clone::>("list_index") { + if let Some(policy_list) = ctx + .widget() + .clone::("policy_list") + .get(index) + { + ctx.widget().set("policy_count", policy_list.len()); + } + } + } + + /// navigate to previous policy data entry. + fn navigate_back(&mut self, ctx: &mut Context) { + ctx.get_widget(self.text_box) + .set("name", String16::from("")); + self.open = false; + ctx.widget().set::>("list_index", None); + ctx.widget().set("policy_count", 0 as usize); + self.navigate(self.back_entity, ctx); + } + + /// If policy data element 'name' is empty, disable the add button + /// otherwise otherwise enabled it. + fn adjust_add_button_enabled(&self, text_box: Entity, ctx: &mut Context) { + if ctx.get_widget(text_box).get::("name").is_empty() { + ctx.get_widget(self.add_button).set("enabled", false); + } else { + ctx.get_widget(self.add_button).set("enabled", true); + } + + ctx.get_widget(self.add_button).update_theme_by_state(true); + } + + /// Toggle the invalid element of the given policy data entry + fn toggle_invalid( + &self, + entry: Entity, + index: usize, + registry: &mut Registry, + ctx: &mut Context, + ) { + let invalid: bool = *ctx.get_widget(entry).get("invalid"); + + if let Some(idx) = ctx.widget().clone::>("list_index") { + if let Some(policy_list) = ctx + .widget() + .get_mut::("policy_list") + .get_mut(idx) + { + if let Some(task) = policy_list.get_mut(index) { + policy.selected = selected; + } + } + } + self.save(registry, ctx); + } + + pub fn open(&mut self, ctx: &mut Context) { + if let Some(index) = ctx.widget().clone::>("list_index") { + let mut name: String16 = "".into(); + let mut policy_count = 0; + if let Some(policy_list) = ctx.widget().get::("policy_list").get(index) { + name = String16::from(policy_list.name.as_str()); + policy_count = policy_list.len(); + } + ctx.widget().set("name", title); + ctx.widget().set("policy_count", policy_count); + self.open = true; + } + } + + /// Change status of given text box to edit mode. + fn edit_entry(&self, text_box: Entity, ctx: &mut Context) { + if *ctx.get_widget(text_box).get::("focused") { + ctx.get_widget(text_box).set("enabled", false); + ctx.push_event_by_window(FocusEvent::RemoveFocus(text_box)); + return; + } + + if let Some(old_focused_element) = ctx.window().get::("global").focused_widget { + ctx.push_event_by_window(FocusEvent::RemoveFocus(old_focused_element)); + } + + ctx.get_widget(text_box).set("enabled", true); + + // select all + ctx.get_widget(text_box) + .get_mut::("text_selection") + .start_index = 0; + ctx.get_widget(text_box) + .get_mut::("text_selection") + .length = ctx.get_widget(text_box).get::("name").len(); + ctx.push_event_by_window(FocusEvent::RequestFocus(text_box)); + } + + fn remove_entry(&self, index: usize, registry: &mut Registry, ctx: &mut Context) { + if let Some(idx) = ctx.widget().clone::>("list_index") { + if let Some(policy_list) = ctx + .widget() + .get_mut::("policy_list") + .get_mut(idx) + { + policy_list.remove(index); + } + } + self.adjust_count(ctx); + self.save(registry, ctx); + } + + fn update_entry( + &self, + text_box: Entity, + index: usize, + registry: &mut Registry, + ctx: &mut Context, + ) { + let text: String16 = ctx.get_widget(text_box).clone("text"); + + if let Some(idx) = ctx.widget().clone::>("list_index") { + if let Some(policy_list) = ctx + .widget() + .get_mut::("policy_list") + .get_mut(idx) + { + if let Some(task) = policy_list.get_mut(index) { + task.text = text.to_string(); + } + } + } + self.save(registry, ctx); + } +} + +impl State for PolicyState { + fn init(&mut self, _: &mut Registry, ctx: &mut Context) { + self.back_entity = (*ctx.widget().get::("back_entity")).into(); + self.add_button = ctx + .entity_of_child(ID_POLICY_ADD_BUTTON) + .expect("PolicyState.init: Can't find child 'Add button'."); + self.text_box = ctx + .entity_of_child(ID_POLICY_TEXT_BOX) + .expect("PolicyState.init: Can't find child 'Text Box'."); + } + + fn update(&mut self, registry: &mut Registry, ctx: &mut Context) { + if !self.open { + self.open(ctx); + } + if let Some(action) = self.action { + match action { + Action::InputTextChanged(text_box) => { + self.adjust_add_button_enabled(text_box, ctx); + } + Action::CreateEntry(entity) => { + if let Some(text) = self.fetch_text(ctx, entity) { + self.create_entry(name, registry, ctx); + } + } + Action::RemoveEntry(index) => { + self.remove_entry(index, registry, ctx); + } + Action::SelectionChanged(entity, index) => { + self.toggle_selection(entity, index, registry, ctx); + } + Action::TextChanged(entity, index) => { + self.update_entry(entity, index, registry, ctx); + } + Action::EditEntry(text_box) => { + self.last_focused = Some(text_box); + self.edit_entry(text_box, ctx); + } + Action::RemoveFocus(text_box) => { + ctx.get_widget(text_box).set("enabled", false); + ctx.push_event_by_window(FocusEvent::RemoveFocus(text_box)); + } + Action::NavigateBack() => { + self.navigate_back(ctx); + } + } + } + self.action = None; + } +} diff --git a/frontend/src/policydata_view.rs b/frontend/src/policydata_view.rs new file mode 100644 index 0000000..35c5c37 --- /dev/null +++ b/frontend/src/policydata_view.rs @@ -0,0 +1,280 @@ +use orbtk::prelude::*; + +use crate::{ + data::PolicyData, + keys::*, + policy_state::{Action, PolicyState}, +}; + +type ListIndex = Option; + +widget!(PolicyList { + back_entity: u32, + list_index: ListIndex, + policy_list: PolicyList, + policy_count: usize, + name: String16 +}); + +impl Template for PolicyList { + fn template(self, id: Entity, ctx: &mut BuildContext) -> Self { + // listing the policy elements + let items_widget = ItemsWidget::new() + .id(ID_POLICY_ITEMS_WIDGET) + .v_align("start") + .items_builder(move |ctx, index| { + let mut policy_code = "".to_string(); + let mut date_inserted = None; + let mut invalid = false; + + if let Some(list_index) = ctx.get_widget(id).clone::("list_index") { + if let Some(task_overview) = ctx + .get_widget(id) + .get::(PROP_POLICY_LIST) + .get(list_index) + { + if let Some(policy) = policy_list.get(index) { + policy_code = policy.policy_code.clone(); + date_inserted = policy.date_inserted.clone(); + invalid = policy.invalid; + } + } + } + + let text_box = TextBox::new() + .text(text) + .enabled(false) + .v_align("center") + .water_mark("Insert text...") + .class("inplace") + .attach(Grid::column(3)) + .on_changed(move |ctx, entity| { + ctx.get_mut::(id) + .action(Action::TextChanged(entity, index)); + }) + .on_activate(move |ctx, entity| { + ctx.get_mut::(id) + .action(Action::RemoveFocus(entity)); + }) + .build(ctx); + + Grid::new() + .height(48.0) + .columns( + Columns::new() + .add(10.0) + .add(24.0) + .add(8.0) + .add("*") + .add(8.0) + .add(32.0) + .add(4.0) + .add(32.0) + .add(8.0) + .build(), + ) + .child( + CheckBox::new() + .attach(Grid::column(1)) + .v_align("center") + .invalid(invalid) + .on_changed(move |ctx, entity| { + ctx.get_mut::(id) + .action(Action::StatusChanged(entity, index)); + }) + .build(ctx), + ) + .child(text_box) + .child( + ToggleButton::new() + .selected(("focused", text_box)) + .class(CLASS_ICON_ONLY) + .attach(Grid::column(5)) + .min_size(32.0, 32.0) + .v_align("center") + .build(ctx), + ) + .child( + Button::new() + .class(CLASS_ICON_ONLY) + .attach(Grid::column(5)) + .min_size(32.0, 32.0) + .v_align("center") + // todo use remove from icons + // .icon(material_font_icons::DELETE_FONT_ICON) + .icon("") + .on_mouse_down(|_, _| true) + .on_click(move |ctx, _| { + ctx.get_mut::(id) + .action(Action::EditEntry(text_box)); + true + }) + .build(ctx), + ) + .child( + Button::new() + .class("icon_only") + .attach(Grid::column(7)) + .min_size(32.0, 32.0) + .v_align("center") + // todo use remove from icons + // .icon(material_font_icons::DELETE_FONT_ICON) + .icon("") + .on_mouse_down(|_, _| true) + .on_click(move |ctx, _| { + ctx.get_mut::(id) + .action(Action::RemoveEntry(index)); + true + }) + .build(ctx), + ) + .build(ctx) + }) + .count((PROP_POLICY_COUNT, id)) + .build(ctx); + + let scroll_viewer = ScrollViewer::new() + .scroll_viewer_mode(("disabled", "auto")) + .child(items_widget) + .build(ctx); + + let task_text_box = TextBox::new() + .id(ID_POLICY_TEXT_BOX) + .attach(Grid::row(4)) + .v_align("center") + .margin((4.0, 0.0, 0.0, 0.0)) + .lost_focus_on_activation(false) + .on_activate(move |ctx, entity| { + ctx.get_mut::(id) + .action(Action::CreateEntry(entity)); + }) + .on_changed(move |ctx, entity| { + ctx.get_mut::(id) + .action(Action::InputTextChanged(entity)); + }) + .build(ctx); + + self.name("PolicyList").child( + Grid::new() + .rows( + Rows::new() + .add(52.0) + .add(1.0) + .add("*") + .add(1.0) + .add("auto") + .build(), + ) + .columns( + Columns::new() + .add("*") + .add(4.0) + .add(36.0) + .build(), + ) + // Content + .child( + Container::new() + .attach(Grid::row(2)) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .child(scroll_viewer) + .child( + ScrollIndicator::new() + .padding((0.0, 4.0, 0.0, 0.0)) + .content_id(items_widget.0) + .scroll_offset(scroll_viewer) + .build(ctx), + ) + .build(ctx), + ) + // Top Bar + .child( + Container::new() + .class(CLASS_TOP_BAR) + .attach(Grid::row(0)) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .child( + Grid::new() + .columns( + Columns::new() + .add(32.0) + .add(4.0) + .add("*") + .add(4.0) + .add(32.0) + .build(), + ) + .child( + Button::new() + .height(32.0) + .icon("") + .class(CLASS_ICON_ONLY) + .v_align("center") + .on_click(move |ctx, _| { + ctx.get_mut::(id) + .action(Action::NavigateBack()); + true + }) + .build(ctx), + ) + .child( + TextBlock::new() + .class(CLASS_HEADER) + .attach(Grid::column(2)) + .v_align("center") + .h_align("center") + .text(("name", id)) + .build(ctx), + ) + .build(ctx), + ) + .build(ctx), + ) + .child( + Container::new() + .class("separator") + .attach(Grid::row(1)) + .attach(Grid::column_span(3)) + .build(ctx), + ) + .child( + Container::new() + .class("separator") + .attach(Grid::row(3)) + .attach(Grid::column_span(3)) + .build(ctx), + ) + // Bottom bar + .child( + Container::new() + .class(CLASS_BOTTOM_BAR) + .attach(Grid::row(4)) + .attach(Grid::column(0)) + .attach(Grid::column_span(3)) + .build(ctx), + ) + .child(task_text_box) + .child( + Button::new() + .id(ID_POLICY_ADD_BUTTON) + .class(CLASS_ICON_ONLY) + .attach(Grid::row(4)) + .attach(Grid::column(2)) + .margin((0.0, 0.0, 4.0, 0.0)) + .enabled(false) + .min_size(32.0, 32.0) + .v_align("center") + .icon(material_font_icons::ADD_FONT_ICON) + .on_click(move |ctx, _| { + ctx.get_mut::(id) + .action(Action::CreateEntry(task_text_box)); + true + }) + .build(ctx), + ) + .build(ctx), + ) + } +}