Use conditional widget to switch between plugin and search view

This commit is contained in:
Exidex 2023-08-13 15:51:13 +02:00
parent 9bd13fa5c8
commit 1401ef489f

View file

@ -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<SearchListEntry, gtk::SingleSelection>,
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::<SearchListEntry>();
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::<SearchListEntry>();
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<Self>) {
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::<gtk::Widget>());
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: &gtk::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::<gtk::Widget>());
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);
}