1
0
Fork 0

Canvas slider widget:

Added value display
Add ticks, round mouse dragging result to nearest multiple of step size,
implement scroll handler
Add keybindings for adjusting slider value
This commit is contained in:
TheFGFSEagle 2023-02-15 00:46:18 +01:00 committed by James Turner
parent be80d67754
commit 372d68a775
5 changed files with 237 additions and 67 deletions

View file

@ -200,11 +200,6 @@ gui.Widget = {
me._trigger("mouse-leave");
me._onStateChange();
});
root.addEventListener("keypress", func(e) {
if (me._focused) {
root.onKeyPressed(e);
}
});
# if we have keyboard bindings defined, add the listener for them
if (size(me._bindings)) {

View file

@ -29,6 +29,15 @@ var WidgetsFactoryDialog = {
w.setText("Label " ~ i);
});
});
m.widgetsMenu.createItem(text: "Benchmark slider", cb: func {
m.benchmark_widget(widget: canvas.gui.widgets.Slider, proc_func: func(w, i) {
w.setValue(i);
}, cfg: {
"value-position": canvas.gui.widgets.Slider.ValuePosition.Below,
"value-style": canvas.gui.widgets.Slider.ValueStyle.Moving,
"ticks-position": gui.widgets.Slider.TicksPosition.Below,
});
});
m.widgetsMenu.createItem(text: "Benchmark radio button", cb: func {
m.benchmark_radio_button(func(w, i) {
w.setText("Radio button " ~ i);
@ -201,11 +210,15 @@ var WidgetsFactoryDialog = {
m.benchmark_tab.addItem(m.benchmark_statistics);
m.numericControlsTab = VBoxLayout.new();
m.tabs.addTab("ncTab", "Numeric Controls", m.numericControlsTab);
m.slider = gui.widgets.Slider.new(m.tabsContent, style,
{"max-value" : 100,
"page-step" : 20,
"tick-count" : 10})
m.tabs.addTab("numeric-controls", "Numeric controls", m.numericControlsTab);
m.slider = gui.widgets.Slider.new(m.tabsContent, style, {
"max-value" : 100,
"page-size" : 20,
"tick-step" : 10,
"value-style": gui.widgets.Slider.ValueStyle.Moving,
"value-position": gui.widgets.Slider.ValuePosition.Above,
"ticks-position": gui.widgets.Slider.TicksPosition.Above,
})
.setValue(42);
m.numericControlsTab.addItem(m.slider);
@ -213,11 +226,12 @@ var WidgetsFactoryDialog = {
return m;
},
benchmark_widget: func(widget, proc_func=nil, amount=50) {
benchmark_widget: func(widget, proc_func=nil, amount=50, cfg=nil) {
cfg = cfg or {};
var start = systime();
me.benchmark_tab_scroll_layout.clear();
for (var i = 0; i < amount; i += 1) {
var w = widget.new(me.benchmark_tab_scroll.getContent(), canvas.style, {});
var w = widget.new(me.benchmark_tab_scroll.getContent(), canvas.style, cfg);
if (proc_func != nil) {
proc_func(w, i);
}
@ -227,12 +241,14 @@ var WidgetsFactoryDialog = {
me.benchmark_statistics.setText("Took " ~ time ~ " seconds to add " ~ amount ~ " widgets.");
},
benchmark_radio_button: func(proc_func=nil, amount=50) {
benchmark_radio_button: func(proc_func=nil, amount=50, cfg= nil) {
cfg = cfg or {};
var start = systime();
me.benchmark_tab_scroll_layout.clear();
var r = canvas.gui.widgets.RadioButton.new(me.benchmark_tab_scroll.getContent());
cfg["parentRadio"] = r;
for (var i = 1; i < amount; i += 1) {
var w = canvas.gui.widgets.RadioButton.new(me.benchmark_tab_scroll.getContent(), canvas.style, {parentRadio: r});
var w = canvas.gui.widgets.RadioButton.new(me.benchmark_tab_scroll.getContent(), canvas.style, cfg);
if (proc_func != nil) {
proc_func(w, i);
}

View file

@ -934,23 +934,55 @@ DefaultStyle.widgets.slider = {
me._createElement("fill", "image")
.set("slice", "2 6");
me._ticks = me._root.createChild("path")
.set("stroke-width", me._style.getSize("slider-ticks-width", 1));
me._fillHeight = me._fill.imageSize()[1];
me._createElement("thumb", "image");
me._thumbSize = me._thumb.imageSize();
me._ticks = 0;
me._ticksPath = nil;
me._value = me._root.createChild("text")
.set("font", "LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", me._style.getSize("slider-value-font-size", me._style.getSize("base-font-size")))
.set("alignment", "center-top");
},
_updateLayoutSizes: func(model) {
me.update(model);
var h = me._thumb.imageSize()[1] + 6;
if (model._valueDisplayPosition != model.ValuePosition.None) {
h += me._style.getSize("slider-value-font-size", me._style.getSize("base-font-size")) +
me._style.getSize("slider-thumb-value-margin", 8);
}
if (model._ticksPosition != model.TicksPosition.None and model._valueDisplayPosition != model._ticksPosition) {
h += me._style.getSize("slider-fill-ticks-margin", 3) + me._style.getSize("slider-tick-length", 10);
}
model.setLayoutMinimumSize([50, h]);
model.setLayoutSizeHint([(model._maxValue - model._minValue) / (model._stepSize or 1), h]);
model.setLayoutMaximumSize([model._MAX_SIZE, h]);
},
setNormValue: func(model, normValue)
{
var (w, h) = model._size;
var w = model._size[0];
var halfThumbWidth = me._thumbSize[0] * 0.5;
var availWidthPos = w - me._thumbSize[0];
var thumbX = math.round(availWidthPos * normValue);
var thumbY = (h - me._thumbSize[1]) * 0.5;
me._thumb.setTranslation(thumbX - halfThumbWidth, thumbY);
var valueX = 0;
if (model._valueDisplayStyle == model.ValueStyle.Moving) {
var startPos = me._value.maxWidth() / 2;
var thumbPos = thumbX + me._thumbSize[0] * 0.5;
var endPos = w - (me._value.maxWidth() / 2);
valueX = math.clamp(thumbPos, startPos, endPos);
} elsif (model._valueDisplayStyle == model.ValueStyle.Fixed) {
valueX = w / 2;
}
me._value.setTranslation(valueX, me._value.getTranslation()[1]);
me._thumb.setTranslation(thumbX, me._thumb.getTranslation()[1]);
me._fill.setSize(thumbX, me._fillHeight);
me._fill.setTranslation(halfThumbWidth, me._fill.getTranslation()[1]);
me._value.setText(model._value);
},
update: func(model)
@ -974,6 +1006,7 @@ DefaultStyle.widgets.slider = {
}
me._fill.set("src", file ~ ".png");
me._fillHeight = me._fill.imageSize()[1];
# set thumb state
file = me._style._dir_widgets ~ "/";
@ -981,14 +1014,30 @@ DefaultStyle.widgets.slider = {
if( !model._enabled ) {
file ~= "-disabled";
} else {
if (model._down)
if (model._thumbDown)
file ~= "-focused";
if (model._hover)
file ~= "-hover";
}
me._thumb.set("src", file ~ ".png");
me._thumbSize = me._thumb.imageSize();
var color_name = model._windowFocus() ? "fg_color" : "backdrop_fg_color";
me._value.set("fill", me._style.getColor(color_name));
if (model._valueDisplayPosition != model.ValuePosition.None) {
me._value.show();
} else {
me._value.hide();
}
me._ticks.set("stroke", me._style.getColor("slider_ticks"));
if (model._ticksPosition != model.TicksPosition.None) {
me._ticks.show();
} else {
me._ticks.hide();
}
# update the position as well, since other stuff
# may have changed
me.setNormValue(model, model._normValue());
@ -996,28 +1045,67 @@ DefaultStyle.widgets.slider = {
setSize: func(model, w, h)
{
var fillTop = (h - me._fillHeight) * 0.5;
me._bg.setTranslation(0, fillTop);
me._fill.setTranslation(0, fillTop);
me._bg.setSize(w, me._fillHeight);
var valueFontSize = me._style.getSize("slider-value-font-size", me._style.getSize("base-font-size"));
var thumbValueMargin = me._style.getSize("slider-thumb-value-margin", 8);
var fillTicksMargin = me._style.getSize("slider-fill-ticks-margin", 3);
var ticksOffset = fillTicksMargin + me._style.getSize("slider-tick-length", 10);
var thumbY = (h - me._thumbSize[1]) * 0.5;
if (model._valueDisplayPosition != model.ValuePosition.None) {
thumbY -= (valueFontSize + thumbValueMargin) * 0.5;
}
if (model._ticksPosition != model.TicksPosition.None and model._ticksPosition != model._valueDisplayPosition) {
thumbY -= ticksOffset * 0.5;
}
var valueY = thumbY;
if (model._valueDisplayPosition == model.ValuePosition.Above) {
thumbY += valueFontSize + thumbValueMargin;
} elsif (model._valueDisplayPosition == model.ValuePosition.Below) {
valueY += me._thumbSize[1] + thumbValueMargin;
}
var fillY = thumbY + (me._thumbSize[1] - me._fillHeight) * 0.5;
var ticksY = fillY;
if (model._ticksPosition == model.TicksPosition.Below) {
ticksY += me._fillHeight + fillTicksMargin;
} elsif (model._ticksPosition == model.TicksPosition.Above) {
if (model._valueDisplayPosition == model.ValuePosition.Below) {
thumbY += ticksOffset;
fillY += ticksOffset;
valueY += ticksOffset;
} else {
ticksY -= ticksOffset;
}
}
me._bg.setTranslation(me._thumbSize[0] / 2, fillY);
me._fill.setTranslation(me._fill.getTranslation()[0], fillY);
me._ticks.setTranslation(me._thumbSize[0] / 2, ticksY);
me._thumb.setTranslation(me._thumb.getTranslation()[0], thumbY);
me._value.setTranslation(me._value.getTranslation()[0], valueY);
me._bg.setSize(w - me._thumbSize[0], me._fillHeight);
me.setNormValue(model, model._normValue());
me._drawTicks(model);
},
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
}
_drawTicks: func(model) {
me._ticks.reset();
var range = model._maxValue - model._minValue;
if (range <= 0 or model._tickStep <= 0) {
return;
}
var availWidthPos = model._size[0] - me._thumbSize[0];
var pixelsPerUnit = availWidthPos / range;
var remainder = math.mod(range, model._tickStep);
var numTicks = int((range - remainder) / model._tickStep);
if (remainder == 0) {
numTicks -= 1;
}
for (var i = 1; i <= numTicks; i += 1) {
me._ticks.moveTo(i * pixelsPerUnit * model._tickStep, 0)
.vert(me._style.getSize("slider-tick-length", 8));
}
},
@ -1032,7 +1120,7 @@ DefaultStyle.widgets.slider = {
if( type == "text" )
{
me[ mem ].set("font", "LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", 14)
.set("character-size", me._style.getSize("slider-value-font-size", me._style.getSize("base-font-size")))
.set("alignment", "left-center");
}
}

View file

@ -4,47 +4,65 @@
# SPDX-License-Identifier: GPL-2.0-or-later
gui.widgets.Slider = {
ValueStyle: {
Fixed: 0,
Moving: 1,
},
ValuePosition: {
None: 0,
Above: 1,
Below: 2,
},
TicksPosition: {
None: 0,
Above: 1,
Below: 2,
},
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._thumbDown = 0;
m._minValue = cfg.get("min-value", 0);
m._maxValue = cfg.get("max-value", 100);
m._value = 50;
m._pageStep = cfg.get("page-step", 0);
m._numTicks = cfg.get("tick-count", 0);
m._value = cfg.get("value", 50);
m._stepSize = cfg.get("step-size", 1);
m._pageSize = cfg.get("page-size", 10);
m._tickStep = cfg.get("tick-step", 10);
m._tickStyle = cfg.get("ticks-style", 0);
m._valueDisplayStyle = cfg.get("value-style", 0);
m._ticksPosition = cfg.get("ticks-position", m.TicksPosition.None);
m._valueDisplayStyle = cfg.get("value-style", m.ValueStyle.Moving);
m._valueDisplayPosition = cfg.get("value-position", m.ValuePosition.None);
# TODO : select where value is shown
# TODO : select where tick marks are shown
m._setView( style.createWidget(parent, cfg.get("type", "slider"), cfg) );
m._view.updateRanges(m._minValue, m._maxValue, m._numTicks);
m._setView(style.createWidget(parent, cfg.get("type", "slider"), cfg));
m._view._updateLayoutSizes(m);
return m;
},
setValue: func(val)
{
me._value = val;
if( me._view != nil ) {
me._value = math.clamp(val, me._minValue, me._maxValue);
if (me._view != nil) {
me._view.setNormValue(me, me._normValue());
}
return me;
},
setDown: func(down = 1)
{
if (me._down == down )
return me;
setValuePosition: func(pos) {
me._valueDisplayPosition = pos;
me._view._updateLayoutSizes(me);
},
me._down = down;
me._onStateChange();
return me;
setValueStyle: func(style) {
me._valueDisplayStyle = style;
me._view._updateLayoutSizes(me);
},
setTicksPositon: func(pos) {
me._ticksPosition = pos;
me._view._updateLayoutSizes(me);
},
# protected:
@ -53,19 +71,62 @@ gui.widgets.Slider = {
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("click", func(e) {
me._dragThumb(e);
});
view._thumb.addEventListener("drag", func(e) {
me._dragThumb(e);
e.stopPropagation();
});
view._thumb.addEventListener("mousedown", func(e) {
me._thumbDown = 1;
me._onStateChange();
});
view._thumb.addEventListener("mouseup", func(e) {
me._thumbDown = 0;
me._onStateChange();
});
view._root.addEventListener("wheel", func(e) {
if (!me._enabled) {
return;
}
me.setValue(me._value + e.deltaY * me._stepSize);
e.stopPropagation();
});
view._root.addEventListener("keydown", func(e) {
var value = me._value;
if (contains([
keyboard.FunctionKeys.Left, keyboard.FunctionKeys.KP_Left,
keyboard.FunctionKeys.Down, keyboard.FunctionKeys.KP_Down,
keyboard.PrintableKeys.Minus, keyboard.FunctionKeys.KP_Subtract,
], e.keyCode)) {
value -= me._stepSize;
} elsif (contains([
keyboard.FunctionKeys.Right, keyboard.FunctionKeys.KP_Right,
keyboard.FunctionKeys.Up, keyboard.FunctionKeys.KP_Up,
keyboard.PrintableKeys.Plus, keyboard.FunctionKeys.KP_Add,
], e.keyCode)) {
value += me._stepSize;
} elsif (contains([keyboard.FunctionKeys.Page_Down, keyboard.FunctionKeys.KP_Page_Down], e.keyCode)) {
value -= me._pageSize;
} elsif (contains([keyboard.FunctionKeys.Page_Up, keyboard.FunctionKeys.KP_Page_Up], e.keyCode)) {
value += me._pageSize;
} elsif (contains([keyboard.FunctionKeys.Home, keyboard.FunctionKeys.KP_Home], e.keyCode)) {
value = me._minValue;
} elsif (contains([keyboard.FunctionKeys.End, keyboard.FunctionKeys.KP_End], e.keyCode)) {
value = me._maxValue;
}
me.setValue(value);
});
},
_dragThumb: func(event)
{
{
if (!me._enabled) {
return;
}
var vr = me._view._root;
var viewPosX = vr.canvasToLocal([event.clientX, event.clientY])[0];
var width = me._size[0];
@ -76,7 +137,11 @@ gui.widgets.Slider = {
me.setValue(me._maxValue);
} else {
var norm = viewPosX / width;
me.setValue(norm * ( me._maxValue - me._minValue));
var mouseValue = me._minValue + norm * ( me._maxValue - me._minValue);
if (me._stepSize != 0) {
mouseValue = math.round(mouseValue / me._stepSize) * me._stepSize;
}
me.setValue(mouseValue);
}
},

View file

@ -311,6 +311,12 @@
<green type="float">0.5</green>
<blue type="float">0.5</blue>
</switch_thumb_border_color>
<slider_ticks>
<red type="float">0.5</red>
<green type="float">0.5</green>
<blue type="float">0.5</blue>
</slider_ticks>
</colors>
<sizes>