From 62c4282383b0b5ad927ee61056b57a28e3403894 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:54:21 +0100 Subject: [PATCH] feat: ContextDrawer struct in Application trait --- examples/about/src/main.rs | 15 ++-- src/app/context_drawer.rs | 53 ++++++++++++++ src/app/core.rs | 8 --- src/app/mod.rs | 105 +++++++++++++--------------- src/widget/context_drawer/mod.rs | 4 +- src/widget/context_drawer/widget.rs | 16 ++--- src/widget/mod.rs | 4 +- 7 files changed, 121 insertions(+), 84 deletions(-) create mode 100644 src/app/context_drawer.rs diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index 6bc4a4ff..7ef14d36 100644 --- a/examples/about/src/main.rs +++ b/examples/about/src/main.rs @@ -3,6 +3,7 @@ //! Application API example +use cosmic::app::context_drawer::{context_drawer, ContextDrawer}; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::widget::column; use cosmic::iced_core::Size; @@ -35,6 +36,7 @@ pub struct App { core: Core, nav_model: nav_bar::Model, about: About, + show_about: bool, } /// Implement [`cosmic::Application`] to integrate with COSMIC. @@ -80,6 +82,7 @@ impl cosmic::Application for App { core, nav_model, about, + show_about: false, }; let command = app.update_title(); @@ -98,20 +101,18 @@ impl cosmic::Application for App { self.update_title() } - fn context_drawer(&self) -> Option> { - if !self.core.window.show_context { - return None; + fn context_drawer(&self) -> Option> { + match self.show_about { + true => Some(context_drawer(widget::about(&self.about, Message::Open), Message::ToggleAbout)), + false => None, } - - Some(widget::about(&self.about, Message::Open)) } /// Handle application events here. fn update(&mut self, message: Self::Message) -> Task { match message { Message::ToggleAbout => { - self.core.window.show_context = !self.core.window.show_context; - self.core.set_show_context(self.core.window.show_context) + self.show_about = !self.show_about; } Message::Open(url) => match open::that_detached(url) { Ok(_) => (), diff --git a/src/app/context_drawer.rs b/src/app/context_drawer.rs new file mode 100644 index 00000000..5df60c13 --- /dev/null +++ b/src/app/context_drawer.rs @@ -0,0 +1,53 @@ +use std::borrow::Cow; + +use crate::Element; + +pub struct ContextDrawer<'a, Message: Clone + 'static> { + pub title: Option>, + pub header_actions: Vec>, + pub header: Option>, + pub content: Element<'a, Message>, + pub footer: Option>, + pub on_close: Message, +} + +pub fn context_drawer<'a, Message: Clone + 'static>( + content: impl Into>, + on_close: Message, +) -> ContextDrawer<'a, Message> { + ContextDrawer { + title: None, + content: content.into(), + header_actions: vec![], + footer: None, + on_close, + header: None, + } +} + +impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { + /// Set a context drawer header title + pub fn title(mut self, title: impl Into>) -> Self { + self.title = Some(title.into()); + self + } + /// App-specific actions at the start of the context drawer header + pub fn header_actions>>( + mut self, + header_actions: impl IntoIterator, + ) -> Self { + self.header_actions = header_actions.into_iter().map(|a| a.into()).collect(); + self + } + /// Non-scrolling elements placed below the context drawer title row + pub fn header(mut self, header: impl Into>) -> Self { + self.header = Some(header.into()); + self + } + + /// Elements placed below the context drawer scrollable + pub fn footer(mut self, footer: impl Into>) -> Self { + self.footer = Some(footer.into()); + self + } +} diff --git a/src/app/core.rs b/src/app/core.rs index c805188b..f090cdc4 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -26,8 +26,6 @@ pub struct NavBar { #[allow(clippy::struct_excessive_bools)] #[derive(Clone)] pub struct Window { - /// Label to display as context drawer title. - pub context_title: String, /// Label to display as header bar title. pub header_title: String, pub use_template: bool, @@ -127,7 +125,6 @@ impl Default for Core { }) .unwrap_or_default(), window: Window { - context_title: String::new(), header_title: String::new(), use_template: true, content_container: true, @@ -188,11 +185,6 @@ impl Core { self.is_condensed_update(); } - /// Set context drawer header title - pub fn set_context_title(&mut self, title: String) { - self.window.context_title = title; - } - /// Set header bar title pub fn set_header_title(&mut self, title: String) { self.window.header_title = title; diff --git a/src/app/mod.rs b/src/app/mod.rs index b9166e68..b921c706 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -7,6 +7,7 @@ //! example in our repository. pub mod command; +pub mod context_drawer; mod core; pub mod cosmic; #[cfg(all(feature = "winit", feature = "multi-window"))] @@ -54,10 +55,9 @@ pub use self::core::Core; pub use self::settings::Settings; use crate::prelude::*; use crate::theme::THEME; -use crate::widget::{ - container, context_drawer, horizontal_space, id_container, menu, nav_bar, popover, -}; +use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover}; use apply::Apply; +use context_drawer::ContextDrawer; use iced::window; use iced::{Length, Subscription}; pub use message::Message; @@ -454,22 +454,7 @@ where fn init(core: Core, flags: Self::Flags) -> (Self, Task); /// Displays a context drawer on the side of the application window when `Some`. - fn context_drawer(&self) -> Option> { - None - } - - /// App-specific actions at the start of the context drawer header - fn context_header_actions(&self) -> Vec>> { - Vec::new() - } - - /// Non-scrolling elements placed below the context drawer title row - fn context_drawer_header(&self) -> Option>> { - None - } - - /// Elements placed below the context drawer scrollable - fn context_drawer_footer(&self) -> Option>> { + fn context_drawer(&self) -> Option> { None } @@ -637,11 +622,6 @@ pub trait ApplicationExt: Application { /// Get the title of a window. fn title(&self, id: window::Id) -> &str; - /// Set the context drawer title. - fn set_context_title(&mut self, title: String) { - self.core_mut().set_context_title(title); - } - /// Set the context drawer visibility. fn set_show_context(&mut self, show: bool) { self.core_mut().set_show_context(show); @@ -745,58 +725,69 @@ impl ApplicationExt for App { }; if self.nav_model().is_none() || core.show_content() { - let main_content = self.view().map(Message::App); + let main_content = self.view(); //TODO: reduce duplication let context_width = core.context_width(has_nav); if core.window.context_is_overlay { if let Some(context) = self.context_drawer() { widgets.push( - context_drawer( - &core.window.context_title, - self.context_header_actions(), - self.context_drawer_header(), - self.context_drawer_footer(), - Message::Cosmic(cosmic::Message::ContextDrawer(false)), - main_content, - context.map(Message::App), - context_width, + Element::from( + crate::widget::context_drawer( + context.title, + context.header_actions, + context.header, + context.footer, + context.on_close, + main_content, + context.content, + context_width, + ) + .apply(|drawer| { + Element::from(id_container( + drawer, + iced_core::id::Id::new("COSMIC_context_drawer"), + )) + }) + .apply(container) + .padding(if content_container { + [0, 8, 8, 0] + } else { + [0, 0, 0, 0] + }), ) - .apply(|drawer| { - Element::from(id_container( - drawer, - iced_core::id::Id::new("COSMIC_context_drawer"), - )) - }) - .apply(container) - .padding(if content_container { - [0, 8, 8, 0] - } else { - [0, 0, 0, 0] - }) - .into(), + .map(Message::App), ); } else { //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content - widgets.push(container(main_content).padding(main_content_padding).into()); + widgets.push( + container(main_content.map(Message::App)) + .padding(main_content_padding) + .into(), + ); } } else { //TODO: hide content when out of space //TODO: container and padding are temporary, until //the `resize_border` is moved to not cover window content - widgets.push(container(main_content).padding(main_content_padding).into()); + widgets.push( + container(main_content.map(Message::App)) + .padding(main_content_padding) + .into(), + ); if let Some(context) = self.context_drawer() { widgets.push( - crate::widget::ContextDrawer::new_inner( - &core.window.context_title, - self.context_header_actions(), - self.context_drawer_header(), - self.context_drawer_footer(), - context.map(Message::App), - Message::Cosmic(cosmic::Message::ContextDrawer(false)), + Element::from(crate::widget::ContextDrawer::new_inner( + context.title, + context.header_actions, + context.header, + context.footer, + context.content, + context.on_close, context_width, - ) + )) + .map(Message::App) .apply(container) .width(context_width) .apply(|drawer| { diff --git a/src/widget/context_drawer/mod.rs b/src/widget/context_drawer/mod.rs index 5a90fc8a..f1621220 100644 --- a/src/widget/context_drawer/mod.rs +++ b/src/widget/context_drawer/mod.rs @@ -6,13 +6,15 @@ mod overlay; mod widget; +use std::borrow::Cow; + pub use widget::ContextDrawer; use crate::Element; /// An overlayed widget that attaches a toggleable context drawer to the view. pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>( - title: &'a str, + title: Option>, header_actions: Vec>, header_opt: Option>, footer_opt: Option>, diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index 759749fe..30738e83 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -1,6 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +use std::borrow::Cow; + use crate::widget::{button, column, container, icon, row, scrollable, text, LayerContainer}; use crate::{Apply, Element, Renderer, Theme}; @@ -24,7 +26,7 @@ pub struct ContextDrawer<'a, Message> { impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { pub fn new_inner( - title: &'a str, + title: Option>, header_actions: Vec>, header_opt: Option>, footer_opt: Option>, @@ -44,12 +46,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { .. } = crate::theme::active().cosmic().spacing; - let title_opt = if title.is_empty() { - None - } else { - Some(text::heading(title).width(Length::FillPortion(1)).center()) - }; - let horizontal_padding = if max_width < 392.0 { space_s } else { space_l }; let header_row = row::with_capacity(3) @@ -60,7 +56,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { .spacing(space_xxs) .width(Length::FillPortion(1)), ) - .push_maybe(title_opt) + .push_maybe( + title.map(|title| text::heading(title).width(Length::FillPortion(1)).center()), + ) .push( button::text("Close") .trailing_icon(icon::from_name("go-next-symbolic")) @@ -114,7 +112,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { /// Creates an empty [`ContextDrawer`]. pub fn new( - title: &'a str, + title: Option>, header_actions: Vec>, header_opt: Option>, footer_opt: Option>, diff --git a/src/widget/mod.rs b/src/widget/mod.rs index b84e54ef..6ac7c8e5 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -119,9 +119,9 @@ pub use color_picker::{ColorPicker, ColorPickerModel}; #[doc(inline)] pub use iced::widget::qr_code; -pub mod context_drawer; +pub(crate) mod context_drawer; #[doc(inline)] -pub use context_drawer::{context_drawer, ContextDrawer}; +pub(crate) use context_drawer::{context_drawer, ContextDrawer}; #[doc(inline)] pub use column::{column, Column};