frontend: policylists: component to manage the policy code lists
* policylist_view: GUI the user will interact with (orbtk Widget) will act on policy lists, that combines the policy data elements. * policydata_state: rust code with helper methods Signed-off-by: Ralf Zerres <ralf.zerres@networkx.de>
This commit is contained in:
248
frontend/src/policylist_state.rs
Normal file
248
frontend/src/policylist_state.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
use orbtk::prelude::*;
|
||||
|
||||
use crate::{
|
||||
base_state::BaseState,
|
||||
data::{PolicyData, PolicyList},
|
||||
keys::*,
|
||||
};
|
||||
|
||||
/// Actions that can execute on the task view.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Action {
|
||||
InputTextChanged(Entity),
|
||||
NewEntry(Entity),
|
||||
RemoveFocus(Entity),
|
||||
RemoveEntry(usize),
|
||||
OpenPolicyList(Entity),
|
||||
SetEntry(Entity),
|
||||
TextChanged(Entity, usize),
|
||||
}
|
||||
|
||||
/// Handles the requests of the `OverviewView`.
|
||||
#[derive(Default, AsAny)]
|
||||
pub struct PolicyListState {
|
||||
action:Option<Action>,
|
||||
add_button: Entity,
|
||||
items_widget: Entity,
|
||||
policylist_view: Entity,
|
||||
last_focused: Option<Entity>,
|
||||
text_box: Entity,
|
||||
}
|
||||
|
||||
impl BaseState for PolicyListState {}
|
||||
|
||||
impl PolicyListState {
|
||||
/// Sets a new action.
|
||||
pub fn action(&mut self, action: Action) {
|
||||
self.action = action.into();
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
/* Old syntax
|
||||
if ctx.get_widget(text_box).get::<String>("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);
|
||||
*/
|
||||
|
||||
if TextBox::get(ctx.get_widget(text_box)).text().is_empty() {
|
||||
Button::get(ctx.child("add_button")).set_enabled(false);
|
||||
} else {
|
||||
Button::get(ctx.child("add_button")).set_enabled(true);
|
||||
}
|
||||
// new syntax missing
|
||||
ctx.get_widget(self.add_button).update_theme_by_state(true);
|
||||
}
|
||||
|
||||
/// update number of available policy data entries.
|
||||
fn adjust_count(&self, ctx: &mut Context) {
|
||||
if let Some(index) = ctx.widget().clone::<Option<usize>>("list_index") {
|
||||
if let Some(policy_data) = ctx
|
||||
.widget()
|
||||
.clone::<PolicyList>("policy_list")
|
||||
.get(index)
|
||||
{
|
||||
ctx.widget().set("policy_list_count", policy_data.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new policy list.
|
||||
fn new_entry(&self, text: String, registry: &mut Registry, ctx: &mut Context) {
|
||||
ctx.widget()
|
||||
.get_mut::<PolicyList>(PROP_POLICY_LIST)
|
||||
.push(PolicyList::new(text));
|
||||
self.adjust_count(ctx);
|
||||
self.save(registry, ctx);
|
||||
}
|
||||
|
||||
// opens a policy list.
|
||||
fn open_policy_list(&self, index: usize, ctx: &mut Context) {
|
||||
ctx.get_widget(self.text_box)
|
||||
.set("name", String::from(""));
|
||||
ctx.get_widget(self.policy_list)
|
||||
.set("list_index", Some(index));
|
||||
self.navigate(self.policy_list, ctx);
|
||||
}
|
||||
|
||||
// removes a policy list.
|
||||
fn remove_entry(&self, index: usize, registry: &mut Registry, ctx: &mut Context) {
|
||||
ctx.widget()
|
||||
.get_mut::<PolicyList>(PROP_POLICY_LIST)
|
||||
.remove(index);
|
||||
self.adjust_count(ctx);
|
||||
self.save(registry, ctx);
|
||||
}
|
||||
|
||||
/// Change status of given text box to edit mode.
|
||||
fn set_entry(&self, text_box: Entity, ctx: &mut Context) {
|
||||
if *ctx.get_widget(text_box).get::<bool>("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>("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::<TextSelection>("text_selection")
|
||||
.start_index = 0;
|
||||
ctx.get_widget(text_box)
|
||||
.get_mut::<TextSelection>("text_selection")
|
||||
.length = ctx.get_widget(text_box).get::<String16>("name").len();
|
||||
ctx.push_event_by_window(FocusEvent::RequestFocus(text_box));
|
||||
}
|
||||
|
||||
/// 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::<Option<usize>>("list_index") {
|
||||
if let Some(policy_list) = ctx
|
||||
.widget()
|
||||
.get_mut::<PolicyData>("policy_list")
|
||||
.get_mut(idx)
|
||||
{
|
||||
if let Some(policy_list) = policy_list.get_mut(index) {
|
||||
policy_list.selected = selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
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::<Option<usize>>("list_index") {
|
||||
if let Some(policy_list) = ctx
|
||||
.widget()
|
||||
.get_mut::<PolicyList>("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 PolicyListState {
|
||||
fn init(&mut self, registry: &mut Registry, ctx: &mut Context) {
|
||||
self.text_box = ctx
|
||||
.entity_of_child(ID_POLICY_LIST_TEXT_BOX)
|
||||
.expect("PolicyListState.init: Child 'Text box' not found.");
|
||||
self.add_button = ctx
|
||||
.entity_of_child(ID_POLICY_LIST_ADD_BUTTON)
|
||||
.expect("PolicyListState.init: Child 'Add button' not found.");
|
||||
self.items_widget = ctx
|
||||
.entity_of_child(ID_POLICY_LIST_ITEMS_WIDGET)
|
||||
.expect("PolicyListState.init: Child 'Items widget' not found.");
|
||||
self.policy_list = (*ctx.widget().get::<u32>("policy_list_view")).into();
|
||||
|
||||
if let Ok(tasks) = registry
|
||||
.get::<Settings>("settings")
|
||||
.load::<PolicyList>(PROP_POLICY_LIST)
|
||||
{
|
||||
ctx.widget().set(PROP_POLICY_LIST, tasks);
|
||||
}
|
||||
|
||||
self.adjust_count(ctx);
|
||||
}
|
||||
|
||||
fn update(&mut self, registry: &mut Registry, ctx: &mut Context) {
|
||||
// clear focus on focus moved
|
||||
if self.last_focused != ctx.window().get::<Global>("global").focused_widget {
|
||||
if let Some(last_focused) = self.last_focused {
|
||||
ctx.get_widget(last_focused).set("focused", false);
|
||||
ctx.get_widget(last_focused)
|
||||
.set("visibility", Visibility::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(action) = self.action {
|
||||
match action {
|
||||
Action::InputTextChanged(text_box) => {
|
||||
self.adjust_add_button_enabled(text_box, ctx);
|
||||
}
|
||||
Action::NewEntry(entity) => {
|
||||
if let Some(text) = self.fetch_text(ctx, entity) {
|
||||
self.new_entry(text, registry, ctx);
|
||||
}
|
||||
}
|
||||
Action::RemoveEntry(index) => {
|
||||
self.remove_entry(index, registry, ctx);
|
||||
}
|
||||
Action::TextChanged(entity, index) => {
|
||||
let text: String16 = ctx.get_widget(entity).clone("text");
|
||||
|
||||
if let Some(header) = ctx
|
||||
.widget()
|
||||
.get_mut::<PolicyList>("policy_list")
|
||||
.get_mut(index)
|
||||
{
|
||||
header.name = text.to_string();
|
||||
}
|
||||
|
||||
self.save(registry, ctx);
|
||||
}
|
||||
Action::SetEntry(text_box) => {
|
||||
self.last_focused = Some(text_box);
|
||||
self.set_entry(text_box, ctx);
|
||||
}
|
||||
Action::RemoveFocus(text_box) => {
|
||||
self.last_focused = None;
|
||||
ctx.push_event_by_window(FocusEvent::RemoveFocus(text_box));
|
||||
}
|
||||
Action::OpenPolicyList(index) => {
|
||||
self.open_policy_list(index, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.action = None;
|
||||
}
|
||||
}
|
||||
279
frontend/src/policylist_view.rs
Normal file
279
frontend/src/policylist_view.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
use orbtk::prelude::*;
|
||||
|
||||
use crate::{
|
||||
data::PolicyList,
|
||||
keys::*,
|
||||
policylist_state::{Action, PolicyListState},
|
||||
};
|
||||
|
||||
type ListIndex = Option<usize>;
|
||||
|
||||
widget!(
|
||||
/// Starter page that offers the dialog to enter an identifier of a policy.
|
||||
/// This identifier is checked agains a map of valid policy codes.
|
||||
PolicyListView<PolicyListState> {
|
||||
policy_list: PolicyList,
|
||||
policy_list_count: u32
|
||||
}
|
||||
);
|
||||
|
||||
impl Template for PolicyListView {
|
||||
fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
|
||||
// all items of our policy lists
|
||||
let items_widget = ItemsWidget::new()
|
||||
.id(ID_POLICY_LIST_ITEMS_WIDGET)
|
||||
.v_align("start")
|
||||
.items_builder(move |ctx, index| {
|
||||
let mut name = "".to_string();
|
||||
let mut selected = false;
|
||||
|
||||
if let Some(policy_list) = ctx
|
||||
.get_widget(id)
|
||||
.get::<PolicyList>(PROP_POLICY_LIST)
|
||||
.get(index)
|
||||
{
|
||||
name = policy_list.name.clone();
|
||||
}
|
||||
|
||||
// plus button: open new policy
|
||||
let helper_button = Button::new()
|
||||
.min_height(48.0)
|
||||
.class(CLASS_ITEM_BUTTON)
|
||||
.attach(Grid::column(0))
|
||||
.attach(Grid::row(0))
|
||||
.attach(Grid::column_span(1))
|
||||
.on_click(move |ctx, _| {
|
||||
ctx.get_mut::<PolicyListState>(id)
|
||||
.action(Action::OpenPolicyList(index));
|
||||
true
|
||||
})
|
||||
.build(ctx);
|
||||
|
||||
let text_block = TextBlock::new()
|
||||
.foreground(helper_button)
|
||||
.margin((14.0, 0.0, 0.0, 0.0))
|
||||
.v_align("center")
|
||||
.attach(Grid::column(0))
|
||||
.text(name)
|
||||
.element("text-box")
|
||||
.build(ctx);
|
||||
|
||||
let text_box = TextBox::new()
|
||||
.margin((8.0, 0.0, 0.0, 0.0))
|
||||
.visibility("collapsed")
|
||||
.v_align("center")
|
||||
.water_mark("Insert policy name...")
|
||||
.attach(Grid::column(0))
|
||||
.text(text_block)
|
||||
.on_changed(move |ctx, entity| {
|
||||
ctx.get_mut::<PolicyListState>(id)
|
||||
.action(Action::TextChanged(entity, index));
|
||||
})
|
||||
.on_activate(move |ctx, entity| {
|
||||
ctx.get_mut::<PolicyListState>(id)
|
||||
.action(Action::RemoveFocus(entity));
|
||||
})
|
||||
.build(ctx);
|
||||
|
||||
Grid::new()
|
||||
.height(48.0)
|
||||
.columns(
|
||||
Columns::new()
|
||||
.add("*")
|
||||
.add(8.0)
|
||||
.add(32.0)
|
||||
.add(4.0)
|
||||
.add(32.0)
|
||||
.add(8.0)
|
||||
.build(),
|
||||
)
|
||||
.child(helper_button)
|
||||
.child(text_box)
|
||||
.child(text_block)
|
||||
.child(
|
||||
ToggleButton::new()
|
||||
.selected(("focused", text_box))
|
||||
.class(CLASS_ICON_ONLY)
|
||||
.attach(Grid::column(2))
|
||||
.min_size(32.0, 32.0)
|
||||
.v_align("center")
|
||||
.build(ctx),
|
||||
)
|
||||
.child(
|
||||
Button::new()
|
||||
// .selected(("focused", text_box))
|
||||
.class(CLASS_ICON_ONLY)
|
||||
.attach(Grid::column(2))
|
||||
.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::<PolicyListState>(id)
|
||||
.action(Action::SetEntry(text_box));
|
||||
true
|
||||
})
|
||||
.build(ctx),
|
||||
)
|
||||
.child(
|
||||
Button::new()
|
||||
.class("icon_only")
|
||||
.attach(Grid::column(4))
|
||||
.min_size(32.0, 32.0)
|
||||
.v_align("center")
|
||||
// todo use Tray icon for action remove
|
||||
// .icon(material_font_icons::DELETE_FONT_ICON)
|
||||
.icon("")
|
||||
.on_mouse_down(|_, _| true)
|
||||
.on_click(move |ctx, _| {
|
||||
ctx.get_mut::<PolicyListState>(id)
|
||||
.action(Action::RemoveEntry(index));
|
||||
true
|
||||
})
|
||||
.build(ctx),
|
||||
)
|
||||
.build(ctx)
|
||||
})
|
||||
.policy_list_count(PROP_POLICY_LIST_COUNT, id)
|
||||
.build(ctx);
|
||||
|
||||
// create new policy list element
|
||||
let policy_list_text_box = TextBox::new()
|
||||
.id(ID_POLICY_LIST_TEXT_BOX)
|
||||
.attach(Grid::row(4))
|
||||
.v_align("center")
|
||||
.margin((4.0, 0.0, 0.0, 2.0))
|
||||
.lost_focus_on_activation(false)
|
||||
.on_activate(move |ctx, entity| {
|
||||
ctx.get_mut::<PolicyListState>(id)
|
||||
.action(Action::NewEntry(entity));
|
||||
})
|
||||
.on_changed(move |ctx, entity| {
|
||||
ctx.get_mut::<PolicyListState>(id)
|
||||
.action(Action::InputTextChanged(entity));
|
||||
})
|
||||
.build(ctx);
|
||||
|
||||
let scroll_viewer = ScrollViewer::new()
|
||||
.scroll_viewer_mode(("disabled", "auto"))
|
||||
.child(items_widget)
|
||||
.build(ctx);
|
||||
|
||||
self.name("Policy Lists")
|
||||
.policy_list(PolicyList::default())
|
||||
.policy_list_count(0)
|
||||
.child(
|
||||
Grid::new()
|
||||
.rows(
|
||||
Rows::new()
|
||||
.add(52.0)
|
||||
.add(1.0)
|
||||
.add("*")
|
||||
.add(1.0)
|
||||
.add(40.0)
|
||||
.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()
|
||||
.child(
|
||||
TextBlock::new()
|
||||
.class(CLASS_HEADER)
|
||||
.v_align("center")
|
||||
.h_align("center")
|
||||
.text("Lists with policy data collections")
|
||||
.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(
|
||||
// workaround, todo fix scroll viewer mouse behavior in OrbTk
|
||||
Button::new()
|
||||
.attach(Grid::row(4))
|
||||
.attach(Grid::column(0))
|
||||
.attach(Grid::column_span(3))
|
||||
.on_mouse_down(|_, _| true)
|
||||
.on_mouse_up(|_, _| true)
|
||||
.on_click(|_, _| true)
|
||||
.class(CLASS_TRANSPARENT)
|
||||
.build(ctx),
|
||||
)
|
||||
.child(policy_list_text_box)
|
||||
.child(
|
||||
Button::new()
|
||||
.id(ID_POLICY_LIST_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::<PolicyListState>(id)
|
||||
.action(Action::NewEntry(policy_list_text_box));
|
||||
true
|
||||
})
|
||||
.build(ctx),
|
||||
)
|
||||
.build(ctx),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user