1
0
Fork 0
fgdata/Nasal/canvas/gui/styles/DefaultStyle.nas

731 lines
18 KiB
Text
Raw Normal View History

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;
}
2014-03-19 22:20:09 +00:00
var w = {
parents: [factory],
_style: me
};
call(factory.new, [parent, cfg], w);
return w;
},
widgets: {}
};
# A button
DefaultStyle.widgets.button = {
2014-03-19 22:20:09 +00:00
new: func(parent, cfg)
{
me._root = parent.createChild("group", "button");
2014-03-19 22:20:09 +00:00
me._bg =
me._root.createChild("path");
2014-03-19 22:20:09 +00:00
me._border =
me._root.createChild("image", "button")
.set("slice", "10 12"); #"7")
2014-03-19 22:20:09 +00:00
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)
{
2019-01-12 22:03:39 +00:00
me._label.setText(text);
var min_width = math.max(80, me._label.maxWidth() + 16);
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 ~ "/";
2014-06-17 09:44:05 +00:00
# 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);
2014-06-10 22:00:55 +00:00
if( model._enabled )
{
2014-06-10 22:00:55 +00:00
if( model._focused and !backdrop )
file ~= "-focused";
if( model._hover and !model._down )
file ~= "-hover";
}
else
2014-06-10 22:00:55 +00:00
file ~= "-disabled";
me._border.set("src", file ~ ".png");
}
};
2014-03-19 22:20:09 +00:00
# A checkbox
2014-06-17 19:02:59 +00:00
DefaultStyle.widgets.checkbox = {
new: func(parent, cfg)
{
me._label_position = cfg.get("label-position", "right");
2014-06-17 19:02:59 +00:00
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)
{
if (me._label_position == "left") {
me._label.setTranslation(3, int((h / 2) + 1));
me._icon.setTranslation(me._label.maxWidth() + 6, int((h - 18) / 2));
} else {
me._icon.setTranslation(3, int((h - 18) / 2));
me._label.setTranslation(24, int(h / 2) + 1);
}
2014-06-17 19:02:59 +00:00
return me;
},
setText: func(model, text)
{
2019-01-12 22:03:39 +00:00
me._label.setText(text);
2014-06-17 19:02:59 +00:00
var min_width = me._label.maxWidth() + 3 + 24;
model.setLayoutMinimumSize([min_width, 18]);
model.setLayoutSizeHint([min_width, 24]);
2014-06-17 19:02:59 +00:00
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 ( !isstr(text) or size(text) == 0 )
{
model.setHeightForWidthFunc(nil);
return me._deleteElement('text');
}
me._createElement("text", "text")
2019-01-12 22:03:39 +00:00
.setText(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.setLayoutMinimumSize([min_width, 14]);
model.setLayoutSizeHint([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;
}
};
# A one line text input field
DefaultStyle.widgets["line-edit"] = {
new: func(parent, cfg)
{
me._hpadding = cfg.get("hpadding", 8);
me._root = parent.createChild("group", "line-edit");
me._border =
me._root.createChild("image", "border")
.set("slice", "10 12"); #"7")
me._text =
me._root.createChild("text", "input")
.set("font", "LiberationFonts/LiberationSans-Regular.ttf")
.set("character-size", 14)
.set("alignment", "left-baseline")
.set("clip-frame", Element.PARENT);
me._cursor =
me._root.createChild("path", "cursor")
.set("stroke", "#333")
.set("stroke-width", 1)
.moveTo(me._hpadding, 5)
.vert(10);
me._hscroll = 0;
},
setSize: func(model, w, h)
{
me._border.setSize(w, h);
me._text.set(
"clip",
"rect(0, " ~ (w - me._hpadding) ~ ", " ~ h ~ ", " ~ me._hpadding ~ ")"
);
me._cursor.setDouble("coord[2]", h - 10);
return me.update(model);
},
setText: func(model, text)
{
2019-01-12 22:03:39 +00:00
me._text.setText(text);
model._onStateChange();
},
update: func(model)
{
var backdrop = !model._windowFocus();
var file = me._style._dir_widgets ~ "/";
if( backdrop )
file ~= "backdrop-";
file ~= "entry";
if( !model._enabled )
file ~= "-disabled";
else if( model._focused and !backdrop )
file ~= "-focused";
me._border.set("src", file ~ ".png");
var color_name = backdrop ? "backdrop_fg_color" : "fg_color";
me._text.set("fill", me._style.getColor(color_name));
me._cursor.setVisible(model._enabled and model._focused and !backdrop);
var width = model._size[0] - 2 * me._hpadding;
var cursor_pos = me._text.getCursorPos(0, model._cursor)[0];
var text_width = me._text.getCursorPos(0, me._text.lineLength(0))[0];
if( text_width <= width )
# fit -> align left (TODO handle different alignment)
me._hscroll = 0;
else if( me._hscroll + cursor_pos > width )
# does not fit, cursor to the right
me._hscroll = width - cursor_pos;
else if( me._hscroll + cursor_pos < 0 )
# does not fit, cursor to the left
me._hscroll = -cursor_pos;
else if( me._hscroll + text_width < width )
# does not fit, limit scroll to align with right side
me._hscroll = width - text_width;
var text_pos = me._hscroll + me._hpadding;
me._text
.setTranslation(text_pos, model._size[1] / 2 + 5)
.update();
me._cursor
.setDouble("coord[0]", text_pos + cursor_pos)
.update();
}
};
2014-03-19 22:20:09 +00:00
# ScrollArea
DefaultStyle.widgets["scroll-area"] = {
new: func(parent, cfg)
{
me._root = parent.createChild("group", "scroll-area");
2014-03-19 22:20:09 +00:00
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");
2014-03-19 22:20:09 +00:00
},
setColorBackground: func
{
if( size(arg) == 1 )
var arg = arg[0];
me._bg.setColorFill(arg);
},
update: func(model)
2014-03-19 22:20:09 +00:00
{
me.horiz.reset();
if( model._max_scroll[0] > 1 )
2014-03-19 22:20:09 +00:00
# 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]);
2014-03-19 22:20:09 +00:00
me.vert.reset();
if( model._max_scroll[1] > 1 )
2014-03-19 22:20:09 +00:00
# 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]);
2014-03-19 22:20:09 +00:00
me._bg.reset()
.rect(0, 0, model._size[0], model._size[1]);
2014-03-19 22:20:09 +00:00
me.content.set(
"clip",
"rect(0, " ~ model._size[0] ~ ", " ~ model._size[1] ~ ", 0)"
2014-03-19 22:20:09 +00:00
);
},
# 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];
2014-03-19 22:20:09 +00:00
}
};
# 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;
}
}