0338d82c55
Rewrite the way scrolling for ScrollAreas is handled: Store content position instead of scrollbar positions to keep position on resize and promote moving the content instead of the contents to as primary API. Let the mousewheel scroll by fixed content offset instead of scrollbar offset to make it actually usable (especially with low scrolling distance).
363 lines
8.8 KiB
Text
363 lines
8.8 KiB
Text
var DefaultStyle = {
|
|
new: func(name, name_icon_theme)
|
|
{
|
|
return {
|
|
parents: [ gui.Style.new(name, name_icon_theme),
|
|
DefaultStyle ]
|
|
};
|
|
},
|
|
createWidget: func(parent, type, cfg)
|
|
{
|
|
var factory = me.widgets[type];
|
|
if( factory == nil )
|
|
{
|
|
debug.warn("DefaultStyle: unknown widget type (" ~ type ~ ")");
|
|
return nil;
|
|
}
|
|
|
|
var w = {
|
|
parents: [factory],
|
|
_style: me
|
|
};
|
|
call(factory.new, [parent, cfg], w);
|
|
return w;
|
|
},
|
|
widgets: {}
|
|
};
|
|
|
|
# A button
|
|
DefaultStyle.widgets.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._label =
|
|
me._root.createChild("text")
|
|
.set("font", "LiberationFonts/LiberationSans-Regular.ttf")
|
|
.set("character-size", 14)
|
|
.set("alignment", "center-baseline");
|
|
},
|
|
setSize: func(model, w, h)
|
|
{
|
|
me._bg.reset()
|
|
.rect(3, 3, w - 6, h - 6, {"border-radius": 5});
|
|
me._border.setSize(w, h);
|
|
},
|
|
setText: func(model, text)
|
|
{
|
|
me._label.set("text", text);
|
|
|
|
var min_width = math.max(80, me._label.maxWidth() + 16);
|
|
model.setMinimumSize([min_width, 16]);
|
|
model.setSizeHint([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));
|
|
|
|
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 ~= "button";
|
|
|
|
if( model._down )
|
|
{
|
|
file ~= "-active";
|
|
me._label.setTranslation(w / 2 + 1, h / 2 + 6);
|
|
}
|
|
else
|
|
me._label.setTranslation(w / 2, h / 2 + 5);
|
|
|
|
if( model._enabled )
|
|
{
|
|
if( model._focused and !backdrop )
|
|
file ~= "-focused";
|
|
|
|
if( model._hover and !model._down )
|
|
file ~= "-hover";
|
|
}
|
|
else
|
|
file ~= "-disabled";
|
|
|
|
me._border.set("src", file ~ ".png");
|
|
}
|
|
};
|
|
|
|
# A checbox
|
|
DefaultStyle.widgets.checkbox = {
|
|
new: func(parent, cfg)
|
|
{
|
|
me._root = parent.createChild("group", "checkbox");
|
|
me._icon =
|
|
me._root.createChild("image", "checkbox-icon")
|
|
.setSize(18, 18);
|
|
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)
|
|
{
|
|
me._icon.setTranslation(0, int((h - 18) / 2));
|
|
me._label.setTranslation(24, int(h / 2) + 1);
|
|
|
|
return me;
|
|
},
|
|
setText: func(model, text)
|
|
{
|
|
me._label.set("text", text);
|
|
|
|
var min_width = me._label.maxWidth() + 24;
|
|
model.setMinimumSize([min_width, 18]);
|
|
model.setSizeHint([min_width, 24]);
|
|
|
|
return me;
|
|
},
|
|
update: func(model)
|
|
{
|
|
var backdrop = !model._windowFocus();
|
|
var (w, h) = model._size;
|
|
var file = me._style._dir_widgets ~ "/";
|
|
|
|
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 ~= "check";
|
|
|
|
if( model._down )
|
|
file ~= "-selected";
|
|
else
|
|
file ~= "-unselected";
|
|
|
|
if( model._enabled )
|
|
{
|
|
if( model._hover )
|
|
file ~= "-hover";
|
|
}
|
|
else
|
|
file ~= "-disabled";
|
|
|
|
me._icon.set("src", file ~ ".png");
|
|
}
|
|
};
|
|
|
|
# A label
|
|
DefaultStyle.widgets.label = {
|
|
new: func(parent, cfg)
|
|
{
|
|
me._root = parent.createChild("group", "label");
|
|
},
|
|
setSize: func(model, w, h)
|
|
{
|
|
if( me['_bg'] != nil )
|
|
me._bg.reset().rect(0, 0, w, h);
|
|
if( me['_img'] != nil )
|
|
me._img.set("size[0]", w)
|
|
.set("size[1]", h);
|
|
if( me['_text'] != nil )
|
|
{
|
|
# TODO different alignment
|
|
me._text.setTranslation(2, 2 + h / 2);
|
|
me._text.set(
|
|
"max-width",
|
|
model._cfg.get("wordWrap", 0) ? (w - 4) : 0
|
|
);
|
|
}
|
|
return me;
|
|
},
|
|
setText: func(model, text)
|
|
{
|
|
if( text == nil or size(text) == 0 )
|
|
{
|
|
model.setHeightForWidthFunc(nil);
|
|
return me._deleteElement('text');
|
|
}
|
|
|
|
me._createElement("text", "text")
|
|
.set("text", text);
|
|
|
|
var hfw_func = nil;
|
|
var min_width = me._text.maxWidth() + 4;
|
|
var width_hint = min_width;
|
|
|
|
if( model._cfg.get("wordWrap", 0) )
|
|
{
|
|
var m = me;
|
|
hfw_func = func(w) m.heightForWidth(w);
|
|
min_width = math.min(32, min_width);
|
|
|
|
# prefer approximately quadratic text blocks
|
|
if( width_hint > 24 )
|
|
width_hint = int(math.sqrt(width_hint * 24));
|
|
}
|
|
|
|
model.setHeightForWidthFunc(hfw_func);
|
|
model.setMinimumSize([min_width, 14]);
|
|
model.setSizeHint([width_hint, 24]);
|
|
|
|
return me.update(model);
|
|
},
|
|
setImage: func(model, img)
|
|
{
|
|
if( img == nil or size(img) == 0 )
|
|
return me._deleteElement('img');
|
|
|
|
me._createElement("img", "image")
|
|
.set("src", img)
|
|
.set("preserveAspectRatio", "xMidYMid slice");
|
|
|
|
return me;
|
|
},
|
|
# @param bg CSS color or 'none'
|
|
setBackground: func(model, bg)
|
|
{
|
|
if( bg == nil or bg == "none" )
|
|
return me._deleteElement("bg");
|
|
|
|
me._createElement("bg", "path")
|
|
.set("fill", bg);
|
|
|
|
me.setSize(model, model._size[0], model._size[1]);
|
|
return me;
|
|
},
|
|
heightForWidth: func(w)
|
|
{
|
|
if( me['_text'] == nil )
|
|
return -1;
|
|
|
|
return math.max(14, me._text.heightForWidth(w - 4));
|
|
},
|
|
update: func(model)
|
|
{
|
|
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, "label-" ~ 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;
|
|
}
|
|
};
|
|
|
|
# ScrollArea
|
|
DefaultStyle.widgets["scroll-area"] = {
|
|
new: func(parent, cfg)
|
|
{
|
|
me._root = parent.createChild("group", "scroll-area");
|
|
|
|
me._bg = me._root.createChild("path", "background")
|
|
.set("fill", "#e0e0e0");
|
|
me.content = me._root.createChild("group", "scroll-content")
|
|
.set("clip-frame", Element.PARENT);
|
|
me.vert = me._newScroll(me._root, "vert");
|
|
me.horiz = me._newScroll(me._root, "horiz");
|
|
},
|
|
setColorBackground: func
|
|
{
|
|
if( size(arg) == 1 )
|
|
var arg = arg[0];
|
|
me._bg.setColorFill(arg);
|
|
},
|
|
update: func(model)
|
|
{
|
|
me.horiz.reset();
|
|
if( model._max_scroll[0] > 1 )
|
|
# only show scroll bar if horizontally scrollable
|
|
me.horiz.moveTo( model._scroller_offset[0] + model._scroller_pos[0],
|
|
model._size[1] - 2 )
|
|
.horiz(model._scroller_size[0]);
|
|
|
|
me.vert.reset();
|
|
if( model._max_scroll[1] > 1 )
|
|
# only show scroll bar if vertically scrollable
|
|
me.vert.moveTo( model._size[0] - 2,
|
|
model._scroller_offset[1] + model._scroller_pos[1] )
|
|
.vert(model._scroller_size[1]);
|
|
|
|
me._bg.reset()
|
|
.rect(0, 0, model._size[0], model._size[1]);
|
|
me.content.set(
|
|
"clip",
|
|
"rect(0, " ~ model._size[0] ~ ", " ~ model._size[1] ~ ", 0)"
|
|
);
|
|
},
|
|
# private:
|
|
_newScroll: func(el, orient)
|
|
{
|
|
return el.createChild("path", "scroll-" ~ orient)
|
|
.set("stroke", "#f07845")
|
|
.set("stroke-width", 4);
|
|
},
|
|
# Calculate size and limits of scroller
|
|
#
|
|
# @param model
|
|
# @param dir 0 for horizontal, 1 for vertical
|
|
# @return [scroller_size, min_pos, max_pos]
|
|
_updateScrollMetrics: func(model, dir)
|
|
{
|
|
if( model._content_size[dir] <= model._size[dir] )
|
|
return;
|
|
|
|
model._scroller_size[dir] =
|
|
math.max(
|
|
12,
|
|
model._size[dir] * (model._size[dir] / model._content_size[dir])
|
|
);
|
|
model._scroller_offset[dir] = 0;
|
|
model._scroller_delta[dir] = model._size[dir] - model._scroller_size[dir];
|
|
}
|
|
};
|