diff --git a/src/gtk/gui/mod.rs b/src/gtk/gui/mod.rs index f6ef88c..4776804 100644 --- a/src/gtk/gui/mod.rs +++ b/src/gtk/gui/mod.rs @@ -2,7 +2,7 @@ use std::path::Path; use gtk::gdk::Key; use gtk::glib; use gtk::prelude::*; -use relm4::{ComponentParts, ComponentSender, SimpleComponent}; +use relm4::{ComponentParts, ComponentSender, RelmRemoveAllExt, SimpleComponent}; use relm4::typed_list_view::TypedListView; use search_entry::SearchListEntry; @@ -20,6 +20,15 @@ pub struct AppModel { search: SearchHandle, list: TypedListView, plugin_manager: PluginManager, + state: AppState, +} + +enum AppState { + SearchView, + PluginView { + plugin_id: String, + entrypoint_id: String, + } } pub struct AppInput { @@ -31,9 +40,11 @@ pub struct AppInput { #[derive(Debug)] pub enum AppMsg { OpenView { + plugin_container: gtk::Box, plugin_id: String, entrypoint_id: String, }, + CloseCurrentView, PromptChanged { value: String } @@ -55,43 +66,60 @@ impl SimpleComponent for AppModel { set_default_height: 400, set_default_width: 650, - gtk::Box::new(gtk::Orientation::Vertical, 0) { - #[name = "search"] - gtk::Entry { - set_margin_top: SPACING, - set_margin_bottom: SPACING, - set_margin_start: SPACING, - set_margin_end: SPACING, - connect_changed[sender] => move |entry| { - sender.input(AppMsg::PromptChanged { - value: entry.buffer().text().to_string(), - }); + match model.state { + AppState::SearchView => { + gtk::Box::new(gtk::Orientation::Vertical, 0) { + #[name = "search"] + gtk::Entry { + set_margin_top: SPACING, + set_margin_bottom: SPACING, + set_margin_start: SPACING, + set_margin_end: SPACING, + connect_changed[sender] => move |entry| { + sender.input(AppMsg::PromptChanged { + value: entry.buffer().text().to_string(), + }); + } + }, + + gtk::Separator::new(gtk::Orientation::Horizontal), + + gtk::ScrolledWindow { + set_hscrollbar_policy: gtk::PolicyType::Never, + set_vexpand: true, + set_margin_top: SPACING, + set_margin_bottom: SPACING, + set_margin_start: SPACING, + set_margin_end: SPACING, + + #[local_ref] + list_view -> gtk::ListView { + connect_activate[sender, plugin_container] => move |list_view, pos| { + let item = get_item_from_list_view(list_view, pos); + let item = item.borrow::(); + + sender.input(AppMsg::OpenView { + plugin_container: plugin_container.clone(), + plugin_id: item.plugin_id().to_owned(), + entrypoint_id: item.entrypoint_id().to_owned() + }); + } + }, + }, } }, - - gtk::Separator::new(gtk::Orientation::Horizontal), - - gtk::ScrolledWindow { - set_hscrollbar_policy: gtk::PolicyType::Never, - set_vexpand: true, - set_margin_top: SPACING, - set_margin_bottom: SPACING, - set_margin_start: SPACING, - set_margin_end: SPACING, - - #[local_ref] - list_view -> gtk::ListView { - connect_activate[sender] => move |list_view, pos| { - let item = get_item_from_list_view(list_view, pos); - let item = item.borrow::(); - - sender.input(AppMsg::OpenView { - plugin_id: item.plugin_id().to_owned(), - entrypoint_id: item.entrypoint_id().to_owned() - }); + AppState::PluginView { .. } => { + #[name = "plugin_container"] + gtk::Box::new(gtk::Orientation::Vertical, 0) { + add_controller = gtk::EventControllerKey { + connect_key_released[sender] => move |_controller, key, _keycode, _state| { + if key == Key::q { + sender.input(AppMsg::CloseCurrentView); + } + } } - }, - }, + } + } } } } @@ -124,6 +152,7 @@ impl SimpleComponent for AppModel { search, list, plugin_manager, + state: AppState::SearchView }; let list_view = &model.list.view; @@ -135,13 +164,30 @@ impl SimpleComponent for AppModel { fn update(&mut self, message: Self::Input, _sender: ComponentSender) { match message { - AppMsg::OpenView { plugin_id, entrypoint_id} => { - create_list_view( - self.plugin_manager.clone(), - self.window.clone(), - &plugin_id, - &entrypoint_id, - ) + AppMsg::OpenView { plugin_container, plugin_id, entrypoint_id} => { + plugin_container.remove_all(); + + let mut ui_context = self.plugin_manager.ui_context(&plugin_id).unwrap(); + ui_context.set_current_container(plugin_container.clone().upcast::()); + ui_context.send_event(UiEvent::ViewCreated { view_name: entrypoint_id.to_owned() }); + + self.state = AppState::PluginView { + plugin_id: plugin_id.clone(), + entrypoint_id: entrypoint_id.clone() + }; + } + AppMsg::CloseCurrentView => { + match &self.state { + AppState::SearchView => { + panic!("invalid state"); + } + AppState::PluginView { plugin_id, .. } => { + let mut ui_context = self.plugin_manager.ui_context(&plugin_id).unwrap(); + ui_context.send_event(UiEvent::ViewDestroyed); + + self.state = AppState::SearchView; + } + } } AppMsg::PromptChanged { value } => { let result: Vec<_> = self.search.search(&value).unwrap() @@ -174,27 +220,3 @@ fn get_item_from_list_view(list_view: >k::ListView, position: u32) -> glib::Bo return object; } - -fn create_list_view(mut plugin_manager: PluginManager, window: gtk::ApplicationWindow, plugin_id: &str, entrypoint_id: &str) { - // FIXME this is ugly, but relm's conditional widgets seem broken when used on enums - let mut ui_context = plugin_manager.ui_context(&plugin_id).unwrap(); - - let prev_child = window.child().unwrap().clone(); - - let container = gtk::Box::new(gtk::Orientation::Vertical, 0); - window.set_child(Some(&container.clone())); - ui_context.set_current_container(container.clone().upcast::()); - ui_context.send_event(UiEvent::ViewCreated { view_name: entrypoint_id.to_owned() }); - - let window = window.clone(); - let controller = gtk::EventControllerKey::new(); - controller.connect_key_pressed(move |_controller, key, _keycode, _state| { - if key == Key::q { - ui_context.send_event(UiEvent::ViewDestroyed); - window.set_child(Some(&prev_child)); - } - - gtk::Inhibit(false) - }); - container.add_controller(controller); -}