1
0
Fork 0

Added list widget

This commit is contained in:
TheFGFSEagle 2023-01-19 19:12:18 +01:00 committed by James Turner
parent 8a953d71a3
commit 2b9d10804b
5 changed files with 503 additions and 6 deletions

View file

@ -42,6 +42,7 @@ loadWidget("Button");
loadWidget("CheckBox");
loadWidget("Label");
loadWidget("LineEdit");
loadWidget("List");
loadWidget("MenuBar");
loadWidget("PropertyWidgets");
loadWidget("ScrollArea");

View file

@ -57,8 +57,12 @@ var WidgetsFactoryDialog = {
var r2 = gui.widgets.HorizontalRule.new(m.tabsContent, style, {});
m.tab_1.addItem(r2);
m.tab_2 = VBoxLayout.new();
m.tab_2 = HBoxLayout.new();
m.tabs.addTab("tab-2", "Tab 2", m.tab_2);
m.button_box = VBoxLayout.new();
m.tab_2.addItem(m.button_box);
m.button = gui.widgets.Button.new(m.tabsContent, style, {})
.setText("A button")
.setFixedSize(60, 30)
@ -67,13 +71,13 @@ var WidgetsFactoryDialog = {
MessageBox.information("You clicked the button …", "… and entered '" ~ (text != nil ? text : "nothing") ~ "' !");
});
});
m.tab_2.addItem(m.button);
m.button_box.addItem(m.button);
m.image = gui.widgets.Label.new(m.tabsContent, style, {})
.setImage("Textures/Splash1.png")
.setVisible(0)
.setFixedSize(128, 128);
m.tab_2.addItem(m.image);
m.button_box.addItem(m.image);
m.image._view._root.addEventListener("mousedown", func (e) {
logprint(LOG_INFO, "Image was clicked at:" ~ e.localX ~ "," ~ e.localY);
logprint(LOG_INFO, "Client pos:" ~ e.clientX ~ "," ~ e.clientY);
@ -97,7 +101,7 @@ var WidgetsFactoryDialog = {
.listen("toggled", func (e) {
m.image.setVisible(int(e.detail.checked));
});
m.tab_2.addItem(m.checkable_button);
m.button_box.addItem(m.checkable_button);
m.upsize_button = gui.widgets.Button.new(m.tabsContent, style, {})
.setText("Upsize window")
@ -106,7 +110,7 @@ var WidgetsFactoryDialog = {
var s = m.window.getSize();
m.window.setSize(s[0] + 100, s[1] + 100);
});
m.tab_2.addItem(m.upsize_button, 5);
m.button_box.addItem(m.upsize_button);
m.downsize_button = gui.widgets.Button.new(m.tabsContent, style, {})
.setText("Downsize window")
@ -115,11 +119,30 @@ var WidgetsFactoryDialog = {
var s = m.window.getSize();
m.window.setSize(s[0] - 100, s[1] - 100);
});
m.tab_2.addItem(m.downsize_button, 5);
m.button_box.addItem(m.downsize_button);
m.list_box = VBoxLayout.new();
m.tab_2.addItem(m.list_box);
m.list = gui.widgets.List.new(m.tabsContent);
for (var i = 0; i < 30; i += 1) {
m.list.createItem("Item " ~ i);
}
m.list.listen("selection-changed", func {
m.list_selection_label.setText("Selected items: " ~ (string.join(", ", map(func(item) item._text, m.list.getSelectedItems())) or "none"));
});
m.list.setSizeHint([m.list._MAX_SIZE, m.list._MAX_SIZE]);
m.list_box.addItem(m.list);
m.list_selection_label = gui.widgets.Label.new(m.tabsContent, canvas.style, {})
.setText("Selected items: none");
m.list_selection_label.setAlignment(canvas.AlignBottom);
m.list_box.addItem(m.list_selection_label);
m.benchmark_tab = VBoxLayout.new();
m.tabs.addTab("benchmark", "Benchmark", m.benchmark_tab);
m.benchmark_tab_scroll = canvas.gui.widgets.ScrollArea.new(m.tabsContent, canvas.style, {});
m.benchmark_tab_scroll.setSizeHint([m.list._MAX_SIZE, m.list._MAX_SIZE]);
m.benchmark_tab_scroll_layout = VBoxLayout.new();
m.benchmark_tab_scroll.setLayout(m.benchmark_tab_scroll_layout);
m.benchmark_tab.addItem(m.benchmark_tab_scroll);

View file

@ -964,6 +964,164 @@ DefaultStyle.widgets["menu-bar"] = {
return me;
},
update: func(model) {
me._bg.set("fill", me._style.getColor("bg_color"));
return me;
}
};
# A button
DefaultStyle.widgets["combo-box"] = {
new: func(parent, cfg)
{
me._root = parent.createChild("group", "combo-box");
me._bg =
me._root.createChild("path");
me._border =
me._root.createChild("image", "border")
.set("slice", "10 6"); #"7")
me._buttonBorder =
me._root.createChild("image", "border-button")
.set("slice", "10 6"); #"7")
me._arrowIcon = me._root.createChild("image", "arrow");
me._label =
me._root.createChild("text")
.set("font", "LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", 14)
.set("alignment", "left-center");
},
setSize: func(model, w, h)
{
var halfWidth = int(w * 0.5);
me._bg.reset()
.rect(3, 3, w - 6, h - 6, {"border-radius": 5});
# we split the two pieces
me._border.setSize(halfWidth, h);
me._buttonBorder.setTranslation(halfWidth, 0);
me._buttonBorder.setSize(w - halfWidth, h);
var arrowSize = me._arrowIcon.imageSize();
me._arrowIcon.setTranslation(w - (arrowSize[0] + 20), (h - arrowSize[1]) * 0.5);
me._label.setTranslation(20, h * 0.5);
},
setText: func(model, text)
{
me._label.setText(text);
var min_width = math.max(80, me._label.maxWidth() + 16 + me._arrowIcon.imageSize()[0]);
model.setLayoutMinimumSize([min_width, 16]);
model.setLayoutSizeHint([min_width, 28]);
return me;
},
update: func(model)
{
var backdrop = !model._windowFocus();
var (w, h) = model._size;
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";
else if( !model._enabled )
bg_color_name = "button_bg_color_insensitive";
else if( model._down )
bg_color_name = "button_bg_color_down";
else if( model._hover )
bg_color_name = "button_bg_color_hover";
me._bg.set("fill", me._style.getColor(bg_color_name));
var arrowIconFile = file ~ "combobox-arrow";
if( backdrop )
{
file ~= "backdrop-";
me._label.set("fill", me._style.getColor("backdrop_fg_color"));
}
else
me._label.set("fill", me._style.getColor("fg_color"));
file ~= "combobox";
var buttonFile = file ~ "-button";
file ~= "-entry";
var suffix = "";
if( model._down )
{ # no pressed image for the left half
buttonFile ~= "-pressed";
}
if( model._enabled ) {
if( model._focused and !backdrop )
suffix ~= "-focused";
} else {
suffix ~= "-disabled";
arrowIconFile ~= "-disabled";
}
me._border.set("src", file ~ suffix ~ ".png");
me._buttonBorder.set("src", buttonFile ~ suffix ~ ".png");
me._arrowIcon.set("src", arrowIconFile ~ ".png");
}
};
DefaultStyle.widgets["list-item"] = {
new: func(parent, cfg) {
me._root = parent.createChild("group", "list-item");
me._bg = me._root.createChild("path");
me._label = me._root.createChild("text")
.set("font", "LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", 14)
.set("alignment", "left-baseline");
},
setSize: func(model, w, h) {
me._bg.reset().rect(0, 0, w, 24);
me._label.setTranslation(5, int(h / 2) + 4);
return me;
},
_updateLayoutSizes: func(model) {
var min_width = 5 + me._label.maxWidth() + 5;
model.setLayoutMinimumSize([min_width, 24]);
model.setLayoutSizeHint([min_width, 24]);
return me;
},
setText: func(model, text) {
me._label.setText(text);
return me._updateLayoutSizes(model);
},
update: func(model) {
me._bg.set("fill", me._style.getColor("list_item_bg" ~ (model._selected ? "_selected" : "")));
var text_color_name = "list_item_fg";
if (model._selected) {
text_color_name ~= "_selected";
}
me._label.set("fill", me._style.getColor(text_color_name));
return me;
}
};
DefaultStyle.widgets.list = {
new: func(parent, cfg) {
me._root = parent.createChild("group", "list");
me._bg = me._root.createChild("path");
},
setSize: func(model, w, h) {
me._bg.reset().rect(0, 0, w, h);
return me;
},
update: func(model) {
me._bg.set("fill", me._style.getColor("bg_color"));

View file

@ -0,0 +1,291 @@
# List.nas - a list widget similar to Qt's QListWidget
# SPDX-FileCopyrightText: (C) 2022 Frederic Croix <thefgfseagle@gmail.com>
# SPDX-License-Identifier: GPL-2.0-or-later
gui.widgets.ListItem = {
new: func(parent, style = nil, cfg = nil) {
var m = gui.Widget.new(gui.widgets.ListItem);
style = style or canvas.style;
m._cfg = Config.new(cfg or {});
m._focus_policy = m.NoFocus;
m._data = m._cfg.get("data", {});
m._text = m._cfg.get("text", "");
m._selected = m._cfg.get("selected", 0);
m._list = nil;
m._setView(style.createWidget(parent, "list-item", m._cfg));
m.setLayoutMinimumSize([48, 24]);
m.setLayoutMaximumSize([m._MAX_SIZE, 24]);
m.setText(m._text);
m.setSelected(m._selected);
return m;
},
# @description Set the data for this item.
# @param key Union[scalar, hash] required If @param key is a hash, this item's data is replaced with that hash.
# If @param key is a scalar, the data field with that name will be set to value.
# If @param key is anything else, an error will be raised.
# @param value Any optional The value to set the data field with key @param key to, if @param key is a scalar;
# otherwise, this argument is ignored.
# @return canvas.gui.widgets.ListItem This list item to support method chaining.
setData: func(key, value = nil) {
if (isscalar(key)) {
me._data[key] = value;
} elsif (ishash(key)) {
me._data = key;
} else {
die("cannot set data field with non-scalar key !");
}
return me;
},
# @description Get the data of this item.
# @param key Union[scalar, nil] The scalar key of the data field to return the value of, or nil to return the whole data.
# @return Any If @param key is a scalar, the value of the field with key @param key, else the whole data as a hash.
getData: func(key = nil) {
if (key != nil) {
if (!isscalar(key)) {
die("cannot get data field with non-scalar key !")
}
return me._data[key];
} else {
return me._data;
}
},
# @description Clear data
# @return canvas.gui.widgets.ListItem This list item to support method chaining.
clearData: func {
me._data = {};
return me;
},
setText: func(text) {
me._text = text;
me._view.setText(me, text);
return me;
},
getText: func {
return me._text;
},
setSelected: func(selected = 1) {
if (me.getParent() != nil) {
me.getParent().setItemSelection(me, selected);
}
return me;
},
getSelected: func {
return me._selected;
},
_setSelected: func(selected) {
if (selected != me._selected) {
me._selected = selected;
me.update();
me._trigger("selection-state-changed", {"selected": selected});
if (me._selected) {
me._trigger("selected");
} else {
me._trigger("unselected");
}
}
return me;
},
_setView: func(view) {
call(gui.Widget._setView, [view], me);
var el = view._root;
el.addEventListener("click", func me.setSelected());
},
update: func {
me._view.update(me);
return me;
},
};
gui.widgets.List = {
new: func(parent, style = nil, cfg = nil) {
var m = gui.Widget.new(gui.widgets.List);
m._style = style = style or canvas.style;
m._cfg = Config.new(cfg or {});
m._focus_policy = m.NoFocus;
m._setView(style.createWidget(parent, "list", m._cfg));
m._scroll = gui.widgets.ScrollArea.new(m._view._root, style, {});
m._scrollLayout = VBoxLayout.new();
m._scroll.setLayout(m._scrollLayout);
m._scrollLayout.setSpacing(0);
m.setLayoutMinimumSize([48, 24]);
return m;
},
setSize: func {
if (size(arg) == 1) {
var arg = arg[0];
}
var (x, y) = arg;
me._size = [x, y];
me._scroll.setSize(x, y);
return me.update();
},
_onItemSelectionStateChanged: func {
var items = me.getSelectedItems();
me._trigger("selection-changed");
},
# @description Add the given item to this list.
# @param item canvas.gui.widgets.ListItem required The item to be added to this list.
# @return canvas.gui.widgets.List This list to support method chaining.
addItem: func(item) {
item.listen("selection-state-changed", func me._onItemSelectionStateChanged());
me._scrollLayout.addItem(item);
item.setParent(me);
return me;
},
# @description Create an item with the given text and optionally the given config.
# @param text str required Text of the new item.
# @param cfg hash optional Additional configuration of the item.
# @return canvas.gui.widgets.ListItem The created list item.
createItem: func(text, cfg = nil) {
cfg = cfg or {};
cfg["text"] = text;
var item = gui.widgets.ListItem.new(me._scroll.getContent(), me._style, cfg);
me.addItem(item);
return item;
},
# @description Count the items of this list.
# @return int Number of items in this list.
count: func {
return me._scrollLayout.count();
},
# @description Remove all items from this list.
# @return canvas.gui.widgets.List This list to support method chaining.
clear: func {
me._scrollLayout.clear();
return me;
},
# @description Get the item with the given index or text
# @param text_or_index Union[int, str] required If text_or_index is an integer, the item at index text_or_index is returned.
# If text_or_index is a string, the item with text == text_or_index is returned.
# @return canvas.gui.widgets.ListItem The item with the given text or index, or nil if no such item is found.
getItem: func(text_or_index) {
if (isint(text_or_index)) {
return me._scrollLayout.itemAt(text_or_index);
} else {
for (var i = 0; i < me.count(); i += 1) {
if (me._scrollLayout.itemAt(i)._text == text_or_index) {
return me.getItem(i);
}
}
}
},
# @description Find the index of the given item or item with the given text.
# @param text_or_item Union[str, canvas.gui.widgets.ListItem] required The item or text of the item to return the index of.
# @return int The index of the given item or item with the given text, or -1 if the given item or no item with the given text is found.
findItem: func(text_or_item) {
for (var i = 0; i < me.count(); i += 1) {
if (
(isstr(text_or_item) and me._scrollLayout.itemAt(i)._text == text_or_index) or
text_or_item == me.getItem(i)
) {
return i;
}
}
return -1;
},
# @description Remove and return the item with the given text or index.
# @param text_or_item Union[str, int] required The text or index of the item to remove and return
# @return int The item with the given text or index, or nil if no item with the given text or index is found.
takeItem: func(text_or_index) {
var index = text_or_index;
if (!isint(index)) {
index = me.findItem(index);
if (index < 0) {
return nil;
}
} elsif (index < 0) {
return nil;
}
return me._scrollLayout.takeAt(index);
},
# @description Remove the item with the given text or index or given item.
# @param text_or_index_or_item Union[str, int, canvas.gui.widgets.ListItem] required The text or index of the item, or the item, to remove.
# @return canvas.gui.widgets.List This list to support method chaining.
removeItem: func(text_or_index_or_item) {
if (isscalar(text_or_index_or_item)) {
me.takeItem(text_or_index_or_item);
} else {
me._scrollLayout.removeItem(item);
}
return me;
},
# @description Unselect all items of this list.
# @return canvas.gui.widgets.List This list to support method chaining.
clearSelection: func {
for (var i = 0; i < me.count(); i += 1) {
me.getItem(i)._selected = 0;
me.getItem(i).update();
}
return me;
},
# @description Set the selected state of the item item with the given index or text.
# @param text_or_index Union[int, str] required If text_or_index is an integer, the item at index text_or_index is returned.
# If text_or_index is a string, the item with text == text_or_index is returned.
# @param selected bool optional The selection state of the item, defaults to selected if not given.
# @return canvas.gui.widgets.List This list to support method chaining.
setItemSelection: func(text_or_index_or_item, selected = 1) {
var item = text_or_index_or_item;
if (typeof(item) == "scalar") {
item = me.getItem(text_or_index_or_item);
if (!item) {
die("no item found with given text or index '" ~ text_or_index_or_item ~ "' found !");
}
}
me.clearSelection();
item._setSelected(selected);
return me;
},
# @description Get a vector containing all selected items of this list
# @return vector[canvas.gui.widgets.ListItem] Vector containing the selected items
getSelectedItems: func {
var items = [];
for (var i = 0; i < me.count(); i += 1) {
var item = me.getItem(i);
if (item._selected) {
append(items, item);
}
}
return items;
},
update: func {
me._scroll.update();
me._view.update(me);
return me;
}
}

View file

@ -185,5 +185,29 @@
<green type="float">0.945</green>
<blue type="float">0.941</blue>
</menu_item_submenu_indicator_hovered>
<list_item_fg>
<red type="float">0.298</red>
<green type="float">0.298</green>
<blue type="float">0.298</blue>
</list_item_fg>
<list_item_fg_selected>
<red type="float">0.95</red>
<green type="float">0.95</green>
<blue type="float">0.95</blue>
</list_item_fg_selected>
<list_item_bg>
<red type="float">0.949</red>
<green type="float">0.945</green>
<blue type="float">0.941</blue>
</list_item_bg>
<list_item_bg_selected>
<red type="float">0.15</red>
<green type="float">0.15</green>
<blue type="float">1</blue>
</list_item_bg_selected>
</colors>
</PropertyList>