1
0
Fork 0

Add additional widgets for PUI compat

Styling of these is not complete yet.
This commit is contained in:
James Turner 2022-06-30 12:40:33 +02:00
parent 10a31e1f3c
commit 82cb79feb3
7 changed files with 542 additions and 1 deletions

View file

@ -31,11 +31,17 @@ loadGUIFile("Config.nas");
loadGUIFile("Style.nas");
loadGUIFile("Widget.nas");
loadGUIFile("styles/DefaultStyle.nas");
# widgets
loadWidget("Button");
loadWidget("CheckBox");
loadWidget("Label");
loadWidget("LineEdit");
loadWidget("ScrollArea");
loadWidget("Rule");
loadWidget("Slider");
# standard dialogs
loadDialog("InputDialog");
loadDialog("MessageBox");

View file

@ -196,7 +196,7 @@ DefaultStyle.widgets.label = {
},
setText: func(model, text)
{
if( text == nil or size(text) == 0 )
if ( !isstr(text) or size(text) == 0 )
{
model.setHeightForWidthFunc(nil);
return me._deleteElement('text');
@ -451,3 +451,274 @@ DefaultStyle.widgets["scroll-area"] = {
model._scroller_delta[dir] = model._size[dir] - model._scroller_size[dir];
}
};
# A horizontal or vertical rule line
# possibly with a text label embedded
DefaultStyle.widgets.rule = {
new: func(parent, cfg)
{
me._root = parent.createChild("group", "rule");
me._createElement("bg", "image");
me._isVertical = cfg.get("isVertical");
if (me._isVertical) {
me._bg.set("slice", "0 20");
me._baseFile = "vrule";
} else {
me._bg.set("slice", "10 0");
me._baseFile = "hrule";
}
},
setSize: func(model, w, h)
{
if( me['_text'] != nil )
{
# first 20 px
me._bg.setTranslation(2, 0);
me._bg.setSize(20, h);
# TODO handle eliding for translations?
me._text.setTranslation(22, 2 + h / 2);
var maxW = model._cfg.get("maxTextWidth", -1);
if (maxW > 0) {
me._text.set("max-width", maxW);
}
var bg2Left = maxW > 0 ? maxW : me._text.maxWidth() + 22;
me._bg2.setTranslation(bg2Left, 0);
me._bg2.setSize(w - bg2Left, h);
} else {
me._bg.setSize(w, h);
}
return me;
},
setText: func(model, text)
{
if( text == nil or size(text) == 0 )
{
# force a resize?
me._deleteElement('bg2');
return me._deleteElement('text');
}
if (me._isVertical) {
logprint(LOG_DEVALERT, "Text label not supported for vertical rules, yet");
return;
}
me._createElement("text", "text")
.setText(text);
var width_hint = me._text.maxWidth() + 40;
me._createElement("bg2", "image")
.set("slice", "10 0");
model.setLayoutMinimumSize([40, 14]);
# TODO mark as expanding?
model.setLayoutSizeHint([width_hint, 24]);
return me.update(model);
},
update: func(model)
{
var file = me._style._dir_widgets ~ "/";
file ~= me._baseFile;
if( !model._enabled )
file ~= "-disabled";
me._bg.set("src", file ~ ".png");
if ( me['_bg2'] != nil)
me._bg2.set("src", file ~ ".png");
# different color if disabled?
if( me['_text'] != nil )
{
var color_name = model._windowFocus() ? "fg_color" : "backdrop_fg_color";
me._text.set("fill", me._style.getColor(color_name));
}
},
# protected:
_createElement: func(name, type)
{
var mem = '_' ~ name;
if( me[ mem ] == nil )
{
me[ mem ] = me._root.createChild(type, "rule-" ~ name);
if( type == "text" )
{
me[ mem ].set("font", "LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", 14)
.set("alignment", "left-center");
}
}
return me[ mem ];
},
_deleteElement: func(name)
{
name = '_' ~ name;
if( me[ name ] != nil )
{
me[ name ].del();
me[ name ] = nil;
}
return me;
}
};
# a frame (sometimes called a group box), with optional label
# and enable/disable checkbox
DefaultStyle.widgets.frame = {
new: func(parent, cfg)
{
me._root = parent.createChild("group", "frame-box");
me._createElement("bg", "image")
.set("slice", "10 10");
me.content = me._root.createChild("group", "frame-content");
# handle label + checkable flag
},
update: func(model)
{
var file = me._style._dir_widgets ~ "/";
file ~= "backdrop-";
if( !model._enabled )
file ~= "-disabled";
me._bg.set("src", file ~ ".png");
},
# protected:
_createElement: func(name, type)
{
var mem = '_' ~ name;
if( me[ mem ] == nil )
{
me[ mem ] = me._root.createChild(type, "frame-" ~ name);
if( type == "text" )
{
me[ mem ].set("font", "LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", 14)
.set("alignment", "left-center");
}
}
return me[ mem ];
},
_deleteElement: func(name)
{
name = '_' ~ name;
if( me[ name ] != nil )
{
me[ name ].del();
me[ name ] = nil;
}
return me;
}
};
# a horionztal or vertical slider, for selecting /
# dragging over a numerical range
DefaultStyle.widgets.slider = {
new: func(parent, cfg)
{
me._root = parent.createChild("group", "slider");
me._createElement("bg", "image")
.set("slice", "10 10");
me._createElement("thumb", "image")
.set("slice", "10 10");
me._ticks = 0;
me._ticksPath = nil;
},
setNormValue: func(model, normValue)
{
var (w, h) = model._size;
var availWidthPos = w - h; # pixel range the thumb can move over
me._thumb.setTranslation(round(availWidthPos * normValue), 0);
},
update: func(model)
{
# set background state
var file = me._style._dir_widgets ~ "/";
file ~= "backdrop-";
if( !model._enabled )
file ~= "-disabled";
me._bg.set("src", file ~ ".png");
# set thumb state
file = me._style._dir_widgets ~ "/";
file ~= "button-"; # should we use a seperate thumb?
if( !model._enabled )
file ~= "-disabled";
else if (model._down)
file ~= "-down";
elsif (model._hover)
file ~= "-hovered";
me._thumb.set("src", file ~ ".png");
# set thumb size
var (w, h) = model._size;
# fixme assumes horizonal for now
me._thumb.setSize(h, h);
# update the position as well, since other stuff
# may have changed
me.setNormValue(model, model._normValue());
},
updateRanges: func(minValue, maxValue, numTicks = 0)
{
if (me._ticks != numTicks) {
# update tick marks
if (numTicks == 0) {
me._ticks = 0;
me._deleteElement('ticksPath');
} else {
me._createElement('ticksPath', 'path');
me._ticks = numTicks;
# set style
# loop adding ticks
}
}
},
# protected:
_createElement: func(name, type)
{
var mem = '_' ~ name;
if( me[ mem ] == nil )
{
me[ mem ] = me._root.createChild(type, "slider-" ~ name);
if( type == "text" )
{
me[ mem ].set("font", "LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", 14)
.set("alignment", "left-center");
}
}
return me[ mem ];
},
_deleteElement: func(name)
{
name = '_' ~ name;
if( me[ name ] != nil )
{
me[ name ].del();
me[ name ] = nil;
}
return me;
}
}

View file

@ -0,0 +1,68 @@
# Frame.nas: container with a visual frame around it,
# and optional checkbox / label (usuallt at the top / left)
# to enable / disable it
# Copyright (C) 2022 James Turner
# SPDX-License-Identifier: GPL-2.0-or-later
gui.widgets.Frame = {
new: func(parent, style, cfg)
{
var m = gui.Widget.new(gui.widgets.Frame);
m._cfg = Config.new(cfg);
# m._focus_policy = m.NoFocus; maybe?
m._setView( style.createWidget(parent, "frame", m._cfg) );
m._checkable = cfg.get("checkable", 0);
m._label = cfg.get("label");
m._layout = nil;
m.setLayoutSizeHint([200, 200]);
m.setLayoutMaximumSize([m._MAX_SIZE, m._MAX_SIZE]);
return m;
},
getContent: func()
{
return me._view.content;
},
setLabel: func(text)
{
me._label = text;
me._view.update(me);
return me;
},
setCheckable: func(e)
{
me._checkable = e;
me._view.update(me);
return me;
},
setLayout: func(;)
{
me._layout = l;
l.setParent(me);
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()
{
# var offset = [ me._content_offset[0] - me._content_pos[0],
# me._content_offset[1] - me._content_pos[1] ];
me.getContent().setTranslation(10, 10);
me.getContent().setSize([me._size[0] - 20, me._size[1] - 20]);
me._view.update(me);
me.getContent().update();
return me;
},
};

View file

@ -0,0 +1,95 @@
# RadioButton.nas : radio button, and group helper
# to manage updating checked state conherently
# Copyright (C) 2022 James Turner
# SPDX-License-Identifier: GPL-2.0-or-later
gui.widgets.RadioButton = {
new: func(parent, style, cfg)
{
cfg["type"] = "radio";
var m = gui.widgets.Button.new(parent, style, cfg);
m._checkable = 1;
append(m.parents, gui.widgets.RadioButton);
if (contains(pr, "radioGroup") {
pr.radioGroup.addRadio(m);
}
return m;
},
del: func()
{
var pr = getParent();
if (contains(pr, "radioGroup") {
pr.radioGroup.removeRadio(me);
}
},
setCheckable: nil,
setChecked: func(checked = 1)
{
# call our base version
me.parents[0].setChecked(checked);
if (checked) {
me._setRadioGroupSiblingsUnchecked();
}
},
# protected members
_setRadioGroupSiblingsUnchecked: func
{
var pr = getParent();
if (contains(pr, "radioGroup") {
pr.radioGroup._updateChecked(me);
} else {
# todo, remove me, it's okay to manually manage RadioButtons
logprint(LOG_DEV, "No radio group defined");
}
}
};
# auto manage radio-button checked state
gui.widgets.RadioGroup = {
new: func(nm = 'unnamed')
{
var m = {parents:[gui.widgets.RadioGroup, name:nm, radios:[]]};
return m;
},
addRadio: func(r)
{
if (r.parents[0] != RadioButton) {
logprint(LOG_ALERT, "Adding non-RadioButton to RadioGroup")
return;
}
append(me.radios, r);
},
removeRadio: func(r)
{
if (r.parents[0] != RadioButton) {
logprint(LOG_ALERT, "Remove non-RadioButton from RadioGroup")
return;
}
# should we update some other item to be checked?
me.radios.remove(r);
},
setEnabled: func(doEnable = 1)
{
foreach (var r : me.radios) {
r.setEnabled(doEnable);
}
},
# protected methods
# update check state of all radios in the group
_updateChecked: func(active)
{
foreach (var r : me.radios) {
r.setChecked(r == active);
}
}
};

View file

@ -0,0 +1,47 @@
# Rule.nas : horizontal or vertical dividing line,
# optionally with a text label, eg to name a section
# Copyright (C) 2022 James Turner
# SPDX-License-Identifier: GPL-2.0-or-later
gui.widgets.HorizontalRule = {
new: func(parent, style, cfg)
{
var m = gui.Widget.new(gui.widgets.HorizontalRule);
m._cfg = Config.new(cfg);
m._focus_policy = m.NoFocus;
m._setView( style.createWidget(parent, "rule", m._cfg) );
# should ask Style the rule height, not hard-code 1px
m.setLayoutMinimumSize([16, 1]);
m.setLayoutSizeHint([m._MAX_SIZE, 1]); # expand to fill
m.setLayoutMaximumSize([m._MAX_SIZE, 1]);
return m;
},
setText: func(text)
{
me._view.setText(me, text);
return me;
}
};
gui.widgets.VerticalRule = {
new: func(parent, style, cfg)
{
var m = gui.Widget.new(gui.widgets.VerticalRule);
m._cfg = Config.new(cfg);
m._focus_policy = m.NoFocus;
m._setView( style.createWidget(parent, "rule", m._cfg) );
# should ask Style the rule height, not hard-code 1px
m.setLayoutMinimumSize([1, 16]);
m.setLayoutSizeHint([1, m._MAX_SIZE]); # expand to fill
m.setLayoutMaximumSize([1, m._MAX_SIZE]);
return m;
},
setText: func(text)
{
me._view.setText(me, text);
return me;
}
};

View file

@ -0,0 +1,54 @@
gui.widgets.Slider = {
new: func(parent, style, cfg)
{
var cfg = Config.new(cfg);
var m = gui.Widget.new(gui.widgets.Slider);
m._focus_policy = m.StrongFocus;
m._down = 0;
m._minValue = 0;
m._maxValue = 100;
m._value = 50;
m._pageStep = 10;
m._numTicks = 10;
if( style != nil ) {
m._setView( style.createWidget(parent, cfg.get("type", "slider"), cfg) );
m._view.updateRanges(m._minValue, m._maxValue, m._numTicks);
}
return m;
},
setValue: func(val)
{
if( me._view != nil ) {
me._view.setNormValue(me._normValue());
}
return me;
},
# protected:
_setView: func(view)
{
call(gui.Widget._setView, [view], me);
# var el = view._root;
# el.addEventListener("mousedown", func if( me._enabled ) me.setDown(1));
# el.addEventListener("mouseup", func if( me._enabled ) me.setDown(0));
# el.addEventListener("click", func if( me._enabled ) me.toggle());
# el.addEventListener("mouseleave",func me.setDown(0));
# el.addEventListener("drag", func(e) e.stopPropagation());
},
# return value as its normalised equivalent
_normValue: func
{
var range = me._maxValue - me._minValue;
var v = math.clamp(me._value, me._minValue, me._maxValue) - me._minValue;
return v / range;
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B