diff --git a/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas b/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas index a0ef88dc5..433032985 100644 --- a/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas +++ b/Nasal/canvas/gui/dialogs/WidgetsFactoryDialog.nas @@ -36,7 +36,7 @@ var WidgetsFactoryDialog = { }); m.vbox.addItem(m.menubar); - m.tabs = gui.widgets.TabWidget.new(m.root, style, {}); + m.tabs = gui.widgets.TabWidget.new(m.root, style, {"tabs-closeable": 1}); m.tabsContent = m.tabs.getContent(); m.vbox.addItem(m.tabs); @@ -57,7 +57,9 @@ var WidgetsFactoryDialog = { m.checkbox_right = gui.widgets.CheckBox.new(m.tabsContent, style, {"label-position": "left"}) .setText("Checkbox with text on the left side"); m.tab_1.addItem(m.checkbox_right); - m.property_checkbox = gui.widgets.PropertyCheckBox.new(m.tabsContent, style, {"node": props.globals.getNode("/controls/lighting/nav-lights")}) + m.property_checkbox = gui.widgets.PropertyCheckBox.new(m.tabsContent, style, { + "node": props.globals.getNode("/controls/lighting/nav-lights"), + }) .setText("Nav lights"); m.tab_1.addItem(m.property_checkbox); diff --git a/Nasal/canvas/gui/styles/DefaultStyle.nas b/Nasal/canvas/gui/styles/DefaultStyle.nas index 6f067e601..4775ea1d3 100644 --- a/Nasal/canvas/gui/styles/DefaultStyle.nas +++ b/Nasal/canvas/gui/styles/DefaultStyle.nas @@ -596,7 +596,7 @@ DefaultStyle.widgets["scroll-area"] = { }; DefaultStyle.widgets["tab-widget"] = { - tabBarHeight: 30, + tabBarHeight: 36, new: func(parent, cfg) { me._root = parent.createChild("group", "tab-widget"); me.bg = me._root.createChild("path", "background") @@ -636,15 +636,16 @@ DefaultStyle.widgets["tab-widget-tab-button"] = { setSize: func(model, w, h) { me._bg.reset().rect(3, 0, w - 6, h); me._selected_indicator.reset().moveTo(3, h - 2).horiz(w - 6); - me._label.setTranslation(w / 2, h / 2 + 5); + me._label.setTranslation((w - 24 - 8) / 2, h / 2 + 5); + model._close_button.move(w - 24 - 8, h / 2 - 12); }, 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]); + var min_width = math.max(80, me._label.maxWidth() + 12 + (model._cfg.get("tab-closeable") ? 24 + 12 : 0)); + model.setLayoutMinimumSize([min_width, 36]); + model.setLayoutSizeHint([min_width, 36]); return me; }, @@ -674,6 +675,66 @@ DefaultStyle.widgets["tab-widget-tab-button"] = { } }; +DefaultStyle.widgets["tab-button-close-button"] = { + new: func(parent, cfg) { + me._root = parent.createChild("group", "button"); + me._bg = me._root.createChild("path"); + me._border = me._root.createChild("image", "button") + .set("slice", "10 12"); #"7") + me._cross = me._root.createChild("text") + .set("font", "LiberationFonts/LiberationSans-Regular.ttf") + .set("character-size", 21) + .set("alignment", "center-baseline") + .setText("×"); + }, + setSize: func(model, w, h) { + me._bg.reset().rect(3, 3, w - 6, h - 6, {"border-radius": 5}); + me._border.setSize(w, h); + me._cross.setTranslation(w / 2, h / 2 + 7); + }, + update: func(model) { + var backdrop = !model._windowFocus(); + var file = me._style._dir_widgets ~ "/"; + + # TODO unify color names with image names + var bg_color_name = "button_bg_color"; + if (backdrop) { + bg_color_name = "button_backdrop_bg_color"; + } elsif (model._down) { + bg_color_name = "button_bg_color_down"; + } elsif (model._hover) { + bg_color_name = "button_bg_color_hover"; + } + me._bg.set("fill", me._style.getColor(bg_color_name)); + + if (model._hover or model._down) { + me._cross.set("fill", me._style.getColor("fg_color")); + me._border.show(); + me._bg.show(); + } else { + me._cross.set("fill", me._style.getColor("backdrop_fg_color")); + me._border.hide(); + me._bg.hide(); + } + if (backdrop) { + file ~= "backdrop-"; + } + file ~= "button"; + + if (model._down) { + file ~= "-active"; + } + + if (model._focused and !backdrop) { + file ~= "-focused"; + } + if (model._hover and !model._down) { + file ~= "-hover"; + } + me._border.set("src", file ~ ".png"); + } +}; + # 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 index 185b7fee8..f133b34d7 100644 --- a/Nasal/canvas/gui/widgets/TabWidget.nas +++ b/Nasal/canvas/gui/widgets/TabWidget.nas @@ -46,14 +46,20 @@ # tabs.addTab("tab3", "Texture 3", tab3); gui.widgets.TabWidgetTabButton = { - new: func(parent, style, cfg) { - var cfg = Config.new(cfg); + new: func(parent, style = nil, cfg = nil) { var m = gui.Widget.new(gui.widgets.TabWidgetTabButton); + m._cfg = Config.new(cfg or {}); + m._style = style or canvas.style; m._focus_policy = m.StrongFocus; m._selected = 0; - m._setView(style.createWidget(parent, "tab-widget-tab-button", cfg)); - + m._setView(m._style.createWidget(parent, "tab-widget-tab-button", m._cfg)); + m._close_button = gui.widgets.Button.new(m._view._root, style, {"type": "tab-button-close-button"}) + .setSize(24, 24) + .listen("clicked", func(e) { + m._trigger("close-button-clicked"); + }); + m._close_button._onStateChange(); return m; }, setText: func(text) { @@ -68,8 +74,7 @@ gui.widgets.TabWidgetTabButton = { me._selected = selected; me._trigger("toggled", {selected: selected}); - me._onStateChange(); - me._view.update(me); + me.update(); return me; }, _setView: func(view) { @@ -82,15 +87,25 @@ gui.widgets.TabWidgetTabButton = { el.addEventListener("drag", func(e) { e.stopPropagation() }); #el.addEventListener("drag", me.drag); + }, + update: func { + if (me._cfg.get("tab-closeable")) { + me._close_button.show(); + } else { + me._close_button.hide(); + } + me._view.update(me); + me._close_button._onStateChange(); } }; gui.widgets.TabWidget = { - new: func(parent, style, cfg) { + new: func(parent, style = nil, cfg = nil) { var m = gui.Widget.new(gui.widgets.TabWidget); - m._cfg = Config.new(cfg); + m._cfg = Config.new(cfg or {}); + m._style = style or canvas.style; m._focus_policy = m.NoFocus; - m._setView(style.createWidget(parent, "tab-widget", m._cfg)); + m._setView(m._style.createWidget(parent, "tab-widget", m._cfg)); m._layout = VBoxLayout.new(); m._layout.setCanvas(m._view._root.getCanvas()); m._layout.setParent(m); @@ -102,9 +117,10 @@ gui.widgets.TabWidget = { m._currentTabId = nil; m._tabs = {}; m._tabButtons = {}; + m._closeable_tabs = m._cfg.get("tabs-closeable", 0); - m.setLayoutMinimumSize([50, 30]); - m.setLayoutSizeHint([100, 30]); + m.setLayoutMinimumSize([50, 36]); + m.setLayoutSizeHint([100, 36]); return m; }, @@ -134,8 +150,13 @@ gui.widgets.TabWidget = { die("cannot add multiple tabs with the same id: " ~ id); } - me._tabButtons[id] = gui.widgets.TabWidgetTabButton.new(me._view.tabBar, canvas.style, {}) + me._tabButtons[id] = gui.widgets.TabWidgetTabButton.new(me._view.tabBar, canvas.style, { + "tab-closeable": me._cfg.get("tabs-closeable"), + }) .setText(label) + .listen("close-button-clicked", func { + me.removeTab(id); + }) .listen("toggled", func (e) { if (e.detail.selected and id != me._currentTabId) { me.setCurrentTab(id); @@ -178,7 +199,7 @@ gui.widgets.TabWidget = { return; # no need to do anything } - if (me._currentTabId) { + if (me._currentTabId and me._tabButtons[me._currentTabId] != nil) { me._tabButtons[me._currentTabId].setSelected(0); } me._tabButtons[id].setSelected();