menu_bar: add min_width/max_width constraints and update examples

This commit is contained in:
Bryan Hyland 2026-02-02 09:49:03 -08:00
parent 6158af1991
commit 7903ee12c0
7 changed files with 191 additions and 55 deletions

View file

@ -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),
],
),
],

View file

@ -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),
],
))
}

View file

@ -160,23 +160,26 @@ pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> 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,

View file

@ -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)),
],
))
}

View file

@ -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,

View file

@ -1647,7 +1647,12 @@ fn get_children_layout<Message>(
) -> (Size, Vec<f32>, Vec<Size>) {
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<Size> = match item_height {

View file

@ -36,6 +36,10 @@ pub struct MenuTree<Message> {
pub(crate) children: Vec<MenuTree<Message>>,
/// The width of the menu tree
pub(crate) width: Option<u16>,
/// The min width of the menu tree
pub(crate) min_width: Option<u16>,
/// The max width of the menu tree
pub(crate) max_width: Option<u16>,
/// The height of the menu tree
pub(crate) height: Option<u16>,
}
@ -48,6 +52,8 @@ impl<Message: Clone + 'static> MenuTree<Message> {
item: item.into(),
children: Vec::new(),
width: None,
min_width: None,
max_width: None,
height: None,
}
}
@ -62,6 +68,8 @@ impl<Message: Clone + 'static> MenuTree<Message> {
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<Message: Clone + 'static> MenuTree<Message> {
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<A: MenuAction, L: Into<Cow<'static, str>>> {
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<A: MenuAction, L: Into<Cow<'static, str>>> {
/// Kind of menu item.
kind: MenuItemKind<A, L>,
/// Optional width override for this item's submenu.
/// Optional width override for this item.
width: Option<u16>,
/// Optional min width for this item.
min_width: Option<u16>,
/// Optional max width for this item.
max_width: Option<u16>,
}
impl<A: MenuAction, L: Into<Cow<'static, str>>> MenuItem<A, L> {
/// Create from a kind with no width set
pub fn new(kind: MenuItemKind<A, L>) -> 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<A: MenuAction, L: Into<Cow<'static, str>>> MenuItem<A, L> {
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<icon::Handle>, action: A) -> Self {
Self::new(MenuItemKind::Button(label, icon, action))
}
/// Create a disabled button menu item.
pub fn button_disabled(label: L, icon: Option<icon::Handle>, action: A) -> Self {
Self::new(MenuItemKind::ButtonDisabled(label, icon, action))
}
/// Create a checkbox menu item.
pub fn checkbox(label: L, icon: Option<icon::Handle>, 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<MenuItem<A, L>>) -> 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);
}
}