2023-01-05 13:30:22 +00:00
|
|
|
|
# SPDX-FileCopyrightText: (C) 2022 Frederic Croix <thefgfseagle@gmail.com>
|
|
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
#
|
|
|
|
|
# Usage example for the menu, with submenu:
|
|
|
|
|
#
|
|
|
|
|
# var m = canvas.gui.Menu.new();
|
|
|
|
|
# var i1 = m.createItem("Text");
|
|
|
|
|
# var i2 = m.createItem("Text 2");
|
|
|
|
|
# var sub = canvas.gui.Menu.new();
|
|
|
|
|
# var s1 = sub.createItem("Sub", nil);
|
|
|
|
|
# var s2 = sub.createItem("Sub", nil);
|
|
|
|
|
# i1.setMenu(sub);
|
|
|
|
|
# m.setPosition(200, 200);
|
|
|
|
|
# m.show();
|
|
|
|
|
|
|
|
|
|
gui.MenuItem = {
|
2023-01-10 19:42:01 +00:00
|
|
|
|
MenuPosition: {
|
|
|
|
|
Above: 0x0,
|
|
|
|
|
Right: 0x1,
|
|
|
|
|
Below: 0x2,
|
|
|
|
|
Left: 0x4
|
|
|
|
|
},
|
2023-01-05 13:30:22 +00:00
|
|
|
|
# @description Create a new menu item widget
|
|
|
|
|
# @cfg_field text: str Text of the new menu item
|
2023-01-08 14:35:46 +00:00
|
|
|
|
# @cfg_field shortcut: str String representation of the keyboard shortcut for the item
|
2023-01-05 13:30:22 +00:00
|
|
|
|
# @cfg_field cb: callable Function / method to call when the item is clicked
|
|
|
|
|
# @cfg_field icon: str Path of an icon to be displayed (relative to the path in `canvas.style._dir_widgets`)
|
|
|
|
|
# @cfg_field enabled: bool Initial state of the menu item: enabled (1) or disabled (0)
|
|
|
|
|
new: func(parent, style, cfg) {
|
|
|
|
|
var cfg = Config.new(cfg);
|
|
|
|
|
var m = gui.Widget.new(gui.MenuItem);
|
|
|
|
|
m._text = cfg.get("text", "Menu item");
|
2023-01-08 14:35:46 +00:00
|
|
|
|
m._shortcut = cfg.get("shortcut", nil);
|
2023-01-05 13:30:22 +00:00
|
|
|
|
m._cb = cfg.get("cb", nil);
|
|
|
|
|
m._icon = cfg.get("icon", nil);
|
|
|
|
|
m._enabled = cfg.get("enabled", 1);
|
2023-01-10 19:42:01 +00:00
|
|
|
|
m._menu_position = cfg.get("menu_position", gui.MenuItem.MenuPosition.Right);
|
2023-01-05 13:30:22 +00:00
|
|
|
|
m._hovered = 0;
|
|
|
|
|
m._menu = nil;
|
|
|
|
|
m._parent_menu = nil;
|
2023-01-10 19:42:01 +00:00
|
|
|
|
m._is_menubar_item = 0;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
|
|
|
|
|
m._setView(style.createWidget(parent, cfg.get("type", "menu-item"), cfg));
|
|
|
|
|
|
|
|
|
|
m.setLayoutMinimumSize([48, 24]);
|
|
|
|
|
m.setLayoutSizeHint([64, 24]);
|
|
|
|
|
m.setLayoutMaximumSize([1024, 24]);
|
2023-01-06 11:19:32 +00:00
|
|
|
|
|
2023-01-08 14:35:46 +00:00
|
|
|
|
m.setText(m._text);
|
|
|
|
|
m.setIcon(m._icon);
|
|
|
|
|
m.setShortcut(m._shortcut);
|
2023-01-05 13:30:22 +00:00
|
|
|
|
return m;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setMenu: func(menu) {
|
2023-01-10 19:42:01 +00:00
|
|
|
|
menu._parent_item = me;
|
|
|
|
|
menu._canvas_item = me._parent_menu._canvas_item;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
me._menu = menu;
|
|
|
|
|
|
|
|
|
|
return me.update();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onClicked: func(e) {
|
|
|
|
|
if (!me._menu and me._cb) {
|
|
|
|
|
me._cb(e);
|
|
|
|
|
}
|
2023-01-10 19:42:01 +00:00
|
|
|
|
if (me._parent_menu != nil) {
|
|
|
|
|
me._parent_menu.hide();
|
|
|
|
|
}
|
2023-01-05 13:30:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onMouseEnter: func(e) {
|
|
|
|
|
for (var i = 0; i < me._parent_menu._layout.count(); i += 1) {
|
|
|
|
|
var item = me._parent_menu._layout.itemAt(i);
|
|
|
|
|
item._hovered = 0;
|
2023-01-10 19:42:01 +00:00
|
|
|
|
if (item._menu != nil) {
|
2023-01-05 13:30:22 +00:00
|
|
|
|
item._menu.hide();
|
|
|
|
|
}
|
|
|
|
|
item.update();
|
|
|
|
|
}
|
|
|
|
|
if (me._enabled) {
|
|
|
|
|
me._hovered = 1;
|
2023-01-10 19:42:01 +00:00
|
|
|
|
var x = e.screenX - e.localX;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
var y = e.screenY - e.localY;
|
|
|
|
|
me._showMenu(x, y);
|
|
|
|
|
}
|
|
|
|
|
me.update();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
onMouseLeave: func(e) {
|
|
|
|
|
if (me._menu == nil) {
|
|
|
|
|
me._hovered = 0;
|
|
|
|
|
}
|
|
|
|
|
me.update();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
removeMenu: func() {
|
|
|
|
|
me._menu = nil;
|
|
|
|
|
return me.update();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_showMenu: func(x, y) {
|
|
|
|
|
if (me._menu) {
|
2023-01-10 19:42:01 +00:00
|
|
|
|
var pos = [0, 0];
|
|
|
|
|
if (me._menu_position == gui.MenuItem.MenuPosition.Right) {
|
|
|
|
|
pos = [x + me.geometry()[2], y];
|
|
|
|
|
} elsif (me._menu_position == gui.MenuItem.MenuPosition.Below) {
|
|
|
|
|
pos = [x, me.geometry()[3] + y];
|
|
|
|
|
} elsif (me._menu_position == gui.MenuItem.MenuPosition.Above) {
|
|
|
|
|
pos = [x, y - me._menu.getSize()[1]];
|
|
|
|
|
} elsif (me._menu_position == gui.MenuItem.MenuPosition.Left) {
|
|
|
|
|
pos = [x - me._menu.getSize()[0], y];
|
|
|
|
|
}
|
|
|
|
|
me._menu.setPosition(pos[0], pos[1]);
|
2023-01-05 13:30:22 +00:00
|
|
|
|
me._menu.show();
|
|
|
|
|
me._menu.setFocus();
|
|
|
|
|
}
|
2023-01-10 19:42:01 +00:00
|
|
|
|
me._hovered = 1;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_hideMenu: func {
|
|
|
|
|
if (me._menu) {
|
|
|
|
|
me._menu.clearFocus();
|
|
|
|
|
me._menu.hide();
|
|
|
|
|
}
|
|
|
|
|
me._hovered = 0;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setEnabled: func(enabled = 1) {
|
|
|
|
|
me._enabled = enabled;
|
|
|
|
|
return me.update();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setText: func(text) {
|
|
|
|
|
me._text = text;
|
|
|
|
|
me._view.setText(me, text);
|
|
|
|
|
return me.update();
|
|
|
|
|
},
|
2023-01-06 11:19:32 +00:00
|
|
|
|
|
|
|
|
|
setShortcut: func(shortcut) {
|
2023-01-08 14:35:46 +00:00
|
|
|
|
me._shortcut = keyboard.Shortcut.new(shortcut);
|
|
|
|
|
if (me._parent_menu != nil and me._parent_menu._canvas_item != nil and me._cb != nil) {
|
|
|
|
|
me._parent_menu._canvas_item.bindShortcut(me._shortcut, me._cb);
|
|
|
|
|
}
|
|
|
|
|
me._view.setShortcut(me, me._shortcut);
|
2023-01-06 11:19:32 +00:00
|
|
|
|
return me.update();
|
|
|
|
|
},
|
2023-01-05 13:30:22 +00:00
|
|
|
|
|
2023-01-08 14:35:46 +00:00
|
|
|
|
_setParentMenu: func(m) {
|
|
|
|
|
me._parent_menu = m;
|
|
|
|
|
if (me._parent_menu != nil and me._parent_menu._canvas_item != nil and me._cb != nil) {
|
2023-01-10 19:42:01 +00:00
|
|
|
|
if (me._shortcut != nil) {
|
|
|
|
|
me._parent_menu._canvas_item.bindShortcut(me._shortcut, me._cb);
|
|
|
|
|
}
|
|
|
|
|
if (me._menu != nil) {
|
|
|
|
|
for (var i = 0; i < me._menu.count(); i += 1) {
|
|
|
|
|
me._menu.getItem(i).setCanvasItem(me._parent_menu._canvas_item);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-08 14:35:46 +00:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2023-01-05 13:30:22 +00:00
|
|
|
|
setIcon: func(icon) {
|
|
|
|
|
me._icon = icon;
|
|
|
|
|
me._view.setIcon(icon);
|
|
|
|
|
return me.update();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setCallback: func(cb = nil) {
|
|
|
|
|
me._cb = cb;
|
|
|
|
|
return me;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
update: func {
|
2023-01-10 19:42:01 +00:00
|
|
|
|
if (me._view != nil) {
|
|
|
|
|
me._view.update(me);
|
|
|
|
|
}
|
2023-01-05 13:30:22 +00:00
|
|
|
|
return me;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_setView: func(view) {
|
|
|
|
|
call(gui.Widget._setView, [view], me);
|
|
|
|
|
|
|
|
|
|
var el = view._root;
|
|
|
|
|
el.addEventListener("click", func(e) me.onClicked(e));
|
|
|
|
|
|
|
|
|
|
el.addEventListener("mouseenter", func(e) me.onMouseEnter(e));
|
|
|
|
|
el.addEventListener("mouseleave", func(e) me.onMouseLeave(e));
|
|
|
|
|
el.addEventListener("drag", func(e) e.stopPropagation());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
gui.Menu = {
|
|
|
|
|
new: func(id = nil) {
|
|
|
|
|
var m = gui.Popup.new([100, 60], id);
|
|
|
|
|
m.parents = [gui.Menu] ~ m.parents;
|
|
|
|
|
m.style = style;
|
2023-01-10 19:42:01 +00:00
|
|
|
|
m._parent_item = nil;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
|
|
|
|
|
m._canvas = m.createCanvas().setColorBackground(style.getColor("bg_color"));
|
|
|
|
|
m._root = m._canvas.createGroup();
|
|
|
|
|
m._layout = VBoxLayout.new();
|
|
|
|
|
m._layout.setSpacing(0);
|
2023-01-08 14:35:46 +00:00
|
|
|
|
m._canvas_item = nil;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
m.setLayout(m._layout);
|
|
|
|
|
m.hide();
|
|
|
|
|
|
|
|
|
|
return m;
|
|
|
|
|
},
|
|
|
|
|
|
2023-01-08 14:35:46 +00:00
|
|
|
|
setCanvasItem: func(item) {
|
|
|
|
|
me._canvas_item = item;
|
|
|
|
|
for (var i = 0; i < me._layout.count(); i += 1) {
|
|
|
|
|
me._layout.itemAt(i)._setParentMenu(me);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2023-01-05 13:30:22 +00:00
|
|
|
|
# @description Add the given menu item to the menu (normally a `canvas.gui.MenuItem`, but can be any `canvas.gui.Widget` in theory)
|
|
|
|
|
# @return canvas.gui.Menu Return me to enable method chaining
|
|
|
|
|
addItem: func(item) {
|
2023-01-08 14:35:46 +00:00
|
|
|
|
item._setParentMenu(me);
|
2023-01-05 13:30:22 +00:00
|
|
|
|
me._layout.addItem(item);
|
2023-01-10 19:42:01 +00:00
|
|
|
|
me.setSize(math.max(me._layout.minimumSize()[0], 64), math.max(me._layout.minimumSize()[1], 24));
|
2023-01-05 13:30:22 +00:00
|
|
|
|
return me;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# @description Create, insert and return a `canvas.gui.MenuItem` with given text and an optional callback, icon and enabled state.
|
2023-01-06 11:19:32 +00:00
|
|
|
|
# @param text: str required Text to display on the menu item
|
2023-01-05 13:30:22 +00:00
|
|
|
|
# @param cb: callable optional Function / method to call when the item is clicked - if no callback is wanted, nil can be used
|
2023-01-06 11:19:32 +00:00
|
|
|
|
# @param shortcut: str String representation of the keyboard shortcut for the item
|
2023-01-05 13:30:22 +00:00
|
|
|
|
# @param icon: str optional Path to the icon (relative to canvas.style._dir_widgets) or nil if none should be displayed
|
|
|
|
|
# @param enabled: bool optional Whether the item should be enabled (1) or disabled (0)
|
|
|
|
|
# @return canvas.gui.MenuItem The item that was created
|
2023-01-08 14:35:46 +00:00
|
|
|
|
createItem: func(text = nil, cb = nil, shortcut = nil, icon = nil, enabled = 1) {
|
2023-01-06 11:19:32 +00:00
|
|
|
|
if (text == nil) {
|
|
|
|
|
die("cannot create a menu item without text");
|
|
|
|
|
}
|
|
|
|
|
var item = gui.MenuItem.new(me._root, me.style, {text: text, cb: cb, shortcut: shortcut, icon: icon, enabled: enabled});
|
2023-01-05 13:30:22 +00:00
|
|
|
|
me.addItem(item);
|
2023-01-10 19:42:01 +00:00
|
|
|
|
return me;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# @description Create, insert and return a `canvas.gui.MenuItem with the given text and assign the given submenu to it,
|
|
|
|
|
# optionally add the given icon and set the given enabled state
|
2023-01-06 11:19:32 +00:00
|
|
|
|
# @param text: str required Text to display on the menu item
|
2023-01-05 13:30:22 +00:00
|
|
|
|
# @param menu: canvas.gui.Menu Submenu that shall be assigned to the new menu item
|
|
|
|
|
# @param enabled: bool optional Whether the item should be enabled (1) or disabled (0)
|
|
|
|
|
# @return canvas.gui.MenuItem The item that was created
|
2023-01-10 19:42:01 +00:00
|
|
|
|
addMenu: func(text = nil, menu = nil, enabled = 1) {
|
2023-01-06 11:19:32 +00:00
|
|
|
|
if (text == nil) {
|
|
|
|
|
die("cannot create a menu item without text");
|
|
|
|
|
}
|
|
|
|
|
if (menu == nil) {
|
|
|
|
|
die("cannot create a submenu item without submenu");
|
|
|
|
|
}
|
2023-01-10 19:42:01 +00:00
|
|
|
|
var item = gui.MenuItem.new(me._root, me.style, {text: text, cb: nil, shortcut: nil, icon: nil, enabled: enabled});
|
|
|
|
|
menu._parent_item = item;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
item.setMenu(menu);
|
|
|
|
|
me.addItem(item);
|
2023-01-10 19:42:01 +00:00
|
|
|
|
return me;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
createMenu: func(text = nil, enabled = 1) {
|
|
|
|
|
if (text == nil) {
|
|
|
|
|
die("cannot create a submenu item without text");
|
|
|
|
|
}
|
|
|
|
|
var menu = gui.Menu.new();
|
|
|
|
|
var item = gui.MenuItem.new(me._root, me.style, {text: text, cb: nil, shortcut: nil, icon: nil, enabled: enabled});
|
|
|
|
|
menu._parent_item = item;
|
|
|
|
|
item.setMenu(menu);
|
|
|
|
|
me.addItem(item);
|
|
|
|
|
return menu;
|
2023-01-05 13:30:22 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# @description Remove all items from the menu
|
|
|
|
|
# @return canvas.gui.Menu Return me to enable method chaining
|
|
|
|
|
clear: func {
|
|
|
|
|
me._layout.clear();
|
|
|
|
|
return me;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# @description If `item` is a `canvas.gui.Widget`, remove the given `canvas.gui.Widget` from the menu
|
|
|
|
|
# Else assume `item` to be a scalar and try to find an item of the menu that has a `getText` method
|
|
|
|
|
# and whose result of calling its `getText` method equals `item` and remove that item
|
|
|
|
|
# @param item: Union[str, canvas.gui.Widget] required The widget or the text of the menu item to remove
|
|
|
|
|
removeItem: func(item) {
|
|
|
|
|
if (isa(item, gui.Widget)) {
|
|
|
|
|
me._layout.removeItem(item);
|
|
|
|
|
} else {
|
|
|
|
|
for (var i = 0; i < me._layout.count(); i += 1) {
|
|
|
|
|
if (me._layout.itemAt(i)["getText"] != nil and me._layout.itemAt(i).getText() == item) {
|
|
|
|
|
me._layout.takeAt(i);
|
|
|
|
|
return me;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
die("No menu item with given text '" ~ item ~ "' found, could not remove !");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# @description If `index` is an integer, remove and return the item at the given `index`
|
|
|
|
|
# Else assume `item` to be a scalar and try to find an item of the menu that has a `getText` method
|
|
|
|
|
# and whose result of calling its `getText` method equals `item` and remove and return that item
|
|
|
|
|
# @param index: Union[int, str] required The index or text of the menu item to remove
|
|
|
|
|
# @return canvas.gui.Widget The item with given text `index` or at the given position `index`
|
|
|
|
|
takeAt: func(index) {
|
|
|
|
|
if (isint(index)) {
|
|
|
|
|
return me._layout.takeAt(index);
|
|
|
|
|
} else {
|
|
|
|
|
for (var i = 0; i < me._layout.count(); i += 1) {
|
|
|
|
|
if (me._layout.itemAt(i)["getText"] != nil and me._layout.itemAt(i).getText() == index) {
|
|
|
|
|
return me._layout.takeAt(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
die("No menu item with given text '" ~ index ~ "' found, could not remove !");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# @description Count the items of the menu
|
|
|
|
|
# @return int Number of items
|
|
|
|
|
count: func() {
|
|
|
|
|
return me._layout.count();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# @description If `index` is an integer, eturn the item at the given `index`
|
|
|
|
|
# Else assume `item` to be a scalar and try to find an item of the menu that has a `getText` method
|
|
|
|
|
# and whose result of calling its `getText` method equals `item` and eturn that item
|
|
|
|
|
# @param index: Union[int, str] required The index or text of the menu item to return
|
|
|
|
|
# @return canvas.gui.Widget The item with given text `index` or at the given position `index`
|
|
|
|
|
getItem: func(index) {
|
|
|
|
|
if (isint(index)) {
|
|
|
|
|
return me._layout.itemAt(index);
|
|
|
|
|
} else {
|
|
|
|
|
for (var i = 0; i < me._layout.count(); i += 1) {
|
|
|
|
|
if (me._layout.itemAt(i)["getText"] != nil and me._layout.itemAt(i).getText() == index) {
|
|
|
|
|
return me._layout.itemAt(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
die("No menu item with given text '" ~ index ~ "' found, could not remove !");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2023-01-06 11:19:32 +00:00
|
|
|
|
# @description Show the menu, moving it to the position given by `x` and `y` if ´x` and `y` are not nil
|
|
|
|
|
show: func(x = nil, y = nil) {
|
|
|
|
|
if (x != nil and y != nil) {
|
|
|
|
|
me.setPosition(x, y);
|
|
|
|
|
}
|
|
|
|
|
call(me.parents[1].show, [], me);
|
|
|
|
|
},
|
|
|
|
|
|
2023-01-10 19:42:01 +00:00
|
|
|
|
hide: func {
|
|
|
|
|
if (me._parent_item != nil) {
|
|
|
|
|
me._parent_item._hovered = 0;
|
|
|
|
|
me._parent_item.update();
|
|
|
|
|
}
|
|
|
|
|
call(me.parents[1].hide, [], me);
|
|
|
|
|
},
|
|
|
|
|
|
2023-01-05 13:30:22 +00:00
|
|
|
|
# @description Destructor
|
|
|
|
|
del: func() {
|
|
|
|
|
me.hide();
|
|
|
|
|
me.clear();
|
|
|
|
|
me._canvas.del();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
# @description Update the menu and its items
|
|
|
|
|
update: func() {
|
|
|
|
|
me.parents[1].update();
|
|
|
|
|
for (var i = 0; i < me._layout.count(); i += 1) {
|
|
|
|
|
me._layout.itemAt(i).update();
|
|
|
|
|
}
|
|
|
|
|
return me;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|