1 // Written in the D programming language.
2 
3 /**
4 This module contains declaration of themes and styles implementation.
5 
6 Style - style container
7 Theme - parent for all styles
8 
9 
10 Synopsis:
11 
12 ----
13 import dlangui.widgets.styles;
14 ----
15 
16 Recent changes:
17      Dimensions like fontSize, padding, margins, min/max width and height can be specified in points, e.g. minWidth = "3pt" margins="1pt,2pt,1pt,2pt"
18      % for font size, based on parent font size, e.g. fontSize="120.5%" means parentStyle.fontSize * 120.5 / 100.0;
19 
20 Copyright: Vadim Lopatin, 2014
21 License:   Boost License 1.0
22 Authors:   Vadim Lopatin, coolreader.org@gmail.com
23 */
24 module dlangui.widgets.styles;
25 
26 import dlangui.core.config;
27 
28 private import std.xml;
29 private import std.string;
30 private import std.algorithm;
31 
32 import dlangui.core.types;
33 import dlangui.graphics.colors;
34 import dlangui.graphics.fonts;
35 import dlangui.graphics.drawbuf;
36 import dlangui.graphics.resources;
37 
38 // Standard style constants
39 // Themes should define all of these styles in order to support all controls
40 /// standard style id for TextWidget
41 immutable string STYLE_TEXT = "TEXT";
42 /// standard style id for MultilineTextWidget
43 immutable string STYLE_MULTILINE_TEXT = "MULTILINE_TEXT";
44 /// standard style id for Button
45 immutable string STYLE_BUTTON = "BUTTON";
46 /// standard style id for Button label
47 immutable string STYLE_BUTTON_LABEL = "BUTTON_LABEL";
48 /// standard style id for Button image
49 immutable string STYLE_BUTTON_IMAGE = "BUTTON_IMAGE";
50 /// style id for transparent Button
51 immutable string STYLE_BUTTON_TRANSPARENT = "BUTTON_TRANSPARENT";
52 /// style id for Button w/o margins
53 immutable string STYLE_BUTTON_NOMARGINS = "BUTTON_NOMARGINS";
54 /// standard style id for Switch
55 immutable string STYLE_SWITCH = "SWITCH";
56 /// standard style id for CheckBox
57 immutable string STYLE_CHECKBOX = "CHECKBOX";
58 /// standard style id for CheckBox image
59 immutable string STYLE_CHECKBOX_IMAGE = "CHECKBOX_IMAGE";
60 /// standard style id for CheckBox label
61 immutable string STYLE_CHECKBOX_LABEL = "CHECKBOX_LABEL";
62 /// standard style id for RadioButton
63 immutable string STYLE_RADIOBUTTON = "RADIOBUTTON";
64 /// standard style id for RadioButton image
65 immutable string STYLE_RADIOBUTTON_IMAGE = "RADIOBUTTON_IMAGE";
66 /// standard style id for RadioButton label
67 immutable string STYLE_RADIOBUTTON_LABEL = "RADIOBUTTON_LABEL";
68 /// standard style id for HSpacer
69 immutable string STYLE_HSPACER = "HSPACER";
70 /// standard style id for VSpacer
71 immutable string STYLE_VSPACER = "VSPACER";
72 /// standard style id for ScrollBar
73 immutable string STYLE_SCROLLBAR = "SCROLLBAR";
74 /// standard style id for SliderWidget
75 immutable string STYLE_SLIDER = "SLIDER";
76 /// standard style id for ScrollBar button
77 immutable string STYLE_SCROLLBAR_BUTTON = "SCROLLBAR_BUTTON";
78 /// standard style id for ScrollBar button
79 immutable string STYLE_SCROLLBAR_BUTTON_TRANSPARENT = "SCROLLBAR_BUTTON_TRANSPARENT";
80 /// standard style id for ScrollBar page control
81 immutable string STYLE_PAGE_SCROLL = "PAGE_SCROLL";
82 /// standard style id for TabWidget
83 immutable string STYLE_TAB_WIDGET = "TAB_WIDGET";
84 /// standard style id for Tab with Up alignment
85 immutable string STYLE_TAB_UP = "TAB_UP";
86 /// standard style id for button of Tab with Up alignment
87 immutable string STYLE_TAB_UP_BUTTON = "TAB_UP_BUTTON";
88 /// standard style id for button of Tab with Up alignment
89 immutable string STYLE_TAB_UP_BUTTON_TEXT = "TAB_UP_BUTTON_TEXT";
90 /// standard style id for TabHost
91 immutable string STYLE_TAB_HOST = "TAB_HOST";
92 /// standard style id for PopupMenu
93 immutable string STYLE_POPUP_MENU = "POPUP_MENU";
94 /// standard style id for menu item
95 immutable string STYLE_MENU_ITEM = "MENU_ITEM";
96 /// standard style id for menu item label
97 immutable string STYLE_MENU_LABEL = "MENU_LABEL";
98 /// standard style id for menu item icon
99 immutable string STYLE_MENU_ICON = "MENU_ICON";
100 /// standard style id for menu item accelerators label
101 immutable string STYLE_MENU_ACCEL = "MENU_ACCEL";
102 /// standard style id for main menu item
103 immutable string STYLE_MAIN_MENU_ITEM = "MAIN_MENU_ITEM";
104 /// standard style id for main menu item label
105 immutable string STYLE_MAIN_MENU_LABEL = "MAIN_MENU_LABEL";
106 /// standard style id for main menu
107 immutable string STYLE_MAIN_MENU = "MAIN_MENU";
108 /// standard style id for list items
109 immutable string STYLE_LIST_ITEM = "LIST_ITEM";
110 /// standard style id for EditLine
111 immutable string STYLE_EDIT_LINE = "EDIT_LINE";
112 /// standard style id for EditBox
113 immutable string STYLE_EDIT_BOX = "EDIT_BOX";
114 /// standard style id for lists
115 immutable string STYLE_LIST_BOX = "LIST_BOX";
116 /// standard style id for StringGrid
117 immutable string STYLE_STRING_GRID = "STRING_GRID";
118 /// standard style id for background similar to transparent button
119 immutable string STYLE_TRANSPARENT_BUTTON_BACKGROUND = "TRANSPARENT_BUTTON_BACKGROUND";
120 /// standard style id for GroupBox
121 immutable string STYLE_GROUP_BOX = "GROUP_BOX";
122 /// standard style id for GroupBox caption
123 immutable string STYLE_GROUP_BOX_CAPTION = "GROUP_BOX_CAPTION";
124 /// standard style id for ProgressBarWidget caption
125 immutable string STYLE_PROGRESS_BAR = "PROGRESS_BAR";
126 
127 /// standard style id for tree item
128 immutable string STYLE_TREE_ITEM = "TREE_ITEM";
129 /// standard style id for tree item body (icon + label)
130 immutable string STYLE_TREE_ITEM_BODY = "TREE_ITEM_BODY";
131 /// standard style id for tree item label
132 immutable string STYLE_TREE_ITEM_LABEL = "TREE_ITEM_LABEL";
133 /// standard style id for tree item icon
134 immutable string STYLE_TREE_ITEM_ICON = "TREE_ITEM_ICON";
135 /// standard style id for tree item expand icon
136 immutable string STYLE_TREE_ITEM_EXPAND_ICON = "TREE_ITEM_EXPAND_ICON";
137 /// standard style id for combo box
138 immutable string STYLE_COMBO_BOX = "COMBO_BOX";
139 /// standard style id for combo box button
140 immutable string STYLE_COMBO_BOX_BUTTON = "COMBO_BOX_BUTTON";
141 /// standard style id for combo box body (current item)
142 immutable string STYLE_COMBO_BOX_BODY = "COMBO_BOX_BODY";
143 /// standard style id for app frame status line
144 immutable string STYLE_STATUS_LINE = "STATUS_LINE";
145 
146 /// standard style id for dock host
147 immutable string STYLE_DOCK_HOST = "DOCK_HOST";
148 /// standard style id for dock host body
149 immutable string STYLE_DOCK_HOST_BODY = "DOCK_HOST_BODY";
150 /// standard style id for dock window caption
151 immutable string STYLE_DOCK_WINDOW_CAPTION = "DOCK_WINDOW_CAPTION";
152 /// standard style id for dock window
153 immutable string STYLE_DOCK_WINDOW = "DOCK_WINDOW";
154 /// standard style id for dock window caption label
155 immutable string STYLE_DOCK_WINDOW_CAPTION_LABEL = "DOCK_WINDOW_CAPTION_LABEL";
156 /// standard style id for dock window body
157 immutable string STYLE_DOCK_WINDOW_BODY = "DOCK_WINDOW_BODY";
158 /// standard style id for toolbar separator
159 immutable string STYLE_FLOATING_WINDOW = "FLOATING_WINDOW";
160 
161 /// standard style id for tab control in dock frame
162 immutable string STYLE_TAB_UP_DARK = "TAB_UP_DARK";
163 /// standard style id for tab control tab button in dock frame
164 immutable string STYLE_TAB_UP_BUTTON_DARK = "TAB_UP_BUTTON_DARK";
165 /// standard style id for tab control tab button text in dock frame
166 immutable string STYLE_TAB_UP_BUTTON_DARK_TEXT = "TAB_UP_BUTTON_DARK_TEXT";
167 /// standard style id for tab control in dock frame
168 immutable string STYLE_TAB_DOWN_DARK = "TAB_DOWN_DARK";
169 /// standard style id for tab control tab button in dock frame
170 immutable string STYLE_TAB_DOWN_BUTTON_DARK = "TAB_DOWN_BUTTON_DARK";
171 /// standard style id for tab control tab button text in dock frame
172 immutable string STYLE_TAB_DOWN_BUTTON_DARK_TEXT = "TAB_DOWN_BUTTON_DARK_TEXT";
173 
174 /// standard style id for tooltip popup
175 immutable string STYLE_TOOLTIP = "TOOLTIP";
176 
177 
178 /// standard style id for toolbars layout
179 immutable string STYLE_TOOLBAR_HOST = "TOOLBAR_HOST";
180 /// standard style id for toolbars
181 immutable string STYLE_TOOLBAR = "TOOLBAR";
182 /// standard style id for toolbar button
183 immutable string STYLE_TOOLBAR_BUTTON = "TOOLBAR_BUTTON";
184 /// standard style id for toolbar control, e.g. combobox
185 immutable string STYLE_TOOLBAR_CONTROL = "TOOLBAR_CONTROL";
186 /// standard style id for toolbar separator
187 immutable string STYLE_TOOLBAR_SEPARATOR = "TOOLBAR_SEPARATOR";
188 
189 /// standard style id for settings dialog tree
190 immutable string STYLE_SETTINGS_TREE = "SETTINGS_TREE";
191 /// standard style id for settings dialog content pages frame
192 immutable string STYLE_SETTINGS_PAGES = "SETTINGS_PAGES";
193 /// standard style id for settings dialog page title
194 immutable string STYLE_SETTINGS_PAGE_TITLE = "SETTINGS_PAGE_TITLE";
195 
196 /// window background color resource id
197 immutable string STYLE_COLOR_WINDOW_BACKGROUND = "window_background";
198 /// dialog background color resource id
199 immutable string STYLE_COLOR_DIALOG_BACKGROUND = "dialog_background";
200 
201 
202 
203 // Other style constants
204 
205 /// unspecified align - to take parent's value instead
206 enum ubyte ALIGN_UNSPECIFIED = 0;
207 /// unspecified font size constant - to take parent style property value
208 enum ushort FONT_SIZE_UNSPECIFIED = 0xFFFF;
209 /// unspecified font weight constant - to take parent style property value
210 enum ushort FONT_WEIGHT_UNSPECIFIED = 0x0000;
211 /// unspecified font style constant - to take parent style property value
212 enum ubyte FONT_STYLE_UNSPECIFIED = 0xFF;
213 /// normal font style constant
214 enum ubyte FONT_STYLE_NORMAL = 0x00;
215 /// italic font style constant
216 enum ubyte FONT_STYLE_ITALIC = 0x01;
217 /// use text flags from parent style
218 enum uint TEXT_FLAGS_UNSPECIFIED = uint.max;
219 /// use text flags from parent widget
220 enum uint TEXT_FLAGS_USE_PARENT = uint.max - 1;
221 /// to take layout weight from parent
222 enum int WEIGHT_UNSPECIFIED = -1;
223 
224 /// Align option bit constants
225 enum Align : ubyte {
226     /// alignment is not specified
227     Unspecified = ALIGN_UNSPECIFIED,
228     /// horizontally align to the left of box
229     Left = 1,
230     /// horizontally align to the right of box
231     Right = 2,
232     /// horizontally align to the center of box
233     HCenter = 1 | 2,
234     /// vertically align to the top of box
235     Top = 4,
236     /// vertically align to the bottom of box
237     Bottom = 8,
238     /// vertically align to the center of box
239     VCenter = 4 | 8,
240     /// align to the center of box (VCenter | HCenter)
241     Center = VCenter | HCenter,
242     /// align to the top left corner of box (Left | Top)
243     TopLeft = Left | Top,
244 }
245 
246 /// text drawing flag bits
247 enum TextFlag : uint {
248     /// text contains hot key prefixed with & char (e.g. "&File")
249     HotKeys = 1,
250     /// underline hot key when drawing
251     UnderlineHotKeys = 2,
252     /// underline hot key when drawing
253     UnderlineHotKeysWhenAltPressed = 4,
254     /// underline text when drawing
255     Underline = 8,
256     /// strikethrough text when drawing
257     StrikeThrough = 16 // TODO:
258 }
259 
260 struct DrawableAttributeList {
261     DrawableAttribute[string] _customDrawables;
262     ~this() {
263         clear();
264     }
265     void clear() {
266         foreach(key, ref value; _customDrawables) {
267             if (value) {
268                 destroy(value);
269                 value = null;
270             }
271         }
272         destroy(_customDrawables);
273         _customDrawables = null;
274     }
275     bool hasKey(string key) const {
276         return (key in _customDrawables) !is null;
277     }
278     ref DrawableRef drawable(string id) const {
279         return _customDrawables[id].drawable;
280     }
281     /// get custom drawable attribute
282     string drawableId(string id) const {
283         return _customDrawables[id].drawableId;
284     }
285     void set(string id, string resourceId) {
286         if (id in _customDrawables) {
287             _customDrawables[id].drawableId = resourceId;
288         } else {
289             _customDrawables[id] = new DrawableAttribute(id, resourceId);
290         }
291     }
292     void copyFrom(ref DrawableAttributeList v) {
293         clear();
294         foreach(key, value; v._customDrawables) {
295             set(key, value.drawableId);
296         }
297     }
298     void onThemeChanged() {
299         foreach(key, ref value; _customDrawables) {
300             if (value) {
301                 value.onThemeChanged();
302             }
303         }
304     }
305 }
306 
307 /// style properties
308 class Style {
309 protected:
310     string _id;
311     Theme _theme;
312     Style _parentStyle;
313     string _parentId;
314     uint _stateMask;
315     uint _stateValue;
316     ubyte _align = Align.TopLeft;
317     ubyte _fontStyle = FONT_STYLE_UNSPECIFIED;
318     FontFamily _fontFamily = FontFamily.Unspecified;
319     ushort _fontWeight = FONT_WEIGHT_UNSPECIFIED;
320     int _fontSize = FONT_SIZE_UNSPECIFIED;
321     uint _backgroundColor = COLOR_UNSPECIFIED;
322     uint _textColor = COLOR_UNSPECIFIED;
323     uint _textFlags = 0;
324     uint _alpha;
325     string _fontFace;
326     string _backgroundImageId;
327     Rect _padding;
328     Rect _margins;
329     int _minWidth = SIZE_UNSPECIFIED;
330     int _maxWidth = SIZE_UNSPECIFIED;
331     int _minHeight = SIZE_UNSPECIFIED;
332     int _maxHeight = SIZE_UNSPECIFIED;
333     int _layoutWidth = SIZE_UNSPECIFIED;
334     int _layoutHeight = SIZE_UNSPECIFIED;
335     int _layoutWeight = WEIGHT_UNSPECIFIED;
336     int _maxLines = SIZE_UNSPECIFIED;
337 
338     uint[] _focusRectColors;
339 
340     Style[] _substates;
341     Style[] _children;
342 
343     DrawableAttributeList _customDrawables;
344     uint[string] _customColors;
345     uint[string] _customLength;
346 
347     FontRef _font;
348     DrawableRef _backgroundDrawable;
349 
350 public:
351     void onThemeChanged() {
352         _font.clear();
353         _backgroundDrawable.clear();
354         foreach(s; _substates)
355             s.onThemeChanged();
356         foreach(s; _children)
357             s.onThemeChanged();
358         _customDrawables.onThemeChanged();
359     }
360 
361     @property const(Theme) theme() const {
362         if (_theme !is null)
363             return _theme;
364         return currentTheme;
365     }
366 
367     @property Theme theme() {
368         if (_theme !is null)
369             return _theme;
370         return currentTheme;
371     }
372 
373     @property string id() const { return _id; }
374     @property Style id(string id) {
375         this._id = id;
376         return this;
377     }
378 
379     /// access to parent style for this style
380     @property const(Style) parentStyle() const {
381         if (_parentStyle !is null)
382             return _parentStyle;
383         if (_parentId !is null && currentTheme !is null)
384             return currentTheme.get(_parentId);
385         return currentTheme;
386     }
387 
388     /// access to parent style for this style
389     @property Style parentStyle() {
390         if (_parentStyle !is null)
391             return _parentStyle;
392         if (_parentId !is null && currentTheme !is null)
393             return currentTheme.get(_parentId);
394         return currentTheme;
395     }
396 
397     @property string parentStyleId() {
398         return _parentId;
399     }
400 
401     @property Style parentStyleId(string id) {
402         _parentId = id;
403         if (_parentStyle)
404             if (currentTheme) {
405                 _parentStyle = currentTheme.get(_parentId);
406             }
407         return this;
408     }
409 
410     @property ref DrawableRef backgroundDrawable() const {
411         if (!(cast(Style)this)._backgroundDrawable.isNull)
412             return (cast(Style)this)._backgroundDrawable;
413         string image = backgroundImageId;
414         if (image !is null) {
415             (cast(Style)this)._backgroundDrawable = drawableCache.get(image);
416         } else {
417             uint color = backgroundColor;
418             (cast(Style)this)._backgroundDrawable = isFullyTransparentColor(color) ? new EmptyDrawable() : new SolidFillDrawable(color);
419         }
420         return (cast(Style)this)._backgroundDrawable;
421     }
422 
423     /// get custom drawable attribute
424     ref DrawableRef customDrawable(string id) const {
425         if (_customDrawables.hasKey(id))
426             return _customDrawables.drawable(id);
427         return parentStyle ? parentStyle.customDrawable(id) : currentTheme.customDrawable(id);
428     }
429 
430     /// get custom drawable attribute
431     string customDrawableId(string id) const {
432         if (_customDrawables.hasKey(id))
433             return _customDrawables.drawableId(id);
434         return parentStyle ? parentStyle.customDrawableId(id) : currentTheme.customDrawableId(id);
435     }
436 
437     /// sets custom drawable attribute for style
438     Style setCustomDrawable(string id, string resourceId) {
439         _customDrawables.set(id, resourceId);
440         return this;
441     }
442 
443     /// get custom color attribute
444     uint customColor(string id, uint defColor = COLOR_TRANSPARENT) const {
445         if (id in _customColors)
446             return _customColors[id];
447         return parentStyle ? parentStyle.customColor(id, defColor) : currentTheme.customColor(id, defColor);
448     }
449 
450     /// sets custom color attribute for style
451     Style setCustomColor(string id, uint color) {
452         _customColors[id] = color;
453         return this;
454     }
455 
456     /// get custom length attribute
457     uint customLength(string id, uint defLength = 0) const {
458         if (id in _customLength)
459             return _customLength[id];
460         return parentStyle ? parentStyle.customLength(id, defLength) : currentTheme.customLength(id, defLength);
461     }
462 
463     /// sets custom length attribute for style
464     Style setCustomLength(string id, uint value) {
465         _customLength[id] = value;
466         return this;
467     }
468 
469     void clearCachedObjects() {
470         onThemeChanged();
471     }
472 
473     //===================================================
474     // font properties
475 
476     @property ref FontRef font() const {
477         if (!(cast(Style)this)._font.isNull)
478             return (cast(Style)this)._font;
479         string face = fontFace;
480         int size = fontSize;
481         ushort weight = fontWeight;
482         bool italic = fontItalic;
483         FontFamily family = fontFamily;
484         (cast(Style)this)._font = FontManager.instance.getFont(size, weight, italic, family, face);
485         return (cast(Style)this)._font;
486     }
487 
488     /// font size
489     @property FontFamily fontFamily() const {
490         if (_fontFamily != FontFamily.Unspecified)
491             return _fontFamily;
492         else
493             return parentStyle.fontFamily;
494     }
495 
496     /// font size
497     @property string fontFace() const {
498         if (_fontFace !is null)
499             return _fontFace;
500         else
501             return parentStyle.fontFace;
502     }
503 
504     /// font style - italic
505     @property bool fontItalic() const {
506         if (_fontStyle != FONT_STYLE_UNSPECIFIED)
507             return _fontStyle == FONT_STYLE_ITALIC;
508         else
509             return parentStyle.fontItalic;
510     }
511 
512     /// font weight
513     @property ushort fontWeight() const {
514         if (_fontWeight != FONT_WEIGHT_UNSPECIFIED)
515             return _fontWeight;
516         else
517             return parentStyle.fontWeight;
518     }
519 
520     /// font size
521     @property int fontSize() const {
522         if (_fontSize != FONT_SIZE_UNSPECIFIED) {
523             if (_fontSize & SIZE_IN_PERCENTS_FLAG)
524                 return parentStyle.fontSize * (_fontSize ^ SIZE_IN_PERCENTS_FLAG) / 10000;
525             return toPixels(_fontSize);
526         } else
527             return parentStyle.fontSize;
528     }
529 
530     //===================================================
531     // layout parameters: margins / padding
532 
533     /// padding
534     @property const(Rect) padding() const {
535         if (_stateMask || _padding.left == SIZE_UNSPECIFIED)
536             return toPixels(parentStyle._padding);
537         return toPixels(_padding);
538     }
539 
540     /// margins
541     @property const(Rect) margins() const {
542         if (_stateMask || _margins.left == SIZE_UNSPECIFIED)
543             return toPixels(parentStyle._margins);
544         return toPixels(_margins);
545     }
546 
547     /// alpha (0=opaque .. 255=transparent)
548     @property uint alpha() const {
549         if (_alpha != COLOR_UNSPECIFIED)
550             return _alpha;
551         else
552             return parentStyle.alpha;
553     }
554 
555     /// text color
556     @property uint textColor() const {
557         if (_textColor != COLOR_UNSPECIFIED)
558             return _textColor;
559         else
560             return parentStyle.textColor;
561     }
562 
563     /// text color
564     @property int maxLines() const {
565         if (_maxLines != SIZE_UNSPECIFIED)
566             return _maxLines;
567         else
568             return parentStyle.maxLines;
569     }
570 
571     /// text flags
572     @property uint textFlags() const {
573         if (_textFlags != TEXT_FLAGS_UNSPECIFIED)
574             return _textFlags;
575         else
576             return parentStyle.textFlags;
577     }
578 
579     //===================================================
580     // background
581 
582     /// background color
583     @property uint backgroundColor() const {
584         if (_backgroundColor != COLOR_UNSPECIFIED)
585             return _backgroundColor;
586         else
587             return parentStyle.backgroundColor;
588     }
589 
590     /// font size
591     @property string backgroundImageId() const {
592         if (_backgroundImageId == COLOR_DRAWABLE)
593             return null;
594         else if (_backgroundImageId !is null)
595             return _backgroundImageId;
596         else
597             return parentStyle.backgroundImageId;
598     }
599 
600     //===================================================
601     // size restrictions
602 
603     /// minimal width constraint, 0 if limit is not set
604     @property uint minWidth() const {
605         if (_minWidth != SIZE_UNSPECIFIED)
606             return toPixels(_minWidth);
607         else
608             return parentStyle.minWidth;
609     }
610     /// max width constraint, returns SIZE_UNSPECIFIED if limit is not set
611     @property uint maxWidth() const {
612         if (_maxWidth != SIZE_UNSPECIFIED)
613             return toPixels(_maxWidth);
614         else
615             return parentStyle.maxWidth;
616     }
617     /// minimal height constraint, 0 if limit is not set
618     @property uint minHeight() const {
619         if (_minHeight != SIZE_UNSPECIFIED)
620             return toPixels(_minHeight);
621         else
622             return parentStyle.minHeight;
623     }
624     /// max height constraint, SIZE_UNSPECIFIED if limit is not set
625     @property uint maxHeight() const {
626         if (_maxHeight != SIZE_UNSPECIFIED)
627             return toPixels(_maxHeight);
628         else
629             return parentStyle.maxHeight;
630     }
631     /// set min width constraint
632     @property Style minWidth(int value) {
633         _minWidth = value;
634         return this;
635     }
636     /// set max width constraint
637     @property Style maxWidth(int value) {
638         _maxWidth = value;
639         return this;
640     }
641     /// set min height constraint
642     @property Style minHeight(int value) {
643         _minHeight = value;
644         return this;
645     }
646     /// set max height constraint
647     @property Style maxHeight(int value) {
648         _maxHeight = value;
649         return this;
650     }
651 
652 
653     /// layout width parameter
654     @property uint layoutWidth() const {
655         if (_layoutWidth != SIZE_UNSPECIFIED)
656             return _layoutWidth;
657         else
658             return parentStyle.layoutWidth;
659     }
660 
661     /// layout height parameter
662     @property uint layoutHeight() const {
663         if (_layoutHeight != SIZE_UNSPECIFIED)
664             return _layoutHeight;
665         else
666             return parentStyle.layoutHeight;
667     }
668 
669     /// layout weight parameter
670     @property uint layoutWeight() const {
671         if (_layoutWeight != WEIGHT_UNSPECIFIED)
672             return _layoutWeight;
673         else
674             return parentStyle.layoutWeight;
675     }
676 
677     /// set layout height
678     @property Style layoutHeight(int value) {
679         _layoutHeight = value;
680         return this;
681     }
682     /// set layout width
683     @property Style layoutWidth(int value) {
684         _layoutWidth = value;
685         return this;
686     }
687     /// set layout weight
688     @property Style layoutWeight(int value) {
689         _layoutWeight = value;
690         return this;
691     }
692 
693     //===================================================
694     // alignment
695 
696     /// get full alignment (both vertical and horizontal)
697     @property ubyte alignment() const {
698         if (_align != Align.Unspecified)
699             return _align;
700         else
701             return parentStyle.alignment;
702     }
703     /// vertical alignment: Top / VCenter / Bottom
704     @property ubyte valign() const { return alignment & Align.VCenter; }
705     /// horizontal alignment: Left / HCenter / Right
706     @property ubyte halign() const { return alignment & Align.HCenter; }
707 
708     /// set alignment
709     @property Style alignment(ubyte value) {
710         _align = value;
711         return this;
712     }
713 
714     @property Style fontFace(string face) {
715         if (_fontFace != face)
716             clearCachedObjects();
717         _fontFace = face;
718         return this;
719     }
720 
721     @property Style fontFamily(FontFamily family) {
722         if (_fontFamily != family)
723             clearCachedObjects();
724         _fontFamily = family;
725         return this;
726     }
727 
728     @property Style fontStyle(ubyte style) {
729         if (_fontStyle != style)
730             clearCachedObjects();
731         _fontStyle = style;
732         return this;
733     }
734 
735     @property Style fontWeight(ushort weight) {
736         if (_fontWeight != weight)
737             clearCachedObjects();
738         _fontWeight = weight;
739         return this;
740     }
741 
742     @property Style fontSize(int size) {
743         if (_fontSize != size)
744             clearCachedObjects();
745         _fontSize = size;
746         return this;
747     }
748 
749     @property Style textColor(uint color) {
750         _textColor = color;
751         return this;
752     }
753 
754     @property Style maxLines(int lines) {
755         _maxLines = lines;
756         return this;
757     }
758 
759     @property Style alpha(uint alpha) {
760         _alpha = alpha;
761         return this;
762     }
763 
764     @property Style textFlags(uint flags) {
765         _textFlags = flags;
766         return this;
767     }
768 
769     @property Style backgroundColor(uint color) {
770         _backgroundColor = color;
771         _backgroundImageId = COLOR_DRAWABLE;
772         _backgroundDrawable.clear();
773         return this;
774     }
775 
776     @property Style backgroundImageId(string image) {
777         _backgroundImageId = image;
778         _backgroundDrawable.clear();
779         return this;
780     }
781 
782     @property Style margins(Rect rc) {
783         _margins = rc;
784         return this;
785     }
786 
787     Style setMargins(int left, int top, int right, int bottom) {
788         _margins.left = left;
789         _margins.top = top;
790         _margins.right = right;
791         _margins.bottom = bottom;
792         return this;
793     }
794 
795     @property Style padding(Rect rc) {
796         _padding = rc;
797         return this;
798     }
799 
800     /// returns colors to draw focus rectangle (one for solid, two for vertical gradient) or null if no focus rect should be drawn for style
801     @property const(uint[]) focusRectColors() const {
802         if (_focusRectColors) {
803             if (_focusRectColors.length == 1 && _focusRectColors[0] == COLOR_UNSPECIFIED)
804                 return null;
805             return cast(const)_focusRectColors;
806         }
807         return parentStyle.focusRectColors;
808     }
809 
810     /// sets colors to draw focus rectangle or null if no focus rect should be drawn for style
811     @property Style focusRectColors(uint[] colors) {
812         _focusRectColors = colors;
813         return this;
814     }
815 
816     Style setPadding(int left, int top, int right, int bottom) {
817         _padding.left = left;
818         _padding.top = top;
819         _padding.right = right;
820         _padding.bottom = bottom;
821         return this;
822     }
823 
824     debug private static __gshared int _instanceCount;
825     debug @property static int instanceCount() { return _instanceCount; }
826 
827     this(Theme theme, string id) {
828         _theme = theme;
829         _parentStyle = theme;
830         _id = id;
831         debug _instanceCount++;
832         //Log.d("Created style ", _id, ", count=", ++_instanceCount);
833     }
834 
835 
836     ~this() {
837         foreach(ref Style item; _substates) {
838             //Log.d("Destroying substate");
839             destroy(item);
840             item = null;
841         }
842         _substates.destroy();
843         foreach(ref Style item; _children) {
844             destroy(item);
845             item = null;
846         }
847         _children.destroy();
848         _backgroundDrawable.clear();
849         _font.clear();
850         destroy(_customDrawables);
851         debug _instanceCount--;
852         //Log.d("Destroyed style ", _id, ", parentId=", _parentId, ", state=", _stateMask, ", count=", --_instanceCount);
853     }
854 
855     /// create named substyle of this style
856     Style createSubstyle(string id) {
857         Style child = (_theme !is null ? _theme : currentTheme).createSubstyle(id);
858         child._parentStyle = this;
859         _children ~= child;
860         return child;
861     }
862 
863     /// create state substyle for this style
864     Style createState(uint stateMask = 0, uint stateValue = 0) {
865         assert(stateMask != 0);
866         debug(styles) Log.d("Creating substate ", stateMask);
867         Style child = (_theme !is null ? _theme : currentTheme).createSubstyle(null);
868         child._parentStyle = this;
869         child._stateMask = stateMask;
870         child._stateValue = stateValue;
871         child._backgroundColor = COLOR_UNSPECIFIED;
872         child._textColor = COLOR_UNSPECIFIED;
873         child._textFlags = TEXT_FLAGS_UNSPECIFIED;
874         _substates ~= child;
875         return child;
876     }
877 
878     Style clone() {
879         Style res = new Style(_theme, null);
880         res._stateMask = _stateMask;
881         res._stateValue = _stateValue;
882         res._align = _align;
883         res._fontStyle = _fontStyle;
884         res._fontFamily = _fontFamily;
885         res._fontWeight = _fontWeight;
886         res._fontSize = _fontSize;
887         res._backgroundColor = _backgroundColor;
888         res._textColor = _textColor;
889         res._textFlags = _textFlags;
890         res._alpha = _alpha;
891         res._fontFace = _fontFace;
892         res._backgroundImageId = _backgroundImageId;
893         res._padding = _padding;
894         res._margins = _margins;
895         res._minWidth = _minWidth;
896         res._maxWidth = _maxWidth;
897         res._minHeight = _minHeight;
898         res._maxHeight = _maxHeight;
899         res._layoutWidth = _layoutWidth;
900         res._layoutHeight = _layoutHeight;
901         res._layoutWeight = _layoutWeight;
902         res._maxLines = _maxLines;
903 
904         res._focusRectColors = _focusRectColors.dup;
905 
906         res._customDrawables.copyFrom(_customDrawables);
907         res._customColors = _customColors.dup;
908         res._customLength = _customLength.dup;
909         return res;
910     }
911 
912     /// find exact existing state style or create new if no matched styles found
913     Style getOrCreateState(uint stateMask = 0, uint stateValue = 0) {
914         if (stateValue == State.Normal)
915             return this;
916         foreach(item; _substates) {
917             if ((item._stateMask == stateMask) && (item._stateValue == stateValue))
918                 return item;
919         }
920         return createState(stateMask, stateValue);
921     }
922 
923     /// find substyle based on widget state (e.g. focused, pressed, ...)
924     Style forState(uint state) {
925         if (state == State.Normal)
926             return this;
927         //Log.d("forState ", state, " styleId=", _id, " substates=", _substates.length);
928         if (parentStyle !is null && _substates.length == 0 && parentStyle._substates.length > 0) //id is null &&
929             return parentStyle.forState(state);
930         foreach(item; _substates) {
931             if ((item._stateMask & state) == item._stateValue)
932                 return item;
933         }
934         return this; // fallback to current style
935     }
936 
937     /// find substyle based on widget state (e.g. focused, pressed, ...)
938     const(Style) forState(uint state) const {
939         if (state == State.Normal)
940             return this;
941         //Log.d("forState ", state, " styleId=", _id, " substates=", _substates.length);
942         if (parentStyle !is null && _substates.length == 0 && parentStyle._substates.length > 0) //id is null &&
943             return parentStyle.forState(state);
944         foreach(item; _substates) {
945             if ((item._stateMask & state) == item._stateValue)
946                 return item;
947         }
948         return this; // fallback to current style
949     }
950 
951 }
952 
953 /// Theme - root for style hierarhy.
954 class Theme : Style {
955     protected Style[string] _byId;
956 
957     this(string id) {
958         super(this, id);
959         _parentStyle = null;
960         _backgroundColor = COLOR_TRANSPARENT; // transparent
961         _textColor = 0x000000; // black
962         _maxLines = 1;
963         _align = Align.TopLeft;
964         _fontSize = 9 | SIZE_IN_POINTS_FLAG; // TODO: from settings or screen properties / DPI
965         _fontStyle = FONT_STYLE_NORMAL;
966         _fontWeight = 400;
967         _fontFace = "Arial"; // TODO: from settings
968         //_fontFace = "Verdana"; // TODO: from settings
969         _fontFamily = FontFamily.SansSerif;
970         _minHeight = 0;
971         _minWidth = 0;
972         _layoutWidth = WRAP_CONTENT;
973         _layoutHeight = WRAP_CONTENT;
974         _layoutWeight = 1;
975     }
976 
977     ~this() {
978         //Log.d("Theme destructor");
979         if (unknownStyleIds.length > 0) {
980             Log.e("Unknown style statistics: ", unknownStyleIds);
981         }
982         foreach(ref Style item; _byId) {
983             destroy(item);
984             item = null;
985         }
986         _byId.destroy();
987     }
988 
989     override void onThemeChanged() {
990         super.onThemeChanged();
991         foreach(key, value; _byId) {
992             value.onThemeChanged();
993         }
994     }
995 
996     /// create wrapper style which will have currentTheme.get(id) as parent instead of fixed parent - to modify some base style properties in widget
997     Style modifyStyle(string id) {
998         Style style = new Style(null, null);
999         style._parentId = id;
1000         style._align = Align.Unspecified; // inherit
1001         style._padding.left = SIZE_UNSPECIFIED; // inherit
1002         style._margins.left = SIZE_UNSPECIFIED; // inherit
1003         style._textColor = COLOR_UNSPECIFIED; // inherit
1004         style._textFlags = TEXT_FLAGS_UNSPECIFIED; // inherit
1005         Style parent = get(id);
1006         if (parent) {
1007             foreach(item; parent._substates) {
1008                 Style substate = item.clone();
1009                 substate._parentStyle = style;
1010                 style._substates ~= substate;
1011             }
1012         }
1013         return style;
1014     }
1015 
1016     // ================================================
1017     // override to avoid infinite recursion
1018     /// font size
1019     @property override string backgroundImageId() const {
1020         return _backgroundImageId;
1021     }
1022     /// minimal width constraint, 0 if limit is not set
1023     @property override uint minWidth() const {
1024         return _minWidth;
1025     }
1026     /// max width constraint, returns SIZE_UNSPECIFIED if limit is not set
1027     @property override uint maxWidth() const {
1028         return _maxWidth;
1029     }
1030     /// minimal height constraint, 0 if limit is not set
1031     @property override uint minHeight() const {
1032         return _minHeight;
1033     }
1034     /// max height constraint, SIZE_UNSPECIFIED if limit is not set
1035     @property override uint maxHeight() const {
1036         return _maxHeight;
1037     }
1038 
1039     private DrawableRef _emptyDrawable;
1040     override ref DrawableRef customDrawable(string id) const {
1041         if (_customDrawables.hasKey(id))
1042             return _customDrawables.drawable(id);
1043         return (cast(Theme)this)._emptyDrawable;
1044     }
1045 
1046     override string customDrawableId(string id) const {
1047         if (_customDrawables.hasKey(id))
1048             return _customDrawables.drawableId(id);
1049         return null;
1050     }
1051 
1052     /// get custom color attribute - transparent by default
1053     override uint customColor(string id, uint defColor = COLOR_TRANSPARENT) const {
1054         if (id in _customColors)
1055             return _customColors[id];
1056         return defColor;
1057     }
1058 
1059     /// get custom color attribute - transparent by default
1060     override uint customLength(string id, uint defValue = 0) const {
1061         if (id in _customLength)
1062             return _customLength[id];
1063         return defValue;
1064     }
1065 
1066     /// returns colors to draw focus rectangle or null if no focus rect should be drawn for style
1067     @property override const(uint[]) focusRectColors() const {
1068         if (_focusRectColors)
1069             return _focusRectColors;
1070         return null;
1071     }
1072 
1073     /// create new named style or get existing
1074     override Style createSubstyle(string id) {
1075         if (id !is null && id in _byId)
1076             return _byId[id]; // already exists
1077         Style style = new Style(this, id);
1078         if (id !is null)
1079             _byId[id] = style;
1080         style._parentStyle = this; // as initial value, use theme as parent
1081         return style;
1082     }
1083 
1084     /// to track unknown styles refernced from code
1085     int[string] unknownStyleIds;
1086     /// find style by id, returns theme if not style with specified ID is not found
1087     @property Style get(string id) {
1088         if (id is null)
1089             return this;
1090         if (id in _byId)
1091             return _byId[id];
1092         // track unknown style ID references
1093         if (id in unknownStyleIds)
1094             unknownStyleIds[id] = unknownStyleIds[id] + 1;
1095         else {
1096             Log.e("Unknown style ID requested: ", id);
1097             unknownStyleIds[id] = 1;
1098         }
1099         return this;
1100     }
1101 
1102     /// find substyle based on widget state (e.g. focused, pressed, ...)
1103     override const(Style) forState(uint state) const {
1104         return this;
1105     }
1106 
1107     /// find substyle based on widget state (e.g. focused, pressed, ...)
1108     override Style forState(uint state) {
1109         return this;
1110     }
1111 
1112     void dumpStats() {
1113         Log.d("Theme ", _id, ": children:", _children.length, ", substates:", _substates.length, ", mapsize:", _byId.length);
1114     }
1115 }
1116 
1117 /// to access current theme
1118 private __gshared Theme _currentTheme;
1119 /// current theme accessor
1120 @property Theme currentTheme() { return _currentTheme; }
1121 /// set new current theme
1122 @property void currentTheme(Theme theme) {
1123     if (_currentTheme !is null) {
1124         destroy(_currentTheme);
1125     }
1126     _currentTheme = theme;
1127 }
1128 
1129 immutable ATTR_SCROLLBAR_BUTTON_UP = "scrollbar_button_up";
1130 immutable ATTR_SCROLLBAR_BUTTON_DOWN = "scrollbar_button_down";
1131 immutable ATTR_SCROLLBAR_BUTTON_LEFT = "scrollbar_button_left";
1132 immutable ATTR_SCROLLBAR_BUTTON_RIGHT = "scrollbar_button_right";
1133 immutable ATTR_SCROLLBAR_INDICATOR_VERTICAL = "scrollbar_indicator_vertical";
1134 immutable ATTR_SCROLLBAR_INDICATOR_HORIZONTAL = "scrollbar_indicator_horizontal";
1135 
1136 Theme createDefaultTheme() {
1137     Log.d("Creating default theme");
1138     Theme res = new Theme("default");
1139     //res.fontSize(14);
1140     version (Windows) {
1141         res.fontFace = "Verdana";
1142     }
1143     //res.fontFace = "Arial Narrow";
1144     static if (BACKEND_CONSOLE) {
1145         res.fontSize = 1;
1146         res.textColor = 0xFFFFFF;
1147         Style button = res.createSubstyle(STYLE_BUTTON).backgroundColor(0x808080).alignment(Align.Center).setMargins(0, 0, 0, 0).textColor(0x000000);
1148         //button.createState(State.Selected, State.Selected).backgroundColor(0xFFFFFF);
1149         button.createState(State.Pressed, State.Pressed).backgroundColor(0xFFFF00);
1150         button.createState(State.Focused|State.Hovered, State.Focused|State.Hovered).textColor(0x800000).backgroundColor(0xFFFFFF);
1151         button.createState(State.Focused, State.Focused).backgroundColor(0xFFFFFF).textColor(0x000080);
1152         button.createState(State.Hovered, State.Hovered).textColor(0x800000);
1153         Style buttonLabel = res.createSubstyle(STYLE_BUTTON_LABEL).layoutWidth(FILL_PARENT).alignment(Align.Left|Align.VCenter);
1154         //buttonLabel.createState(State.Hovered, State.Hovered).textColor(0x800000);
1155         //buttonLabel.createState(State.Focused, State.Focused).textColor(0x000080);
1156         res.createSubstyle(STYLE_BUTTON_TRANSPARENT).backgroundImageId("btn_background_transparent").alignment(Align.Center);
1157         res.createSubstyle(STYLE_BUTTON_IMAGE).alignment(Align.Center).textColor(0x000000);
1158         res.createSubstyle(STYLE_TEXT).setMargins(0, 0, 0, 0).setPadding(0, 0, 0, 0);
1159         res.createSubstyle(STYLE_HSPACER).layoutWidth(FILL_PARENT).minWidth(5).layoutWeight(100);
1160         res.createSubstyle(STYLE_VSPACER).layoutHeight(FILL_PARENT).minHeight(5).layoutWeight(100);
1161         res.createSubstyle(STYLE_BUTTON_NOMARGINS).alignment(Align.Center); // .setMargins(5,5,5,5)
1162         //button.createState(State.Enabled | State.Focused, State.Focused).backgroundImageId("btn_default_small_normal_disable_focused");
1163         //button.createState(State.Enabled, 0).backgroundImageId("btn_default_small_normal_disable");
1164         //button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed");
1165         //button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected");
1166         //button.createState(State.Hovered, State.Hovered).backgroundImageId("btn_default_small_normal_hover");
1167         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_UP, "scrollbar_btn_up");
1168         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_DOWN, "scrollbar_btn_down");
1169         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_LEFT, "scrollbar_btn_left");
1170         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_RIGHT, "scrollbar_btn_right");
1171         res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_VERTICAL, "scrollbar_indicator_vertical");
1172         res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_HORIZONTAL, "scrollbar_indicator_horizontal");
1173 
1174         Style scrollbar = res.createSubstyle(STYLE_SCROLLBAR);
1175         scrollbar.backgroundColor(0xC0808080);
1176         Style scrollbarButton = button.createSubstyle(STYLE_SCROLLBAR_BUTTON);
1177         Style scrollbarSlider = res.createSubstyle(STYLE_SLIDER);
1178         Style scrollbarPage = res.createSubstyle(STYLE_PAGE_SCROLL).backgroundColor(COLOR_TRANSPARENT);
1179         scrollbarPage.createState(State.Pressed, State.Pressed).backgroundColor(0xC0404080);
1180         scrollbarPage.createState(State.Hovered, State.Hovered).backgroundColor(0xF0404080);
1181 
1182         Style tabUp = res.createSubstyle(STYLE_TAB_UP);
1183         tabUp.backgroundImageId("tab_up_background");
1184         tabUp.layoutWidth(FILL_PARENT);
1185         tabUp.createState(State.Selected, State.Selected).backgroundImageId("tab_up_backgrond_selected");
1186         Style tabUpButtonText = res.createSubstyle(STYLE_TAB_UP_BUTTON_TEXT);
1187         tabUpButtonText.textColor(0x000000).alignment(Align.Center);
1188         tabUpButtonText.createState(State.Selected, State.Selected).textColor(0x000000);
1189         tabUpButtonText.createState(State.Selected|State.Focused, State.Selected|State.Focused).textColor(0x000000);
1190         tabUpButtonText.createState(State.Focused, State.Focused).textColor(0x000000);
1191         tabUpButtonText.createState(State.Hovered, State.Hovered).textColor(0xFFE0E0);
1192         Style tabUpButton = res.createSubstyle(STYLE_TAB_UP_BUTTON);
1193         tabUpButton.backgroundImageId("tab_btn_up");
1194         //tabUpButton.backgroundImageId("tab_btn_up_normal");
1195         //tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected");
1196         //tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected");
1197         //tabUpButton.createState(State.Focused, State.Focused).backgroundImageId("tab_btn_up_focused");
1198         //tabUpButton.createState(State.Hovered, State.Hovered).backgroundImageId("tab_btn_up_hover");
1199         Style tabHost = res.createSubstyle(STYLE_TAB_HOST);
1200         tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
1201         tabHost.backgroundColor(0xF0F0F0);
1202         Style tabWidget = res.createSubstyle(STYLE_TAB_WIDGET);
1203         tabWidget.setPadding(3,3,3,3).backgroundColor(0xEEEEEE);
1204         //tabWidget.backgroundImageId("frame_blue");
1205         //res.dumpStats();
1206 
1207         Style mainMenu = res.createSubstyle(STYLE_MAIN_MENU).backgroundColor(0xEFEFF2).layoutWidth(FILL_PARENT);
1208         Style mainMenuItem = res.createSubstyle(STYLE_MAIN_MENU_ITEM).setPadding(4,2,4,2).backgroundImageId("main_menu_item_background").textFlags(TEXT_FLAGS_USE_PARENT);
1209         Style menuItem = res.createSubstyle(STYLE_MENU_ITEM).setPadding(4,2,4,2); //.backgroundColor(0xE0E080)   ;
1210         menuItem.createState(State.Focused, State.Focused).backgroundColor(0x40C0C000);
1211         menuItem.createState(State.Pressed, State.Pressed).backgroundColor(0x4080C000);
1212         menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
1213         menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00);
1214         res.createSubstyle(STYLE_MENU_ICON).setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).alpha(0xA0);
1215         res.createSubstyle(STYLE_MENU_LABEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys).createState(State.Enabled,0).textColor(0x80404040);
1216         res.createSubstyle(STYLE_MAIN_MENU_LABEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TEXT_FLAGS_USE_PARENT).createState(State.Enabled,0).textColor(0x80404040);
1217         res.createSubstyle(STYLE_MENU_ACCEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).textColor(0x80404040);
1218 
1219         Style transparentButtonBackground = res.createSubstyle(STYLE_TRANSPARENT_BUTTON_BACKGROUND).backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080)   ;
1220         //transparentButtonBackground.createState(State.Focused, State.Focused).backgroundColor(0xC0C0C000);
1221         //transparentButtonBackground.createState(State.Pressed, State.Pressed).backgroundColor(0x4080C000);
1222         //transparentButtonBackground.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
1223         //transparentButtonBackground.createState(State.Hovered, State.Hovered).backgroundColor(0xD0FFFF00);
1224 
1225         Style poopupMenu = res.createSubstyle(STYLE_POPUP_MENU).backgroundImageId("popup_menu_background_normal");
1226 
1227         Style listItem = res.createSubstyle(STYLE_LIST_ITEM).backgroundImageId("list_item_background");
1228         //listItem.createState(State.Selected, State.Selected).backgroundColor(0xC04040FF).textColor(0x000000);
1229         //listItem.createState(State.Enabled, 0).textColor(0x80000000); // half transparent text for disabled item
1230 
1231         Style editLine = res.createSubstyle(STYLE_EDIT_LINE).backgroundImageId(q{
1232                 {
1233                     text: [
1234                        "╔═╗",
1235                        "║ ║",
1236                        "╚═╝"],
1237                     backgroundColor: [0x000080],
1238                     textColor: [0xFF0000],
1239                     ninepatch: [1,1,1,1]
1240                 }
1241             })
1242             .setPadding(0,0,0,0).setMargins(0,0,0,0).minWidth(20)
1243             .fontFace("Arial").fontFamily(FontFamily.SansSerif).fontSize(1);
1244         Style editBox = res.createSubstyle(STYLE_EDIT_BOX).backgroundImageId("editbox_background")
1245             .setPadding(0,0,0,0).setMargins(0,0,0,0).minWidth(30).minHeight(8).layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT)
1246             .fontFace("Courier New").fontFamily(FontFamily.MonoSpace).fontSize(1);
1247     } else {
1248         res.fontSize = 15; // TODO: choose based on DPI
1249         Style button = res.createSubstyle(STYLE_BUTTON).backgroundImageId("btn_background").alignment(Align.Center).setMargins(5,5,5,5);
1250         res.createSubstyle(STYLE_BUTTON_TRANSPARENT).backgroundImageId("btn_background_transparent").alignment(Align.Center);
1251         res.createSubstyle(STYLE_BUTTON_LABEL).layoutWidth(FILL_PARENT).alignment(Align.Left|Align.VCenter);
1252         res.createSubstyle(STYLE_BUTTON_IMAGE).alignment(Align.Center);
1253         res.createSubstyle(STYLE_TEXT).setMargins(2,2,2,2).setPadding(1,1,1,1);
1254         res.createSubstyle(STYLE_HSPACER).layoutWidth(FILL_PARENT).minWidth(5).layoutWeight(100);
1255         res.createSubstyle(STYLE_VSPACER).layoutHeight(FILL_PARENT).minHeight(5).layoutWeight(100);
1256         res.createSubstyle(STYLE_BUTTON_NOMARGINS).backgroundImageId("btn_background").alignment(Align.Center); // .setMargins(5,5,5,5)
1257         //button.createState(State.Enabled | State.Focused, State.Focused).backgroundImageId("btn_default_small_normal_disable_focused");
1258         //button.createState(State.Enabled, 0).backgroundImageId("btn_default_small_normal_disable");
1259         //button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed");
1260         //button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected");
1261         //button.createState(State.Hovered, State.Hovered).backgroundImageId("btn_default_small_normal_hover");
1262         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_UP, "scrollbar_btn_up");
1263         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_DOWN, "scrollbar_btn_down");
1264         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_LEFT, "scrollbar_btn_left");
1265         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_RIGHT, "scrollbar_btn_right");
1266         res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_VERTICAL, "scrollbar_indicator_vertical");
1267         res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_HORIZONTAL, "scrollbar_indicator_horizontal");
1268 
1269         Style scrollbar = res.createSubstyle(STYLE_SCROLLBAR);
1270         scrollbar.backgroundColor(0xC0808080);
1271         Style scrollbarButton = button.createSubstyle(STYLE_SCROLLBAR_BUTTON);
1272         Style scrollbarSlider = res.createSubstyle(STYLE_SLIDER);
1273         Style scrollbarPage = res.createSubstyle(STYLE_PAGE_SCROLL).backgroundColor(COLOR_TRANSPARENT);
1274         scrollbarPage.createState(State.Pressed, State.Pressed).backgroundColor(0xC0404080);
1275         scrollbarPage.createState(State.Hovered, State.Hovered).backgroundColor(0xF0404080);
1276 
1277         Style tabUp = res.createSubstyle(STYLE_TAB_UP);
1278         tabUp.backgroundImageId("tab_up_background");
1279         tabUp.layoutWidth(FILL_PARENT);
1280         tabUp.createState(State.Selected, State.Selected).backgroundImageId("tab_up_backgrond_selected");
1281         Style tabUpButtonText = res.createSubstyle(STYLE_TAB_UP_BUTTON_TEXT);
1282         tabUpButtonText.textColor(0x000000).fontSize(12).alignment(Align.Center);
1283         tabUpButtonText.createState(State.Selected, State.Selected).textColor(0x000000);
1284         tabUpButtonText.createState(State.Selected|State.Focused, State.Selected|State.Focused).textColor(0x000000);
1285         tabUpButtonText.createState(State.Focused, State.Focused).textColor(0x000000);
1286         tabUpButtonText.createState(State.Hovered, State.Hovered).textColor(0xFFE0E0);
1287         Style tabUpButton = res.createSubstyle(STYLE_TAB_UP_BUTTON);
1288         tabUpButton.backgroundImageId("tab_btn_up");
1289         //tabUpButton.backgroundImageId("tab_btn_up_normal");
1290         //tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected");
1291         //tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected");
1292         //tabUpButton.createState(State.Focused, State.Focused).backgroundImageId("tab_btn_up_focused");
1293         //tabUpButton.createState(State.Hovered, State.Hovered).backgroundImageId("tab_btn_up_hover");
1294         Style tabHost = res.createSubstyle(STYLE_TAB_HOST);
1295         tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
1296         tabHost.backgroundColor(0xF0F0F0);
1297         Style tabWidget = res.createSubstyle(STYLE_TAB_WIDGET);
1298         tabWidget.setPadding(3,3,3,3).backgroundColor(0xEEEEEE);
1299         //tabWidget.backgroundImageId("frame_blue");
1300         //res.dumpStats();
1301 
1302         Style mainMenu = res.createSubstyle(STYLE_MAIN_MENU).backgroundColor(0xEFEFF2).layoutWidth(FILL_PARENT);
1303         Style mainMenuItem = res.createSubstyle(STYLE_MAIN_MENU_ITEM).setPadding(4,2,4,2).backgroundImageId("main_menu_item_background").textFlags(TEXT_FLAGS_USE_PARENT);
1304         Style menuItem = res.createSubstyle(STYLE_MENU_ITEM).setPadding(4,2,4,2); //.backgroundColor(0xE0E080)   ;
1305         menuItem.createState(State.Focused, State.Focused).backgroundColor(0x40C0C000);
1306         menuItem.createState(State.Pressed, State.Pressed).backgroundColor(0x4080C000);
1307         menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
1308         menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00);
1309         res.createSubstyle(STYLE_MENU_ICON).setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).alpha(0xA0);
1310         res.createSubstyle(STYLE_MENU_LABEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys).createState(State.Enabled,0).textColor(0x80404040);
1311         res.createSubstyle(STYLE_MAIN_MENU_LABEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TEXT_FLAGS_USE_PARENT).createState(State.Enabled,0).textColor(0x80404040);
1312         res.createSubstyle(STYLE_MENU_ACCEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).textColor(0x80404040);
1313 
1314         Style transparentButtonBackground = res.createSubstyle(STYLE_TRANSPARENT_BUTTON_BACKGROUND).backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080)   ;
1315         //transparentButtonBackground.createState(State.Focused, State.Focused).backgroundColor(0xC0C0C000);
1316         //transparentButtonBackground.createState(State.Pressed, State.Pressed).backgroundColor(0x4080C000);
1317         //transparentButtonBackground.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
1318         //transparentButtonBackground.createState(State.Hovered, State.Hovered).backgroundColor(0xD0FFFF00);
1319 
1320         Style poopupMenu = res.createSubstyle(STYLE_POPUP_MENU).backgroundImageId("popup_menu_background_normal");
1321 
1322         Style listItem = res.createSubstyle(STYLE_LIST_ITEM).backgroundImageId("list_item_background");
1323         //listItem.createState(State.Selected, State.Selected).backgroundColor(0xC04040FF).textColor(0x000000);
1324         //listItem.createState(State.Enabled, 0).textColor(0x80000000); // half transparent text for disabled item
1325 
1326         Style editLine = res.createSubstyle(STYLE_EDIT_LINE).backgroundImageId("editbox_background")
1327             .setPadding(5,6,5,6).setMargins(2,2,2,2).minWidth(40)
1328             .fontFace("Arial").fontFamily(FontFamily.SansSerif).fontSize(16);
1329         Style editBox = res.createSubstyle(STYLE_EDIT_BOX).backgroundImageId("editbox_background")
1330             .setPadding(5,6,5,6).setMargins(2,2,2,2).minWidth(100).minHeight(60).layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT)
1331             .fontFace("Courier New").fontFamily(FontFamily.MonoSpace).fontSize(16);
1332     }
1333 
1334     return res;
1335 }
1336 
1337 /// decode comma delimited dimension list or single value - and put to Rect
1338 Rect decodeRect(string s) {
1339     uint[6] values;
1340     int valueCount = 0;
1341     int start = 0;
1342     for (int i = 0; i <= s.length; i++) {
1343         if (i == s.length || s[i] == ',') {
1344             if (i > start) {
1345                 string item = s[start .. i];
1346                 values[valueCount++] = decodeDimension(item);
1347                 if (valueCount >= 6)
1348                     break;
1349             }
1350             start = i + 1;
1351         }
1352     }
1353     if (valueCount == 1) // same value for all dimensions
1354         return Rect(values[0], values[0], values[0], values[0]);
1355     else if (valueCount == 2) // one value of horizontal, and one for vertical
1356         return Rect(values[0], values[1], values[0], values[1]);
1357     else if (valueCount == 4) // separate left, top, right, bottom
1358         return Rect(values[0], values[1], values[2], values[3]);
1359     Log.e("Invalid rect attribute value ", s);
1360     return Rect(0,0,0,0);
1361 }
1362 
1363 private import std.array : split;
1364 
1365 /// Decode color list attribute, e.g.: "#84A, #99FFFF" -> [0x8844aa, 0x99ffff]
1366 uint[] decodeFocusRectColors(string s) {
1367     if (s.equal("@null"))
1368         return [COLOR_UNSPECIFIED];
1369     string[] colors = split(s, ",");
1370     if (colors.length < 1)
1371         return null;
1372     uint[] res = new uint[colors.length];
1373     for (int i = 0; i < colors.length; i++) {
1374         uint cl = decodeHexColor(colors[i], COLOR_UNSPECIFIED);
1375         if (cl == COLOR_UNSPECIFIED)
1376             return null;
1377         res[i] = cl;
1378     }
1379     return res;
1380 }
1381 
1382 /// parses string like "Left|VCenter" to bit set of Align flags
1383 ubyte decodeAlignment(string s) {
1384     ubyte res = 0;
1385     int start = 0;
1386     for (int i = 0; i <= s.length; i++) {
1387         if (i == s.length || s[i] == '|') {
1388             if (i > start) {
1389                 string item = s[start .. i];
1390                 if (item.equal("Left"))
1391                     res |= Align.Left;
1392                 else if (item.equal("Right"))
1393                     res |= Align.Right;
1394                 else if (item.equal("Top"))
1395                     res |= Align.Top;
1396                 else if (item.equal("Bottom"))
1397                     res |= Align.Bottom;
1398                 else if (item.equal("HCenter"))
1399                     res |= Align.HCenter;
1400                 else if (item.equal("VCenter"))
1401                     res |= Align.VCenter;
1402                 else if (item.equal("Center"))
1403                     res |= Align.Center;
1404                 else if (item.equal("TopLeft"))
1405                     res |= Align.TopLeft;
1406                 else
1407                     Log.e("unknown Align value: ", item);
1408             }
1409             start = i + 1;
1410         }
1411     }
1412     return res;
1413 }
1414 
1415 /// parses string like "HotKeys|UnderlineHotKeysWhenAltPressed" to bit set of TextFlag flags
1416 uint decodeTextFlags(string s) {
1417     uint res = 0;
1418     int start = 0;
1419     for (int i = 0; i <= s.length; i++) {
1420         if (i == s.length || s[i] == '|') {
1421             if (i > start) {
1422                 string item = s[start .. i];
1423                 if (item.equal("HotKeys"))
1424                     res |= TextFlag.HotKeys;
1425                 else if (item.equal("UnderlineHotKeys"))
1426                     res |= TextFlag.UnderlineHotKeys;
1427                 else if (item.equal("UnderlineHotKeysWhenAltPressed"))
1428                     res |= TextFlag.UnderlineHotKeysWhenAltPressed;
1429                 else if (item.equal("Underline"))
1430                     res |= TextFlag.Underline;
1431                 else if (item.equal("Unspecified"))
1432                     res = TEXT_FLAGS_UNSPECIFIED;
1433                 else if (item.equal("Parent"))
1434                     res = TEXT_FLAGS_USE_PARENT;
1435                 else
1436                     Log.e("unknown text flag value: ", item);
1437             }
1438             start = i + 1;
1439         }
1440     }
1441     return res;
1442 }
1443 
1444 /// decode FontFamily item name to value
1445 FontFamily decodeFontFamily(string s) {
1446     if (s.equal("SansSerif"))
1447         return FontFamily.SansSerif;
1448     if (s.equal("Serif"))
1449         return FontFamily.Serif;
1450     if (s.equal("Cursive"))
1451         return FontFamily.Cursive;
1452     if (s.equal("Fantasy"))
1453         return FontFamily.Fantasy;
1454     if (s.equal("MonoSpace"))
1455         return FontFamily.MonoSpace;
1456     if (s.equal("Unspecified"))
1457         return FontFamily.Unspecified;
1458     Log.e("unknown font family ", s);
1459     return FontFamily.SansSerif;
1460 }
1461 
1462 /// decode FontWeight item name to value
1463 FontWeight decodeFontWeight(string s) {
1464     if (s.equal("bold"))
1465         return FontWeight.Bold;
1466     if (s.equal("normal"))
1467         return FontWeight.Normal;
1468     Log.e("unknown font weight ", s);
1469     return FontWeight.Normal;
1470 }
1471 
1472 /// decode layout dimension (FILL_PARENT, WRAP_CONTENT, or just size)
1473 int decodeLayoutDimension(string s) {
1474     if (s.equal("FILL_PARENT") || s.equal("fill"))
1475         return FILL_PARENT;
1476     if (s.equal("WRAP_CONTENT") || s.equal("wrap"))
1477         return WRAP_CONTENT;
1478     return decodeDimension(s);
1479 }
1480 
1481 /// load style attributes from XML element
1482 bool loadStyleAttributes(Style style, Element elem, bool allowStates) {
1483     //Log.d("Theme: loadStyleAttributes ", style.id, " ", elem.tag.attr);
1484     if ("backgroundImageId" in elem.tag.attr)
1485         style.backgroundImageId = elem.tag.attr["backgroundImageId"];
1486     if ("backgroundColor" in elem.tag.attr) {
1487         uint col = decodeHexColor(elem.tag.attr["backgroundColor"]);
1488         style.backgroundColor = col;
1489         //Log.d("    background color=", col);
1490     } else {
1491         //Log.d("    no background color attr");
1492     }
1493     if ("textColor" in elem.tag.attr)
1494         style.textColor = decodeHexColor(elem.tag.attr["textColor"]);
1495     if ("margins" in elem.tag.attr)
1496         style.margins = decodeRect(elem.tag.attr["margins"]);
1497     if ("padding" in elem.tag.attr)
1498         style.padding = decodeRect(elem.tag.attr["padding"]);
1499     if ("align" in elem.tag.attr)
1500         style.alignment = decodeAlignment(elem.tag.attr["align"]);
1501     if ("minWidth" in elem.tag.attr)
1502         style.minWidth = decodeDimension(elem.tag.attr["minWidth"]);
1503     if ("maxWidth" in elem.tag.attr)
1504         style.maxWidth = decodeDimension(elem.tag.attr["maxWidth"]);
1505     if ("minHeight" in elem.tag.attr)
1506         style.minHeight = decodeDimension(elem.tag.attr["minHeight"]);
1507     if ("maxHeight" in elem.tag.attr)
1508         style.maxHeight = decodeDimension(elem.tag.attr["maxHeight"]);
1509     if ("maxLines" in elem.tag.attr)
1510         style.maxLines = decodeDimension(elem.tag.attr["maxLines"]);
1511     if ("fontFace" in elem.tag.attr)
1512         style.fontFace = elem.tag.attr["fontFace"];
1513     if ("fontFamily" in elem.tag.attr)
1514         style.fontFamily = decodeFontFamily(elem.tag.attr["fontFamily"]);
1515     if ("fontSize" in elem.tag.attr)
1516         style.fontSize = cast(int)decodeDimension(elem.tag.attr["fontSize"]);
1517     if ("fontWeight" in elem.tag.attr)
1518         style.fontWeight = cast(ushort)decodeFontWeight(elem.tag.attr["fontWeight"]);
1519     if ("layoutWidth" in elem.tag.attr)
1520         style.layoutWidth = decodeLayoutDimension(elem.tag.attr["layoutWidth"]);
1521     if ("layoutHeight" in elem.tag.attr)
1522         style.layoutHeight = decodeLayoutDimension(elem.tag.attr["layoutHeight"]);
1523     if ("alpha" in elem.tag.attr)
1524         style.alpha = decodeDimension(elem.tag.attr["alpha"]);
1525     if ("textFlags" in elem.tag.attr)
1526         style.textFlags = decodeTextFlags(elem.tag.attr["textFlags"]);
1527     if ("focusRectColors" in elem.tag.attr)
1528         style.focusRectColors = decodeFocusRectColors(elem.tag.attr["focusRectColors"]);
1529     foreach(item; elem.elements) {
1530         if (allowStates && item.tag.name.equal("state")) {
1531             uint stateMask = 0;
1532             uint stateValue = 0;
1533             extractStateFlags(item.tag.attr, stateMask, stateValue);
1534             if (stateMask) {
1535                 Style state = style.getOrCreateState(stateMask, stateValue);
1536                 loadStyleAttributes(state, item, false);
1537             }
1538         } else if (item.tag.name.equal("drawable")) {
1539             // <drawable id="scrollbar_button_up" value="scrollbar_btn_up"/>
1540             string drawableid = attrValue(item, "id");
1541             string drawablevalue = attrValue(item, "value");
1542             if (drawableid)
1543                 style.setCustomDrawable(drawableid, drawablevalue);
1544         } else if (item.tag.name.equal("color")) {
1545             // <color id="buttons_panel_color" value="#303080"/>
1546             string colorid = attrValue(item, "id");
1547             string colorvalue = attrValue(item, "value");
1548             uint color = decodeHexColor(colorvalue, COLOR_TRANSPARENT);
1549             if (colorid)
1550                 style.setCustomColor(colorid, color);
1551         } else if (item.tag.name.equal("length")) {
1552             // <color id="buttons_panel_color" value="#303080"/>
1553             string lenid = attrValue(item, "id");
1554             string lenvalue = attrValue(item, "value");
1555             uint len = decodeDimension(lenvalue);
1556             if (lenid.length > 0 && len > 0)
1557                 style.setCustomLength(lenid, len);
1558         }
1559     }
1560     return true;
1561 }
1562 
1563 /**
1564  * load theme from XML document
1565  *
1566  * Sample:
1567  * ---
1568  * <?xml version="1.0" encoding="utf-8"?>
1569  * <theme id="theme_custom" parent="theme_default">
1570  *       <style id="BUTTON"
1571  *             backgroundImageId="btn_background"
1572  *          >
1573  *       </style>
1574  * </theme>
1575  * ---
1576  *
1577  */
1578 bool loadTheme(Theme theme, Element doc, int level = 0) {
1579     if (!doc.tag.name.equal("theme")) {
1580         Log.e("<theme> element should be main in theme file!");
1581         return false;
1582     }
1583     // <theme>
1584     string id = attrValue(doc, "id");
1585     string parent = attrValue(doc, "parent");
1586     theme.id = id;
1587     if (parent.length > 0) {
1588         // load base theme
1589         if (level < 3) // to prevent infinite recursion
1590             loadTheme(theme, parent, level + 1);
1591     }
1592     loadStyleAttributes(theme, doc, false);
1593     foreach(styleitem; doc.elements) {
1594         if (styleitem.tag.name.equal("style")) {
1595             // load <style>
1596             string styleid = attrValue(styleitem, "id");
1597             string styleparent = attrValue(styleitem, "parent");
1598             if (styleid.length) {
1599                 // create new style
1600                 Style parentStyle = null;
1601                 parentStyle = theme.get(styleparent);
1602                 Style style = parentStyle.createSubstyle(styleid);
1603                 loadStyleAttributes(style, styleitem, true);
1604             } else {
1605                 Log.e("style without ID in theme file");
1606             }
1607         }
1608     }
1609     return true;
1610 }
1611 
1612 /// load theme from file
1613 bool loadTheme(Theme theme, string resourceId, int level = 0) {
1614 
1615     string filename;
1616     try {
1617         filename = drawableCache.findResource(BACKEND_CONSOLE ? "console_" ~ resourceId : resourceId);
1618         if (!filename || !filename.endsWith(".xml"))
1619             return false;
1620         string s = cast(string)loadResourceBytes(filename);
1621         if (!s) {
1622             Log.e("Cannot read XML resource ", resourceId, " from file ", filename);
1623             return false;
1624         }
1625 
1626         // Check for well-formedness
1627         //check(s);
1628 
1629         // Make a DOM tree
1630         auto doc = new Document(s);
1631 
1632         return loadTheme(theme, doc);
1633     } catch (CheckException e) {
1634         Log.e("Invalid XML resource ", resourceId);
1635         return false;
1636     }
1637 }
1638 
1639 /// load theme from XML file (null if failed)
1640 Theme loadTheme(string resourceId) {
1641     Theme res = new Theme(resourceId);
1642     if (loadTheme(res, resourceId)) {
1643         res.id = resourceId;
1644         return res;
1645     }
1646     destroy(res);
1647     return null;
1648 }
1649 
1650 /// custom drawable attribute container for styles
1651 class DrawableAttribute {
1652 protected:
1653     string _id;
1654     string _drawableId;
1655     DrawableRef _drawable;
1656     bool _initialized;
1657 
1658 public:
1659     this(string id, string drawableId) {
1660         _id = id;
1661         _drawableId = drawableId;
1662     }
1663     ~this() {
1664         clear();
1665     }
1666     @property string id() const { return _id; }
1667     @property string drawableId() const { return _drawableId; }
1668     @property void drawableId(string newDrawable) { _drawableId = newDrawable; clear(); }
1669     @property ref DrawableRef drawable() const {
1670         if (!_drawable.isNull)
1671             return (cast(DrawableAttribute)this)._drawable;
1672         (cast(DrawableAttribute)this)._drawable = drawableCache.get(_drawableId);
1673         (cast(DrawableAttribute)this)._initialized = true;
1674         return (cast(DrawableAttribute)this)._drawable;
1675     }
1676     void clear() {
1677         _drawable.clear();
1678         _initialized = false;
1679     }
1680     void onThemeChanged() {
1681         if (!_drawableId) {
1682             _drawable.clear();
1683             _initialized = false;
1684         }
1685     }
1686 }
1687 
1688 /// returns custom drawable replacement id for specified id from current theme, or returns passed value if not found or no current theme
1689 string overrideCustomDrawableId(string id) {
1690     string res = currentTheme ? currentTheme.customDrawableId(id) : id;
1691     return !res ? id : res;
1692 }
1693 
1694 shared static ~this() {
1695     currentTheme = null;
1696 }