diff --git a/dash-frontend/assets/gui/t_group_box.xml b/dash-frontend/assets/gui/t_group_box.xml index 27bb873..d91b25e 100644 --- a/dash-frontend/assets/gui/t_group_box.xml +++ b/dash-frontend/assets/gui/t_group_box.xml @@ -2,8 +2,6 @@ + + + + \ No newline at end of file diff --git a/dash-frontend/assets/gui/view/app_launcher.xml b/dash-frontend/assets/gui/view/app_launcher.xml index 4c628d9..8b0b4bd 100644 --- a/dash-frontend/assets/gui/view/app_launcher.xml +++ b/dash-frontend/assets/gui/view/app_launcher.xml @@ -1,7 +1,40 @@ + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dash-frontend/assets/gui/view/popup_window.xml b/dash-frontend/assets/gui/view/popup_window.xml index bb6dc22..8d2cf37 100644 --- a/dash-frontend/assets/gui/view/popup_window.xml +++ b/dash-frontend/assets/gui/view/popup_window.xml @@ -28,14 +28,14 @@ - + diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index 5e18b1d..a657782 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -19,7 +19,7 @@ use crate::{ Tab, TabParams, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings, }, - util::popup_manager::{PopupManager, PopupManagerParams}, + util::popup_manager::{MountPopupParams, PopupManager, PopupManagerParams}, }; pub struct FrontendWidgets { @@ -27,6 +27,19 @@ pub struct FrontendWidgets { pub id_rect_content: WidgetID, } +#[derive(Clone)] +pub struct FrontendTasks(pub Rc>>); + +impl FrontendTasks { + fn new() -> Self { + Self(Rc::new(RefCell::new(VecDeque::new()))) + } + + pub fn push(&self, task: FrontendTask) { + self.0.borrow_mut().push_back(task); + } +} + pub struct Frontend { pub layout: RcLayout, globals: WguiGlobals, @@ -38,7 +51,7 @@ pub struct Frontend { current_tab: Option>, - tasks: VecDeque, + pub tasks: FrontendTasks, ticks: u32, @@ -56,7 +69,8 @@ pub enum FrontendTask { SetTab(TabType), RefreshClock, RefreshBackground, - MountPopup, + MountPopup(MountPopupParams), + RefreshPopupManager, } impl Frontend { @@ -96,8 +110,8 @@ impl Frontend { let rc_layout = layout.as_rc(); - let mut tasks = VecDeque::::new(); - tasks.push_back(FrontendTask::SetTab(TabType::Home)); + let tasks = FrontendTasks::new(); + tasks.push(FrontendTask::SetTab(TabType::Home)); let id_label_time = state.get_widget_id("label_time")?; let id_rect_content = state.get_widget_id("rect_content")?; @@ -129,7 +143,12 @@ impl Frontend { } pub fn update(&mut self, rc_this: &RcFrontend, width: f32, height: f32, timestep_alpha: f32) -> anyhow::Result<()> { - while let Some(task) = self.tasks.pop_front() { + let mut tasks = { + let mut tasks = self.tasks.0.borrow_mut(); + std::mem::take(&mut *tasks) + }; + + while let Some(task) = tasks.pop_front() { self.process_task(rc_this, task)?; } @@ -182,11 +201,19 @@ impl Frontend { Ok(()) } - fn mount_popup(&mut self) -> anyhow::Result<()> { + fn mount_popup(&mut self, params: MountPopupParams) -> anyhow::Result<()> { let mut layout = self.layout.borrow_mut(); + self + .popup_manager + .mount_popup(self.globals.clone(), &mut layout, self.tasks.clone(), params)?; + Ok(()) + } - self.popup_manager.push_popup(self.globals.clone(), &mut layout)?; - + fn refresh_popup_manager(&mut self) -> anyhow::Result<()> { + let mut layout = self.layout.borrow_mut(); + let mut c = layout.start_common(); + self.popup_manager.refresh(c.common().alterables); + c.finish()?; Ok(()) } @@ -217,16 +244,13 @@ impl Frontend { &self.layout } - pub fn push_task(&mut self, task: FrontendTask) { - self.tasks.push_back(task); - } - fn process_task(&mut self, rc_this: &RcFrontend, task: FrontendTask) -> anyhow::Result<()> { match task { FrontendTask::SetTab(tab_type) => self.set_tab(tab_type, rc_this)?, FrontendTask::RefreshClock => self.update_time()?, FrontendTask::RefreshBackground => self.update_background()?, - FrontendTask::MountPopup => self.mount_popup()?, + FrontendTask::MountPopup(params) => self.mount_popup(params)?, + FrontendTask::RefreshPopupManager => self.refresh_popup_manager()?, } Ok(()) } diff --git a/dash-frontend/src/tab/apps.rs b/dash-frontend/src/tab/apps.rs index 63aa2a8..a91b791 100644 --- a/dash-frontend/src/tab/apps.rs +++ b/dash-frontend/src/tab/apps.rs @@ -1,22 +1,34 @@ -use std::{collections::HashMap, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use wgui::{ assets::AssetPath, - components::button::ComponentButton, + components::button::{ButtonClickCallback, ComponentButton}, + globals::WguiGlobals, + i18n::Translation, layout::WidgetPair, parser::{Fetchable, ParseDocumentParams, ParserData, ParserState}, }; use crate::{ - frontend::FrontendTask, + frontend::{FrontendTask, RcFrontend}, tab::{Tab, TabParams, TabType}, - util::{self, desktop_finder::DesktopEntry}, - views, + util::{ + self, + desktop_finder::DesktopEntry, + popup_manager::{MountPopupParams, PopupHandle}, + }, + views::{self, app_launcher}, }; +struct State { + launcher: Option<(PopupHandle, views::app_launcher::View)>, +} + pub struct TabApps { #[allow(dead_code)] - pub state: ParserState, + pub parser_state: ParserState, + + state: Rc>, #[allow(dead_code)] entries: Vec, @@ -35,6 +47,40 @@ struct AppList { data: Vec, } +// called after the user clicks any desktop entry +fn on_app_click( + frontend: RcFrontend, + globals: WguiGlobals, + entry: DesktopEntry, + state: Rc>, +) -> ButtonClickCallback { + Box::new(move |_common, _evt| { + frontend + .borrow_mut() + .tasks + .push(FrontendTask::MountPopup(MountPopupParams { + title: Translation::from_raw_text(&entry.app_name), + on_content: { + let state = state.clone(); + let entry = entry.clone(); + let globals = globals.clone(); + Box::new(move |data| { + let view = app_launcher::View::new(app_launcher::Params { + entry: entry.clone(), + globals: globals.clone(), + layout: data.layout, + parent_id: data.id_content, + })?; + + state.borrow_mut().launcher = Some((data.handle, view)); + Ok(()) + }) + }, + })); + Ok(()) + }) +} + impl TabApps { pub fn new(mut tab_params: TabParams) -> anyhow::Result { let doc_params = &ParseDocumentParams { @@ -43,21 +89,39 @@ impl TabApps { extra: Default::default(), }; - let mut state = wgui::parser::parse_from_assets(doc_params, tab_params.layout, tab_params.parent_id)?; - gtk::init()?; - let entries = util::desktop_finder::find_entries()?; - let app_list_parent = state.fetch_widget(&tab_params.layout.state, "app_list_parent")?; + let frontend = tab_params.frontend.clone(); + let globals = tab_params.globals.clone(); + let state = Rc::new(RefCell::new(State { launcher: None })); + + let mut parser_state = wgui::parser::parse_from_assets(doc_params, tab_params.layout, tab_params.parent_id)?; + let app_list_parent = parser_state.fetch_widget(&tab_params.layout.state, "app_list_parent")?; let mut app_list = AppList::default(); - app_list.mount_entries(&entries, &mut state, doc_params, &mut tab_params, &app_list_parent)?; + app_list.mount_entries( + &entries, + &mut parser_state, + doc_params, + &mut tab_params, + &app_list_parent, + |button, entry| { + // Set up the click handler for the app button + button.on_click(on_app_click( + frontend.clone(), + globals.clone(), + entry.clone(), + state.clone(), + )); + }, + )?; Ok(Self { app_list, - state, + parser_state, entries, + state, }) } } @@ -70,7 +134,7 @@ impl AppList { params: &mut TabParams, list_parent: &WidgetPair, entry: &DesktopEntry, - ) -> anyhow::Result<()> { + ) -> anyhow::Result> { let mut template_params = HashMap::new(); // entry icon @@ -95,20 +159,7 @@ impl AppList { template_params.insert(Rc::from("name"), Rc::from(entry.app_name.as_str())); let data = parser_state.parse_template(doc_params, "AppEntry", params.layout, list_parent.id, template_params)?; - - let button = data.fetch_component_as::("button")?; - - button.on_click({ - let frontend = params.frontend.clone(); - Box::new(move |_common, _evt| { - frontend.borrow_mut().push_task(FrontendTask::MountPopup); - Ok(()) - }) - }); - - self.data.push(data); - - Ok(()) + data.fetch_component_as::("button") } fn mount_entries( @@ -118,9 +169,11 @@ impl AppList { doc_params: &ParseDocumentParams, params: &mut TabParams, list_parent: &WidgetPair, + on_button: impl Fn(Rc, &DesktopEntry), ) -> anyhow::Result<()> { for entry in entries { - self.mount_entry(parser_state, doc_params, params, list_parent, entry)?; + let button = self.mount_entry(parser_state, doc_params, params, list_parent, entry)?; + on_button(button, entry); } Ok(()) } diff --git a/dash-frontend/src/tab/mod.rs b/dash-frontend/src/tab/mod.rs index 2ab13e7..921314e 100644 --- a/dash-frontend/src/tab/mod.rs +++ b/dash-frontend/src/tab/mod.rs @@ -43,7 +43,7 @@ impl TabType { pub fn register_button(this_rc: RcFrontend, btn: &Rc, tab: TabType) { btn.on_click({ Box::new(move |_common, _evt| { - this_rc.borrow_mut().push_task(FrontendTask::SetTab(tab)); + this_rc.borrow_mut().tasks.push(FrontendTask::SetTab(tab)); Ok(()) }) }); diff --git a/dash-frontend/src/tab/settings.rs b/dash-frontend/src/tab/settings.rs index e312805..3189e24 100644 --- a/dash-frontend/src/tab/settings.rs +++ b/dash-frontend/src/tab/settings.rs @@ -74,7 +74,7 @@ impl TabSettings { state.data.fetch_component_as::("cb_am_pm_clock")?, |settings| &mut settings.general.am_pm_clock, Some(|frontend, _| { - frontend.push_task(FrontendTask::RefreshClock); + frontend.tasks.push(FrontendTask::RefreshClock); }), )?; @@ -85,7 +85,7 @@ impl TabSettings { .fetch_component_as::("cb_opaque_background")?, |settings| &mut settings.general.opaque_background, Some(|frontend, _| { - frontend.push_task(FrontendTask::RefreshBackground); + frontend.tasks.push(FrontendTask::RefreshBackground); }), )?; diff --git a/dash-frontend/src/util/popup_manager.rs b/dash-frontend/src/util/popup_manager.rs index 71f3f3f..df94617 100644 --- a/dash-frontend/src/util/popup_manager.rs +++ b/dash-frontend/src/util/popup_manager.rs @@ -8,25 +8,47 @@ use wgui::{ components::button::ComponentButton, event::{EventAlterables, StyleSetRequest}, globals::WguiGlobals, - layout::{Layout, LayoutTask, WidgetID}, + i18n::Translation, + layout::{Layout, LayoutTask, LayoutTasks, WidgetID}, parser::{Fetchable, ParseDocumentParams, ParserState}, taffy::Display, + widget::label::WidgetLabel, }; +use crate::frontend::{FrontendTask, FrontendTasks}; + pub struct PopupManagerParams<'a> { pub globals: WguiGlobals, pub layout: &'a mut Layout, pub parent_id: WidgetID, } -struct MountedPopup { - #[allow(dead_code)] - state: ParserState, - id_root: WidgetID, +pub struct State { + popup_stack: Vec>>, } -pub struct State { - popup_stack: Vec, +pub struct MountedPopup { + #[allow(dead_code)] + state: ParserState, + id_root: WidgetID, // decorations of a popup + pub id_content: WidgetID, // content of a popup + layout_tasks: LayoutTasks, + frontend_tasks: FrontendTasks, +} + +struct MountedPopupState { + mounted_popup: Option, +} + +#[derive(Clone)] +pub struct PopupHandle { + state: Rc>, +} + +impl PopupHandle { + pub fn close(&self) { + self.state.borrow_mut().mounted_popup = None; // Drop will be called + } } pub struct PopupManager { @@ -35,16 +57,41 @@ pub struct PopupManager { parent_id: WidgetID, } -pub struct PushPopupResult { +pub struct PopupContentFuncData<'a> { + pub layout: &'a mut Layout, + pub handle: PopupHandle, pub id_content: WidgetID, } +pub struct MountPopupParams { + pub title: Translation, + pub on_content: Box anyhow::Result<()>>, +} + +impl Drop for MountedPopup { + fn drop(&mut self) { + self.layout_tasks.push(LayoutTask::RemoveWidget(self.id_root)); + self.frontend_tasks.push(FrontendTask::RefreshPopupManager); + } +} + impl State { fn refresh_stack(&mut self, alterables: &mut EventAlterables) { // show only the topmost popup + self.popup_stack.retain(|weak| { + let Some(popup) = weak.upgrade() else { + return false; + }; + popup.borrow_mut().mounted_popup.is_some() + }); + for (idx, popup) in self.popup_stack.iter().enumerate() { + let popup = popup.upgrade().unwrap(); // safe + let popup = popup.borrow_mut(); + let mounted_popup = popup.mounted_popup.as_ref().unwrap(); // safe; + alterables.set_style( - popup.id_root, + mounted_popup.id_root, StyleSetRequest::Display(if idx == self.popup_stack.len() - 1 { Display::Flex } else { @@ -53,15 +100,6 @@ impl State { ); } } - - fn pop_popup(&mut self, alterables: &mut EventAlterables) { - let Some(popup) = self.popup_stack.pop() else { - return; - }; - - alterables.tasks.push(LayoutTask::RemoveWidget(popup.id_root)); - self.refresh_stack(alterables); - } } impl PopupManager { @@ -75,9 +113,22 @@ impl PopupManager { }) } - pub fn push_popup(&mut self, globals: WguiGlobals, layout: &mut Layout) -> anyhow::Result { + pub fn refresh(&self, alterables: &mut EventAlterables) { + let mut state = self.state.borrow_mut(); + state.refresh_stack(alterables); + } + + /// Mount a new popup on top of the existing popup stack. + /// Only the topmost popup is visible. + pub fn mount_popup( + &mut self, + globals: WguiGlobals, + layout: &mut Layout, + frontend_tasks: FrontendTasks, + params: MountPopupParams, + ) -> anyhow::Result<()> { let doc_params = &ParseDocumentParams { - globals, + globals: globals.clone(), path: AssetPath::BuiltIn("gui/view/popup_window.xml"), extra: Default::default(), }; @@ -86,25 +137,51 @@ impl PopupManager { let id_root = state.get_widget_id("root")?; let id_content = state.get_widget_id("content")?; + { + let mut label_title = state.fetch_widget_as::(&layout.state, "popup_title")?; + label_title.set_text_simple(&mut globals.get(), params.title); + } + let but_back = state.fetch_component_as::("but_back")?; + let mounted_popup = MountedPopup { + state, + id_content, + id_root, + layout_tasks: layout.tasks.clone(), + frontend_tasks: frontend_tasks.clone(), + }; + + let mounted_popup_state = MountedPopupState { + mounted_popup: Some(mounted_popup), + }; + + let popup_handle = PopupHandle { + state: Rc::new(RefCell::new(mounted_popup_state)), + }; + + let mut state = self.state.borrow_mut(); + state.popup_stack.push(Rc::downgrade(&popup_handle.state)); + but_back.on_click({ - let state = self.state.clone(); - Box::new(move |common, _evt| { - state.borrow_mut().pop_popup(common.alterables); + let popup_handle = Rc::downgrade(&popup_handle.state); + Box::new(move |_common, _evt| { + if let Some(popup_handle) = popup_handle.upgrade() { + popup_handle.borrow_mut().mounted_popup = None; // will call Drop + } Ok(()) }) }); - let mounted_popup = MountedPopup { state, id_root }; + frontend_tasks.push(FrontendTask::RefreshPopupManager); - let mut state = self.state.borrow_mut(); - state.popup_stack.push(mounted_popup); + // mount user-set popup content + (*params.on_content)(PopupContentFuncData { + layout, + handle: popup_handle.clone(), + id_content, + })?; - let mut c = layout.start_common(); - state.refresh_stack(c.common().alterables); - c.finish()?; - - Ok(PushPopupResult { id_content }) + Ok(()) } } diff --git a/dash-frontend/src/views/app_launcher.rs b/dash-frontend/src/views/app_launcher.rs index 49d169f..f8a2ed9 100644 --- a/dash-frontend/src/views/app_launcher.rs +++ b/dash-frontend/src/views/app_launcher.rs @@ -1,8 +1,12 @@ +use std::{collections::HashMap, rc::Rc}; + use wgui::{ assets::AssetPath, globals::WguiGlobals, + i18n::Translation, layout::{Layout, WidgetID}, - parser::{ParseDocumentParams, ParserState}, + parser::{Fetchable, ParseDocumentParams, ParserState}, + widget::label::WidgetLabel, }; use crate::util::desktop_finder::DesktopEntry; @@ -10,8 +14,7 @@ use crate::util::desktop_finder::DesktopEntry; pub struct View { #[allow(dead_code)] pub state: ParserState, - - entry: DesktopEntry, + //entry: DesktopEntry, } pub struct Params<'a> { @@ -30,9 +33,30 @@ impl View { }; let mut state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?; + let id_icon_parent = state.get_widget_id("icon_parent")?; + + // app icon + if let Some(icon_path) = ¶ms.entry.icon_path { + let mut template_params: HashMap, Rc> = HashMap::new(); + template_params.insert("path".into(), icon_path.as_str().into()); + state.instantiate_template( + doc_params, + "ApplicationIcon", + params.layout, + id_icon_parent, + template_params, + )?; + } + + let mut label_title = state.fetch_widget_as::(¶ms.layout.state, "label_title")?; + + label_title.set_text_simple( + &mut params.globals.get(), + Translation::from_raw_text(¶ms.entry.app_name), + ); Ok(Self { - entry: params.entry, + //entry: params.entry, state, }) } diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 3ed881f..8b5f5bd 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -237,7 +237,6 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul // force-override style style.flex_wrap = taffy::FlexWrap::NoWrap; style.align_items = Some(AlignItems::Center); - style.justify_content = Some(JustifyContent::Center); // make checkbox interaction box larger by setting padding and negative margin style.padding = taffy::Rect {