feat!(app): ContextDrawer return type for context_drawer method

This commit is contained in:
wiiznokes 2024-11-16 03:38:29 +01:00 committed by GitHub
parent aaadf7199e
commit a355a049d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 115 additions and 68 deletions

View file

@ -24,4 +24,5 @@ features = [
"wgpu", "wgpu",
"single-instance", "single-instance",
"multi-window", "multi-window",
"about",
] ]

View file

@ -3,6 +3,7 @@
//! Application API example //! Application API example
use cosmic::app::context_drawer::{self, ContextDrawer};
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::widget::column; use cosmic::iced::widget::column;
use cosmic::iced_core::Size; use cosmic::iced_core::Size;
@ -35,6 +36,7 @@ pub struct App {
core: Core, core: Core,
nav_model: nav_bar::Model, nav_model: nav_bar::Model,
about: About, about: About,
show_about: bool,
} }
/// Implement [`cosmic::Application`] to integrate with COSMIC. /// Implement [`cosmic::Application`] to integrate with COSMIC.
@ -80,6 +82,7 @@ impl cosmic::Application for App {
core, core,
nav_model, nav_model,
about, about,
show_about: false,
}; };
let command = app.update_title(); let command = app.update_title();
@ -98,20 +101,17 @@ impl cosmic::Application for App {
self.update_title() self.update_title()
} }
fn context_drawer(&self) -> Option<Element<Self::Message>> { fn context_drawer(&self) -> Option<ContextDrawer<Self::Message>> {
if !self.core.window.show_context { self.show_about
return None; .then(|| context_drawer::about(&self.about, Message::Open, Message::ToggleAbout))
}
Some(widget::about(&self.about, Message::Open))
} }
/// Handle application events here. /// Handle application events here.
fn update(&mut self, message: Self::Message) -> Task<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
Message::ToggleAbout => { Message::ToggleAbout => {
self.core.window.show_context = !self.core.window.show_context; self.set_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) { Message::Open(url) => match open::that_detached(url) {
Ok(_) => (), Ok(_) => (),

62
src/app/context_drawer.rs Normal file
View file

@ -0,0 +1,62 @@
use std::borrow::Cow;
use crate::Element;
pub struct ContextDrawer<'a, Message: Clone + 'static> {
pub title: Option<Cow<'a, str>>,
pub header_actions: Vec<Element<'a, Message>>,
pub header: Option<Element<'a, Message>>,
pub content: Element<'a, Message>,
pub footer: Option<Element<'a, Message>>,
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<Element<'a, Message>>,
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<Cow<'a, str>>) -> 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<Item = Element<'a, Message>>,
) -> 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<Element<'a, Message>>) -> Self {
self.header = Some(header.into());
self
}
/// Elements placed below the context drawer scrollable
pub fn footer(mut self, footer: impl Into<Element<'a, Message>>) -> Self {
self.footer = Some(footer.into());
self
}
}

View file

@ -26,8 +26,6 @@ pub struct NavBar {
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
#[derive(Clone)] #[derive(Clone)]
pub struct Window { pub struct Window {
/// Label to display as context drawer title.
pub context_title: String,
/// Label to display as header bar title. /// Label to display as header bar title.
pub header_title: String, pub header_title: String,
pub use_template: bool, pub use_template: bool,
@ -127,7 +125,6 @@ impl Default for Core {
}) })
.unwrap_or_default(), .unwrap_or_default(),
window: Window { window: Window {
context_title: String::new(),
header_title: String::new(), header_title: String::new(),
use_template: true, use_template: true,
content_container: true, content_container: true,
@ -188,11 +185,6 @@ impl Core {
self.is_condensed_update(); 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 /// Set header bar title
pub fn set_header_title(&mut self, title: String) { pub fn set_header_title(&mut self, title: String) {
self.window.header_title = title; self.window.header_title = title;

View file

@ -7,6 +7,7 @@
//! example in our repository. //! example in our repository.
pub mod command; pub mod command;
pub mod context_drawer;
mod core; mod core;
pub mod cosmic; pub mod cosmic;
#[cfg(all(feature = "winit", feature = "multi-window"))] #[cfg(all(feature = "winit", feature = "multi-window"))]
@ -54,10 +55,9 @@ pub use self::core::Core;
pub use self::settings::Settings; pub use self::settings::Settings;
use crate::prelude::*; use crate::prelude::*;
use crate::theme::THEME; use crate::theme::THEME;
use crate::widget::{ use crate::widget::{container, horizontal_space, id_container, menu, nav_bar, popover};
container, context_drawer, horizontal_space, id_container, menu, nav_bar, popover,
};
use apply::Apply; use apply::Apply;
use context_drawer::ContextDrawer;
use iced::window; use iced::window;
use iced::{Length, Subscription}; use iced::{Length, Subscription};
pub use message::Message; pub use message::Message;
@ -455,22 +455,8 @@ where
fn init(core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>); fn init(core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>);
/// Displays a context drawer on the side of the application window when `Some`. /// Displays a context drawer on the side of the application window when `Some`.
fn context_drawer(&self) -> Option<Element<Self::Message>> { /// Use the [`ApplicationExt::set_show_context`] function for this to take effect.
None fn context_drawer(&self) -> Option<ContextDrawer<Self::Message>> {
}
/// App-specific actions at the start of the context drawer header
fn context_header_actions(&self) -> Vec<Element<Message<Self::Message>>> {
Vec::new()
}
/// Non-scrolling elements placed below the context drawer title row
fn context_drawer_header(&self) -> Option<Element<Message<Self::Message>>> {
None
}
/// Elements placed below the context drawer scrollable
fn context_drawer_footer(&self) -> Option<Element<Message<Self::Message>>> {
None None
} }
@ -638,11 +624,6 @@ pub trait ApplicationExt: Application {
/// Get the title of a window. /// Get the title of a window.
fn title(&self, id: window::Id) -> &str; 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. /// Set the context drawer visibility.
fn set_show_context(&mut self, show: bool) { fn set_show_context(&mut self, show: bool) {
self.core_mut().set_show_context(show); self.core_mut().set_show_context(show);
@ -746,21 +727,21 @@ impl<App: Application> ApplicationExt for App {
}; };
if self.nav_model().is_none() || core.show_content() { 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 //TODO: reduce duplication
let context_width = core.context_width(has_nav); 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() { if let Some(context) = self.context_drawer() {
widgets.push( widgets.push(
context_drawer( crate::widget::context_drawer(
&core.window.context_title, context.title,
self.context_header_actions(), context.header_actions,
self.context_drawer_header(), context.header,
self.context_drawer_footer(), context.footer,
Message::Cosmic(cosmic::Message::ContextDrawer(false)), context.on_close,
main_content, main_content,
context.map(Message::App), context.content,
context_width, context_width,
) )
.apply(|drawer| { .apply(|drawer| {
@ -775,29 +756,40 @@ impl<App: Application> ApplicationExt for App {
} else { } else {
[0, 0, 0, 0] [0, 0, 0, 0]
}) })
.into(), .apply(Element::from)
.map(Message::App),
); );
} else { } else {
//TODO: container and padding are temporary, until //TODO: container and padding are temporary, until
//the `resize_border` is moved to not cover window content //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 { } else {
//TODO: hide content when out of space //TODO: hide content when out of space
//TODO: container and padding are temporary, until //TODO: container and padding are temporary, until
//the `resize_border` is moved to not cover window content //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() { if let Some(context) = self.context_drawer() {
widgets.push( widgets.push(
crate::widget::ContextDrawer::new_inner( crate::widget::ContextDrawer::new_inner(
&core.window.context_title, context.title,
self.context_header_actions(), context.header_actions,
self.context_drawer_header(), context.header,
self.context_drawer_footer(), context.footer,
context.map(Message::App), context.content,
Message::Cosmic(cosmic::Message::ContextDrawer(false)), context.on_close,
context_width, context_width,
) )
.apply(Element::from)
.map(Message::App)
.apply(container) .apply(container)
.width(context_width) .width(context_width)
.apply(|drawer| { .apply(|drawer| {

View file

@ -6,13 +6,15 @@
mod overlay; mod overlay;
mod widget; mod widget;
use std::borrow::Cow;
pub use widget::ContextDrawer; pub use widget::ContextDrawer;
use crate::Element; use crate::Element;
/// An overlayed widget that attaches a toggleable context drawer to the view. /// An overlayed widget that attaches a toggleable context drawer to the view.
pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>( pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>(
title: &'a str, title: Option<Cow<'a, str>>,
header_actions: Vec<Element<'a, Message>>, header_actions: Vec<Element<'a, Message>>,
header_opt: Option<Element<'a, Message>>, header_opt: Option<Element<'a, Message>>,
footer_opt: Option<Element<'a, Message>>, footer_opt: Option<Element<'a, Message>>,

View file

@ -1,6 +1,8 @@
// Copyright 2023 System76 <info@system76.com> // Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::borrow::Cow;
use crate::widget::{button, column, container, icon, row, scrollable, text, LayerContainer}; use crate::widget::{button, column, container, icon, row, scrollable, text, LayerContainer};
use crate::{Apply, Element, Renderer, Theme}; use crate::{Apply, Element, Renderer, Theme};
@ -24,7 +26,7 @@ pub struct ContextDrawer<'a, Message> {
impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
pub fn new_inner<Drawer>( pub fn new_inner<Drawer>(
title: &'a str, title: Option<Cow<'a, str>>,
header_actions: Vec<Element<'a, Message>>, header_actions: Vec<Element<'a, Message>>,
header_opt: Option<Element<'a, Message>>, header_opt: Option<Element<'a, Message>>,
footer_opt: Option<Element<'a, Message>>, footer_opt: Option<Element<'a, Message>>,
@ -44,12 +46,6 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
.. ..
} = crate::theme::active().cosmic().spacing; } = 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 horizontal_padding = if max_width < 392.0 { space_s } else { space_l };
let header_row = row::with_capacity(3) let header_row = row::with_capacity(3)
@ -60,7 +56,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
.spacing(space_xxs) .spacing(space_xxs)
.width(Length::FillPortion(1)), .width(Length::FillPortion(1)),
) )
.push_maybe(title_opt) .push_maybe(
title.map(|title| text::heading(title).width(Length::FillPortion(1)).center()),
)
.push( .push(
button::text("Close") button::text("Close")
.trailing_icon(icon::from_name("go-next-symbolic")) .trailing_icon(icon::from_name("go-next-symbolic"))
@ -114,7 +112,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
/// Creates an empty [`ContextDrawer`]. /// Creates an empty [`ContextDrawer`].
pub fn new<Content, Drawer>( pub fn new<Content, Drawer>(
title: &'a str, title: Option<Cow<'a, str>>,
header_actions: Vec<Element<'a, Message>>, header_actions: Vec<Element<'a, Message>>,
header_opt: Option<Element<'a, Message>>, header_opt: Option<Element<'a, Message>>,
footer_opt: Option<Element<'a, Message>>, footer_opt: Option<Element<'a, Message>>,