From ae0f833f15e22b3757a63f303aef6158445faf8d Mon Sep 17 00:00:00 2001 From: Exidex <16986685+exidex@users.noreply.github.com> Date: Sun, 30 Mar 2025 19:25:02 +0200 Subject: [PATCH] Rework shortcut selector ui in settings --- rust/client/src/ui/widget_container.rs | 2 +- .../src/components/shortcut_selector.rs | 363 +++++++++++++----- rust/management_client/src/ui.rs | 30 +- rust/management_client/src/views/general.rs | 136 +++---- rust/management_client/src/views/plugins.rs | 156 ++++---- .../src/views/plugins/table.rs | 19 +- 6 files changed, 416 insertions(+), 290 deletions(-) diff --git a/rust/client/src/ui/widget_container.rs b/rust/client/src/ui/widget_container.rs index 3aa92f0..8416711 100644 --- a/rust/client/src/ui/widget_container.rs +++ b/rust/client/src/ui/widget_container.rs @@ -76,7 +76,7 @@ impl PluginWidgetContainer { self.images = images; // use new state with values from old state but only widget ids which exists in new state - // so we this way we use already existing values but remove state for removed widgets + // so this way we use already existing values but remove state for removed widgets let old_state = mem::replace(&mut self.state, create_state(&container)); for (key, value) in old_state.into_iter() { diff --git a/rust/management_client/src/components/shortcut_selector.rs b/rust/management_client/src/components/shortcut_selector.rs index df54fe5..f46dcdc 100644 --- a/rust/management_client/src/components/shortcut_selector.rs +++ b/rust/management_client/src/components/shortcut_selector.rs @@ -1,10 +1,13 @@ +use gauntlet_common::model::EntrypointId; use gauntlet_common::model::PhysicalShortcut; +use gauntlet_common::model::PluginId; use gauntlet_common_ui::physical_key_model; use gauntlet_common_ui::shortcut_to_text; use iced::advanced::graphics::core::event; use iced::advanced::graphics::core::keyboard; use iced::advanced::layout; use iced::advanced::mouse; +use iced::advanced::overlay; use iced::advanced::renderer; use iced::advanced::widget::tree; use iced::advanced::widget::Tree; @@ -12,95 +15,123 @@ use iced::advanced::Clipboard; use iced::advanced::Layout; use iced::advanced::Shell; use iced::advanced::Widget; -use iced::alignment; use iced::keyboard::key::Physical; use iced::mouse::Button; +use iced::widget::column; use iced::widget::container; use iced::widget::container::draw_background; -use iced::widget::container::layout; use iced::widget::row; use iced::widget::text; -use iced::Element; +use iced::Alignment; use iced::Event; use iced::Length; use iced::Padding; +use iced::Point; use iced::Rectangle; use iced::Renderer; use iced::Size; +use iced::Vector; -pub struct ShortcutSelector<'a, Message, Theme> -where - Theme: Catalog + text::Catalog + container::Catalog, -{ - padding: Padding, - width: Length, - height: Length, - max_width: f32, - max_height: f32, - horizontal_alignment: alignment::Horizontal, - vertical_alignment: alignment::Vertical, +use crate::theme::text::TextStyle; +use crate::theme::Element; +use crate::theme::GauntletSettingsTheme; - on_shortcut_captured: Box) -> Message + 'a>, - on_capturing_change: Box Message + 'a>, - - content: Element<'a, Message, Theme>, +pub struct ShortcutData { + pub shortcut: Option, + pub error: Option, } -impl<'a, Message: 'a, Theme> ShortcutSelector<'a, Message, Theme> +pub fn shortcut_selector<'a, 'b: 'a, 'c, Message: 'a, Id: 'a, F>( + shortcut_id: Id, + current_shortcut: &'b ShortcutData, + on_shortcut_captured: F, + overlay_class: ::Class<'a>, +) -> Element<'a, Message> where - Theme: Catalog + text::Catalog + container::Catalog + 'a, + F: 'a + Fn(Id, Option) -> Message, + Id: Clone, { - pub fn new( - current_shortcut: &Option, + Element::new(ShortcutSelector::new::( + shortcut_id, + current_shortcut, + on_shortcut_captured, + overlay_class, + )) +} + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum ShortcutId { + Global, + Entrypoint { + plugin_id: PluginId, + entrypoint_id: EntrypointId, + }, +} + +pub struct ShortcutSelector<'a, 'b, Message, Id> +where + Id: Clone, +{ + on_shortcut_captured: Box) -> Message + 'a>, + + shortcut_id: Id, + current_shortcut: &'b ShortcutData, + + content: Element<'a, Message>, + popup: Element<'a, Message>, + overlay_class: ::Class<'a>, +} + +impl<'a, 'b, 'c, Message: 'a, Id> ShortcutSelector<'a, 'b, Message, Id> +where + Id: Clone, +{ + pub fn new( + shortcut_id: Id, + current_shortcut: &'b ShortcutData, on_shortcut_captured: F, - on_capturing_change: F2, + overlay_class: ::Class<'a>, ) -> Self where - F: 'a + Fn(Option) -> Message, - F2: 'a + Fn(bool) -> Message, + F: 'a + Fn(Id, Option) -> Message, { - let mut content: Vec> = vec![]; + let content = render_shortcut(¤t_shortcut.shortcut); - if let Some(current_shortcut) = current_shortcut { - let (key_name, alt_modifier_text, meta_modifier_text, control_modifier_text, shift_modifier_text) = - shortcut_to_text(current_shortcut); + let content = container(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x(Length::Fill) + .center_y(Length::Fill) + .into(); - if let Some(meta_modifier_text) = meta_modifier_text { - content.push(meta_modifier_text); - } + let recording_text: Element<_> = text("Recording shortcut...").into(); - if let Some(control_modifier_text) = control_modifier_text { - content.push(control_modifier_text); - } + let backspace_test: Element<_> = text("Backspace - Unset Shortcut").class(TextStyle::Subtitle).into(); - if let Some(shift_modifier_text) = shift_modifier_text { - content.push(shift_modifier_text); - } + let escape_test: Element<_> = text("Escape - Stop Capturing").class(TextStyle::Subtitle).into(); - if let Some(alt_modifier_text) = alt_modifier_text { - content.push(alt_modifier_text); - } + let popup: Element<_> = column(vec![recording_text, backspace_test, escape_test]) + .align_x(Alignment::Center) + .into(); - content.push(key_name); - } - - let content: Element<_, _> = row(content).spacing(8.0).into(); - - let content = container(content).into(); + let popup = container(popup) + .max_height(80) + .center_x(Length::Fill) + .center_y(Length::Fill) + .max_width(300) + .width(Length::Fill) + .height(Length::Fill) + .into(); Self { - padding: Padding::ZERO, - width: Length::Fill, - height: Length::Fill, - max_width: f32::INFINITY, - max_height: f32::INFINITY, - horizontal_alignment: alignment::Horizontal::Center, - vertical_alignment: alignment::Vertical::Center, - on_shortcut_captured: Box::new(on_shortcut_captured), - on_capturing_change: Box::new(on_capturing_change), + shortcut_id, + current_shortcut, content, + popup, + + overlay_class, } } } @@ -110,36 +141,26 @@ struct State { is_capturing: bool, } -impl<'a, Message, Theme> Widget for ShortcutSelector<'a, Message, Theme> +impl<'a, 'b, Message: 'a, Id> Widget for ShortcutSelector<'a, 'b, Message, Id> where - Theme: Catalog + text::Catalog + container::Catalog, + Id: Clone, { fn size(&self) -> Size { Size { - width: self.width, - height: self.height, + width: Length::Fill, + height: Length::Fill, } } fn layout(&self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { - layout( - limits, - self.width, - self.height, - self.max_width, - self.max_height, - self.padding, - self.horizontal_alignment, - self.vertical_alignment, - |limits| self.content.as_widget().layout(tree, renderer, limits), - ) + self.content.as_widget().layout(&mut tree.children[0], renderer, limits) } fn draw( &self, tree: &Tree, renderer: &mut Renderer, - theme: &Theme, + theme: &GauntletSettingsTheme, renderer_style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, @@ -153,18 +174,16 @@ where Status::Active }; - let style = Catalog::style(theme, &::default(), style); + let style = Catalog::style(theme, &::default(), style); draw_background(renderer, &style, layout.bounds()); self.content.as_widget().draw( - tree, + &tree.children[0], renderer, theme, - &renderer::Style { - text_color: renderer_style.text_color, - }, - layout.children().next().unwrap(), + renderer_style, + layout, cursor, viewport, ); @@ -179,11 +198,11 @@ where } fn children(&self) -> Vec { - self.content.as_widget().children() + vec![Tree::new(&self.content), Tree::new(&self.popup)] } fn diff(&self, tree: &mut Tree) { - self.content.as_widget().diff(tree); + tree.diff_children(&[self.content.as_widget(), self.popup.as_widget()]); } fn on_event( @@ -211,21 +230,18 @@ where match physical_key { Physical::Code(code) => { match code { - keyboard::key::Code::Backspace => { + keyboard::key::Code::Backspace if modifiers.is_empty() => { state.is_capturing = false; - let message = (self.on_capturing_change)(false); - shell.publish(message); - - let message = (self.on_shortcut_captured)(None); + let message = (self.on_shortcut_captured)(self.shortcut_id.clone(), None); shell.publish(message); event::Status::Ignored } - keyboard::key::Code::Escape => { + keyboard::key::Code::Escape if modifiers.is_empty() => { state.is_capturing = false; - let message = (self.on_capturing_change)(false); + let message = (self.on_shortcut_captured)(self.shortcut_id.clone(), None); shell.publish(message); event::Status::Ignored @@ -236,10 +252,10 @@ where Some(shortcut) => { state.is_capturing = false; - let message = (self.on_capturing_change)(false); - shell.publish(message); - - let message = (self.on_shortcut_captured)(Some(shortcut)); + let message = (self.on_shortcut_captured)( + self.shortcut_id.clone(), + Some(shortcut), + ); shell.publish(message); event::Status::Captured @@ -263,16 +279,10 @@ where if cursor.is_over(layout.bounds()) { state.is_capturing = true; - let message = (self.on_capturing_change)(true); - shell.publish(message); - event::Status::Captured } else { state.is_capturing = false; - let message = (self.on_capturing_change)(false); - shell.publish(message); - event::Status::Ignored } } @@ -297,15 +307,40 @@ where mouse::Interaction::default() } } -} -impl<'a, Message, Theme> From> for Element<'a, Message, Theme> -where - Message: 'a, - Theme: Catalog + text::Catalog + container::Catalog + 'a, -{ - fn from(shortcut_selector: ShortcutSelector<'a, Message, Theme>) -> Self { - Self::new(shortcut_selector) + fn overlay<'c>( + &'c mut self, + tree: &'c mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + let state = tree.state.downcast_ref::(); + + let mut children = tree.children.iter_mut(); + + let content = self + .content + .as_widget_mut() + .overlay(children.next().unwrap(), layout, renderer, translation); + + let popup = if state.is_capturing { + Some(overlay::Element::new(Box::new(Overlay { + position: layout.position() + translation, + popup: &self.popup, + state: children.next().unwrap(), + content_bounds: layout.bounds(), + class: &self.overlay_class, + }))) + } else { + None + }; + + if content.is_some() || popup.is_some() { + Some(overlay::Group::with_children(content.into_iter().chain(popup).collect()).overlay()) + } else { + None + } } } @@ -322,3 +357,121 @@ pub trait Catalog { fn style(&self, class: &Self::Class<'_>, status: Status) -> container::Style; } + +pub fn render_shortcut<'a, Message: 'a>(shortcut: &Option) -> Element<'a, Message> { + let mut content: Vec> = vec![]; + + if let Some(current_shortcut) = shortcut { + let (key_name, alt_modifier_text, meta_modifier_text, control_modifier_text, shift_modifier_text) = + shortcut_to_text(current_shortcut); + + if let Some(meta_modifier_text) = meta_modifier_text { + content.push(meta_modifier_text); + } + + if let Some(control_modifier_text) = control_modifier_text { + content.push(control_modifier_text); + } + + if let Some(shift_modifier_text) = shift_modifier_text { + content.push(shift_modifier_text); + } + + if let Some(alt_modifier_text) = alt_modifier_text { + content.push(alt_modifier_text); + } + + content.push(key_name); + } + + let content: Element = row(content).spacing(8.0).into(); + + content +} + +struct Overlay<'a, 'b, Message> { + position: Point, + popup: &'b Element<'a, Message>, + state: &'b mut Tree, + content_bounds: Rectangle, + class: &'b ::Class<'a>, +} + +impl<'a, 'b, Message> overlay::Overlay for Overlay<'a, 'b, Message> { + fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { + let padding = 2.0; + let gap = 10.0; + + let viewport = Rectangle::with_size(bounds); + + let popup_layout = self.popup.as_widget().layout( + self.state, + renderer, + &layout::Limits::new(Size::ZERO, viewport.size()).shrink(Padding::new(padding)), + ); + + let text_bounds = popup_layout.bounds(); + let x_center = self.position.x + (self.content_bounds.width - text_bounds.width) / 2.0; + + let mut tooltip_bounds = { + let offset = Vector::new(x_center, self.position.y + self.content_bounds.height + gap + padding); + + Rectangle { + x: offset.x - padding, + y: offset.y - padding, + width: text_bounds.width + padding * 2.0, + height: text_bounds.height + padding * 2.0, + } + }; + + // snap_within_viewport + if tooltip_bounds.x < viewport.x { + tooltip_bounds.x = viewport.x; + } else if viewport.x + viewport.width < tooltip_bounds.x + tooltip_bounds.width { + tooltip_bounds.x = viewport.x + viewport.width - tooltip_bounds.width; + } + + if tooltip_bounds.y < viewport.y { + tooltip_bounds.y = viewport.y; + } else if viewport.y + viewport.height < tooltip_bounds.y + tooltip_bounds.height { + tooltip_bounds.y = viewport.y + viewport.height - tooltip_bounds.height; + } + + layout::Node::with_children( + tooltip_bounds.size(), + vec![popup_layout.translate(Vector::new(padding, padding))], + ) + .translate(Vector::new(tooltip_bounds.x, tooltip_bounds.y)) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &GauntletSettingsTheme, + inherited_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + ) { + let style = ::style(theme, self.class); + + draw_background(renderer, &style, layout.bounds()); + + let defaults = renderer::Style { + text_color: style.text_color.unwrap_or(inherited_style.text_color), + }; + + self.popup.as_widget().draw( + self.state, + renderer, + theme, + &defaults, + layout.children().next().unwrap(), + cursor_position, + &Rectangle::with_size(Size::INFINITY), + ); + } + + fn is_over(&self, _layout: Layout<'_>, _renderer: &Renderer, _cursor_position: Point) -> bool { + false + } +} diff --git a/rust/management_client/src/ui.rs b/rust/management_client/src/ui.rs index 7d6fdcf..79436ba 100644 --- a/rust/management_client/src/ui.rs +++ b/rust/management_client/src/ui.rs @@ -79,7 +79,7 @@ struct ManagementAppModel { } #[derive(Debug, Clone)] -enum ManagementAppMsg { +pub enum ManagementAppMsg { FontLoaded(Result<(), font::Error>), General(ManagementAppGeneralMsgIn), Plugin(ManagementAppPluginMsgIn), @@ -191,34 +191,16 @@ fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task { state.plugins_state.update(message).map(|msg| { match msg { - ManagementAppPluginMsgOut::PluginsReloaded(plugins) => { - ManagementAppMsg::Plugin(ManagementAppPluginMsgIn::PluginsFetched(plugins)) - } - ManagementAppPluginMsgOut::Noop => ManagementAppMsg::Plugin(ManagementAppPluginMsgIn::Noop), - ManagementAppPluginMsgOut::DownloadPlugin { plugin_id } => { - ManagementAppMsg::DownloadPlugin { plugin_id } - } - ManagementAppPluginMsgOut::SelectedItem(selected_item) => { - ManagementAppMsg::Plugin(ManagementAppPluginMsgIn::SelectItem(selected_item)) - } - ManagementAppPluginMsgOut::HandleBackendError(err) => ManagementAppMsg::HandleBackendError(err), + ManagementAppPluginMsgOut::Inner(msg) => ManagementAppMsg::Plugin(msg), + ManagementAppPluginMsgOut::Outer(msg) => msg, } }) } ManagementAppMsg::General(message) => { state.general_state.update(message).map(|msg| { match msg { - ManagementAppGeneralMsgOut::Noop => ManagementAppMsg::General(ManagementAppGeneralMsgIn::Noop), - ManagementAppGeneralMsgOut::HandleBackendError(err) => ManagementAppMsg::HandleBackendError(err), - ManagementAppGeneralMsgOut::SetGlobalShortcutResponse { - shortcut, - shortcut_error, - } => { - ManagementAppMsg::General(ManagementAppGeneralMsgIn::SetGlobalShortcutResponse { - shortcut, - shortcut_error, - }) - } + ManagementAppGeneralMsgOut::Inner(msg) => ManagementAppMsg::General(msg), + ManagementAppGeneralMsgOut::Outer(msg) => msg, } }) } @@ -266,7 +248,7 @@ fn update(state: &mut ManagementAppModel, message: ManagementAppMsg) -> Task, theme: SettingsTheme, window_position_mode: WindowPositionMode, - current_shortcut: Option, - current_shortcut_error: Option, - currently_capturing: bool, + current_shortcut: ShortcutData, } #[derive(Debug, Clone)] pub enum ManagementAppGeneralMsgIn { - ShortcutCaptured(Option), - CapturingChanged(bool), + ShortcutCaptured(ShortcutId, Option), ThemeChanged(SettingsTheme), WindowPositionModeChanged(WindowPositionMode), - SetGlobalShortcutResponse { + HandleShortcutResponse { + id: ShortcutId, shortcut: Option, shortcut_error: Option, }, @@ -57,12 +58,8 @@ pub enum ManagementAppGeneralMsgIn { #[derive(Debug, Clone)] pub enum ManagementAppGeneralMsgOut { - Noop, - SetGlobalShortcutResponse { - shortcut: Option, - shortcut_error: Option, - }, - HandleBackendError(BackendApiError), + Inner(ManagementAppGeneralMsgIn), + Outer(ManagementAppMsg), } impl ManagementAppGeneralState { @@ -71,9 +68,10 @@ impl ManagementAppGeneralState { backend_api, theme: SettingsTheme::AutoDetect, window_position_mode: WindowPositionMode::Static, - current_shortcut: None, - current_shortcut_error: None, - currently_capturing: false, + current_shortcut: ShortcutData { + shortcut: None, + error: None, + }, } } @@ -84,7 +82,7 @@ impl ManagementAppGeneralState { }; match message { - ManagementAppGeneralMsgIn::ShortcutCaptured(shortcut) => { + ManagementAppGeneralMsgIn::ShortcutCaptured(id, shortcut) => { let mut backend_api = backend_api.clone(); Task::perform( @@ -99,12 +97,14 @@ impl ManagementAppGeneralState { }, move |result| { let shortcut = shortcut.clone(); + let id = id.clone(); handle_backend_error(result, move |shortcut_error| { - ManagementAppGeneralMsgOut::SetGlobalShortcutResponse { + ManagementAppGeneralMsgOut::Inner(ManagementAppGeneralMsgIn::HandleShortcutResponse { + id, shortcut, shortcut_error, - } + }) }) }, ) @@ -118,13 +118,10 @@ impl ManagementAppGeneralState { } => { self.theme = theme; self.window_position_mode = window_position_mode; - self.current_shortcut = shortcut; - self.current_shortcut_error = shortcut_error; - - Task::done(ManagementAppGeneralMsgOut::Noop) - } - ManagementAppGeneralMsgIn::CapturingChanged(capturing) => { - self.currently_capturing = capturing; + self.current_shortcut = ShortcutData { + shortcut, + error: shortcut_error, + }; Task::none() } @@ -139,7 +136,9 @@ impl ManagementAppGeneralState { Ok(()) }, - |result| handle_backend_error(result, |()| ManagementAppGeneralMsgOut::Noop), + |result| { + handle_backend_error(result, |()| ManagementAppGeneralMsgOut::Outer(ManagementAppMsg::Noop)) + }, ) } ManagementAppGeneralMsgIn::WindowPositionModeChanged(mode) => { @@ -153,15 +152,20 @@ impl ManagementAppGeneralState { Ok(()) }, - |result| handle_backend_error(result, |()| ManagementAppGeneralMsgOut::Noop), + |result| { + handle_backend_error(result, |()| ManagementAppGeneralMsgOut::Outer(ManagementAppMsg::Noop)) + }, ) } - ManagementAppGeneralMsgIn::SetGlobalShortcutResponse { + ManagementAppGeneralMsgIn::HandleShortcutResponse { + id, shortcut, shortcut_error, } => { - self.current_shortcut = shortcut; - self.current_shortcut_error = shortcut_error; + self.current_shortcut = ShortcutData { + shortcut, + error: shortcut_error, + }; Task::none() } @@ -169,12 +173,12 @@ impl ManagementAppGeneralState { } pub fn view(&self) -> Element { - let global_shortcut_selector: Element<_> = ShortcutSelector::new( + let global_shortcut_selector = shortcut_selector( + ShortcutId::Global, &self.current_shortcut, - move |value| ManagementAppGeneralMsgIn::ShortcutCaptured(value), - move |value| ManagementAppGeneralMsgIn::CapturingChanged(value), - ) - .into(); + move |id, shortcut| ManagementAppGeneralMsgIn::ShortcutCaptured(id, shortcut), + ContainerStyle::Box, + ); let global_shortcut_field: Element<_> = container(global_shortcut_selector) .width(Length::Fill) @@ -200,8 +204,6 @@ impl ManagementAppGeneralState { let content: Element<_> = container(content).width(Length::Fill).into(); - let content: Element<_> = container(content).width(Length::Fill).into(); - content } @@ -290,59 +292,41 @@ impl ManagementAppGeneralState { } fn shortcut_capture_after(&self) -> Element { - if self.currently_capturing { - let hint1: Element<_> = text("Backspace - Unset Shortcut") - .width(Length::Fill) - .class(TextStyle::Subtitle) + if let Some(current_shortcut_error) = &self.current_shortcut.error { + let error_icon: Element<_> = value(Bootstrap::ExclamationTriangleFill) + .font(BOOTSTRAP_FONT) + .class(TextStyle::Destructive) .into(); - let hint2: Element<_> = text("Escape - Stop Capturing") - .width(Length::Fill) - .class(TextStyle::Subtitle) + let error_text: Element<_> = text(current_shortcut_error).class(TextStyle::Destructive).into(); + + let error_text: Element<_> = container(error_text) + .padding(16.0) + .max_width(300) + .class(ContainerStyle::Box) .into(); - column(vec![hint1, hint2]) + let tooltip: Element<_> = tooltip(error_icon, error_text, Position::Bottom).into(); + + let content = container(tooltip) .width(Length::FillPortion(3)) - .align_x(Alignment::Center) + .align_y(alignment::Vertical::Center) .padding(Padding::from([0.0, 8.0])) - .into() + .into(); + + content } else { - if let Some(current_shortcut_error) = &self.current_shortcut_error { - let error_icon: Element<_> = value(Bootstrap::ExclamationTriangleFill) - .font(BOOTSTRAP_FONT) - .class(TextStyle::Destructive) - .into(); - - let error_text: Element<_> = text(current_shortcut_error).class(TextStyle::Destructive).into(); - - let error_text: Element<_> = container(error_text) - .padding(16.0) - .max_width(300) - .class(ContainerStyle::Box) - .into(); - - let tooltip: Element<_> = tooltip(error_icon, error_text, Position::Bottom).into(); - - let content = container(tooltip) - .width(Length::FillPortion(3)) - .align_y(alignment::Vertical::Center) - .padding(Padding::from([0.0, 8.0])) - .into(); - - content - } else { - Space::with_width(Length::FillPortion(3)).into() - } + Space::with_width(Length::FillPortion(3)).into() } } } -pub fn handle_backend_error( +fn handle_backend_error( result: Result, convert: impl FnOnce(T) -> ManagementAppGeneralMsgOut, ) -> ManagementAppGeneralMsgOut { match result { Ok(val) => convert(val), - Err(err) => ManagementAppGeneralMsgOut::HandleBackendError(err), + Err(err) => ManagementAppGeneralMsgOut::Outer(ManagementAppMsg::HandleBackendError(err)), } } diff --git a/rust/management_client/src/views/plugins.rs b/rust/management_client/src/views/plugins.rs index 9147edb..5cf806a 100644 --- a/rust/management_client/src/views/plugins.rs +++ b/rust/management_client/src/views/plugins.rs @@ -32,13 +32,13 @@ use iced_fonts::BOOTSTRAP_FONT; use crate::theme::button::ButtonStyle; use crate::theme::text::TextStyle; use crate::theme::Element; +use crate::ui::ManagementAppMsg; use crate::views::plugins::preferences::preferences_ui; use crate::views::plugins::preferences::PluginPreferencesMsg; use crate::views::plugins::preferences::SelectItem; use crate::views::plugins::table::PluginTableMsgIn; use crate::views::plugins::table::PluginTableMsgOut; use crate::views::plugins::table::PluginTableState; -use crate::views::plugins::table::PluginTableUpdateResult; mod preferences; mod table; @@ -48,19 +48,17 @@ pub enum ManagementAppPluginMsgIn { PluginTableMsg(PluginTableMsgIn), PluginPreferenceMsg(PluginPreferencesMsg), FetchPlugins, - PluginsFetched(HashMap), + PluginsReloaded(HashMap), RemovePlugin { plugin_id: PluginId }, + ToggleShowEntrypoint { plugin_id: PluginId }, DownloadPlugin { plugin_id: PluginId }, SelectItem(SelectedItem), Noop, } pub enum ManagementAppPluginMsgOut { - PluginsReloaded(HashMap), - SelectedItem(SelectedItem), - DownloadPlugin { plugin_id: PluginId }, - HandleBackendError(BackendApiError), - Noop, + Inner(ManagementAppPluginMsgIn), + Outer(ManagementAppMsg), } pub struct ManagementAppPluginsState { @@ -99,7 +97,7 @@ impl ManagementAppPluginsState { tracing::debug!("Opening selected item: {:?}", select_item); Self { - backend_api, + backend_api: backend_api.clone(), plugin_data: Rc::new(RefCell::new(PluginDataContainer::new())), preference_user_data: HashMap::new(), selected_item: select_item, @@ -115,71 +113,77 @@ impl ManagementAppPluginsState { match message { ManagementAppPluginMsgIn::PluginTableMsg(message) => { - match self.table_state.update(message) { - PluginTableUpdateResult::Command(command) => command.map(|_| ManagementAppPluginMsgOut::Noop), - PluginTableUpdateResult::Value(msg) => { - match msg { - PluginTableMsgOut::SetPluginState { enabled, plugin_id } => { - let mut backend_client = backend_api.clone(); + self.table_state.update(message).then(move |msg| { + match msg { + PluginTableMsgOut::SetPluginState { enabled, plugin_id } => { + let mut backend_client = backend_api.clone(); - Task::perform( - async move { - backend_client.set_plugin_state(plugin_id, enabled).await?; + Task::perform( + async move { + backend_client.set_plugin_state(plugin_id, enabled).await?; - let plugins = backend_client.plugins().await?; + let plugins = backend_client.plugins().await?; - Ok(plugins) - }, - |result| { - handle_backend_error(result, |plugins| { - ManagementAppPluginMsgOut::PluginsReloaded(plugins) - }) - }, - ) - } - PluginTableMsgOut::SetEntrypointState { - enabled, - plugin_id, - entrypoint_id, - } => { - let mut backend_client = backend_api.clone(); + Ok(plugins) + }, + |result| { + handle_backend_error(result, |plugins| { + ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( + plugins, + )) + }) + }, + ) + } + PluginTableMsgOut::SetEntrypointState { + enabled, + plugin_id, + entrypoint_id, + } => { + let mut backend_client = backend_api.clone(); - Task::perform( - async move { - backend_client - .set_entrypoint_state(plugin_id, entrypoint_id, enabled) - .await?; + Task::perform( + async move { + backend_client + .set_entrypoint_state(plugin_id, entrypoint_id, enabled) + .await?; - let plugins = backend_client.plugins().await?; + let plugins = backend_client.plugins().await?; - Ok(plugins) - }, - |result| { - handle_backend_error(result, |plugins| { - ManagementAppPluginMsgOut::PluginsReloaded(plugins) - }) - }, - ) - } - PluginTableMsgOut::SelectItem(selected_item) => { - Task::done(ManagementAppPluginMsgOut::SelectedItem(selected_item)) - } - PluginTableMsgOut::ToggleShowEntrypoints { plugin_id } => { - let plugins = { - let mut plugin_data = self.plugin_data.borrow_mut(); - let settings_plugin_data = plugin_data.plugins_state.get_mut(&plugin_id).unwrap(); - settings_plugin_data.show_entrypoints = !settings_plugin_data.show_entrypoints; - - plugin_data.plugins.clone() - }; - - self.apply_plugin_fetch(plugins); - - Task::none() - } + Ok(plugins) + }, + |result| { + handle_backend_error(result, |plugins| { + ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded( + plugins, + )) + }) + }, + ) + } + PluginTableMsgOut::SelectItem(selected_item) => { + Task::done(ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::SelectItem( + selected_item, + ))) + } + PluginTableMsgOut::ToggleShowEntrypoints { plugin_id } => { + Task::done(ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::Noop)) } } - } + }) + } + ManagementAppPluginMsgIn::ToggleShowEntrypoint { plugin_id } => { + let plugins = { + let mut plugin_data = self.plugin_data.borrow_mut(); + let settings_plugin_data = plugin_data.plugins_state.get_mut(&plugin_id).unwrap(); + settings_plugin_data.show_entrypoints = !settings_plugin_data.show_entrypoints; + + plugin_data.plugins.clone() + }; + + self.apply_plugin_fetch(plugins); + + Task::none() } ManagementAppPluginMsgIn::PluginPreferenceMsg(msg) => { match msg { @@ -204,7 +208,11 @@ impl ManagementAppPluginsState { Ok(()) }, - |result| handle_backend_error(result, |()| ManagementAppPluginMsgOut::Noop), + |result| { + handle_backend_error(result, |()| { + ManagementAppPluginMsgOut::Outer(ManagementAppMsg::Noop) + }) + }, ) } } @@ -219,11 +227,13 @@ impl ManagementAppPluginsState { Ok(plugins) }, |result| { - handle_backend_error(result, |plugins| ManagementAppPluginMsgOut::PluginsReloaded(plugins)) + handle_backend_error(result, |plugins| { + ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded(plugins)) + }) }, ) } - ManagementAppPluginMsgIn::PluginsFetched(plugins) => { + ManagementAppPluginMsgIn::PluginsReloaded(plugins) => { self.apply_plugin_fetch(plugins); Task::none() @@ -242,12 +252,16 @@ impl ManagementAppPluginsState { Ok(plugins) }, |result| { - handle_backend_error(result, |plugins| ManagementAppPluginMsgOut::PluginsReloaded(plugins)) + handle_backend_error(result, |plugins| { + ManagementAppPluginMsgOut::Inner(ManagementAppPluginMsgIn::PluginsReloaded(plugins)) + }) }, ) } ManagementAppPluginMsgIn::DownloadPlugin { plugin_id } => { - Task::done(ManagementAppPluginMsgOut::DownloadPlugin { plugin_id }) + Task::done(ManagementAppPluginMsgOut::Outer(ManagementAppMsg::DownloadPlugin { + plugin_id, + })) } ManagementAppPluginMsgIn::SelectItem(selected_item) => { self.selected_item = selected_item; @@ -696,6 +710,6 @@ pub fn handle_backend_error( ) -> ManagementAppPluginMsgOut { match result { Ok(val) => convert(val), - Err(err) => ManagementAppPluginMsgOut::HandleBackendError(err), + Err(err) => ManagementAppPluginMsgOut::Outer(ManagementAppMsg::HandleBackendError(err)), } } diff --git a/rust/management_client/src/views/plugins/table.rs b/rust/management_client/src/views/plugins/table.rs index bb35d10..99438f7 100644 --- a/rust/management_client/src/views/plugins/table.rs +++ b/rust/management_client/src/views/plugins/table.rs @@ -62,11 +62,6 @@ pub struct PluginTableState { body: Id, } -pub enum PluginTableUpdateResult { - Command(Task<()>), - Value(PluginTableMsgOut), -} - impl PluginTableState { pub fn new() -> Self { Self { @@ -82,22 +77,20 @@ impl PluginTableState { } } - pub fn update(&mut self, message: PluginTableMsgIn) -> PluginTableUpdateResult { + pub fn update(&mut self, message: PluginTableMsgIn) -> Task { match message { - PluginTableMsgIn::TableSyncHeader(offset) => { - PluginTableUpdateResult::Command(scrollable::scroll_to(self.header.clone(), offset)) - } + PluginTableMsgIn::TableSyncHeader(offset) => scrollable::scroll_to(self.header.clone(), offset), PluginTableMsgIn::EnabledToggleItem(item) => { match item { EnabledItem::Plugin { enabled, plugin_id } => { - PluginTableUpdateResult::Value(PluginTableMsgOut::SetPluginState { enabled, plugin_id }) + Task::done(PluginTableMsgOut::SetPluginState { enabled, plugin_id }) } EnabledItem::Entrypoint { enabled, plugin_id, entrypoint_id, } => { - PluginTableUpdateResult::Value(PluginTableMsgOut::SetEntrypointState { + Task::done(PluginTableMsgOut::SetEntrypointState { enabled, plugin_id, entrypoint_id, @@ -105,9 +98,9 @@ impl PluginTableState { } } } - PluginTableMsgIn::SelectItem(item) => PluginTableUpdateResult::Value(PluginTableMsgOut::SelectItem(item)), + PluginTableMsgIn::SelectItem(item) => Task::done(PluginTableMsgOut::SelectItem(item)), PluginTableMsgIn::ToggleShowEntrypoints { plugin_id } => { - PluginTableUpdateResult::Value(PluginTableMsgOut::ToggleShowEntrypoints { plugin_id }) + Task::done(PluginTableMsgOut::ToggleShowEntrypoints { plugin_id }) } } }