improv(context_drawer): move title out of header row

This moves the title below the header row containing actions and the close button, allowing more room for the title and actions.
Also makes actions an `Element` instead of a `Vec<Element>`, providing more flexibility for developers.
This commit is contained in:
Vukašin Vojinović 2025-11-03 19:16:20 +01:00 committed by Michael Murphy
parent 2299b46862
commit b6c6d1cb7b
4 changed files with 60 additions and 91 deletions

View file

@ -7,7 +7,7 @@ use crate::Element;
pub struct ContextDrawer<'a, Message: Clone + 'static> {
pub title: Option<Cow<'a, str>>,
pub header_actions: Vec<Element<'a, Message>>,
pub actions: Option<Element<'a, Message>>,
pub header: Option<Element<'a, Message>>,
pub content: Element<'a, Message>,
pub footer: Option<Element<'a, Message>>,
@ -29,29 +29,28 @@ pub fn context_drawer<'a, Message: Clone + 'static>(
) -> ContextDrawer<'a, Message> {
ContextDrawer {
title: None,
actions: None,
header: 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
/// Set a context drawer 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();
/// App-specific actions at the top-left corner of the context drawer
pub fn actions(mut self, actions: impl Into<Element<'a, Message>>) -> Self {
self.actions = Some(actions.into());
self
}
/// Non-scrolling elements placed below the context drawer title row
/// Elements placed above the context drawer scrollable
pub fn header(mut self, header: impl Into<Element<'a, Message>>) -> Self {
self.header = Some(header.into());
self
@ -64,20 +63,16 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
}
pub fn map<Out: Clone + 'static>(
mut self,
self,
on_message: fn(Message) -> Out,
) -> ContextDrawer<'a, Out> {
ContextDrawer {
title: self.title,
content: self.content.map(on_message),
actions: self.actions.map(|el| el.map(on_message)),
header: self.header.map(|el| el.map(on_message)),
content: self.content.map(on_message),
footer: self.footer.map(|el| el.map(on_message)),
on_close: on_message(self.on_close),
header_actions: self
.header_actions
.into_iter()
.map(|el| el.map(on_message))
.collect(),
}
}
}

View file

@ -603,7 +603,7 @@ impl<App: Application> ApplicationExt for App {
widgets.push(
crate::widget::context_drawer(
context.title,
context.header_actions,
context.actions,
context.header,
context.footer,
context.on_close,
@ -640,7 +640,7 @@ impl<App: Application> ApplicationExt for App {
widgets.push(
crate::widget::ContextDrawer::new_inner(
context.title,
context.header_actions,
context.actions,
context.header,
context.footer,
context.content,

View file

@ -15,9 +15,9 @@ 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: Option<Cow<'a, str>>,
header_actions: Vec<Element<'a, Message>>,
header_opt: Option<Element<'a, Message>>,
footer_opt: Option<Element<'a, Message>>,
actions: Option<Element<'a, Message>>,
header: Option<Element<'a, Message>>,
footer: Option<Element<'a, Message>>,
on_close: Message,
content: Content,
drawer: Drawer,
@ -28,13 +28,6 @@ where
Drawer: Into<Element<'a, Message>>,
{
ContextDrawer::new(
title,
header_actions,
header_opt,
footer_opt,
content,
drawer,
on_close,
max_width,
title, actions, header, footer, content, drawer, on_close, max_width,
)
}

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: MPL-2.0
use super::overlay::Overlay;
use crate::widget::{LayerContainer, button, column, container, icon, row, scrollable, text};
use crate::widget::{self, LayerContainer, button, column, container, icon, row, scrollable, text};
use crate::{Apply, Element, Renderer, Theme, fl};
use std::borrow::Cow;
@ -25,9 +25,9 @@ pub struct ContextDrawer<'a, Message> {
impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
pub fn new_inner<Drawer>(
title: Option<Cow<'a, str>>,
header_actions: Vec<Element<'a, Message>>,
header_opt: Option<Element<'a, Message>>,
footer_opt: Option<Element<'a, Message>>,
actions: Option<Element<'a, Message>>,
header: Option<Element<'a, Message>>,
footer: Option<Element<'a, Message>>,
drawer: Drawer,
on_close: Message,
max_width: f32,
@ -38,7 +38,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
#[inline(never)]
fn inner<'a, Message: Clone + 'static>(
title: Option<Cow<'a, str>>,
header_actions: Vec<Element<'a, Message>>,
actions_opt: Option<Element<'a, Message>>,
header_opt: Option<Element<'a, Message>>,
footer_opt: Option<Element<'a, Message>>,
drawer: Element<'a, Message>,
@ -53,68 +53,57 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
..
} = crate::theme::spacing();
let (horizontal_padding, title_portion, side_portion) = if max_width < 392.0 {
(space_s, 1, 1)
} else {
(space_l, 2, 1)
};
let horizontal_padding = if max_width < 392.0 { space_s } else { space_l };
let title = title.map(|title| {
text::heading(title)
text::title4(title)
.apply(container)
.center_x(Length::FillPortion(title_portion))
.padding([if actions_opt.is_some() { space_m } else { 0 }, 0, 0, 0])
.width(Length::Fill)
});
let (actions_width, close_width) = if title.is_some() {
(
Length::FillPortion(side_portion),
Length::FillPortion(side_portion),
)
let actions = if let Some(actions) = actions_opt {
actions
.apply(container)
.width(Length::Fill)
.apply(Element::from)
} else {
(Length::Fill, Length::Shrink)
widget::horizontal_space().apply(Element::from)
};
let header_row = row::with_capacity(3)
.width(Length::Fixed(480.0))
let header_row = row::with_capacity(2)
.align_y(Alignment::Center)
.push(
row::with_children(header_actions)
.spacing(space_xxs)
.width(actions_width),
)
.push_maybe(title)
.push(actions)
.push(
button::text(fl!("close"))
.trailing_icon(icon::from_name("go-next-symbolic"))
.on_press(on_close)
.apply(container)
.width(close_width)
.align_x(Alignment::End),
.on_press(on_close),
);
let header = column::with_capacity(2)
.width(Length::Fixed(480.0))
let header_element =
header_opt.map(|el| el.apply(container).padding([space_m, 0, 0, 0]));
let header = column::with_capacity(3)
.align_x(Alignment::Center)
.spacing(space_m)
.padding([space_m, horizontal_padding])
.push(header_row)
.push_maybe(header_opt);
.push_maybe(title)
.push_maybe(header_element);
let footer = footer_opt.map(|element| {
container(element)
.width(Length::Fixed(480.0))
.align_y(Alignment::Center)
.padding([space_xxs, horizontal_padding])
});
let pane = column::with_capacity(3)
.push(header)
.push(
scrollable(container(drawer).padding([
0,
horizontal_padding,
if footer.is_some() { 0 } else { space_l },
horizontal_padding,
]))
.height(Length::Fill)
.width(Length::Shrink),
container(drawer)
.padding([
0,
horizontal_padding,
if footer.is_some() { 0 } else { space_l },
horizontal_padding,
])
.apply(scrollable)
.height(Length::Fill),
)
.push_maybe(footer);
@ -136,9 +125,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
inner(
title,
header_actions,
header_opt,
footer_opt,
actions,
header,
footer,
drawer.into(),
on_close,
max_width,
@ -148,9 +137,9 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
/// Creates an empty [`ContextDrawer`].
pub fn new<Content, Drawer>(
title: Option<Cow<'a, str>>,
header_actions: Vec<Element<'a, Message>>,
header_opt: Option<Element<'a, Message>>,
footer_opt: Option<Element<'a, Message>>,
actions: Option<Element<'a, Message>>,
header: Option<Element<'a, Message>>,
footer: Option<Element<'a, Message>>,
content: Content,
drawer: Drawer,
on_close: Message,
@ -160,15 +149,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
Content: Into<Element<'a, Message>>,
Drawer: Into<Element<'a, Message>>,
{
let drawer = Self::new_inner(
title,
header_actions,
header_opt,
footer_opt,
drawer,
on_close,
max_width,
);
let drawer = Self::new_inner(title, actions, header, footer, drawer, on_close, max_width);
ContextDrawer {
id: None,
@ -188,7 +169,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> {
/// Map the message type of the context drawer to another
#[inline]
pub fn map<Out: Clone + 'static>(
mut self,
self,
on_message: fn(Message) -> Out,
) -> ContextDrawer<'a, Out> {
ContextDrawer {