From b6c6d1cb7b364f8859a8140da916e0d66e605fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Vojinovi=C4=87?= <150025636+git-f0x@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:16:20 +0100 Subject: [PATCH] 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`, providing more flexibility for developers. --- src/app/context_drawer.rs | 31 ++++----- src/app/mod.rs | 4 +- src/widget/context_drawer/mod.rs | 15 ++--- src/widget/context_drawer/widget.rs | 101 +++++++++++----------------- 4 files changed, 60 insertions(+), 91 deletions(-) diff --git a/src/app/context_drawer.rs b/src/app/context_drawer.rs index b33d2ba..ac9d567 100644 --- a/src/app/context_drawer.rs +++ b/src/app/context_drawer.rs @@ -7,7 +7,7 @@ use crate::Element; pub struct ContextDrawer<'a, Message: Clone + 'static> { pub title: Option>, - pub header_actions: Vec>, + pub actions: Option>, pub header: Option>, pub content: Element<'a, Message>, pub footer: Option>, @@ -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>) -> 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(); + + /// App-specific actions at the top-left corner of the context drawer + pub fn actions(mut self, actions: impl Into>) -> 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>) -> Self { self.header = Some(header.into()); self @@ -64,20 +63,16 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { } pub fn map( - 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(), } } } diff --git a/src/app/mod.rs b/src/app/mod.rs index eaf0bae..090698d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -603,7 +603,7 @@ impl 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 ApplicationExt for App { widgets.push( crate::widget::ContextDrawer::new_inner( context.title, - context.header_actions, + context.actions, context.header, context.footer, context.content, diff --git a/src/widget/context_drawer/mod.rs b/src/widget/context_drawer/mod.rs index f162122..107c1ff 100644 --- a/src/widget/context_drawer/mod.rs +++ b/src/widget/context_drawer/mod.rs @@ -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>, - header_actions: Vec>, - header_opt: Option>, - footer_opt: Option>, + actions: Option>, + header: Option>, + footer: Option>, on_close: Message, content: Content, drawer: Drawer, @@ -28,13 +28,6 @@ where Drawer: Into>, { 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, ) } diff --git a/src/widget/context_drawer/widget.rs b/src/widget/context_drawer/widget.rs index c65fe08..cb4b7f9 100644 --- a/src/widget/context_drawer/widget.rs +++ b/src/widget/context_drawer/widget.rs @@ -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( title: Option>, - header_actions: Vec>, - header_opt: Option>, - footer_opt: Option>, + actions: Option>, + header: Option>, + footer: Option>, 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>, - header_actions: Vec>, + actions_opt: Option>, header_opt: Option>, footer_opt: Option>, 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( title: Option>, - header_actions: Vec>, - header_opt: Option>, - footer_opt: Option>, + actions: Option>, + header: Option>, + footer: Option>, content: Content, drawer: Drawer, on_close: Message, @@ -160,15 +149,7 @@ impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { Content: Into>, Drawer: Into>, { - 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( - mut self, + self, on_message: fn(Message) -> Out, ) -> ContextDrawer<'a, Out> { ContextDrawer {