1var DefaultStyle = {
2  new: func(name, name_icon_theme)
3  {
4    return {
5      parents: [ gui.Style.new(name, name_icon_theme),
6                 DefaultStyle ]
7    };
8  },
9  createWidget: func(parent, type, cfg)
10  {
11    var factory = me.widgets[type];
12    if( factory == nil )
13    {
14      debug.warn("DefaultStyle: unknown widget type (" ~ type ~ ")");
15      return nil;
16    }
17
18    var w = {
19      parents: [factory],
20      _style: me
21    };
22    call(factory.new, [parent, cfg], w);
23    return w;
24  },
25  widgets: {}
26};
27
28# A button
29DefaultStyle.widgets.button = {
30  new: func(parent, cfg)
31  {
32    me._root = parent.createChild("group", "button");
33    me._bg =
34      me._root.createChild("path");
35    me._border =
36      me._root.createChild("image", "button")
37              .set("slice", "10 12"); #"7")
38    me._label =
39      me._root.createChild("text")
40              .set("font", "LiberationFonts/LiberationSans-Regular.ttf")
41              .set("character-size", 14)
42              .set("alignment", "center-baseline");
43  },
44  setSize: func(model, w, h)
45  {
46    me._bg.reset()
47          .rect(3, 3, w - 6, h - 6, {"border-radius": 5});
48    me._border.setSize(w, h);
49  },
50  setText: func(model, text)
51  {
52    me._label.setText(text);
53
54    var min_width = math.max(80, me._label.maxWidth() + 16);
55    model.setLayoutMinimumSize([min_width, 16]);
56    model.setLayoutSizeHint([min_width, 28]);
57
58    return me;
59  },
60  update: func(model)
61  {
62    var backdrop = !model._windowFocus();
63    var (w, h) = model._size;
64    var file = me._style._dir_widgets ~ "/";
65
66    # TODO unify color names with image names
67    var bg_color_name = "button_bg_color";
68    if( backdrop )
69      bg_color_name = "button_backdrop_bg_color";
70    else if( !model._enabled )
71      bg_color_name = "button_bg_color_insensitive";
72    else if( model._down )
73      bg_color_name = "button_bg_color_down";
74    else if( model._hover )
75      bg_color_name = "button_bg_color_hover";
76    me._bg.set("fill", me._style.getColor(bg_color_name));
77
78    if( backdrop )
79    {
80      file ~= "backdrop-";
81      me._label.set("fill", me._style.getColor("backdrop_fg_color"));
82    }
83    else
84      me._label.set("fill", me._style.getColor("fg_color"));
85    file ~= "button";
86
87    if( model._down )
88    {
89      file ~= "-active";
90      me._label.setTranslation(w / 2 + 1, h / 2 + 6);
91    }
92    else
93      me._label.setTranslation(w / 2, h / 2 + 5);
94
95    if( model._enabled )
96    {
97      if( model._focused and !backdrop )
98        file ~= "-focused";
99
100      if( model._hover and !model._down )
101        file ~= "-hover";
102    }
103    else
104      file ~= "-disabled";
105
106    me._border.set("src", file ~ ".png");
107  }
108};
109
110# A checkbox
111DefaultStyle.widgets.checkbox = {
112  new: func(parent, cfg)
113  {
114    me._root = parent.createChild("group", "checkbox");
115    me._icon =
116      me._root.createChild("image", "checkbox-icon")
117              .setSize(18, 18);
118    me._label =
119      me._root.createChild("text")
120              .set("font", "LiberationFonts/LiberationSans-Regular.ttf")
121              .set("character-size", 14)
122              .set("alignment", "left-center");
123  },
124  setSize: func(model, w, h)
125  {
126    me._icon.setTranslation(0, int((h - 18) / 2));
127    me._label.setTranslation(24, int(h / 2) + 1);
128
129    return me;
130  },
131  setText: func(model, text)
132  {
133    me._label.setText(text);
134
135    var min_width = me._label.maxWidth() + 24;
136    model.setLayoutMinimumSize([min_width, 18]);
137    model.setLayoutSizeHint([min_width, 24]);
138
139    return me;
140  },
141  update: func(model)
142  {
143    var backdrop = !model._windowFocus();
144    var (w, h) = model._size;
145    var file = me._style._dir_widgets ~ "/";
146
147    if( backdrop )
148    {
149      file ~= "backdrop-";
150      me._label.set("fill", me._style.getColor("backdrop_fg_color"));
151    }
152    else
153      me._label.set("fill", me._style.getColor("fg_color"));
154    file ~= "check";
155
156    if( model._down )
157      file ~= "-selected";
158    else
159      file ~= "-unselected";
160
161    if( model._enabled )
162    {
163      if( model._hover )
164        file ~= "-hover";
165    }
166    else
167      file ~= "-disabled";
168
169    me._icon.set("src", file ~ ".png");
170  }
171};
172
173# A label
174DefaultStyle.widgets.label = {
175  new: func(parent, cfg)
176  {
177    me._root = parent.createChild("group", "label");
178  },
179  setSize: func(model, w, h)
180  {
181    if( me['_bg'] != nil )
182      me._bg.reset().rect(0, 0, w, h);
183    if( me['_img'] != nil )
184      me._img.set("size[0]", w)
185             .set("size[1]", h);
186    if( me['_text'] != nil )
187    {
188      # TODO different alignment
189      me._text.setTranslation(2, 2 + h / 2);
190      me._text.set(
191        "max-width",
192        model._cfg.get("wordWrap", 0) ? (w - 4) : 0
193      );
194    }
195    return me;
196  },
197  setText: func(model, text)
198  {
199    if( text == nil or size(text) == 0 )
200    {
201      model.setHeightForWidthFunc(nil);
202      return me._deleteElement('text');
203    }
204
205    me._createElement("text", "text")
206      .setText(text);
207
208    var hfw_func = nil;
209    var min_width = me._text.maxWidth() + 4;
210    var width_hint = min_width;
211
212    if( model._cfg.get("wordWrap", 0) )
213    {
214      var m = me;
215      hfw_func = func(w) m.heightForWidth(w);
216      min_width = math.min(32, min_width);
217
218      # prefer approximately quadratic text blocks
219      if( width_hint > 24 )
220        width_hint = int(math.sqrt(width_hint * 24));
221    }
222
223    model.setHeightForWidthFunc(hfw_func);
224    model.setLayoutMinimumSize([min_width, 14]);
225    model.setLayoutSizeHint([width_hint, 24]);
226
227    return me.update(model);
228  },
229  setImage: func(model, img)
230  {
231    if( img == nil or size(img) == 0 )
232      return me._deleteElement('img');
233
234    me._createElement("img", "image")
235      .set("src", img)
236      .set("preserveAspectRatio", "xMidYMid slice");
237
238    return me;
239  },
240  # @param bg CSS color or 'none'
241  setBackground: func(model, bg)
242  {
243    if( bg == nil or bg == "none" )
244      return me._deleteElement("bg");
245
246    me._createElement("bg", "path")
247      .set("fill", bg);
248
249    me.setSize(model, model._size[0], model._size[1]);
250    return me;
251  },
252  heightForWidth: func(w)
253  {
254    if( me['_text'] == nil )
255      return -1;
256
257    return math.max(14, me._text.heightForWidth(w - 4));
258  },
259  update: func(model)
260  {
261    if( me['_text'] != nil )
262    {
263      var color_name = model._windowFocus() ? "fg_color" : "backdrop_fg_color";
264      me._text.set("fill", me._style.getColor(color_name));
265    }
266  },
267# protected:
268  _createElement: func(name, type)
269  {
270    var mem = '_' ~ name;
271    if( me[ mem ] == nil )
272    {
273      me[ mem ] = me._root.createChild(type, "label-" ~ name);
274
275      if( type == "text" )
276      {
277         me[ mem ].set("font", "LiberationFonts/LiberationSans-Regular.ttf")
278                  .set("character-size", 14)
279                  .set("alignment", "left-center");
280      }
281    }
282    return me[ mem ];
283  },
284  _deleteElement: func(name)
285  {
286    name = '_' ~ name;
287    if( me[ name ] != nil )
288    {
289      me[ name ].del();
290      me[ name ] = nil;
291    }
292    return me;
293  }
294};
295
296# A one line text input field
297DefaultStyle.widgets["line-edit"] = {
298  new: func(parent, cfg)
299  {
300    me._hpadding = cfg.get("hpadding", 8);
301
302    me._root = parent.createChild("group", "line-edit");
303    me._border =
304      me._root.createChild("image", "border")
305              .set("slice", "10 12"); #"7")
306    me._text =
307      me._root.createChild("text", "input")
308              .set("font", "LiberationFonts/LiberationSans-Regular.ttf")
309              .set("character-size", 14)
310              .set("alignment", "left-baseline")
311              .set("clip-frame", Element.PARENT);
312    me._cursor =
313      me._root.createChild("path", "cursor")
314              .set("stroke", "#333")
315              .set("stroke-width", 1)
316              .moveTo(me._hpadding, 5)
317              .vert(10);
318    me._hscroll = 0;
319  },
320  setSize: func(model, w, h)
321  {
322    me._border.setSize(w, h);
323    me._text.set(
324      "clip",
325      "rect(0, " ~ (w - me._hpadding) ~ ", " ~ h ~ ", " ~ me._hpadding ~ ")"
326    );
327    me._cursor.setDouble("coord[2]", h - 10);
328
329    return me.update(model);
330  },
331  setText: func(model, text)
332  {
333    me._text.setText(text);
334    model._onStateChange();
335  },
336  update: func(model)
337  {
338    var backdrop = !model._windowFocus();
339    var file = me._style._dir_widgets ~ "/";
340
341    if( backdrop )
342      file ~= "backdrop-";
343
344    file ~= "entry";
345
346    if( !model._enabled )
347      file ~= "-disabled";
348    else if( model._focused and !backdrop )
349      file ~= "-focused";
350
351    me._border.set("src", file ~ ".png");
352
353    var color_name = backdrop ? "backdrop_fg_color" : "fg_color";
354    me._text.set("fill", me._style.getColor(color_name));
355
356    me._cursor.setVisible(model._enabled and model._focused and !backdrop);
357
358    var width = model._size[0] - 2 * me._hpadding;
359    var cursor_pos = me._text.getCursorPos(0, model._cursor)[0];
360    var text_width = me._text.getCursorPos(0, me._text.lineLength(0))[0];
361
362    if( text_width <= width )
363      # fit -> align left (TODO handle different alignment)
364      me._hscroll = 0;
365    else if( me._hscroll + cursor_pos > width )
366      # does not fit, cursor to the right
367      me._hscroll = width - cursor_pos;
368    else if( me._hscroll + cursor_pos < 0 )
369      # does not fit, cursor to the left
370      me._hscroll = -cursor_pos;
371    else if( me._hscroll + text_width < width )
372      # does not fit, limit scroll to align with right side
373      me._hscroll = width - text_width;
374
375    var text_pos = me._hscroll + me._hpadding;
376
377    me._text
378      .setTranslation(text_pos, model._size[1] / 2 + 5)
379      .update();
380    me._cursor
381      .setDouble("coord[0]", text_pos + cursor_pos)
382      .update();
383  }
384};
385
386# ScrollArea
387DefaultStyle.widgets["scroll-area"] = {
388  new: func(parent, cfg)
389  {
390    me._root = parent.createChild("group", "scroll-area");
391
392    me._bg     = me._root.createChild("path", "background")
393                         .set("fill", "#e0e0e0");
394    me.content = me._root.createChild("group", "scroll-content")
395                         .set("clip-frame", Element.PARENT);
396    me.vert  = me._newScroll(me._root, "vert");
397    me.horiz = me._newScroll(me._root, "horiz");
398  },
399  setColorBackground: func
400  {
401    if( size(arg) == 1 )
402    	  var arg = arg[0];
403    	me._bg.setColorFill(arg);
404  },
405  update: func(model)
406  {
407    me.horiz.reset();
408    if( model._max_scroll[0] > 1 )
409      # only show scroll bar if horizontally scrollable
410      me.horiz.moveTo( model._scroller_offset[0] + model._scroller_pos[0],
411                       model._size[1] - 2 )
412              .horiz(model._scroller_size[0]);
413
414    me.vert.reset();
415    if( model._max_scroll[1] > 1 )
416      # only show scroll bar if vertically scrollable
417      me.vert.moveTo( model._size[0] - 2,
418                      model._scroller_offset[1] + model._scroller_pos[1] )
419             .vert(model._scroller_size[1]);
420
421    me._bg.reset()
422          .rect(0, 0, model._size[0], model._size[1]);
423    me.content.set(
424      "clip",
425      "rect(0, " ~ model._size[0] ~ ", " ~ model._size[1] ~ ", 0)"
426    );
427  },
428# private:
429  _newScroll: func(el, orient)
430  {
431    return el.createChild("path", "scroll-" ~ orient)
432             .set("stroke", "#f07845")
433             .set("stroke-width", 4);
434  },
435  # Calculate size and limits of scroller
436  #
437  # @param model
438  # @param dir 0 for horizontal, 1 for vertical
439  # @return [scroller_size, min_pos, max_pos]
440  _updateScrollMetrics: func(model, dir)
441  {
442    if( model._content_size[dir] <= model._size[dir] )
443      return;
444
445    model._scroller_size[dir] =
446      math.max(
447        12,
448        model._size[dir] * (model._size[dir] / model._content_size[dir])
449      );
450    model._scroller_offset[dir] = 0;
451    model._scroller_delta[dir] = model._size[dir] - model._scroller_size[dir];
452  }
453};
454