From 450135960c384a0e4777f8476963e6da056670fa Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 25 Aug 2026 23:05:51 +0300 Subject: [PATCH] fix(ui): widen linetype dropdown menus Refs #782 --- src/ui/mod.rs | 1 + src/ui/properties.rs | 6 +- src/ui/wide_menu.rs | 159 ++++++++++++++++++++++++++++++++++++++++ src/ui/window/layers.rs | 33 +++++---- 4 files changed, 183 insertions(+), 16 deletions(-) create mode 100644 src/ui/wide_menu.rs diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 6a8c7cd2d..d42db3013 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -16,6 +16,7 @@ pub mod side_toolbar; pub mod statusbar; pub mod style; pub mod text_util; +pub mod wide_menu; pub mod window; pub mod wrap_bar; diff --git a/src/ui/properties.rs b/src/ui/properties.rs index 5d562dd45..b6d129710 100644 --- a/src/ui/properties.rs +++ b/src/ui/properties.rs @@ -30,6 +30,7 @@ const PATTERN_CARD_W: f32 = 158.0; const PATTERN_PREVIEW_H: f32 = 58.0; const PATTERN_PICKER_W: f32 = 348.0; const PATTERN_PICKER_H: f32 = 720.0; +const LINETYPE_MENU_W: f32 = 220.0; use crate::app::Message; use crate::scene::model::object::{PropSection, PropValue}; @@ -1049,7 +1050,10 @@ impl PropertiesPanel { .on_open(Message::PropColorPickerClose) .width(Length::Fill); - prop_row_widget(label, combo.into()) + prop_row_widget( + label, + crate::ui::wide_menu::wide_menu(combo, LINETYPE_MENU_W), + ) } fn render_choice_row<'a>( diff --git a/src/ui/wide_menu.rs b/src/ui/wide_menu.rs new file mode 100644 index 000000000..f2308dc08 --- /dev/null +++ b/src/ui/wide_menu.rs @@ -0,0 +1,159 @@ +use crate::app::Message; +use iced::advanced::layout::{self, Layout}; +use iced::advanced::widget::{self, tree, Widget}; +use iced::advanced::{mouse, overlay, renderer, Shell}; +use iced::{Element, Event, Length, Point, Rectangle, Renderer, Size, Theme, Vector}; + +pub fn wide_menu<'a>( + content: impl Into>, + menu_width: f32, +) -> Element<'a, Message> { + Element::new(WideMenu { + content: content.into(), + menu_width, + }) +} + +struct WideMenu<'a> { + content: Element<'a, Message>, + menu_width: f32, +} + +#[derive(Default)] +struct State { + menu_layout: layout::Node, +} + +impl Widget for WideMenu<'_> { + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn diff(&mut self, tree: &mut widget::Tree) { + tree.diff_children(std::slice::from_mut(&mut self.content)); + } + + fn size(&self) -> Size { + self.content.as_widget().size() + } + + fn layout( + &mut self, + tree: &mut widget::Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let content = self.content.as_widget_mut().layout( + &mut tree.children[0], + renderer, + limits, + ); + + layout::Node::with_children(content.size(), vec![content]) + } + + fn operate( + &mut self, + tree: &mut widget::Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + self.content.as_widget_mut().operate( + &mut tree.children[0], + layout.child(0), + renderer, + operation, + ); + } + + fn update( + &mut self, + tree: &mut widget::Tree, + event: &Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) { + self.content.as_widget_mut().update( + &mut tree.children[0], + event, + layout.child(0), + cursor, + renderer, + shell, + viewport, + ); + } + + fn mouse_interaction( + &self, + tree: &widget::Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.content.as_widget().mouse_interaction( + &tree.children[0], + layout.child(0), + cursor, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &widget::Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + style, + layout.child(0), + cursor, + viewport, + ); + } + + fn overlay<'a>( + &'a mut self, + tree: &'a mut widget::Tree, + layout: Layout<'a>, + renderer: &Renderer, + viewport: &Rectangle, + translation: Vector, + ) -> Option> { + let bounds = layout.child(0).bounds(); + let (state, children) = (&mut tree.state, &mut tree.children); + let state = state.downcast_mut::(); + + state.menu_layout = layout::Node::new(Size::new( + self.menu_width.max(bounds.width), + bounds.height, + )) + .move_to(Point::new(bounds.x, bounds.y)); + + self.content.as_widget_mut().overlay( + &mut children[0], + Layout::new(&state.menu_layout), + renderer, + viewport, + translation, + ) + } +} diff --git a/src/ui/window/layers.rs b/src/ui/window/layers.rs index 11253ee51..9eb9050dd 100644 --- a/src/ui/window/layers.rs +++ b/src/ui/window/layers.rs @@ -46,6 +46,7 @@ const ICON_SZ: f32 = ROW_H * 0.62; // ≈16 px at ROW_H=26 const FONT_SZ: f32 = ROW_H * 0.42; // ≈11 px at ROW_H=26 /// Vertical padding for combo_box / text_input so their total height = ROW_H. const COMBO_PAD_V: f32 = (ROW_H - FONT_SZ * 1.3 - 2.0) / 2.0; +const LINETYPE_MENU_W: f32 = 220.0; /// Widget id for the layer-table scrollable, so a freshly created layer can be /// scrolled into view after it is added (#271). pub const LAYER_TABLE_SCROLL_ID: &str = "layer-manager-table-scroll"; @@ -831,22 +832,24 @@ fn layer_row<'a>( art: String::new(), // art comes from combo state items; just match by name }; let lt_cell: Element<'_, Message> = if let Some(state) = lt_combo { - combo_box( - state, - t!("linetype").as_ref(), - Some(&cur_lt_item), - |item: LinetypeItem| Message::LayerLinetypeSet(item.name), + crate::ui::wide_menu::wide_menu( + combo_box( + state, + t!("linetype").as_ref(), + Some(&cur_lt_item), + |item: LinetypeItem| Message::LayerLinetypeSet(item.name), + ) + .size(FONT_SZ) + .padding(Padding { + top: COMBO_PAD_V, + bottom: COMBO_PAD_V, + left: 4.0, + right: 4.0, + }) + .width(Length::Fixed(COL_LT)) + .input_style(combo_input_style), + LINETYPE_MENU_W, ) - .size(FONT_SZ) - .padding(Padding { - top: COMBO_PAD_V, - bottom: COMBO_PAD_V, - left: 4.0, - right: 4.0, - }) - .width(Length::Fixed(COL_LT)) - .input_style(combo_input_style) - .into() } else { text(layer.linetype.as_str()) .size(FONT_SZ)