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