From ec5dceeed29befe744f085367a91aab972604f06 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 19 Jan 2024 14:42:06 -0800 Subject: [PATCH] Custom widget to avoid stretching toplevel items Avoids close button aligned way to right. And fixes offset for drag surface. But left aligns workspaces (need new container?). --- src/view/mod.rs | 55 +++--- src/widgets/mod.rs | 111 +++++++---- src/widgets/mouse_interaction_wrapper.rs | 4 +- src/widgets/workspace_bar.rs | 1 + src/widgets/workspace_item.rs | 224 +++++++++++++++++++++++ 5 files changed, 334 insertions(+), 61 deletions(-) create mode 100644 src/widgets/workspace_item.rs diff --git a/src/view/mod.rs b/src/view/mod.rs index 2cbd610..b2ba293 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -205,22 +205,28 @@ pub(crate) fn toplevel_preview(toplevel: &Toplevel) -> cosmic::Element { row![label] } .padding(4); - column![ - close_button(Msg::CloseToplevel(toplevel.handle.clone())), - widget::button(capture_image(toplevel.img.as_ref())) - .selected( - toplevel - .info - .state - .contains(&zcosmic_toplevel_handle_v1::State::Activated) - ) - .style(cosmic::theme::Button::Image) - .on_press(Msg::ActivateToplevel(toplevel.handle.clone())), - widget::button(label).on_press(Msg::ActivateToplevel(toplevel.handle.clone())) - ] - .spacing(4) - .align_items(iced::Alignment::Center) - .width(iced::Length::Fill) + crate::widgets::workspace_item( + vec![ + close_button(Msg::CloseToplevel(toplevel.handle.clone())).into(), + widget::button(capture_image(toplevel.img.as_ref())) + .selected( + toplevel + .info + .state + .contains(&zcosmic_toplevel_handle_v1::State::Activated), + ) + .style(cosmic::theme::Button::Image) + .on_press(Msg::ActivateToplevel(toplevel.handle.clone())) + .into(), + widget::button(label) + .on_press(Msg::ActivateToplevel(toplevel.handle.clone())) + .into(), + ], + Axis::Vertical, + ) + //.spacing(4) + //.align_items(iced::Alignment::Center) + //.width(iced::Length::Fill) .into() } @@ -252,15 +258,16 @@ fn toplevel_previews<'a>( WorkspaceLayout::Vertical => (iced::Length::FillPortion(4), iced::Length::Fill), WorkspaceLayout::Horizontal => (iced::Length::Fill, iced::Length::FillPortion(4)), }; - row(toplevels + let entries = toplevels .map(|t| toplevel_previews_entry(t, output)) - .collect()) - .width(width) - .height(height) - .spacing(16) - .padding(12) - .align_items(iced::Alignment::Center) - .into() + .collect(); + row(entries) + .width(width) + .height(height) + .spacing(16) + .padding(12) + .align_items(iced::Alignment::Center) + .into() } fn capture_image(image: Option<&CaptureImage>) -> cosmic::Element<'_, Msg> { diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index baa631d..ba50268 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,5 +1,10 @@ use cosmic::iced::{ - advanced::{layout, mouse, renderer, widget::Tree, Layout, Widget}, + advanced::{ + layout, mouse, overlay, renderer, + widget::{tree, Id, Operation, OperationOutputWrapper, Tree}, + Clipboard, Layout, Shell, Widget, + }, + event::{self, Event}, Length, Rectangle, }; use std::marker::PhantomData; @@ -8,32 +13,28 @@ mod image_bg; pub use image_bg::image_bg; mod workspace_bar; pub use workspace_bar::workspace_bar; +mod workspace_item; +pub use workspace_item::workspace_item; mod mouse_interaction_wrapper; pub use mouse_interaction_wrapper::mouse_interaction_wrapper; -pub fn layout_wrapper>(inner: T) -> LayoutWrapper { +trait Foo {} + +pub fn layout_wrapper<'a, Msg, T: Into>>( + inner: T, +) -> LayoutWrapper<'a, Msg> { LayoutWrapper { - inner, + content: inner.into(), _msg: PhantomData, } } -pub struct LayoutWrapper> { - inner: T, +pub struct LayoutWrapper<'a, Msg> { + content: cosmic::Element<'a, Msg>, _msg: PhantomData, } -impl> Widget - for LayoutWrapper -{ - fn width(&self) -> Length { - self.inner.width() - } - - fn height(&self) -> Length { - self.inner.height() - } - +impl<'a, Msg> Widget for LayoutWrapper<'a, Msg> { fn layout( &self, tree: &mut Tree, @@ -41,32 +42,70 @@ impl> Widget limits: &layout::Limits, ) -> layout::Node { dbg!(limits); - dbg!(self.inner.layout(tree, renderer, limits)) + dbg!(self.content.as_widget().layout(tree, renderer, limits)) } - fn draw( - &self, - state: &Tree, - renderer: &mut cosmic::Renderer, - theme: &cosmic::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - ) { - self.inner - .draw(state, renderer, theme, style, layout, cursor, viewport) - } + delegate::delegate! { + to self.content.as_widget() { + fn tag(&self) -> tree::Tag; + fn state(&self) -> tree::State; + fn children(&self) -> Vec; + fn width(&self) -> Length; + fn height(&self) -> Length; + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &cosmic::Renderer, + operation: &mut dyn Operation>, + ); + fn draw( + &self, + state: &Tree, + renderer: &mut cosmic::Renderer, + theme: &cosmic::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ); + fn mouse_interaction( + &self, + _tree: &Tree, + _layout: Layout<'_>, + _cursor: mouse::Cursor, + _viewport: &Rectangle, + _renderer: &cosmic::Renderer, + ) -> mouse::Interaction; + fn id(&self) -> Option; + } - fn children(&self) -> Vec { - self.inner.children() + to self.content.as_widget_mut() { + fn diff(&mut self, tree: &mut Tree); + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &cosmic::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Msg>, + viewport: &Rectangle, + ) -> event::Status; + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &cosmic::Renderer, + ) -> Option>; + fn set_id(&mut self, id: Id); + } } } -impl<'a, Msg: 'a, T: Widget + 'a> From> - for cosmic::Element<'a, Msg> -{ - fn from(widget: LayoutWrapper) -> Self { +impl<'a, Msg: 'a> From> for cosmic::Element<'a, Msg> { + fn from(widget: LayoutWrapper<'a, Msg>) -> Self { cosmic::Element::new(widget) } } diff --git a/src/widgets/mouse_interaction_wrapper.rs b/src/widgets/mouse_interaction_wrapper.rs index 04426d3..1fb9014 100644 --- a/src/widgets/mouse_interaction_wrapper.rs +++ b/src/widgets/mouse_interaction_wrapper.rs @@ -1,7 +1,7 @@ use cosmic::iced::{ advanced::{ layout, mouse, overlay, renderer, - widget::{tree, Operation, OperationOutputWrapper, Tree}, + widget::{tree, Id, Operation, OperationOutputWrapper, Tree}, Clipboard, Layout, Shell, Widget, }, event::{self, Event}, @@ -58,6 +58,7 @@ impl<'a, Msg> Widget for MouseInteractionWrapper<'a, Msg> cursor: mouse::Cursor, viewport: &Rectangle, ); + fn id(&self) -> Option; } to self.content.as_widget_mut() { @@ -79,6 +80,7 @@ impl<'a, Msg> Widget for MouseInteractionWrapper<'a, Msg> layout: Layout<'_>, renderer: &cosmic::Renderer, ) -> Option>; + fn set_id(&mut self, id: Id); } } diff --git a/src/widgets/workspace_bar.rs b/src/widgets/workspace_bar.rs index 74175d9..3106c05 100644 --- a/src/widgets/workspace_bar.rs +++ b/src/widgets/workspace_bar.rs @@ -1,4 +1,5 @@ // Custom varian of row/column +// Gives each child widget a maximim size on main axis of total/n use cosmic::iced::{ advanced::{ diff --git a/src/widgets/workspace_item.rs b/src/widgets/workspace_item.rs new file mode 100644 index 0000000..1d9aca2 --- /dev/null +++ b/src/widgets/workspace_item.rs @@ -0,0 +1,224 @@ +// TODO rename +// combine widgets + +use cosmic::iced::{ + advanced::{ + layout::{self, flex::Axis}, + mouse, renderer, + widget::{Operation, OperationOutputWrapper, Tree}, + Clipboard, Layout, Shell, Widget, + }, + event::{self, Event}, + Length, Point, Rectangle, Size, +}; +use std::marker::PhantomData; + +// Duplicate of private methods +trait AxisExt { + fn main(&self, size: Size) -> f32; + fn cross(&self, size: Size) -> f32; + fn pack(&self, main: f32, cross: f32) -> (f32, f32); +} + +impl AxisExt for Axis { + fn main(&self, size: Size) -> f32 { + match self { + Axis::Horizontal => size.width, + Axis::Vertical => size.height, + } + } + + fn cross(&self, size: Size) -> f32 { + match self { + Axis::Horizontal => size.height, + Axis::Vertical => size.width, + } + } + + fn pack(&self, main: f32, cross: f32) -> (f32, f32) { + match self { + Axis::Horizontal => (main, cross), + Axis::Vertical => (cross, main), + } + } +} + +pub fn workspace_item<'a, Msg>( + children: Vec>, + axis: Axis, +) -> WorkspaceItem<'a, Msg> { + WorkspaceItem { + axis, + children, + _msg: PhantomData, + } +} + +pub struct WorkspaceItem<'a, Msg> { + axis: Axis, + children: Vec>, + _msg: PhantomData, +} + +impl<'a, Msg> Widget for WorkspaceItem<'a, Msg> { + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + // TODO Make depend on orientation or drop that option + Length::Shrink + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &cosmic::Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let max_main = self.axis.main(limits.max()); + let max_cross = self.axis.cross(limits.max()); + + // XXX cleaner solution + // Get layout of main widget, to set overall cross axis size + let (max_width, max_height) = self.axis.pack(max_main, max_cross); + let child_limits = layout::Limits::new(Size::ZERO, Size::new(max_width, max_height)); + let layout = self.children[1].layout(tree, renderer, &child_limits); + + let max_cross = self.axis.cross(layout.size()); + + // XXX sill allocating maximum main axis? + // - what was it doing before? + let mut total_main = 0.0; + let nodes = self + .children + .iter() + .zip(tree.children.iter_mut()) + .map(|(child, tree)| { + let (max_width, max_height) = self.axis.pack(max_main, max_cross); + let child_limits = + layout::Limits::new(Size::ZERO, Size::new(max_width, max_height)); + let mut layout = child.layout(tree, renderer, &child_limits); + // Center on cross axis + let cross = ((max_cross - self.axis.cross(layout.size())) / 2.).max(0.); + let (x, y) = self.axis.pack(total_main, cross); + layout.move_to(Point::new(x, y)); + total_main += self.axis.main(layout.size()); + layout + }) + .collect(); + + let (total_width, total_height) = self.axis.pack(total_main, max_cross); + let size = Size::new(total_width, total_height); + layout::Node::with_children(size, nodes) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &cosmic::Renderer, + operation: &mut dyn Operation>, + ) { + operation.container(None, layout.bounds(), &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child + .as_widget() + .operate(state, layout, renderer, operation); + }); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &cosmic::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Msg>, + viewport: &Rectangle, + ) -> event::Status { + self.children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &cosmic::Renderer, + ) -> mouse::Interaction { + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child + .as_widget() + .mouse_interaction(state, layout, cursor, viewport, renderer) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut cosmic::Renderer, + theme: &cosmic::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + if let Some(viewport) = layout.bounds().intersection(viewport) { + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + { + child + .as_widget() + .draw(state, renderer, theme, style, layout, cursor, &viewport); + } + } + } + + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(&mut self.children); + } +} + +impl<'a, Msg: 'static> From> for cosmic::Element<'a, Msg> { + fn from(widget: WorkspaceItem<'a, Msg>) -> Self { + cosmic::Element::new(widget) + } +}