diff --git a/Nasal/canvas/gui.nas b/Nasal/canvas/gui.nas index de375e4c2..3bb2f5713 100644 --- a/Nasal/canvas/gui.nas +++ b/Nasal/canvas/gui.nas @@ -14,7 +14,6 @@ var gui = { focused_window: nil, open_popups: [], region_highlight: nil, - menubar: nil, # Window/dialog stacking order STACK_INDEX: { @@ -31,6 +30,8 @@ var loadDialog = func(name) loadGUIFile("dialogs/" ~ name ~ ".nas"); loadGUIFile("Config.nas"); loadGUIFile("Menu.nas"); +loadGUIFile("MenuBar.nas"); +loadGUIFile("Overlay.nas"); loadGUIFile("Popup.nas"); loadGUIFile("Style.nas"); loadGUIFile("Widget.nas"); @@ -606,21 +607,14 @@ getDesktop().addEventListener("mousedown", func { } }); -# disabled until this is hooked up to the PUICompat code -#gui.menubar = gui.MenuBar.new(); - # Provide old 'Dialog' for backwards compatiblity (should be removed for 3.0) var Dialog = { new: func(size, type = nil, id = nil) { - debug.warn("'canvas.Dialog' is deprectated! (use canvas.Window instead)"); + debug.warn("'canvas.Dialog' is deprecated! (use canvas.Window instead)"); return Window.new(size, type, id); } }; var unloadGUI = func() { - if (gui.menubar) { - gui.menubar.del(); - gui.menubar = nil; - } } diff --git a/Nasal/canvas/gui/MenuBar.nas b/Nasal/canvas/gui/MenuBar.nas new file mode 100644 index 000000000..28174c718 --- /dev/null +++ b/Nasal/canvas/gui/MenuBar.nas @@ -0,0 +1,58 @@ +gui.MenuBar = { + new: func() { + var obj = { + parents: [gui.MenuBar, canvas.gui.Overlay.new([24, 24], "global-menu-bar")], + }; + + obj._canvas = obj.createCanvas().setColorBackground(canvas.style.getColor("bg_color")); + obj._root = obj._canvas.createGroup(); + obj._layout = canvas.VBoxLayout.new(); + obj._canvas.setLayout(obj._layout); + obj._menuBar = canvas.gui.widgets.MenuBar.new(obj._root, canvas.style, {}); + obj._layout.addItem(obj._menuBar); + + var wrapSizeChangingWidgetFunc = func(sizeChangingWidgetFuncName) { + var sizeChangingWidgetFunc = gui.widgets.MenuBar[sizeChangingWidgetFuncName]; + var wrapperFunc = func { + var result = call(sizeChangingWidgetFunc, arg, obj._menuBar); + obj.updateSize(); + return result; + } + obj[sizeChangingWidgetFuncName] = wrapperFunc; + } + var wrapWidgetFunc = func(widgetFuncName) { + var widgetFunc = gui.widgets.MenuBar[widgetFuncName]; + var wrapperFunc = func { + return call(widgetFunc, arg, obj._menuBar); + } + obj[widgetFuncName] = wrapperFunc; + } + + wrapSizeChangingWidgetFunc("addMenu"); + wrapSizeChangingWidgetFunc("createMenu"); + wrapSizeChangingWidgetFunc("clear"); + wrapSizeChangingWidgetFunc("removeMenu"); + wrapSizeChangingWidgetFunc("takeAt"); + wrapWidgetFunc("count"); + wrapWidgetFunc("getItem"); + wrapWidgetFunc("getMenu"); + + return obj; + }, + + setSize: func(s) { + call(me.parents[1].setSize, [s], me); + me._menuBar.setSize(s); + return me; + }, + + updateSize: func { + me.setSize(me._menuBar._size); + }, + + update: func { + me._menuBar.update(); + return me; + }, +}; + diff --git a/Nasal/canvas/gui/Overlay.nas b/Nasal/canvas/gui/Overlay.nas new file mode 100644 index 000000000..f485db108 --- /dev/null +++ b/Nasal/canvas/gui/Overlay.nas @@ -0,0 +1,245 @@ +# SPDX-FileCopyrightText: (C) 2022 Frederic Croix +# SPDX-License-Identifier: GPL-2.0-or-later + +gui.Overlay = { + # Constructor + # + # @param size ([width, height]) + new: func(size_, id = nil) { + var ghost = _newWindowGhost(id); + var m = { + parents: [gui.Overlay, PropertyElement, ghost], + _ghost: ghost, + _node: props.wrapNode(ghost._node_ghost), + _widgets: [], + _canvas: nil, + _focused: 1, + }; + + m.setSize(size_); + m._updateDecoration(); + + # arg = [child, listener_node, mode, is_child_event] + setlistener(m._node, func m._propCallback(arg[0], arg[2]), 0, 2); + + return m; + }, + # Destructor + del: func { + if (me["_canvas"] != nil) { + var placements = me._canvas._node.getChildren("placement"); + # Do not remove canvas if other placements exist + if (size(placements) > 1) { + foreach (var p; placements) { + if (p.getValue("type") == "window" and p.getValue("id") == me.get("id")) { + p.remove(); + } + } + } else { + me._canvas.del(); + } + me._canvas = nil; + } + if (me._node != nil) { + me._node.remove(); + me._node = nil; + } + }, + # Create the canvas to be used for this Window + # + # @return The new canvas + createCanvas: func() { + var size = [ + me.get("content-size[0]"), + me.get("content-size[1]") + ]; + + me._canvas = new({ + size: [size[0], size[1]], + view: size, + placement: { + type: "window", + id: me.get("id") + }, + + # Standard alpha blending + "blend-source-rgb": "src-alpha", + "blend-destination-rgb": "one-minus-src-alpha", + + # Just keep current alpha (TODO allow using rgb textures instead of rgba?) + "blend-source-alpha": "zero", + "blend-destination-alpha": "one" + }); + + me._canvas._focused_widget = nil; + me._canvas.data("focused", me._focused); + + return me._canvas; + }, + # Set an existing canvas to be used for this Window + setCanvas: func(canvas_) { + if (ghosttype(canvas_) != "Canvas") { + return debug.warn("Not a Canvas"); + } + + canvas_.addPlacement({type: "window", "id": me.get("id")}); + me['_canvas'] = canvas_; + + canvas_._focused_widget = nil; + canvas_.data("focused", me._focused); + + return me; + }, + # Get the displayed canvas + getCanvas: func(create = 0) { + if (me['_canvas'] == nil and create) { + me.createCanvas(); + } + + return me['_canvas']; + }, + setLayout: func(l) { + if (me['_canvas'] == nil) { + me.createCanvas(); + } + + me._canvas.update(); # Ensure placement is applied + me._ghost.setLayout(l); + return me; + }, + setPosition: func { + if (size(arg) == 1) { + var arg = arg[0]; + } + var (x, y) = arg; + + me.setInt("tf/t[0]", x); + me.setInt("tf/t[1]", y); + return me; + }, + setSize: func { + if (size(arg) == 1) { + var arg = arg[0]; + } + var (w, h) = arg; + + me.set("content-size[0]", w); + me.set("content-size[1]", h); + + if (me["_canvas"] == nil) { + print("canvas is nil"); + return; + } + + for (var i = 0; i < 2; i += 1) { + var size = me.get("content-size[" ~ i ~ "]"); + me._canvas.set("size[" ~ i ~ "]", size); + me._canvas.set("view[" ~ i ~ "]", size); + } + + return me; + }, + getSize: func { + var w = me.get("content-size[0]"); + var h = me.get("content-size[1]"); + return [w,h]; + }, + # Raise to top of window stack + raise: func() { + # on writing the z-index the window always is moved to the top of all other + # windows with the same z-index. + me.setInt("z-index", me.get("z-index", gui.STACK_INDEX["always-on-top"])); + }, + hide: func(parents = 0) { + me._ghost.hide(); + }, + show: func() { + me._ghost.show(); + me.raise(); + if (me._canvas != nil) { + me._canvas.update(); + } + }, + # Hide / show the window based on whether it's currently visible + toggle: func() { + if (me.isVisible()) { + me.hide(); + } else { + me.show(); + } + }, + _onStateChange: func { + var event = canvas.CustomEvent.new("wm.focus-" ~ (me._focused ? "in" : "out")); + + if (me.getCanvas() != nil) { + me.getCanvas().data("focused", me._focused).dispatchEvent(event); + } + }, + #mode 0 = value changed, +-1 add/remove node + _propCallback: func(child, mode) { + if (!me._node.equals(child.getParent())) { + return; + } + var name = child.getName(); + + # support for CSS like position: absolute; with right and/or bottom margin + if (name == "right") { + me._handlePositionAbsolute(child, mode, name, 0); + } elsif (name == "bottom") { + me._handlePositionAbsolute(child, mode, name, 1); + } + + if (mode == 0) { + if (name == "size") { + me._resizeDecoration(); + } + } + }, + _handlePositionAbsolute: func(child, mode, name, index) { + # mode + # -1 child removed + # 0 value changed + # 1 child added + + if (mode == 0) { + me._updatePos(index, name); + } elsif (mode == 1) { + me["_listener_" ~ name] = [ + setlistener( + "/sim/gui/canvas/size[" ~ index ~ "]", + func me._updatePos(index, name) + ), + setlistener( + me._node.getNode("content-size[" ~ index ~ "]"), + func me._updatePos(index, name) + ) + ]; + } elsif (mode == -1) { + for (var i = 0; i < 2; i += 1) { + removelistener(me["_listener_" ~ name][i]); + } + } + }, + _updatePos: func(index, name) { + me.setInt( + "tf/t[" ~ index ~ "]", + getprop("/sim/gui/canvas/size[" ~ index ~ "]") - me.get(name) - me.get("content-size[" ~ index ~ "]") + ); + }, + getCanvasDecoration: func() { + return wrapCanvas(me._getCanvasDecoration()); + }, + _updateDecoration: func() { + me.setBool("update", 1); + + #var canvas_deco = me.getCanvasDecoration(); + #var group_deco = canvas_deco.getGroup("decoration"); + + me._resizeDecoration(); + me._onStateChange(); + }, + _resizeDecoration: func() { + } +}; + + diff --git a/Nasal/gui/MenuBar.nas b/Nasal/gui/MenuBar.nas index 001a30050..b567bf378 100644 --- a/Nasal/gui/MenuBar.nas +++ b/Nasal/gui/MenuBar.nas @@ -1,60 +1,56 @@ -# SPDX-FileCopyrightText: (C) 2022 James Turner +# SPDX-FileCopyrightText: (C) 2023 TheFGFSEagle # SPDX-License-Identifier: GPL-2.0-or-later +var menubar = nil; -var GUIMenuItem = { - - aboutToShow: func() { - - }, - - # return a Canvas object (group) of the contents - show: func(viewParent) { - - } - -}; - -var GUIMenu = { - - aboutToShow: func() { - - }, - - # return a Canvas object (group) of the contents - show: func(viewParent) { - # loop over children - } - -}; - -var GUIMenuBar = { - aboutToShow: func() { - - }, - - # return a Canvas object (group) of the contents - show: func(viewParent) { - - } - -}; - -# this is the callback function invoked by C++ to build Nasal peers -# for the C++ menu objects. -var _createMenuObject = func(type) -{ - if (type == "menubar") { - - } else if (type == "menuitem") { - - } else if (type == "seperator") { - - } else if (type == "menu") { - # do we need to distuinguish submenus here: - } - - return nil; +var _addItem = func(parent, itemGhost) { + var item = parent.createItem( + text: itemGhost.label, + cb: itemGhost.fire, + cb_me: itemGhost, + shortcut: itemGhost.shortcut, + enabled: itemGhost.enabled, + ); +} + +var _addMenu = func(parent, menuGhost) { + var menu = parent.createMenu(menuGhost.label); + foreach (var item; menuGhost.items) { + _addItem(menu, item); + } +} + +var _createMenuBar = func(menubarGhost) { + if (menubar != nil) { + menubar.del(); + menubar = nil; + } + menubar = canvas.gui.MenuBar.new(); + foreach (var menu; menubarGhost.menus) { + _addMenu(menubar, menu); + } + menubar.show(); +} + +var _destroyMenuBar = func(menubarGhost) { + if (menubar != nil) { + return; + } + menubar.del(); + menubar = nil; +} + +var _showMenuBar = func(menubarGhost) { + if (menubar == nil) { + return; + } + menubar.show(); +} + +var _hideMenuBar = func(menubarGhost) { + if (menubar == nil) { + return; + } + menubar.hide(); } -logprint(LOG_INFO, "Did load GUI menubar");