diff --git a/examples/application/src/main.rs b/examples/application/src/main.rs index 45805579..c9a54b87 100644 --- a/examples/application/src/main.rs +++ b/examples/application/src/main.rs @@ -236,26 +236,26 @@ impl cosmic::Application for App { ( "hi 1".into(), vec![ - menu::Item::Button("hi 12", None, Action::Hi), - menu::Item::Button("hi 13", None, Action::Hi2), + menu::Item::button("hi 12", None, Action::Hi), + menu::Item::button("hi 13", None, Action::Hi2), ], ), ( "hi 2".into(), vec![ - menu::Item::Button("hi 21", None, Action::Hi), - menu::Item::Button("hi 22", None, Action::Hi2), - menu::Item::Folder( + menu::Item::button("hi 21", None, Action::Hi), + menu::Item::button("hi 22", None, Action::Hi2), + menu::Item::folder( "nest 3 2 >".into(), vec![ - menu::Item::Button("21", None, Action::Hi), - menu::Item::Button("242", None, Action::Hi2), - menu::Item::Button("2443", None, Action::Hi3), - menu::Item::Folder( + menu::Item::button("21", None, Action::Hi), + menu::Item::button("242", None, Action::Hi2), + menu::Item::button("2443", None, Action::Hi3), + menu::Item::folder( "nest 4 2 >".into(), vec![ - menu::Item::Button("243", None, Action::Hi2), - menu::Item::Button("2444", None, Action::Hi), + menu::Item::button("243", None, Action::Hi2), + menu::Item::button("2444", None, Action::Hi), ], ), ], @@ -265,34 +265,34 @@ impl cosmic::Application for App { ( "hi 3".into(), vec![ - menu::Item::Button("hi 31", None, Action::Hi), - menu::Item::Button("hi 332", None, Action::Hi2), - menu::Item::Button("hi 3333", None, Action::Hi3), - menu::Item::Button("hi 33334", None, Action::Hi3), - menu::Item::Button("hi 333335", None, Action::Hi3), - menu::Item::Button("hi 3333336", None, Action::Hi3), + menu::Item::button("hi 31", None, Action::Hi), + menu::Item::button("hi 332", None, Action::Hi2), + menu::Item::button("hi 3333", None, Action::Hi3), + menu::Item::button("hi 33334", None, Action::Hi3), + menu::Item::button("hi 333335", None, Action::Hi3), + menu::Item::button("hi 3333336", None, Action::Hi3), ], ), ( "hiiiiiiiiiiiiiiiiiii 4".into(), vec![ - menu::Item::Button("hi 4", None, Action::Hi), - menu::Item::Button("hi 44", None, Action::Hi2), - menu::Item::Button("hi 444", None, Action::Hi3), - menu::Item::Folder( + menu::Item::button("hi 4", None, Action::Hi), + menu::Item::button("hi 44", None, Action::Hi2), + menu::Item::button("hi 444", None, Action::Hi3), + menu::Item::folder( "nest 4 >".into(), vec![ - menu::Item::Button("hi 41", None, Action::Hi), - menu::Item::Button("hi 442", None, Action::Hi2), - menu::Item::Folder( + menu::Item::button("hi 41", None, Action::Hi), + menu::Item::button("hi 442", None, Action::Hi2), + menu::Item::folder( "nest 3 4 >".into(), vec![ - menu::Item::Button("hi 443", None, Action::Hi2), - menu::Item::Button("hi 4444", None, Action::Hi), - menu::Item::Button("hi 44444", None, Action::Hi3), - menu::Item::Button("hi 444445", None, Action::Hi3), - menu::Item::Button("hi 4444446", None, Action::Hi3), - menu::Item::Button("hi 44444447", None, Action::Hi3), + menu::Item::button("hi 443", None, Action::Hi2), + menu::Item::button("hi 4444", None, Action::Hi), + menu::Item::button("hi 44444", None, Action::Hi3), + menu::Item::button("hi 444445", None, Action::Hi3), + menu::Item::button("hi 4444446", None, Action::Hi3), + menu::Item::button("hi 44444447", None, Action::Hi3), ], ), ], diff --git a/examples/context-menu/src/main.rs b/examples/context-menu/src/main.rs index db66ba1b..1950906d 100644 --- a/examples/context-menu/src/main.rs +++ b/examples/context-menu/src/main.rs @@ -122,19 +122,21 @@ impl App { Some(menu::items( &HashMap::new(), vec![ - menu::Item::Button("New window", None, ContextMenuAction::WindowNew), - menu::Item::Divider, - menu::Item::Folder( + menu::Item::button("New window", None, ContextMenuAction::WindowNew), + menu::Item::divider(), + menu::Item::folder( "View", - vec![menu::Item::CheckBox( + vec![menu::Item::checkbox( "Hide content", None, self.hide_content, ContextMenuAction::ToggleHideContent, )], - ), - menu::Item::Divider, - menu::Item::Button("Quit", None, ContextMenuAction::WindowClose), + ) + .width(200) + .min_width(180), + menu::Item::divider(), + menu::Item::button("Quit", None, ContextMenuAction::WindowClose), ], )) } diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index 8b5a1cb7..bae10c68 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -160,23 +160,26 @@ pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap) -> El menu::items( key_binds, vec![ - menu::Item::Button( + menu::Item::button( "New window", Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), Action::WindowNew, ), - menu::Item::Divider, - menu::Item::Folder( + menu::Item::divider(), + menu::Item::folder( "View", - vec![menu::Item::CheckBox( + vec![menu::Item::checkbox( "Hide content", Some(cosmic::widget::icon::from_name("view-conceal-symbolic").into()), config.hide_content, Action::ToggleHideContent, )], - ), - menu::Item::Divider, - menu::Item::Button( + ) + .width(280) + .min_width(200) + .max_width(300), + menu::Item::divider(), + menu::Item::button( "Quit", Some(cosmic::widget::icon::from_name("window-close-symbolic").into()), Action::WindowClose, diff --git a/examples/nav-context/src/main.rs b/examples/nav-context/src/main.rs index fdfb90f9..7a677cd2 100644 --- a/examples/nav-context/src/main.rs +++ b/examples/nav-context/src/main.rs @@ -135,9 +135,9 @@ impl cosmic::Application for App { Some(menu::items( &HashMap::new(), vec![ - menu::Item::Button("Move Up", None, NavMenuAction::MoveUp(id)), - menu::Item::Button("Move Down", None, NavMenuAction::MoveDown(id)), - menu::Item::Button("Delete", None, NavMenuAction::Delete(id)), + menu::Item::button("Move Up", None, NavMenuAction::MoveUp(id)), + menu::Item::button("Move Down", None, NavMenuAction::MoveDown(id)), + menu::Item::button("Delete", None, NavMenuAction::Delete(id)), ], )) } diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs index bbd9cf5b..abb579c6 100644 --- a/examples/table-view/src/main.rs +++ b/examples/table-view/src/main.rs @@ -212,7 +212,7 @@ impl cosmic::Application for App { .item_context(move |item| { Some(widget::menu::items( &HashMap::new(), - vec![widget::menu::Item::Button( + vec![widget::menu::Item::button( format!("Action on {}", item.name.to_string()), None, Action::None, @@ -227,7 +227,7 @@ impl cosmic::Application for App { .item_context(|item| { Some(widget::menu::items( &HashMap::new(), - vec![widget::menu::Item::Button( + vec![widget::menu::Item::button( format!("Action on {}", item.name), None, Action::None, @@ -238,12 +238,12 @@ impl cosmic::Application for App { Some(widget::menu::items( &HashMap::new(), vec![ - widget::menu::Item::Button( + widget::menu::Item::button( format!("Action on {} category", category.to_string()), None, Action::None, ), - widget::menu::Item::Button( + widget::menu::Item::button( format!("Other action on {} category", category.to_string()), None, Action::None, diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index c455cd13..4b2a7498 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1647,7 +1647,12 @@ fn get_children_layout( ) -> (Size, Vec, Vec) { let width = match item_width { ItemWidth::Uniform(u) => f32::from(u), - ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)), + ItemWidth::Static(s) => { + let base = f32::from(menu_tree.width.unwrap_or(s)); + let min = menu_tree.min_width.map(f32::from).unwrap_or(0.0); + let max = menu_tree.max_width.map(f32::from).unwrap_or(f32::MAX); + base.clamp(min, max) + } }; let child_sizes: Vec = match item_height { diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 378e0939..b8bb4a9e 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -36,6 +36,10 @@ pub struct MenuTree { pub(crate) children: Vec>, /// The width of the menu tree pub(crate) width: Option, + /// The min width of the menu tree + pub(crate) min_width: Option, + /// The max width of the menu tree + pub(crate) max_width: Option, /// The height of the menu tree pub(crate) height: Option, } @@ -48,6 +52,8 @@ impl MenuTree { item: item.into(), children: Vec::new(), width: None, + min_width: None, + max_width: None, height: None, } } @@ -62,6 +68,8 @@ impl MenuTree { item: item.into(), children: children.into_iter().map(Into::into).collect(), width: None, + min_width: None, + max_width: None, height: None, } } @@ -76,6 +84,18 @@ impl MenuTree { self } + /// Sets the min width of the menu tree. + pub fn min_width(mut self, min: u16) -> Self { + self.min_width = Some(min); + self + } + + /// Sets the max width of the menu tree. + pub fn max_width(mut self, max: u16) -> Self { + self.max_width = Some(max); + self + } + /// Sets the height of the menu tree. /// See [`ItemHeight`] /// @@ -170,19 +190,66 @@ pub enum MenuItemKind>> { Divider, } +/// A menu item with optional width configuration. +/// +/// # Examples +/// +/// ```ignore +/// use cosmic::widget::menu; +/// +/// // Simple button +/// menu::Item::button("Save", None, Action::Save); +/// +/// // Button with icon +/// menu::Item::button( +/// "Open", +/// Some(cosmic::widget::icon::from_name("document-open-symbolic").into()), +/// Action::Open, +/// ); +/// +/// // Checkbox +/// menu::Item::checkbox("Show Hidden", None, true, Action::ToggleHidden); +/// +/// // Folder with custom width +/// menu::Item::folder("Recent", vec![ +/// menu::Item::button("file1.txt", None, Action::OpenRecent(0)), +/// ]).width(300); +/// +/// // Divider +/// menu::Item::divider(); +/// +/// // Folder with custom width constraints +/// menu::Item::folder("Recent", vec![ +/// menu::Item::button("file1.txt", None, Action::OpenRecent(0)), +/// ]).width(300).min_width(200).max_width(400); +/// +/// // Using min_width to ensure a minimum size +/// menu::Item::button("Short", None, Action::Short).min_width(150); +/// +/// // Using max_width to cap the size +/// menu::Item::button("Very Long Label Here", None, Action::Long).max_width(200); +/// ``` #[derive(Clone)] -/// A menu item with optional width configuration pub struct MenuItem>> { /// Kind of menu item. kind: MenuItemKind, - /// Optional width override for this item's submenu. + /// Optional width override for this item. width: Option, + /// Optional min width for this item. + min_width: Option, + /// Optional max width for this item. + max_width: Option, } impl>> MenuItem { /// Create from a kind with no width set pub fn new(kind: MenuItemKind) -> Self { - Self { kind, width: None } + Self { + kind, + width: None, + min_width: None, + max_width: None, + } } /// Builder method to set width @@ -191,22 +258,39 @@ impl>> MenuItem { self } + /// Builder method to set minimum width + pub fn min_width(mut self, min: u16) -> Self { + self.min_width = Some(min); + self + } + + /// Builder method to set max width + pub fn max_width(mut self, max: u16) -> Self { + self.max_width = Some(max); + self + } + + /// Create a button menu item. pub fn button(label: L, icon: Option, action: A) -> Self { Self::new(MenuItemKind::Button(label, icon, action)) } + /// Create a disabled button menu item. pub fn button_disabled(label: L, icon: Option, action: A) -> Self { Self::new(MenuItemKind::ButtonDisabled(label, icon, action)) } + /// Create a checkbox menu item. pub fn checkbox(label: L, icon: Option, checked: bool, action: A) -> Self { Self::new(MenuItemKind::CheckBox(label, icon, checked, action)) } + /// Create a folder (submenu) menu item. pub fn folder(label: L, children: Vec>) -> Self { Self::new(MenuItemKind::Folder(label, children)) } + /// Create a divider between menu items. pub fn divider() -> Self { Self::new(MenuItemKind::Divider) } @@ -283,6 +367,8 @@ pub fn menu_items< let mut trees = vec![]; let spacing = crate::theme::spacing(); let item_width = item.width; + let item_min_width = item.min_width; + let item_max_width = item.max_width; match item.kind { MenuItemKind::Button(label, icon, action) => { @@ -308,6 +394,14 @@ pub fn menu_items< tree = tree.width(width); } + if let Some(min_width) = item_min_width { + tree = tree.min_width(min_width); + } + + if let Some(max_width) = item_max_width { + tree = tree.max_width(max_width); + } + trees.push(tree); } MenuItemKind::ButtonDisabled(label, icon, action) => { @@ -334,6 +428,14 @@ pub fn menu_items< tree = tree.width(width); } + if let Some(min_width) = item_min_width { + tree = tree.min_width(min_width); + } + + if let Some(max_width) = item_max_width { + tree = tree.max_width(max_width); + } + trees.push(tree); } MenuItemKind::CheckBox(label, icon, value, action) => { @@ -372,6 +474,14 @@ pub fn menu_items< tree = tree.width(width); } + if let Some(min_width) = item_min_width { + tree = tree.min_width(min_width); + } + + if let Some(max_width) = item_max_width { + tree = tree.max_width(max_width); + } + trees.push(tree); } MenuItemKind::Folder(label, children) => { @@ -405,6 +515,14 @@ pub fn menu_items< tree = tree.width(width); } + if let Some(min_width) = item_min_width { + tree = tree.min_width(min_width); + } + + if let Some(max_width) = item_max_width { + tree = tree.max_width(max_width); + } + trees.push(tree); } MenuItemKind::Divider => { @@ -417,6 +535,14 @@ pub fn menu_items< tree = tree.width(width); } + if let Some(min_width) = item_min_width { + tree = tree.min_width(min_width); + } + + if let Some(max_width) = item_max_width { + tree = tree.max_width(max_width); + } + trees.push(tree); } }