feat!(app): ContextDrawer return type for context_drawer method
This commit is contained in:
parent
aaadf7199e
commit
a355a049d9
7 changed files with 115 additions and 68 deletions
|
|
@ -24,4 +24,5 @@ features = [
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"single-instance",
|
"single-instance",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
|
"about",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -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
62
src/app/context_drawer.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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| {
|
||||||
|
|
|
||||||
|
|
@ -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>>,
|
||||||
|
|
|
||||||
|
|
@ -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>>,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue