From 8d5e4aaf1c90e8fd4aa193e73a3b22e8aacb804e Mon Sep 17 00:00:00 2001 From: TheFGFSEagle Date: Tue, 20 Dec 2022 17:41:39 +0100 Subject: [PATCH 1/4] Added basic tab widget --- Nasal/canvas/api/element.nas | 16 +++ Nasal/canvas/gui.nas | 1 + Nasal/canvas/gui/styles/DefaultStyle.nas | 84 +++++++++++- Nasal/canvas/gui/widgets/TabWidget.nas | 162 +++++++++++++++++++++++ gui/styles/AmbianceClassic/style.xml | 42 +++++- 5 files changed, 299 insertions(+), 6 deletions(-) create mode 100644 Nasal/canvas/gui/widgets/TabWidget.nas diff --git a/Nasal/canvas/api/element.nas b/Nasal/canvas/api/element.nas index 0a4d1d0c1..ae8e19737 100644 --- a/Nasal/canvas/api/element.nas +++ b/Nasal/canvas/api/element.nas @@ -185,6 +185,22 @@ var Element = { } return center; }, + + # Set size in pixels + # Use either with x, y size: + # e.setSize(, ) + # or with a vector containing x, y: + # e.setSize([, ]) + setSize: func { + if (size(arg) == 1) { + var (x, y) = arg[0]; + } else { + var (x, y) = arg; + } + var (sx, sy) = me.getScale(); + var (curx, cury) = me.getSize(); + me.setScale(x / (curx / sy), y / (cury / sy)); + }, #return vector [sx, sy] with dimensions of bounding box getSize: func { diff --git a/Nasal/canvas/gui.nas b/Nasal/canvas/gui.nas index cb80fc847..9d6c1bb1b 100644 --- a/Nasal/canvas/gui.nas +++ b/Nasal/canvas/gui.nas @@ -40,6 +40,7 @@ loadWidget("LineEdit"); loadWidget("ScrollArea"); loadWidget("Rule"); loadWidget("Slider"); +loadWidget("TabWidget"); # standard dialogs loadDialog("InputDialog"); diff --git a/Nasal/canvas/gui/styles/DefaultStyle.nas b/Nasal/canvas/gui/styles/DefaultStyle.nas index 9dd4c7755..fea09b05f 100644 --- a/Nasal/canvas/gui/styles/DefaultStyle.nas +++ b/Nasal/canvas/gui/styles/DefaultStyle.nas @@ -126,7 +126,7 @@ DefaultStyle.widgets.checkbox = { { if (me._label_position == "left") { me._label.setTranslation(3, int((h / 2) + 1)); - me._icon.setTranslation(me._label.maxWidth() + 6, int((h - 18) / 2)); + me._icon.setTranslation(w - 24, int((h - 18) / 2)); } else { me._icon.setTranslation(3, int((h - 18) / 2)); me._label.setTranslation(24, int(h / 2) + 1); @@ -458,6 +458,88 @@ DefaultStyle.widgets["scroll-area"] = { } }; +DefaultStyle.widgets["tab-widget"] = { + new: func(parent, cfg) { + me._root = parent.createChild("group", "tab-widget"); + me._bg = me._root.createChild("path", "background") + .set("fill", "#e0e0e0"); + me.tabBar = me._root.createChild("group", "tab-widget-tabbar"); + me.tabBarHeight = 30; + me.content = me._root.createChild("group", "tab-widget-content"); + me.content.setTranslation(0, me.tabBarHeight); + }, + + update: func(model) { + me._bg.set("fill", me._style.getColor("bg_color")); + }, + + setSize: func(model, w, h) { + me._bg.reset().rect(0, 0, model._size[0], model._size[1]); + me.tabBar.set("clip", sprintf("rect(0, %d, %d, 0)", model._size[0], me.tabBarHeight)); + me.content.set("clip", sprintf("rect(0, %d, %d, 0)", model._size[0], model._size[1] - me.tabBarHeight)); + + return me.update(model); + }, +}; + +# Tab button for the tab widget +DefaultStyle.widgets["tab-widget-tab-button"] = { + new: func(parent, cfg) { + me._root = parent.createChild("group", "tab-widget-tab-button"); + me._bg = me._root.createChild("path") + .set("fill", me._style.getColor("tab_widget_tab_button_bg_focused")) + .set("stroke", me._style.getColor("tab_widget_tab_button_border")) + .set("stroke-width", 1); + me._selected_indicator = me._root.createChild("path") + .set("stroke", me._style.getColor("tab_widget_tab_button_selected_indicator")) + .set("stroke-width", 3); + me._label = me._root.createChild("text") + .set("font", "LiberationFonts/LiberationSans-Regular.ttf") + .set("character-size", 14) + .set("alignment", "center-baseline"); + }, + + setSize: func(model, w, h) { + me._bg.reset().rect(3, 0, w - 6, h); + me._selected_indicator.reset().moveTo(3, h).horiz(w - 6); + }, + + setText: func(model, text) { + me._label.setText(text); + + var min_width = math.max(80, me._label.maxWidth() + 16); + model.setLayoutMinimumSize([min_width, 30]); + model.setLayoutSizeHint([min_width, 30]); + + return me; + }, + + update: func(model) { + var backdrop = !model._windowFocus(); + var (w, h) = model._size; + + me._label.setTranslation(w / 2, h / 2 + 5); + + var bg_color_name = "tab_widget_tab_button_bg_focused"; + if (backdrop) { + bg_color_name = "tab_widget_tab_button_bg_unfocused"; + } else if (model._selected) { + bg_color_name = "tab_widget_tab_button_bg_selected"; + } else if (model._hover) { + bg_color_name = "tab_widget_tab_button_bg_hovered"; + } + me._bg.set("fill", me._style.getColor(bg_color_name)); + + me._selected_indicator.setVisible(model._selected); + + if (backdrop) { + me._label.set("fill", me._style.getColor("backdrop_fg_color")); + } else { + me._label.set("fill", me._style.getColor("fg_color")); + } + } +}; + # A horizontal or vertical rule line # possibly with a text label embedded DefaultStyle.widgets.rule = { diff --git a/Nasal/canvas/gui/widgets/TabWidget.nas b/Nasal/canvas/gui/widgets/TabWidget.nas new file mode 100644 index 000000000..5441799dc --- /dev/null +++ b/Nasal/canvas/gui/widgets/TabWidget.nas @@ -0,0 +1,162 @@ +# TabWidget.nas - widget with a tabs bar and a content area which always displays +# exactly one of its tabs + +# SPDX-FileCopyrightText: (C) 2022 Frederic Croix +# SPDX-License-Identifier: GPL-2.0-or-later + + +gui.widgets.TabWidgetTabButton = { + new: func(parent, style, cfg) { + var cfg = Config.new(cfg); + var m = gui.Widget.new(gui.widgets.TabWidgetTabButton); + m._focus_policy = m.StrongFocus; + m._selected = 0; + + m._setView(style.createWidget(parent, "tab-widget-tab-button", cfg)); + + return m; + }, + setText: func(text) { + me._view.setText(me, text); + + return me; + }, + setSelected: func(selected = 1) { + if (me._selected == selected) { + return me; + } + + me._selected = selected; + me._trigger("toggled", {selected: selected}); + me._onStateChange(); + me._view.update(me); + return me; + }, + _setView: func(view) { + call(gui.Widget._setView, [view], me); + + var el = view._root; + #el.addEventListener("mousedown", func me.dragStart()); + #el.addEventListener("mouseup", func me.dragStop()); + el.addEventListener("click", func me.setSelected()); + + el.addEventListener("drag", func(e) { e.stopPropagation() }); + #el.addEventListener("drag", me.drag); + } +}; + +gui.widgets.TabWidget = { + new: func(parent, style, cfg) { + var m = gui.Widget.new(gui.widgets.TabWidget); + m._cfg = Config.new(cfg); + m._focus_policy = m.NoFocus; + m._setView(style.createWidget(parent, "tab-widget", m._cfg)); + m._layout = VBoxLayout.new(); + m._layout.setCanvas(m._view._root.getCanvas()); + m._tabBar = HBoxLayout.new(); + m._layout.addItem(m._tabBar); + m._currentTab = nil; + m._currentTabId = nil; + m._tabs = {}; + m._tabButtons = {}; + + m.setLayoutMinimumSize([50, 30]); + m.setLayoutSizeHint([100, 30]); + + return m; + }, + + hasTab: func(id) { + return me._tabs[id] != nil; + }, + + getTab: func(id) { + if (!me.hasTab(id)) { + die("tab with id '" ~ id ~ "' does not exist"); + } + + return me._tabs[id]; + }, + + addTab: func(id, label, widget) { + if (me.hasTab(id)) { + die("cannot add multiple tabs with the same id: " ~ id); + } + + me._tabButtons[id] = gui.widgets.TabWidgetTabButton.new(me._view._root, canvas.style, {}) + .setText(label) + .listen("toggled", func (e) { + if (e.detail.selected and id != me._currentTabId) { + me.setCurrentTab(id); + } + }); + + me._tabBar.addItem(me._tabButtons[id]); + me._tabs[id] = widget; + me._layout.addItem(widget); + me.setCurrentTab(keys(me._tabs)[0]); + + return me; + }, + + removeTab: func(id) { + if (!me.hasTab(id)) { + die("tab with id '" ~ id ~ "' does not exist"); + } + + me._tabs[id].setVisible(0); + me._layout.removeItem(me._tabs[id]); + delete(me._tabs, id); + me._tabBar.removeItem(me._tabButtons[id]); + delete(me._tabButtons, id); + if (size(keys(me._tabs)) > 0) { + me.setCurrentTab(keys(me._tabs)[-1]); + } + + return me; + }, + + setCurrentTab: func(id) { + if (!me.hasTab(id)) { + die("tab with id '" ~ id ~ "' does not exist"); + } + + if (me._currentTabId) { + me._tabButtons[me._currentTabId].setSelected(0); + } + me._tabButtons[id].setSelected(); + foreach (var tabid; keys(me._tabs)) { + me._tabs[tabid].setVisible(0); + } + me._tabs[id].setVisible(1); + me._currentTabId = id; + me._currentTab = me._tabs[id]; + + return me.update(); + }, + + setSize: func { + if (size(arg) == 1) { + var arg = arg[0]; + } + var (x, y) = arg; + me._size = [x, y]; + return me.update(); + }, + + # Needs to be called when the size of the content changes. + update: func() { + if(me._layout.getParent() == nil) { + me._layout.setParent(me); + } + + me._tabBar.setGeometry([0, 0, me._size[0], me._view.tabBarHeight]); + if (me._currentTab) { + me._currentTab.setGeometry([0, me._view.tabBarHeight, me._size[0], me._size[1] - me._view.tabBarHeight]); + } + + me._view.update(me); + + return me; + }, +}; diff --git a/gui/styles/AmbianceClassic/style.xml b/gui/styles/AmbianceClassic/style.xml index c13aa3322..2426e5d5b 100644 --- a/gui/styles/AmbianceClassic/style.xml +++ b/gui/styles/AmbianceClassic/style.xml @@ -1,5 +1,4 @@ - - + - <red type="float">0.275</red> @@ -95,7 +93,41 @@ <green type="float">0.89</green> <blue type="float">0.89</blue> </button_bg_color_insensitive> - + + <tab_widget_tab_button_border> + <red type="float">0.905</red> + <green type="float">0.897</green> + <blue type="float">0.885</blue> + </tab_widget_tab_button_border> + + <tab_widget_tab_button_selected_indicator> + <red type="float">0.11</red> + <green type="float">0.42</green> + <blue type="float">0.8</blue> + </tab_widget_tab_button_selected_indicator> + + <tab_widget_tab_button_bg_focused> + <red type="float">0.86</red> + <green type="float">0.855</green> + <blue type="float">0.85</blue> + </tab_widget_tab_button_bg_focused> + + <tab_widget_tab_button_bg_unfocused> + <red type="float">0.9</red> + <green type="float">0.895</green> + <blue type="float">0.89</blue> + </tab_widget_tab_button_bg_unfocused> + + <tab_widget_tab_button_bg_hovered> + <red type="float">0.93</red> + <green type="float">0.925</green> + <blue type="float">0.92</blue> + </tab_widget_tab_button_bg_hovered> + + <tab_widget_tab_button_bg_selected> + <red type="float">0.96</red> + <green type="float">0.955</green> + <blue type="float">0.95</blue> + </tab_widget_tab_button_bg_selected> </colors> - </PropertyList> From 09e15ddf169950825e02dbb031d6f8a83b651a85 Mon Sep 17 00:00:00 2001 From: TheFGFSEagle <thefgfseagle@gmail.com> Date: Wed, 21 Dec 2022 10:14:19 +0100 Subject: [PATCH 2/4] Added canvas checkbox subclass whose check state is always the bool value of a node and vice versa --- Nasal/canvas/gui.nas | 2 ++ Nasal/canvas/gui/widgets/PropertyWidgets.nas | 33 ++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 Nasal/canvas/gui/widgets/PropertyWidgets.nas diff --git a/Nasal/canvas/gui.nas b/Nasal/canvas/gui.nas index 9d6c1bb1b..9ba19a7ed 100644 --- a/Nasal/canvas/gui.nas +++ b/Nasal/canvas/gui.nas @@ -37,6 +37,7 @@ loadWidget("Button"); loadWidget("CheckBox"); loadWidget("Label"); loadWidget("LineEdit"); +loadWidget("PropertyWidgets"); loadWidget("ScrollArea"); loadWidget("Rule"); loadWidget("Slider"); @@ -308,6 +309,7 @@ var Window = { { me._ghost.show(); me.raise(); + me._canvas.update(); }, # Hide / show the window based on whether it's currently visible toggle: func() diff --git a/Nasal/canvas/gui/widgets/PropertyWidgets.nas b/Nasal/canvas/gui/widgets/PropertyWidgets.nas new file mode 100644 index 000000000..9e9c911da --- /dev/null +++ b/Nasal/canvas/gui/widgets/PropertyWidgets.nas @@ -0,0 +1,33 @@ +# PropertyWidgets.nas - subclassed canvas widgets that are synced with a property node + +# SPDX-FileCopyrightText: (C) 2022 Frederic Croix <thefgfseagle@gmail.com> +# SPDX-License-Identifier: GPL-2.0-or-later + +gui.widgets.PropertyCheckBox = { + new: func(node, parent, style, cfg) { + if (!isa(node, props.Node)) { + node = props.globals.getNode(node, 1); + } + + var m = gui.widgets.CheckBox.new(parent, style, cfg); + m._checkable = 1; + m._node = node; + + append(m.parents, gui.widgets.PropertyCheckBox); + + m.setChecked(m._node.getBoolValue()); + m.listen("toggled", func(e) { + m._node.setBoolValue(int(e.detail.checked)); + }); + m._listener = setlistener(m._node, func(n) { + m.setChecked(n.getBoolValue()); + }, 1, 0); + + return m; + }, + + del: func { + removelistener(me._listener); + }, +}; + From 4bc559bcf4364b25f1032dcb9f94d8959d9ea1c6 Mon Sep 17 00:00:00 2001 From: TheFGFSEagle <thefgfseagle@gmail.com> Date: Thu, 29 Dec 2022 07:27:04 +0100 Subject: [PATCH 3/4] Added canvas widget factory dialog --- Nasal/canvas/gui.nas | 1 + .../gui/dialogs/WidgetsFactoryDialog.nas | 63 +++++++++++++++++++ gui/dialogs/devel-extensions.xml | 7 +++ 3 files changed, 71 insertions(+) create mode 100644 Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas diff --git a/Nasal/canvas/gui.nas b/Nasal/canvas/gui.nas index 9ba19a7ed..9d7c7906d 100644 --- a/Nasal/canvas/gui.nas +++ b/Nasal/canvas/gui.nas @@ -46,6 +46,7 @@ loadWidget("TabWidget"); # standard dialogs loadDialog("InputDialog"); loadDialog("MessageBox"); +loadDialog("WidgetsFactoryDialog"); var style = DefaultStyle.new("AmbianceClassic", "Humanity"); var WindowButton = { diff --git a/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas b/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas new file mode 100644 index 000000000..605ecc8df --- /dev/null +++ b/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas @@ -0,0 +1,63 @@ +var WidgetsFactoryDialog = { + new: func { + var m = { + parents: [WidgetsFactoryDialog, canvas.Window.new([500, 500], "dialog")] + }; + + m.setBool("resize", 1); + + m.root = m.getCanvas(1) + .set("background", style.getColor("bg_color")) + .createGroup(); + m.vbox = VBoxLayout.new(); + vbox.setContentsMargin(10); + m.setLayout(vbox); + + m.tabs = gui.widgets.TabWidget.new(m.root, style, {}); + m.vbox.addItem(m.tabs); + + m.tab_1 = VBoxLayout.new(); + m.tabs.addTab("tab-1", "Tab 1", m.tab_1); + m.label = gui.widgets.Label.new(m.root, style, {}) + .setText("A label") + .setBackground("#ff0000"); + m.tab_1.addItem(m.label); + m.checkbox_left = gui.widgets.CheckBox.new(m.root, style, {"label-position": "left"}) + .setText("Checkbox with text on the left side"); + m.tab_1.addItem(m.checkbox_left); + m.checkbox_right = gui.widgets.CheckBox.new(m.root, style, {"label-position": "right"}) + .setText("Checkbox with text on the right side") + m.tab_1.addItem(m.checkbox_right); + m.property_checkbox = gui.widgets.PropertyCheckBox.new(props.globals.getNode("/controls/lighting/nav-lights"), m.root, style, {}) + .setText("Nav lights"); + m.tab_1.addItem(m.property_checkbox); + + m.tab_2 = VBoxLayout.new(); + m.tabs.addTab("tab-2", "Tab 2", m.tab_2); + m.button = gui.widgets.Button.new(m.root, style, {}) + .setText("A button") + .listen("clicked", func { + var s = InputDialog.getText("You clicked the button …", "Enter some text:"); + MessageBox.information("You clicked the button …", "… and entered '" ~ s ~ "' !"); + }); + m.tab_2.addItem(m.button); + m.image = gui.widgets.Label.new(m.root, style, {}) + .setImage("Textures/Splash1.png"); + m.tab_2.addItem(m.image); + m.checkable_button = gui.widgets.Button.new(m.root, style, {}) + .setCheckable(1) + .setChecked(0) + .setText("Checkable button") + .listen("toggled", func (e) { + m.image.setVisible(int(e.detail.checked)); + }); + m.table.addItem(m.checkable_button); + + return m; + }, + + del: func { + me.property_checkbox.del(); + } +}; + diff --git a/gui/dialogs/devel-extensions.xml b/gui/dialogs/devel-extensions.xml index e36d48acb..2b799e0ed 100644 --- a/gui/dialogs/devel-extensions.xml +++ b/gui/dialogs/devel-extensions.xml @@ -245,6 +245,13 @@ <script>canvas.MapStructure_selfTest();</script> </binding> </button> + <button> + <legend>Canvas widgets factory</legend> + <binding> + <command>nasal</command> + <script>canvas.WidgetsFactoryDialog.new();</script> + </binding> + </button> </group> <group> From b2f37b67f8221233877172e4a45e393a34361525 Mon Sep 17 00:00:00 2001 From: TheFGFSEagle <thefgfseagle@gmail.com> Date: Thu, 29 Dec 2022 23:26:51 +0100 Subject: [PATCH 4/4] Added usage example for the TabWidget --- .../gui/dialogs/WidgetsFactoryDialog.nas | 39 +++++++++------ Nasal/canvas/gui/styles/DefaultStyle.nas | 18 +++---- Nasal/canvas/gui/widgets/TabWidget.nas | 49 ++++++++++++++++++- 3 files changed, 79 insertions(+), 27 deletions(-) diff --git a/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas b/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas index 605ecc8df..4b1077b90 100644 --- a/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas +++ b/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas @@ -10,48 +10,55 @@ var WidgetsFactoryDialog = { .set("background", style.getColor("bg_color")) .createGroup(); m.vbox = VBoxLayout.new(); - vbox.setContentsMargin(10); - m.setLayout(vbox); + #m.vbox.setContentsMargin(10); + m.setLayout(m.vbox); m.tabs = gui.widgets.TabWidget.new(m.root, style, {}); + m.tabsContent = m.tabs.getContent(); m.vbox.addItem(m.tabs); m.tab_1 = VBoxLayout.new(); m.tabs.addTab("tab-1", "Tab 1", m.tab_1); - m.label = gui.widgets.Label.new(m.root, style, {}) + m.label = gui.widgets.Label.new(m.tabsContent, style, {}) .setText("A label") - .setBackground("#ff0000"); + .setBackground("#ffaaaa"); m.tab_1.addItem(m.label); - m.checkbox_left = gui.widgets.CheckBox.new(m.root, style, {"label-position": "left"}) - .setText("Checkbox with text on the left side"); + m.checkbox_left = gui.widgets.CheckBox.new(m.tabsContent, style, {"label-position": "right"}) + .setText("Wanna check something ?"); m.tab_1.addItem(m.checkbox_left); - m.checkbox_right = gui.widgets.CheckBox.new(m.root, style, {"label-position": "right"}) - .setText("Checkbox with text on the right side") + m.checkbox_right = gui.widgets.CheckBox.new(m.tabsContent, style, {"label-position": "right"}) + .setText("Checkbox with text on the right side"); m.tab_1.addItem(m.checkbox_right); - m.property_checkbox = gui.widgets.PropertyCheckBox.new(props.globals.getNode("/controls/lighting/nav-lights"), m.root, style, {}) + m.property_checkbox = gui.widgets.PropertyCheckBox.new(props.globals.getNode("/controls/lighting/nav-lights"), m.tabsContent, style, {}) .setText("Nav lights"); m.tab_1.addItem(m.property_checkbox); m.tab_2 = VBoxLayout.new(); m.tabs.addTab("tab-2", "Tab 2", m.tab_2); - m.button = gui.widgets.Button.new(m.root, style, {}) + m.button = gui.widgets.Button.new(m.tabsContent, style, {}) .setText("A button") .listen("clicked", func { - var s = InputDialog.getText("You clicked the button …", "Enter some text:"); - MessageBox.information("You clicked the button …", "… and entered '" ~ s ~ "' !"); + InputDialog.getText("You clicked the button …", "Enter some text:", func (button, text) { + MessageBox.information("You clicked the button …", "… and entered '" ~ (text != nil ? text : "nothing") ~ "' !"); + }); }); m.tab_2.addItem(m.button); - m.image = gui.widgets.Label.new(m.root, style, {}) - .setImage("Textures/Splash1.png"); + m.image = gui.widgets.Label.new(m.tabsContent, style, {}) + .setImage("Textures/Splash1.png") + .setFixedSize(128, 128); m.tab_2.addItem(m.image); - m.checkable_button = gui.widgets.Button.new(m.root, style, {}) + # XXX: setVisible(0) must be called AFTER adding the widget to the layout + # doing that before layout.addItem causes FG to crash with a SIGSEGV + # see https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/CABg8F9Rb87Fy%252B2ppXjJouYcZH9xyiQxts-jkdrPq0GK_Ymq-6w%2540mail.gmail.com/ + m.image.setVisible(0); + m.checkable_button = gui.widgets.Button.new(m.tabsContent, style, {}) .setCheckable(1) .setChecked(0) .setText("Checkable button") .listen("toggled", func (e) { m.image.setVisible(int(e.detail.checked)); }); - m.table.addItem(m.checkable_button); + m.tab_2.addItem(m.checkable_button); return m; }, diff --git a/Nasal/canvas/gui/styles/DefaultStyle.nas b/Nasal/canvas/gui/styles/DefaultStyle.nas index fea09b05f..2c28da5f8 100644 --- a/Nasal/canvas/gui/styles/DefaultStyle.nas +++ b/Nasal/canvas/gui/styles/DefaultStyle.nas @@ -459,26 +459,26 @@ DefaultStyle.widgets["scroll-area"] = { }; DefaultStyle.widgets["tab-widget"] = { + tabBarHeight: 30, new: func(parent, cfg) { me._root = parent.createChild("group", "tab-widget"); - me._bg = me._root.createChild("path", "background") + me.bg = me._root.createChild("path", "background") .set("fill", "#e0e0e0"); me.tabBar = me._root.createChild("group", "tab-widget-tabbar"); - me.tabBarHeight = 30; me.content = me._root.createChild("group", "tab-widget-content"); - me.content.setTranslation(0, me.tabBarHeight); }, update: func(model) { - me._bg.set("fill", me._style.getColor("bg_color")); + me.bg.set("fill", me._style.getColor("bg_color")); + me.tabBar.update(); + me.content.update(); }, setSize: func(model, w, h) { - me._bg.reset().rect(0, 0, model._size[0], model._size[1]); - me.tabBar.set("clip", sprintf("rect(0, %d, %d, 0)", model._size[0], me.tabBarHeight)); - me.content.set("clip", sprintf("rect(0, %d, %d, 0)", model._size[0], model._size[1] - me.tabBarHeight)); - - return me.update(model); + me.bg.reset().rect(0, w, h, 0); + me.tabBar.set("clip", sprintf("rect(0, %d, %d, 0)", w, me.tabBarHeight)); + me.content.setTranslation(0, me.tabBarHeight); + me.content.set("clip", sprintf("rect(0, %d, %d, 0)", w, h)); }, }; diff --git a/Nasal/canvas/gui/widgets/TabWidget.nas b/Nasal/canvas/gui/widgets/TabWidget.nas index 5441799dc..30e17119c 100644 --- a/Nasal/canvas/gui/widgets/TabWidget.nas +++ b/Nasal/canvas/gui/widgets/TabWidget.nas @@ -4,6 +4,46 @@ # SPDX-FileCopyrightText: (C) 2022 Frederic Croix <thefgfseagle@gmail.com> # SPDX-License-Identifier: GPL-2.0-or-later +# Usage example +# var window = canvas.Window.new([300, 300], "dialog"); +# var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color")); +# var root = myCanvas.createGroup(); +# var vbox = canvas.VBoxLayout.new(); +# myCanvas.setLayout(vbox); +# +# var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {}); +# var tabsContent = tabs.getContent(); +# vbox.addItem(tabs); +# +# var tab1 = canvas.VBoxLayout.new(); +# var image1 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {}) +# .setImage("Textures/Splash1.png") +# .setFixedSize(128, 128); +# tab1.addItem(image1); +# var text1 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {}) +# .setText("Texture 1"); +# tab1.addItem(text1); +# tabs.addTab("tab1", "Texture 1", tab1); +# +# var tab2 = canvas.VBoxLayout.new(); +# var image2 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {}) +# .setImage("Textures/Splash2.png") +# .setFixedSize(128, 128); +# tab2.addItem(image2); +# var text2 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {}) +# .setText("Texture 2"); +# tab2.addItem(text2); +# tabs.addTab("tab2", "Texture 2", tab2); +# +# var tab3 = canvas.VBoxLayout.new(); +# var image3 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {}) +# .setImage("Textures/Splash3.png") +# .setFixedSize(128, 128); +# tab3.addItem(image3); +# var text3 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {}) +# .setText("Texture 3"); +# tab3.addItem(text3); +# tabs.addTab("tab3", "Texture 3", tab3); gui.widgets.TabWidgetTabButton = { new: func(parent, style, cfg) { @@ -66,6 +106,10 @@ gui.widgets.TabWidget = { return m; }, + getContent: func { + return me._view.content; + }, + hasTab: func(id) { return me._tabs[id] != nil; }, @@ -83,7 +127,7 @@ gui.widgets.TabWidget = { die("cannot add multiple tabs with the same id: " ~ id); } - me._tabButtons[id] = gui.widgets.TabWidgetTabButton.new(me._view._root, canvas.style, {}) + me._tabButtons[id] = gui.widgets.TabWidgetTabButton.new(me._view.tabBar, canvas.style, {}) .setText(label) .listen("toggled", func (e) { if (e.detail.selected and id != me._currentTabId) { @@ -154,7 +198,8 @@ gui.widgets.TabWidget = { if (me._currentTab) { me._currentTab.setGeometry([0, me._view.tabBarHeight, me._size[0], me._size[1] - me._view.tabBarHeight]); } - + me._view.setSize(me, me._size[0], me._size[1]); + me._view.update(me); return me;