Add support for themed application dialog
This commit is contained in:
parent
94905c5201
commit
16aaf10b56
5 changed files with 200 additions and 56 deletions
|
|
@ -44,7 +44,7 @@ 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::{context_drawer, nav_bar};
|
use crate::widget::{context_drawer, nav_bar, popover};
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use iced::Subscription;
|
use iced::Subscription;
|
||||||
#[cfg(all(feature = "winit", feature = "multi-window"))]
|
#[cfg(all(feature = "winit", feature = "multi-window"))]
|
||||||
|
|
@ -418,6 +418,11 @@ where
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Displays a dialog in the center of the application window when `Some`.
|
||||||
|
fn dialog(&self) -> Option<Element<Self::Message>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Attaches elements to the start section of the header.
|
/// Attaches elements to the start section of the header.
|
||||||
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
|
@ -659,7 +664,7 @@ impl<App: Application> ApplicationExt for App {
|
||||||
content_row.into()
|
content_row.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
let view_element: Element<_> = crate::widget::column::with_capacity(2)
|
let view_column = crate::widget::column::with_capacity(2)
|
||||||
.push_maybe(if core.window.show_headerbar {
|
.push_maybe(if core.window.show_headerbar {
|
||||||
Some({
|
Some({
|
||||||
let mut header = crate::widget::header_bar()
|
let mut header = crate::widget::header_bar()
|
||||||
|
|
@ -707,8 +712,16 @@ impl<App: Application> ApplicationExt for App {
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
// The content element contains every element beneath the header.
|
// The content element contains every element beneath the header.
|
||||||
.push(content)
|
.push(content);
|
||||||
.into();
|
|
||||||
|
// Show any current dialog on top and centered over the view content
|
||||||
|
// We have to use a popover even without a dialog to keep the tree from changing
|
||||||
|
let mut popover = popover(view_column);
|
||||||
|
if let Some(dialog) = self.dialog() {
|
||||||
|
popover = popover.popup(dialog.map(Message::App));
|
||||||
|
}
|
||||||
|
|
||||||
|
let view_element: Element<_> = popover.into();
|
||||||
view_element.debug(core.debug)
|
view_element.debug(core.debug)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -530,11 +530,9 @@ impl container::StyleSheet for Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
Container::Dialog => container::Appearance {
|
Container::Dialog => container::Appearance {
|
||||||
icon_color: Some(Color::from(cosmic.primary.component.on)),
|
icon_color: Some(Color::from(cosmic.primary.on)),
|
||||||
text_color: Some(Color::from(cosmic.primary.component.on)),
|
text_color: Some(Color::from(cosmic.primary.on)),
|
||||||
background: Some(iced::Background::Color(
|
background: Some(iced::Background::Color(cosmic.primary.base.into())),
|
||||||
cosmic.primary.component.base.into(),
|
|
||||||
)),
|
|
||||||
border: Border {
|
border: Border {
|
||||||
color: cosmic.primary.divider.into(),
|
color: cosmic.primary.divider.into(),
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
|
|
|
||||||
128
src/widget/dialog.rs
Normal file
128
src/widget/dialog.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
use crate::{iced::Length, style, theme, widget, Element};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
pub fn dialog<'a, Message>(title: impl Into<Cow<'a, str>>) -> Dialog<'a, Message> {
|
||||||
|
Dialog::new(title)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dialog<'a, Message> {
|
||||||
|
title: Cow<'a, str>,
|
||||||
|
icon: Option<Element<'a, Message>>,
|
||||||
|
body: Option<Cow<'a, str>>,
|
||||||
|
controls: Vec<Element<'a, Message>>,
|
||||||
|
primary_action: Option<(Cow<'a, str>, Message, bool)>,
|
||||||
|
secondary_action: Option<(Cow<'a, str>, Message)>,
|
||||||
|
tertiary_action: Option<(Cow<'a, str>, Message)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message> Dialog<'a, Message> {
|
||||||
|
pub fn new(title: impl Into<Cow<'a, str>>) -> Self {
|
||||||
|
Self {
|
||||||
|
title: title.into(),
|
||||||
|
icon: None,
|
||||||
|
body: None,
|
||||||
|
controls: Vec::new(),
|
||||||
|
primary_action: None,
|
||||||
|
secondary_action: None,
|
||||||
|
tertiary_action: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(mut self, icon: impl Into<Element<'a, Message>>) -> Self {
|
||||||
|
self.icon = Some(icon.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn body(mut self, body: impl Into<Cow<'a, str>>) -> Self {
|
||||||
|
self.body = Some(body.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn control(mut self, control: impl Into<Element<'a, Message>>) -> Self {
|
||||||
|
self.controls.push(control.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn primary_action(mut self, name: impl Into<Cow<'a, str>>, message: Message) -> Self {
|
||||||
|
self.primary_action = Some((name.into(), message, false));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn primary_action_destructive(
|
||||||
|
mut self,
|
||||||
|
name: impl Into<Cow<'a, str>>,
|
||||||
|
message: Message,
|
||||||
|
) -> Self {
|
||||||
|
self.primary_action = Some((name.into(), message, true));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn secondary_action(mut self, name: impl Into<Cow<'a, str>>, message: Message) -> Self {
|
||||||
|
self.secondary_action = Some((name.into(), message));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tertiary_action(mut self, name: impl Into<Cow<'a, str>>, message: Message) -> Self {
|
||||||
|
self.tertiary_action = Some((name.into(), message));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: Clone + 'static> From<Dialog<'a, Message>> for Element<'a, Message> {
|
||||||
|
fn from(dialog: Dialog<'a, Message>) -> Self {
|
||||||
|
let cosmic_theme::Spacing {
|
||||||
|
space_l,
|
||||||
|
space_m,
|
||||||
|
space_s,
|
||||||
|
space_xxs,
|
||||||
|
..
|
||||||
|
} = theme::THEME.with(|theme_cell| {
|
||||||
|
let theme = theme_cell.borrow();
|
||||||
|
let theme = theme.cosmic();
|
||||||
|
theme.spacing
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut content_col = widget::column::with_capacity(3 + dialog.controls.len() * 2);
|
||||||
|
content_col = content_col.push(widget::text::title3(dialog.title));
|
||||||
|
if let Some(body) = dialog.body {
|
||||||
|
content_col = content_col.push(widget::vertical_space(Length::Fixed(space_xxs.into())));
|
||||||
|
content_col = content_col.push(widget::text::body(body));
|
||||||
|
}
|
||||||
|
for control in dialog.controls {
|
||||||
|
content_col = content_col.push(widget::vertical_space(Length::Fixed(space_s.into())));
|
||||||
|
content_col = content_col.push(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut content_row = widget::row::with_capacity(2).spacing(space_s);
|
||||||
|
if let Some(icon) = dialog.icon {
|
||||||
|
content_row = content_row.push(icon);
|
||||||
|
}
|
||||||
|
content_row = content_row.push(content_col);
|
||||||
|
|
||||||
|
let mut button_row = widget::row::with_capacity(4).spacing(space_xxs);
|
||||||
|
if let Some((name, message)) = dialog.tertiary_action {
|
||||||
|
button_row = button_row.push(widget::button::text(name).on_press(message));
|
||||||
|
}
|
||||||
|
button_row = button_row.push(widget::horizontal_space(Length::Fill));
|
||||||
|
if let Some((name, message)) = dialog.secondary_action {
|
||||||
|
button_row = button_row.push(widget::button::standard(name).on_press(message));
|
||||||
|
}
|
||||||
|
if let Some((name, message, destructive)) = dialog.primary_action {
|
||||||
|
if destructive {
|
||||||
|
button_row = button_row.push(widget::button::destructive(name).on_press(message));
|
||||||
|
} else {
|
||||||
|
button_row = button_row.push(widget::button::suggested(name).on_press(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Element::from(
|
||||||
|
widget::container(
|
||||||
|
widget::column::with_children(vec![content_row.into(), button_row.into()])
|
||||||
|
.spacing(space_l),
|
||||||
|
)
|
||||||
|
.style(style::Container::Dialog)
|
||||||
|
.padding(space_m)
|
||||||
|
.width(Length::Fixed(570.0)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -58,6 +58,9 @@ pub mod column {
|
||||||
pub mod cosmic_container;
|
pub mod cosmic_container;
|
||||||
pub use cosmic_container::LayerContainer;
|
pub use cosmic_container::LayerContainer;
|
||||||
|
|
||||||
|
pub mod dialog;
|
||||||
|
pub use dialog::{dialog, Dialog};
|
||||||
|
|
||||||
/// An element to distinguish a boundary between two elements.
|
/// An element to distinguish a boundary between two elements.
|
||||||
pub mod divider {
|
pub mod divider {
|
||||||
/// Horizontal variant of a divider.
|
/// Horizontal variant of a divider.
|
||||||
|
|
|
||||||
|
|
@ -18,42 +18,36 @@ pub use iced_style::container::{Appearance, StyleSheet};
|
||||||
|
|
||||||
pub fn popover<'a, Message, Renderer>(
|
pub fn popover<'a, Message, Renderer>(
|
||||||
content: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
|
content: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
|
||||||
popup: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
|
|
||||||
) -> Popover<'a, Message, Renderer> {
|
) -> Popover<'a, Message, Renderer> {
|
||||||
Popover::new(content, popup)
|
Popover::new(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Popover<'a, Message, Renderer> {
|
pub struct Popover<'a, Message, Renderer> {
|
||||||
content: Element<'a, Message, crate::Theme, Renderer>,
|
content: Element<'a, Message, crate::Theme, Renderer>,
|
||||||
// XXX Avoid refcell; improve iced overlay API?
|
// XXX Avoid refcell; improve iced overlay API?
|
||||||
popup: RefCell<Element<'a, Message, crate::Theme, Renderer>>,
|
popup: Option<RefCell<Element<'a, Message, crate::Theme, Renderer>>>,
|
||||||
position: Option<Point>,
|
position: Option<Point>,
|
||||||
show_popup: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Popover<'a, Message, Renderer> {
|
impl<'a, Message, Renderer> Popover<'a, Message, Renderer> {
|
||||||
pub fn new(
|
pub fn new(content: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
|
||||||
content: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
|
|
||||||
popup: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
content: content.into(),
|
content: content.into(),
|
||||||
popup: RefCell::new(popup.into()),
|
popup: None,
|
||||||
position: None,
|
position: None,
|
||||||
show_popup: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn popup(mut self, popup: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
|
||||||
|
self.popup = Some(RefCell::new(popup.into()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn position(mut self, position: Point) -> Self {
|
pub fn position(mut self, position: Point) -> Self {
|
||||||
self.position = Some(position);
|
self.position = Some(position);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_popup(mut self, show_popup: bool) -> Self {
|
|
||||||
self.show_popup = show_popup;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO More options for positioning similar to GdkPopup, xdg_popup
|
// TODO More options for positioning similar to GdkPopup, xdg_popup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,11 +57,19 @@ where
|
||||||
Renderer: iced_core::Renderer,
|
Renderer: iced_core::Renderer,
|
||||||
{
|
{
|
||||||
fn children(&self) -> Vec<Tree> {
|
fn children(&self) -> Vec<Tree> {
|
||||||
vec![Tree::new(&self.content), Tree::new(&*self.popup.borrow())]
|
if let Some(popup) = &self.popup {
|
||||||
|
vec![Tree::new(&self.content), Tree::new(&*popup.borrow())]
|
||||||
|
} else {
|
||||||
|
vec![Tree::new(&self.content)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
tree.diff_children(&mut [&mut self.content, &mut self.popup.borrow_mut()]);
|
if let Some(popup) = &mut self.popup {
|
||||||
|
tree.diff_children(&mut [&mut self.content, &mut popup.borrow_mut()]);
|
||||||
|
} else {
|
||||||
|
tree.diff_children(&mut [&mut self.content]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> Size<Length> {
|
fn size(&self) -> Size<Length> {
|
||||||
|
|
@ -163,37 +165,37 @@ where
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
_renderer: &Renderer,
|
_renderer: &Renderer,
|
||||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||||
if !self.show_popup {
|
if let Some(popup) = &self.popup {
|
||||||
return None;
|
let bounds = layout.bounds();
|
||||||
|
let (position, centered) = match self.position {
|
||||||
|
Some(relative) => (
|
||||||
|
bounds.position() + Vector::new(relative.x, relative.y),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
None => {
|
||||||
|
// Set position to center
|
||||||
|
(
|
||||||
|
Point::new(
|
||||||
|
bounds.x + bounds.width / 2.0,
|
||||||
|
bounds.y + bounds.height / 2.0,
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// XXX needed to use RefCell to get &mut for popup element
|
||||||
|
Some(overlay::Element::new(
|
||||||
|
position,
|
||||||
|
Box::new(Overlay {
|
||||||
|
tree: &mut tree.children[1],
|
||||||
|
content: popup,
|
||||||
|
centered,
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
let bounds = layout.bounds();
|
|
||||||
let (position, centered) = match self.position {
|
|
||||||
Some(relative) => (
|
|
||||||
bounds.position() + Vector::new(relative.x, relative.y),
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
None => {
|
|
||||||
// Set position to center
|
|
||||||
(
|
|
||||||
Point::new(
|
|
||||||
bounds.x + bounds.width / 2.0,
|
|
||||||
bounds.y + bounds.height / 2.0,
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// XXX needed to use RefCell to get &mut for popup element
|
|
||||||
Some(overlay::Element::new(
|
|
||||||
position,
|
|
||||||
Box::new(Overlay {
|
|
||||||
tree: &mut tree.children[1],
|
|
||||||
content: &self.popup,
|
|
||||||
centered,
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue