diff --git a/examples/about/Cargo.toml b/examples/about/Cargo.toml index 76a35791..b257999c 100644 --- a/examples/about/Cargo.toml +++ b/examples/about/Cargo.toml @@ -24,4 +24,5 @@ features = [ "wgpu", "single-instance", "multi-window", + "about", ] diff --git a/examples/about/src/main.rs b/examples/about/src/main.rs index 6bc4a4ff..0625e152 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::{self, 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,17 @@ impl cosmic::Application for App { self.update_title() } - fn context_drawer(&self) -> Option> { - if !self.core.window.show_context { - return None; - } - - Some(widget::about(&self.about, Message::Open)) + fn context_drawer(&self) -> Option> { + self.show_about + .then(|| context_drawer::about(&self.about, Message::Open, Message::ToggleAbout)) } /// 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.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..af937168 --- /dev/null +++ b/src/app/context_drawer.rs @@ -0,0 +1,62 @@ +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, +} + +#[cfg(feature = "about")] +pub fn about<'a, Message: Clone + 'static>( + about: &'a crate::widget::about::About, + on_url_press: impl Fn(String) -> Message, + on_close: Message, +) -> ContextDrawer<'a, Message> { + context_drawer(crate::widget::about(about, on_url_press), on_close) +} + +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().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 7a730c8a..e4b59d62 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; @@ -455,22 +455,8 @@ 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>> { + /// Use the [`ApplicationExt::set_show_context`] function for this to take effect. + fn context_drawer(&self) -> Option> { None } @@ -638,11 +624,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); @@ -746,21 +727,21 @@ 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 core.window.context_is_overlay && core.window.show_context { 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)), + crate::widget::context_drawer( + context.title, + context.header_actions, + context.header, + context.footer, + context.on_close, main_content, - context.map(Message::App), + context.content, context_width, ) .apply(|drawer| { @@ -775,29 +756,40 @@ impl ApplicationExt for App { } else { [0, 0, 0, 0] }) - .into(), + .apply(Element::from) + .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)), + context.title, + context.header_actions, + context.header, + context.footer, + context.content, + context.on_close, context_width, ) + .apply(Element::from) + .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 6c55d30c..866c024c 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>,