// Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 use crate::widget::{ button, column, container, icon, row, scrollable, text, LayerContainer, Space, }; use crate::{Apply, Element, Renderer, Theme}; use super::overlay::Overlay; use iced_core::event::{self, Event}; use iced_core::widget::{Operation, Tree}; use iced_core::Alignment; use iced_core::{ layout, mouse, overlay as iced_overlay, renderer, Clipboard, Layout, Length, Rectangle, Shell, Vector, Widget, }; #[must_use] pub struct ContextDrawer<'a, Message> { id: Option, content: Element<'a, Message>, drawer: Element<'a, Message>, on_close: Option, } impl<'a, Message: Clone + 'static> ContextDrawer<'a, Message> { pub fn new_inner( header: &'a str, drawer: Drawer, on_close: Message, max_width: f32, ) -> Element<'a, Message> where Drawer: Into>, { let cosmic_theme::Spacing { space_m, space_l, .. } = crate::theme::active().cosmic().spacing; let header = row::with_capacity(3) .width(Length::Fixed(480.0)) .align_y(Alignment::Center) .padding([space_m, space_l]) .push(Space::new(Length::FillPortion(1), Length::Fixed(0.0))) .push(text::heading(header).width(Length::FillPortion(1)).center()) .push( button::text("Close") .trailing_icon(icon::from_name("go-next-symbolic")) .on_press(on_close) .apply(container) .width(Length::FillPortion(1)) .align_x(Alignment::End), ); let pane = column::with_capacity(2).push(header).push( scrollable(container(drawer.into()).padding([0, space_l, space_l, space_l])) .height(Length::Fill) .width(Length::Shrink), ); // XXX new limits do not exactly handle the max width well for containers // XXX this is a hack to get around that container( LayerContainer::new(pane) .layer(cosmic_theme::Layer::Primary) .class(crate::style::Container::ContextDrawer) .width(Length::Fill) .height(Length::Fill) .max_width(max_width), ) .width(Length::Fill) .height(Length::Fill) .align_x(Alignment::End) .into() } /// Creates an empty [`ContextDrawer`]. pub fn new( header: &'a str, content: Content, drawer: Drawer, on_close: Message, max_width: f32, ) -> Self where Content: Into>, Drawer: Into>, { let drawer = Self::new_inner(header, drawer, on_close, max_width); ContextDrawer { id: None, content: content.into(), drawer, on_close: None, } } /// Sets the [`Id`] of the [`ContextDrawer`]. pub fn id(mut self, id: iced_core::widget::Id) -> Self { self.id = Some(id); self } // Optionally assigns message to `on_close` event. pub fn on_close_maybe(mut self, message: Option) -> Self { self.on_close = message; self } } impl<'a, Message: Clone> Widget for ContextDrawer<'a, Message> { fn children(&self) -> Vec { vec![Tree::new(&self.content), Tree::new(&self.drawer)] } fn diff(&mut self, tree: &mut Tree) { tree.diff_children(&mut [&mut self.content, &mut self.drawer]); } fn size(&self) -> iced_core::Size { self.content.as_widget().size() } fn layout( &self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.content .as_widget() .layout(&mut tree.children[0], renderer, limits) } fn operate( &self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation<()>, ) { self.content .as_widget() .operate(&mut tree.children[0], layout, renderer, operation); } fn on_event( &mut self, tree: &mut Tree, event: Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { self.content.as_widget_mut().on_event( &mut tree.children[0], event, layout, cursor, renderer, clipboard, shell, viewport, ) } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.content.as_widget().mouse_interaction( &tree.children[0], layout, cursor, viewport, renderer, ) } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, renderer_style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { self.content.as_widget().draw( &tree.children[0], renderer, theme, renderer_style, layout, cursor, viewport, ); } fn overlay<'b>( &'b mut self, tree: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, translation: Vector, ) -> Option> { let bounds = layout.bounds(); let mut position = layout.position(); position.x += translation.x; position.y += translation.y; Some(iced_overlay::Element::new(Box::new(Overlay { content: &mut self.drawer, tree: &mut tree.children[1], width: bounds.width, position, }))) } #[cfg(feature = "a11y")] /// get the a11y nodes for the widget fn a11y_nodes( &self, layout: Layout<'_>, state: &Tree, p: mouse::Cursor, ) -> iced_accessibility::A11yTree { let c_layout = layout.children().next().unwrap(); let c_state = &state.children[0]; self.content.as_widget().a11y_nodes(c_layout, c_state, p) } fn drag_destinations( &self, state: &Tree, layout: Layout<'_>, renderer: &Renderer, dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles, ) { self.content.as_widget().drag_destinations( &state.children[0], layout, renderer, dnd_rectangles, ); } fn id(&self) -> Option { self.id.clone() } fn set_id(&mut self, id: iced_core::widget::Id) { self.id = Some(id); } } impl<'a, Message: 'a + Clone> From> for Element<'a, Message> { fn from(widget: ContextDrawer<'a, Message>) -> Element<'a, Message> { Element::new(widget) } }