slint/internal/compiler/widgets/common/menus.slint
Joshua Goins d1f2fef430
Menu: Add enabled property (#8145)
By default it's enabled, of course. This property is not only added to
MenuItem, but Menu as well - since they can be nested. It's also
possible to select a disabled item, but it's hard to modify that since
it's logic is written in Slint. You should be prevented from activating
it with a tap or key press at least.

See #7791
2025-04-15 08:08:11 +02:00

173 lines
5.5 KiB
Text

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
//! This file contains a generic implementation of the MenuBar and ContextMenu
import { Palette, MenuBarItem, MenuBar, MenuFrame, MenuItem } from "std-widgets-impl.slint";
export component PopupMenuImpl inherits Window {
in property <[MenuEntry]> entries: [];
property <int> current-highlight: -1;
property <int> current-open: -1;
property <length> px: 1rem / 14;
callback sub-menu(menu-entry: MenuEntry) -> [MenuEntry];
callback activated(menu-entry: MenuEntry);
callback close();
forward-focus: focus-scope;
background: transparent;
focus-scope := FocusScope {
frame := MenuFrame {
for entry[index] in entries: MenuItem {
entry: entry;
is-current: current-highlight == index;
set-current => {
focus-scope.focus();
root.current-highlight = index;
open-sub-menu-after-timeout.running = true;
}
clear-current => {
root.current-highlight = -1;
}
activate(entry, y) => {
root.activate(entry, y, index);
}
}
}
open-sub-menu-after-timeout := Timer {
interval: 500ms;
running: false;
triggered => {
self.running = false;
if current-highlight >= 0 {
if entries[current-highlight].has-sub-menu {
activate(entries[current-highlight], y-pos(current-highlight), current-highlight);
} else {
current-open = -1;
sub-menu.close();
}
}
}
}
function y-pos(idx: int) -> length {
frame.padding + idx * (frame.height - 2 * frame.padding) / entries.length
}
key-pressed(event) => {
open-sub-menu-after-timeout.running = false;
if event.text == Key.UpArrow {
if current-highlight < 1 {
current-highlight = entries.length - 1;
} else if entries[current-highlight - 1].is_separator {
current-highlight -= 2;
} else {
current-highlight -= 1;
}
return accept;
} else if event.text == Key.DownArrow {
if current-highlight >= entries.length - 1 {
current-highlight = 0;
} else if entries[current-highlight + 1].is_separator {
current-highlight += 2;
} else {
current-highlight += 1;
}
return accept;
} else if event.text == Key.Return {
if current-highlight >= 0 && current-highlight < entries.length && entries[current-highlight].enabled {
activate(entries[current-highlight], y-pos(current-highlight), current-highlight);
}
return accept;
} else if event.text == Key.RightArrow {
if current-highlight >= 0 && current-highlight < entries.length && entries[current-highlight].has-sub-menu && entries[current-highlight].enabled {
activate(entries[current-highlight], y-pos(current-highlight), current-highlight);
}
return accept;
} else if event.text == Key.LeftArrow {
// TODO: should close only if this menu is a sub menu
root.close();
}
reject
}
}
sub-menu := ContextMenuInternal {
x: 0; y: 0; width: 0; height: 0;
sub-menu(entry) => { root.sub-menu(entry); }
activated(entry) => {
current-open = -1;
root.activated(entry);
root.close();
}
}
function activate(entry : MenuEntry, y: length, index: int) {
open-sub-menu-after-timeout.running = false;
if entry.has-sub-menu {
if current-open != index || !sub-menu.is-open() {
current-open = index;
sub-menu.entries = root.sub-menu(entry);
sub-menu.show({
x: root.width,
y: y - sub-menu.absolute-position.y,
});
}
} else {
current-open = -1;
activated(entry);
close();
}
}
}
export component MenuBarImpl {
callback activated(menu-entry: MenuEntry);
callback sub-menu(menu-entry: MenuEntry) -> [MenuEntry];
property <[MenuEntry]> entries;
preferred-width: 100%;
height: base.min-height;
private property <int> last-open-entry-index : -1;
base := MenuBar {
width: 100%;
for entry[idx] in entries: e := MenuBarItem {
entry: entry;
clicked => {
last-open-entry-index = idx;
context-menu.entries = root.sub-menu(entry);
context-menu.show({ x: e.x, y: root.height });
}
hovered => {
if last-open-entry-index != idx && context-menu.is-open() {
self.clicked();
}
}
}
}
context-menu := ContextMenuInternal {
activated(entry) => { root.activated(entry); self.close(); }
sub-menu(entry) => { root.sub-menu(entry); }
}
}