diff --git a/Nasal/canvas/gui.nas b/Nasal/canvas/gui.nas index c1c228652..473a23811 100644 --- a/Nasal/canvas/gui.nas +++ b/Nasal/canvas/gui.nas @@ -49,6 +49,7 @@ loadWidget("ScrollArea"); loadWidget("RadioButton"); loadWidget("Rule"); loadWidget("Slider"); +loadWidget("Switch"); loadWidget("TabWidget"); loadWidget("ComboBox"); diff --git a/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas b/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas index f277f72dc..d1d6d8229 100644 --- a/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas +++ b/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas @@ -15,20 +15,21 @@ var WidgetsFactoryDialog = { m.menubar = canvas.gui.widgets.MenuBar.new(m.root, canvas.style, {}); m.menubar.setCanvasItem(m.root); - m.menubar.createMenu("File") - .createItem(text: "Quit", cb: func m.del(), shortcut: "+Q"); - var tabsMenu = m.menubar.createMenu("Tabs"); - tabsMenu.createItem(text: "Select first tab", cb: func m.tabs.setCurrentTab("tab-1")); - tabsMenu.createItem(text: "Select second tab", cb: func m.tabs.setCurrentTab("tab-2")); + + m.fileMenu = m.menubar.createMenu("File"); + m.fileMenu.createItem(text: "Quit", cb: func m.del(), shortcut: "+Q"); - var widgetsMenu = m.menubar.createMenu("Widgets"); - widgetsMenu.createItem(text: "Benchmark label", cb: func { + m.tabsMenu = m.menubar.createMenu("Tabs"); + m.tabsMenu.createItem(text: "Select first tab", cb: func m.tabs.setCurrentTab("tab-1")); + m.tabsMenu.createItem(text: "Select second tab", cb: func m.tabs.setCurrentTab("tab-2")); + + m.widgetsMenu = m.menubar.createMenu("Widgets"); + m.widgetsMenu.createItem(text: "Benchmark label", cb: func { m.benchmark_widget(canvas.gui.widgets.Label, func(w, i) { w.setText("Label " ~ i); }); }); - - widgetsMenu.createItem(text: "Benchmark radio button", cb: func { + m.widgetsMenu.createItem(text: "Benchmark radio button", cb: func { m.benchmark_radio_button(func(w, i) { w.setText("Radio button " ~ i); }); @@ -63,8 +64,15 @@ var WidgetsFactoryDialog = { var r2 = gui.widgets.HorizontalRule.new(m.tabsContent, style, {}); m.tab_1.addItem(r2); + m.radio_label = gui.widgets.Label.new(m.tabsContent, style, {}) + .setText("Selected radio button: none"); + m.tab_1.addItem(m.radio_label); m.radio1 = gui.widgets.RadioButton.new(m.tabsContent) .setText("Radio button 1"); + m.radio1.listen("group-checked-radio-changed", func(e) { + m.radio_label.setText("Selected radio button: " ~ (e.detail.checkedRadio != nil ? e.detail.checkedRadio._text : "none")); + }); + m.tab_1.addItem(m.radio1); m.radio2 = gui.widgets.RadioButton.new(parent: m.tabsContent, cfg: {parentRadio: m.radio1}) .setText("Radio button 2"); @@ -140,6 +148,19 @@ var WidgetsFactoryDialog = { }); m.button_box.addItem(m.downsize_button); + m.switch_box = HBoxLayout.new(); + m.button_box.addItem(m.switch_box); + + m.switch_label = gui.widgets.Label.new(m.tabsContent, style, {}) + .setText("Switch state: on"); + m.switch_box.addItem(m.switch_label); + m.switch = gui.widgets.Switch.new(m.tabsContent) + .setChecked(1) + .listen("toggled", func(e) { + m.switch_label.setText("Switch state: " ~ (e.detail.checked ? "on" : "off")); + }); + m.switch_box.addItem(m.switch); + m.list_box = VBoxLayout.new(); m.tab_2.addItem(m.list_box); diff --git a/Nasal/canvas/gui/styles/DefaultStyle.nas b/Nasal/canvas/gui/styles/DefaultStyle.nas index a89835f83..1943c3b35 100644 --- a/Nasal/canvas/gui/styles/DefaultStyle.nas +++ b/Nasal/canvas/gui/styles/DefaultStyle.nas @@ -176,6 +176,39 @@ DefaultStyle.widgets.checkbox = { } }; +DefaultStyle.widgets.switch = { + new: func(parent, cfg) { + me._root = parent.createChild("group", "switch"); + me._bg = me._root.createChild("path", "switch-background"); + me._thumb = me._root.createChild("path", "switch-thumb"); + }, + + setSize: func(model, w, h) { + me._bg.reset() + .moveTo(w / 4, 0.5) + .arcSmallCCWTo((w - 1) / 4, (h - 1) / 2, 0, w / 4, h - 0.5) + .horiz(w / 2) + .arcSmallCCWTo((w - 1) / 4, (h - 1) / 2, 0, w / 4 * 3, 0.5) + .close(); + me._thumb.reset() + .ellipse((w - 1) / 4, (h - 1) / 2, w / 4, h / 2); + }, + + update: func(model) { + var bg_color = "switch_bg_color"; + if (model._down) { + bg_color ~= "_checked"; + } elsif (!model._enabled) { + bg_color ~= "_disabled"; + } + me._bg.set("fill", me._style.getColor(bg_color)); + me._bg.set("stroke", me._style.getColor("switch_bg_border_color")); + me._thumb.set("fill", me._style.getColor("switch_thumb_color")); + me._thumb.set("stroke", me._style.getColor("switch_thumb_border_color")); + me._thumb.setTranslation(model._down * 24, 0); + }, +}; + # A checkbox DefaultStyle.widgets["radio-button"] = { new: func(parent, cfg) { diff --git a/Nasal/canvas/gui/widgets/Button.nas b/Nasal/canvas/gui/widgets/Button.nas index ef9afd364..c6bee433b 100644 --- a/Nasal/canvas/gui/widgets/Button.nas +++ b/Nasal/canvas/gui/widgets/Button.nas @@ -3,10 +3,11 @@ # SPDX-License-Identifier: GPL-2.0-or-later gui.widgets.Button = { - new: func(parent, style, cfg) + new: func(parent, style = nil, cfg = nil) { + style = style or canvas.style; var m = gui.Widget.new(gui.widgets.Button); - m._cfg = Config.new(cfg); + m._cfg = Config.new(cfg or {}); m._focus_policy = m.StrongFocus; m._down = 0; m._checkable = 0; diff --git a/Nasal/canvas/gui/widgets/RadioButton.nas b/Nasal/canvas/gui/widgets/RadioButton.nas index 371317fa3..1803413d1 100644 --- a/Nasal/canvas/gui/widgets/RadioButton.nas +++ b/Nasal/canvas/gui/widgets/RadioButton.nas @@ -37,6 +37,11 @@ gui.widgets.RadioButton = { me._setRadioGroupSiblingsUnchecked(); me._trigger("toggled", {checked: checked}); + if (checked) { + me._trigger("checked"); + } else { + me._trigger("unchecked"); + } me._checked = checked; me._onStateChange(); return me; @@ -77,11 +82,16 @@ gui.widgets.RadioButtonsGroup = { return m; }, + _onRadioToggled: func { + var checked = me.getCheckedRadio(); + foreach (var radio; me.radios) { + radio._trigger("group-checked-radio-changed", {checkedRadio: checked}); + } + }, + addRadio: func(r) { - r.listen("toggled", func(e) { - me._updateChecked(r); - }); + r.listen("toggled", func me._onRadioToggled()); append(me.radios, r); }, @@ -99,6 +109,16 @@ gui.widgets.RadioButtonsGroup = { foreach (var r; me.radios) { r.setEnabled(enabled); } + return me; + }, + + getCheckedRadio: func { + foreach (var radio; me.radios) { + if (radio._checked) { + return radio; + } + } + return nil; }, # update check state of all radios in the group @@ -109,5 +129,6 @@ gui.widgets.RadioButtonsGroup = { r.setChecked(0); } } + return me; }, }; diff --git a/Nasal/canvas/gui/widgets/Switch.nas b/Nasal/canvas/gui/widgets/Switch.nas new file mode 100644 index 000000000..b9fdf1608 --- /dev/null +++ b/Nasal/canvas/gui/widgets/Switch.nas @@ -0,0 +1,16 @@ +gui.widgets.Switch = { + new: func(parent, style = nil, cfg = nil) { + cfg = cfg or {}; + cfg["type"] = "switch"; + var m = gui.widgets.Button.new(parent, style, cfg); + m._checkable = 1; + + append(m.parents, gui.widgets.Switch); + + m.setFixedSize(48, 24); + return m; + }, + setCheckable: nil, + setText: nil, +}; + diff --git a/gui/styles/AmbianceClassic/style.xml b/gui/styles/AmbianceClassic/style.xml index 80243f04a..887ab9dca 100644 --- a/gui/styles/AmbianceClassic/style.xml +++ b/gui/styles/AmbianceClassic/style.xml @@ -257,6 +257,42 @@ 0.5 1 + + + 0.9 + 0.9 + 0.9 + + + + 1 + 1 + 1 + + + + 0.3 + 0.3 + 1 + + + + 0.5 + 0.5 + 0.5 + + + + 0.949 + 0.945 + 0.941 + + + + 0.5 + 0.5 + 0.5 +