Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/astro/src/content/docs/reference/window/contextmenuarea.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ The title shown for this menu item.
When disabled, the `MenuItem` can be selected but not activated.
</SlintProperty>

#### checkable

<SlintProperty propName="checkable" typeName="bool" defaultValue="true">
When true, the `MenuItem` can be checked.
</SlintProperty>

#### checked

<SlintProperty propName="checked" typeName="bool" defaultValue="true">
When true, a checkmark will be shown next to the title of the `MenuItem`.
</SlintProperty>

### icon
<SlintProperty propName="icon" typeName="image">
The icon shown next to the title.
Expand Down
1 change: 1 addition & 0 deletions examples/gallery/gallery.slint
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export component App inherits Window {
}
MenuSeparator {}
MenuItem { title: @tr("MenuBar" => "MenuItem with Icon"); icon: @image-url("thumbsup.png"); }
MenuItem { title: @tr("MenuBar" => "MenuItem with Checkmark"); checkable: true; checked: true; }
}
Menu {
title: @tr("MenuBar" => "Edit");
Expand Down
40 changes: 25 additions & 15 deletions internal/backends/winit/muda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,21 +137,31 @@ impl MudaAdapter {
Box::new(muda::PredefinedMenuItem::separator())
} else if !entry.has_sub_menu {
// the top level always has a sub menu regardless of entry.has_sub_menu
let icon = entry
.icon
.to_rgba8()
.map(|rgba| {
muda::Icon::from_rgba(rgba.as_bytes().to_vec(), rgba.width(), rgba.height())
.ok()
})
.flatten();
Box::new(muda::IconMenuItem::with_id(
id.clone(),
&entry.title,
entry.enabled,
icon,
None,
))
if entry.checkable {
Box::new(muda::CheckMenuItem::with_id(
id.clone(),
&entry.title,
entry.enabled,
entry.checked,
None,
))
} else if let Some(rgba) = entry.icon.to_rgba8() {
let icon = muda::Icon::from_rgba(
rgba.as_bytes().to_vec(),
rgba.width(),
rgba.height(),
)
.ok();
Box::new(muda::IconMenuItem::with_id(
id.clone(),
&entry.title,
entry.enabled,
icon,
None,
))
} else {
Box::new(muda::MenuItem::with_id(id.clone(), &entry.title, entry.enabled, None))
}
} else {
let sub_menu = muda::Submenu::with_id(id.clone(), &entry.title, entry.enabled);
if depth < 15 {
Expand Down
4 changes: 4 additions & 0 deletions internal/common/builtin_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ macro_rules! for_each_builtin_structs {
// keyboard_shortcut: KeySequence,
/// whether the menu entry is enabled
enabled: bool,
/// whether the menu entry is checkable
checkable: bool,
/// whether the menu entry is checked
checked: bool,
/// Sub menu
has_sub_menu: bool,
/// The menu entry is a separator
Expand Down
2 changes: 2 additions & 0 deletions internal/compiler/builtins.slint
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ component MenuItem {
in property <string> title;
callback activated();
in property <bool> enabled: true;
in property <bool> checkable: false;
in-out property <bool> checked: false;
in property <image> icon;
//-disallow_global_types_as_child_elements
//-is_non_item_type
Expand Down
13 changes: 10 additions & 3 deletions internal/compiler/widgets/common/menu-base.slint
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,18 @@ export component MenuItemBase {

spacing: 10px;

Image {
Rectangle {
width: root.icon-size;
y: (parent.height - self.height) / 2;
source: entry.icon;
accessible-role: none;

if entry.checked: Text {
text: "✓";
}

Image {
source: entry.icon;
accessible-role: none;
}
}

label := Text {
Expand Down
19 changes: 18 additions & 1 deletion internal/core/menus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,23 @@ impl MenuFromItemTree {
let children = self.update_shadow_tree_recursive(&item);
let has_sub_menu = !children.is_empty();
let enabled = menu_item.enabled();
let checkable = menu_item.checkable();
let checked = menu_item.checked();
let icon = menu_item.icon();
self.item_cache.borrow_mut().insert(
id.clone(),
ShadowTreeNode { item: ItemRc::downgrade(&item), children },
);
result.push(MenuEntry { title, id, has_sub_menu, is_separator, enabled, icon });
result.push(MenuEntry {
title,
id,
has_sub_menu,
is_separator,
enabled,
checkable,
checked,
icon,
});
}
VisitChildrenResult::CONTINUE
};
Expand Down Expand Up @@ -133,6 +144,10 @@ impl Menu for MenuFromItemTree {
self.item_cache.borrow().get(entry.id.as_str()).and_then(|e| e.item.upgrade())
{
if let Some(menu_item) = menu_item.downcast::<MenuItem>() {
if menu_item.as_pin_ref().checkable() {
menu_item.checked.set(!menu_item.as_pin_ref().checked());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I suspect this is what I’m looking for?

}

menu_item.activated.call(&());
}
}
Expand All @@ -150,6 +165,8 @@ pub struct MenuItem {
pub title: Property<SharedString>,
pub activated: Callback<VoidArg>,
pub enabled: Property<bool>,
pub checkable: Property<bool>,
pub checked: Property<bool>,
pub icon: Property<Image>,
}

Expand Down
18 changes: 18 additions & 0 deletions tests/cases/widgets/contextmenu.slint
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export component TestCase inherits Window {
MenuSeparator {}
}
}
MenuItem {
title: "Entry3";
checkable: true;
checked <=> checkable-menu-item-checked;
}
}

// When this focus scope has the focus, the ContextMenu can handle the Menu key
Expand All @@ -47,6 +52,8 @@ export component TestCase inherits Window {


out property <bool> test: true;

out property <bool> checkable-menu-item-checked;
}

/*
Expand Down Expand Up @@ -76,6 +83,17 @@ slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key:
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::UpArrow));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::Return));
assert_eq!(instance.get_result(), "Open2");

// verify that the checked menu item is not checked
assert!(!instance.get_checkable_menu_item_checked());
// navigate using the keys to the "Checked" menu item and toggle it
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::Menu));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from("\n"));
// verify that the checked menu item is not checked
assert!(!instance.get_checkable_menu_item_checked());
```

```cpp
Expand Down
22 changes: 22 additions & 0 deletions tests/cases/widgets/menubar.slint
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export component TestCase inherits Window {
activated => { open-recent("Recent 3"); }
}
}
MenuItem {
title: "Checkable";
checkable: true;
checked <=> checkable-menu-item-checked;
}
MenuSeparator {}
MenuSeparator {}
MenuItem {
Expand Down Expand Up @@ -71,6 +76,8 @@ export component TestCase inherits Window {
out property <bool> check-geometry: vl.x == 0 && vl.y == 0 && vl.width == root.width && vl.height == root.height;

out property <bool> test: check-geometry;

out property <bool> checkable-menu-item-checked;
}

/*
Expand Down Expand Up @@ -105,10 +112,24 @@ slint_testing::send_mouse_click(&instance, 10., 16.);
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow)); // New
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow)); // Open
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow)); // Open Recent
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow)); // Checkable
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow)); // Save (skipped the separator)
assert_eq!(instance.get_result(), "");
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from("\n"));
assert_eq!(instance.get_result(), "Save");

// verify that the checked menu item is not checked
assert!(!instance.get_checkable_menu_item_checked());
// click on the file menu
slint_testing::send_mouse_click(&instance, 10., 16.);
// navigate using the keys to the "Checked" menu item and toggle it
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from(Key::DownArrow));
slint_testing::send_keyboard_string_sequence(&instance, &SharedString::from("\n"));
// verify that the checked menu item is checked
assert!(instance.get_checkable_menu_item_checked());
```

```cpp
Expand Down Expand Up @@ -142,6 +163,7 @@ slint_testing::send_mouse_click(&instance, 10., 16.);
slint_testing::send_keyboard_string_sequence(&instance, slint::platform::key_codes::DownArrow); // New
slint_testing::send_keyboard_string_sequence(&instance, slint::platform::key_codes::DownArrow); // Open
slint_testing::send_keyboard_string_sequence(&instance, slint::platform::key_codes::DownArrow); // Open Recent
slint_testing::send_keyboard_string_sequence(&instance, slint::platform::key_codes::DownArrow); // Checkable
slint_testing::send_keyboard_string_sequence(&instance, slint::platform::key_codes::DownArrow); // Save (skipped the separator)
assert_eq(instance.get_result(), "");
slint_testing::send_keyboard_string_sequence(&instance, "\n");
Expand Down
Loading