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:
parent
be80d67754
commit
372d68a775
5 changed files with 237 additions and 67 deletions
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue