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 }
299 
300 /// style properties
301 class Style {
302 protected:
303     string _id;
304     Theme _theme;
305     Style _parentStyle;
306     string _parentId;
307     uint _stateMask;
308     uint _stateValue;
309     ubyte _align = Align.TopLeft;
310     ubyte _fontStyle = FONT_STYLE_UNSPECIFIED;
311     FontFamily _fontFamily = FontFamily.Unspecified;
312     ushort _fontWeight = FONT_WEIGHT_UNSPECIFIED;
313     int _fontSize = FONT_SIZE_UNSPECIFIED;
314     uint _backgroundColor = COLOR_UNSPECIFIED;
315     uint _textColor = COLOR_UNSPECIFIED;
316     uint _textFlags = 0;
317     uint _alpha;
318     string _fontFace;
319     string _backgroundImageId;
320     Rect _padding;
321     Rect _margins;
322     int _minWidth = SIZE_UNSPECIFIED;
323     int _maxWidth = SIZE_UNSPECIFIED;
324     int _minHeight = SIZE_UNSPECIFIED;
325     int _maxHeight = SIZE_UNSPECIFIED;
326     int _layoutWidth = SIZE_UNSPECIFIED;
327     int _layoutHeight = SIZE_UNSPECIFIED;
328     int _layoutWeight = WEIGHT_UNSPECIFIED;
329     int _maxLines = SIZE_UNSPECIFIED;
330 
331     uint[] _focusRectColors;
332 
333     Style[] _substates;
334     Style[] _children;
335 
336     DrawableAttributeList _customDrawables;
337     uint[string] _customColors;
338     uint[string] _customLength;
339 
340     FontRef _font;
341     DrawableRef _backgroundDrawable;
342 
343 public:
344     void onThemeChanged() {
345         _backgroundDrawable.clear();
346         foreach(s; _substates)
347             s.onThemeChanged();
348         foreach(s; _children)
349             s.onThemeChanged();
350         _customDrawables.clear();
351     }
352 
353     @property const(Theme) theme() const {
354         if (_theme !is null)
355             return _theme;
356         return currentTheme;
357     }
358 
359     @property Theme theme() {
360         if (_theme !is null)
361             return _theme;
362         return currentTheme;
363     }
364 
365     @property string id() const { return _id; }
366     @property Style id(string id) {
367         this._id = id;
368         return this;
369     }
370 
371     /// access to parent style for this style
372     @property const(Style) parentStyle() const {
373         if (_parentStyle !is null)
374             return _parentStyle;
375         if (_parentId !is null && currentTheme !is null)
376             return currentTheme.get(_parentId);
377         return currentTheme;
378     }
379 
380     /// access to parent style for this style
381     @property Style parentStyle() {
382         if (_parentStyle !is null)
383             return _parentStyle;
384         if (_parentId !is null && currentTheme !is null)
385             return currentTheme.get(_parentId);
386         return currentTheme;
387     }
388 
389     @property string parentStyleId() {
390         return _parentId;
391     }
392 
393     @property Style parentStyleId(string id) {
394         _parentId = id;
395         if (_parentStyle)
396             if (currentTheme) {
397                 _parentStyle = currentTheme.get(_parentId);
398             }
399         return this;
400     }
401 
402     @property ref DrawableRef backgroundDrawable() const {
403         if (!(cast(Style)this)._backgroundDrawable.isNull)
404             return (cast(Style)this)._backgroundDrawable;
405         string image = backgroundImageId;
406         if (image !is null) {
407             (cast(Style)this)._backgroundDrawable = drawableCache.get(image);
408         } else {
409             uint color = backgroundColor;
410             (cast(Style)this)._backgroundDrawable = isFullyTransparentColor(color) ? new EmptyDrawable() : new SolidFillDrawable(color);
411         }
412         return (cast(Style)this)._backgroundDrawable;
413     }
414 
415     /// get custom drawable attribute
416     ref DrawableRef customDrawable(string id) const {
417         if (_customDrawables.hasKey(id))
418             return _customDrawables.drawable(id);
419         return parentStyle ? parentStyle.customDrawable(id) : currentTheme.customDrawable(id);
420     }
421 
422     /// get custom drawable attribute
423     string customDrawableId(string id) const {
424         if (_customDrawables.hasKey(id))
425             return _customDrawables.drawableId(id);
426         return parentStyle ? parentStyle.customDrawableId(id) : currentTheme.customDrawableId(id);
427     }
428 
429     /// sets custom drawable attribute for style
430     Style setCustomDrawable(string id, string resourceId) {
431         _customDrawables.set(id, resourceId);
432         return this;
433     }
434 
435     /// get custom color attribute
436     uint customColor(string id, uint defColor = COLOR_TRANSPARENT) const {
437         if (id in _customColors)
438             return _customColors[id];
439         return parentStyle ? parentStyle.customColor(id, defColor) : currentTheme.customColor(id, defColor);
440     }
441 
442     /// sets custom color attribute for style
443     Style setCustomColor(string id, uint color) {
444         _customColors[id] = color;
445         return this;
446     }
447 
448     /// get custom length attribute
449     uint customLength(string id, uint defLength = 0) const {
450         if (id in _customLength)
451             return _customLength[id];
452         return parentStyle ? parentStyle.customLength(id, defLength) : currentTheme.customLength(id, defLength);
453     }
454 
455     /// sets custom length attribute for style
456     Style setCustomLength(string id, uint value) {
457         _customLength[id] = value;
458         return this;
459     }
460 
461 
462     //===================================================
463     // font properties
464 
465     @property ref FontRef font() const {
466         if (!(cast(Style)this)._font.isNull)
467             return (cast(Style)this)._font;
468         string face = fontFace;
469         int size = fontSize;
470         ushort weight = fontWeight;
471         bool italic = fontItalic;
472         FontFamily family = fontFamily;
473         (cast(Style)this)._font = FontManager.instance.getFont(size, weight, italic, family, face);
474         return (cast(Style)this)._font;
475     }
476 
477     /// font size
478     @property FontFamily fontFamily() const {
479         if (_fontFamily != FontFamily.Unspecified)
480             return _fontFamily;
481         else
482             return parentStyle.fontFamily;
483     }
484 
485     /// font size
486     @property string fontFace() const {
487         if (_fontFace !is null)
488             return _fontFace;
489         else
490             return parentStyle.fontFace;
491     }
492 
493     /// font style - italic
494     @property bool fontItalic() const {
495         if (_fontStyle != FONT_STYLE_UNSPECIFIED)
496             return _fontStyle == FONT_STYLE_ITALIC;
497         else
498             return parentStyle.fontItalic;
499     }
500 
501     /// font weight
502     @property ushort fontWeight() const {
503         if (_fontWeight != FONT_WEIGHT_UNSPECIFIED)
504             return _fontWeight;
505         else
506             return parentStyle.fontWeight;
507     }
508 
509     /// font size
510     @property int fontSize() const {
511         if (_fontSize != FONT_SIZE_UNSPECIFIED) {
512             if (_fontSize & SIZE_IN_PERCENTS_FLAG)
513                 return parentStyle.fontSize * (_fontSize ^ SIZE_IN_PERCENTS_FLAG) / 10000;
514             return toPixels(_fontSize);
515         } else
516             return parentStyle.fontSize;
517     }
518 
519     //===================================================
520     // layout parameters: margins / padding
521 
522     /// padding
523     @property const(Rect) padding() const {
524         if (_stateMask || _padding.left == SIZE_UNSPECIFIED)
525             return toPixels(parentStyle._padding);
526         return toPixels(_padding);
527     }
528 
529     /// margins
530     @property const(Rect) margins() const {
531         if (_stateMask || _margins.left == SIZE_UNSPECIFIED)
532             return toPixels(parentStyle._margins);
533         return toPixels(_margins);
534     }
535 
536     /// alpha (0=opaque .. 255=transparent)
537     @property uint alpha() const {
538         if (_alpha != COLOR_UNSPECIFIED)
539             return _alpha;
540         else
541             return parentStyle.alpha;
542     }
543     
544     /// text color
545     @property uint textColor() const {
546         if (_textColor != COLOR_UNSPECIFIED)
547             return _textColor;
548         else
549             return parentStyle.textColor;
550     }
551 
552     /// text color
553     @property int maxLines() const {
554         if (_maxLines != SIZE_UNSPECIFIED)
555             return _maxLines;
556         else
557             return parentStyle.maxLines;
558     }
559 
560     /// text flags
561     @property uint textFlags() const {
562         if (_textFlags != TEXT_FLAGS_UNSPECIFIED)
563             return _textFlags;
564         else
565             return parentStyle.textFlags;
566     }
567     
568     //===================================================
569     // background
570 
571     /// background color
572     @property uint backgroundColor() const {
573         if (_backgroundColor != COLOR_UNSPECIFIED)
574             return _backgroundColor;
575         else
576             return parentStyle.backgroundColor;
577     }
578 
579     /// font size
580     @property string backgroundImageId() const {
581         if (_backgroundImageId == COLOR_DRAWABLE)
582             return null;
583         else if (_backgroundImageId !is null)
584             return _backgroundImageId;
585         else
586             return parentStyle.backgroundImageId;
587     }
588 
589     //===================================================
590     // size restrictions
591 
592     /// minimal width constraint, 0 if limit is not set
593     @property uint minWidth() const {
594         if (_minWidth != SIZE_UNSPECIFIED)
595             return toPixels(_minWidth);
596         else
597             return parentStyle.minWidth;
598     }
599     /// max width constraint, returns SIZE_UNSPECIFIED if limit is not set
600     @property uint maxWidth() const {
601         if (_maxWidth != SIZE_UNSPECIFIED)
602             return toPixels(_maxWidth);
603         else
604             return parentStyle.maxWidth;
605     }
606     /// minimal height constraint, 0 if limit is not set
607     @property uint minHeight() const {
608         if (_minHeight != SIZE_UNSPECIFIED)
609             return toPixels(_minHeight);
610         else
611             return parentStyle.minHeight;
612     }
613     /// max height constraint, SIZE_UNSPECIFIED if limit is not set
614     @property uint maxHeight() const {
615         if (_maxHeight != SIZE_UNSPECIFIED)
616             return toPixels(_maxHeight);
617         else
618             return parentStyle.maxHeight;
619     }
620     /// set min width constraint
621     @property Style minWidth(int value) {
622         _minWidth = value;
623         return this;
624     }
625     /// set max width constraint
626     @property Style maxWidth(int value) {
627         _maxWidth = value;
628         return this;
629     }
630     /// set min height constraint
631     @property Style minHeight(int value) {
632         _minHeight = value;
633         return this;
634     }
635     /// set max height constraint
636     @property Style maxHeight(int value) {
637         _maxHeight = value;
638         return this;
639     }
640 
641 
642     /// layout width parameter
643     @property uint layoutWidth() const {
644         if (_layoutWidth != SIZE_UNSPECIFIED)
645             return _layoutWidth;
646         else
647             return parentStyle.layoutWidth;
648     }
649 
650     /// layout height parameter
651     @property uint layoutHeight() const {
652         if (_layoutHeight != SIZE_UNSPECIFIED)
653             return _layoutHeight;
654         else
655             return parentStyle.layoutHeight;
656     }
657 
658     /// layout weight parameter
659     @property uint layoutWeight() const {
660         if (_layoutWeight != WEIGHT_UNSPECIFIED)
661             return _layoutWeight;
662         else
663             return parentStyle.layoutWeight;
664     }
665 
666     /// set layout height
667     @property Style layoutHeight(int value) {
668         _layoutHeight = value;
669         return this;
670     }
671     /// set layout width
672     @property Style layoutWidth(int value) {
673         _layoutWidth = value;
674         return this;
675     }
676     /// set layout weight
677     @property Style layoutWeight(int value) {
678         _layoutWeight = value;
679         return this;
680     }
681 
682     //===================================================
683     // alignment
684 
685     /// get full alignment (both vertical and horizontal)
686     @property ubyte alignment() const { 
687         if (_align != Align.Unspecified)
688             return _align; 
689         else
690             return parentStyle.alignment;
691     }
692     /// vertical alignment: Top / VCenter / Bottom
693     @property ubyte valign() const { return alignment & Align.VCenter; }
694     /// horizontal alignment: Left / HCenter / Right
695     @property ubyte halign() const { return alignment & Align.HCenter; }
696 
697     /// set alignment
698     @property Style alignment(ubyte value) {
699         _align = value;
700         return this;
701     }
702 
703     @property Style fontFace(string face) {
704         _fontFace = face;
705         _font.clear();
706         return this;
707     }
708 
709     @property Style fontFamily(FontFamily family) {
710         _fontFamily = family;
711         _font.clear();
712         return this;
713     }
714 
715     @property Style fontStyle(ubyte style) {
716         _fontStyle = style;
717         _font.clear();
718         return this;
719     }
720 
721     @property Style fontWeight(ushort weight) {
722         _fontWeight = weight;
723         _font.clear();
724         return this;
725     }
726 
727     @property Style fontSize(int size) {
728         _fontSize = size;
729         _font.clear();
730         return this;
731     }
732 
733     @property Style textColor(uint color) {
734         _textColor = color;
735         return this;
736     }
737 
738     @property Style maxLines(int lines) {
739         _maxLines = lines;
740         return this;
741     }
742 
743     @property Style alpha(uint alpha) {
744         _alpha = alpha;
745         return this;
746     }
747     
748     @property Style textFlags(uint flags) {
749         _textFlags = flags;
750         return this;
751     }
752     
753     @property Style backgroundColor(uint color) {
754         _backgroundColor = color;
755         _backgroundImageId = COLOR_DRAWABLE;
756         _backgroundDrawable.clear();
757         return this;
758     }
759 
760     @property Style backgroundImageId(string image) {
761         _backgroundImageId = image;
762         _backgroundDrawable.clear();
763         return this;
764     }
765 
766     @property Style margins(Rect rc) {
767         _margins = rc;
768         return this;
769     }
770 
771     Style setMargins(int left, int top, int right, int bottom) {
772         _margins.left = left;
773         _margins.top = top;
774         _margins.right = right;
775         _margins.bottom = bottom;
776         return this;
777     }
778     
779     @property Style padding(Rect rc) {
780         _padding = rc;
781         return this;
782     }
783 
784     /// returns colors to draw focus rectangle (one for solid, two for vertical gradient) or null if no focus rect should be drawn for style
785     @property const(uint[]) focusRectColors() const {
786         if (_focusRectColors) {
787             if (_focusRectColors.length == 1 && _focusRectColors[0] == COLOR_UNSPECIFIED)
788                 return null;
789             return cast(const)_focusRectColors;
790         }
791         return parentStyle.focusRectColors;
792     }
793 
794     /// sets colors to draw focus rectangle or null if no focus rect should be drawn for style
795     @property Style focusRectColors(uint[] colors) {
796         _focusRectColors = colors;
797         return this;
798     }
799 
800     Style setPadding(int left, int top, int right, int bottom) {
801         _padding.left = left;
802         _padding.top = top;
803         _padding.right = right;
804         _padding.bottom = bottom;
805         return this;
806     }
807     
808     debug private static __gshared int _instanceCount;
809     debug @property static int instanceCount() { return _instanceCount; }
810 
811     this(Theme theme, string id) {
812         _theme = theme;
813         _parentStyle = theme;
814         _id = id;
815         debug _instanceCount++;
816         //Log.d("Created style ", _id, ", count=", ++_instanceCount);
817     }
818 
819 
820     ~this() {
821         foreach(ref Style item; _substates) {
822             //Log.d("Destroying substate");
823             destroy(item);
824             item = null;
825         }
826         _substates.destroy();
827         foreach(ref Style item; _children) {
828             destroy(item);
829             item = null;
830         }
831         _children.destroy();
832         _backgroundDrawable.clear();
833         _font.clear();
834         destroy(_customDrawables);
835         debug _instanceCount--;
836         //Log.d("Destroyed style ", _id, ", parentId=", _parentId, ", state=", _stateMask, ", count=", --_instanceCount);
837     }
838 
839     /// create named substyle of this style
840     Style createSubstyle(string id) {
841         Style child = (_theme !is null ? _theme : currentTheme).createSubstyle(id);
842         child._parentStyle = this;
843         _children ~= child;
844         return child;
845     }
846 
847     /// create state substyle for this style
848     Style createState(uint stateMask = 0, uint stateValue = 0) {
849         assert(stateMask != 0);
850         debug(styles) Log.d("Creating substate ", stateMask);
851         Style child = (_theme !is null ? _theme : currentTheme).createSubstyle(null);
852         child._parentStyle = this;
853         child._stateMask = stateMask;
854         child._stateValue = stateValue;
855         child._backgroundColor = COLOR_UNSPECIFIED;
856         child._textColor = COLOR_UNSPECIFIED;
857         child._textFlags = TEXT_FLAGS_UNSPECIFIED;
858         _substates ~= child;
859         return child;
860     }
861 
862     Style clone() {
863         Style res = new Style(_theme, null);
864         res._stateMask = _stateMask;
865         res._stateValue = _stateValue;
866         res._align = _align;
867         res._fontStyle = _fontStyle;
868         res._fontFamily = _fontFamily;
869         res._fontWeight = _fontWeight;
870         res._fontSize = _fontSize;
871         res._backgroundColor = _backgroundColor;
872         res._textColor = _textColor;
873         res._textFlags = _textFlags;
874         res._alpha = _alpha;
875         res._fontFace = _fontFace;
876         res._backgroundImageId = _backgroundImageId;
877         res._padding = _padding;
878         res._margins = _margins;
879         res._minWidth = _minWidth;
880         res._maxWidth = _maxWidth;
881         res._minHeight = _minHeight;
882         res._maxHeight = _maxHeight;
883         res._layoutWidth = _layoutWidth;
884         res._layoutHeight = _layoutHeight;
885         res._layoutWeight = _layoutWeight;
886         res._maxLines = _maxLines;
887 
888         res._focusRectColors = _focusRectColors.dup;
889 
890         res._customDrawables.copyFrom(_customDrawables);
891         res._customColors = _customColors.dup;
892         res._customLength = _customLength.dup;
893         return res;
894     }
895 
896     /// find exact existing state style or create new if no matched styles found
897     Style getOrCreateState(uint stateMask = 0, uint stateValue = 0) {
898         if (stateValue == State.Normal)
899             return this;
900         foreach(item; _substates) {
901             if ((item._stateMask == stateMask) && (item._stateValue == stateValue))
902                 return item;
903         }
904         return createState(stateMask, stateValue);
905     }
906 
907     /// find substyle based on widget state (e.g. focused, pressed, ...)
908     Style forState(uint state) {
909         if (state == State.Normal)
910             return this;
911         //Log.d("forState ", state, " styleId=", _id, " substates=", _substates.length);
912         if (parentStyle !is null && _substates.length == 0 && parentStyle._substates.length > 0) //id is null && 
913             return parentStyle.forState(state);
914         foreach(item; _substates) {
915             if ((item._stateMask & state) == item._stateValue)
916                 return item;
917         }
918         return this; // fallback to current style
919     }
920     
921     /// find substyle based on widget state (e.g. focused, pressed, ...)
922     const(Style) forState(uint state) const {
923         if (state == State.Normal)
924             return this;
925         //Log.d("forState ", state, " styleId=", _id, " substates=", _substates.length);
926         if (parentStyle !is null && _substates.length == 0 && parentStyle._substates.length > 0) //id is null && 
927             return parentStyle.forState(state);
928         foreach(item; _substates) {
929             if ((item._stateMask & state) == item._stateValue)
930                 return item;
931         }
932         return this; // fallback to current style
933     }
934     
935 }
936 
937 /// Theme - root for style hierarhy.
938 class Theme : Style {
939     protected Style[string] _byId;
940 
941     this(string id) {
942         super(this, id);
943         _parentStyle = null;
944         _backgroundColor = COLOR_TRANSPARENT; // transparent
945         _textColor = 0x000000; // black
946         _maxLines = 1;
947         _align = Align.TopLeft;
948         _fontSize = 9 | SIZE_IN_POINTS_FLAG; // TODO: from settings or screen properties / DPI
949         _fontStyle = FONT_STYLE_NORMAL;
950         _fontWeight = 400;
951         _fontFace = "Arial"; // TODO: from settings
952         //_fontFace = "Verdana"; // TODO: from settings
953         _fontFamily = FontFamily.SansSerif;
954         _minHeight = 0;
955         _minWidth = 0;
956         _layoutWidth = WRAP_CONTENT;
957         _layoutHeight = WRAP_CONTENT;
958         _layoutWeight = 1;
959     }
960     
961     ~this() {
962         //Log.d("Theme destructor");
963         if (unknownStyleIds.length > 0) {
964             Log.e("Unknown style statistics: ", unknownStyleIds);
965         }
966         foreach(ref Style item; _byId) {
967             destroy(item);
968             item = null;
969         }
970         _byId.destroy();
971     }
972 
973     /// create wrapper style which will have currentTheme.get(id) as parent instead of fixed parent - to modify some base style properties in widget
974     Style modifyStyle(string id) {
975         Style style = new Style(null, null);
976         style._parentId = id;
977         style._align = Align.Unspecified; // inherit
978         style._padding.left = SIZE_UNSPECIFIED; // inherit
979         style._margins.left = SIZE_UNSPECIFIED; // inherit
980         style._textColor = COLOR_UNSPECIFIED; // inherit
981         style._textFlags = TEXT_FLAGS_UNSPECIFIED; // inherit
982         Style parent = get(id);
983         if (parent) {
984             foreach(item; parent._substates) {
985                 Style substate = item.clone();
986                 substate._parentStyle = style;
987                 style._substates ~= substate;
988             }
989         }
990         return style;
991     }
992 
993     // ================================================
994     // override to avoid infinite recursion
995     /// font size
996     @property override string backgroundImageId() const {
997         return _backgroundImageId;
998     }
999     /// minimal width constraint, 0 if limit is not set
1000     @property override uint minWidth() const {
1001         return _minWidth;
1002     }
1003     /// max width constraint, returns SIZE_UNSPECIFIED if limit is not set
1004     @property override uint maxWidth() const {
1005         return _maxWidth;
1006     }
1007     /// minimal height constraint, 0 if limit is not set
1008     @property override uint minHeight() const {
1009         return _minHeight;
1010     }
1011     /// max height constraint, SIZE_UNSPECIFIED if limit is not set
1012     @property override uint maxHeight() const {
1013         return _maxHeight;
1014     }
1015 
1016     private DrawableRef _emptyDrawable;
1017     override ref DrawableRef customDrawable(string id) const {
1018         if (_customDrawables.hasKey(id))
1019             return _customDrawables.drawable(id);
1020         return (cast(Theme)this)._emptyDrawable;
1021     }
1022 
1023     override string customDrawableId(string id) const {
1024         if (_customDrawables.hasKey(id))
1025             return _customDrawables.drawableId(id);
1026         return null;
1027     }
1028 
1029     /// get custom color attribute - transparent by default
1030     override uint customColor(string id, uint defColor = COLOR_TRANSPARENT) const {
1031         if (id in _customColors)
1032             return _customColors[id];
1033         return defColor;
1034     }
1035 
1036     /// get custom color attribute - transparent by default
1037     override uint customLength(string id, uint defValue = 0) const {
1038         if (id in _customLength)
1039             return _customLength[id];
1040         return defValue;
1041     }
1042 
1043     /// returns colors to draw focus rectangle or null if no focus rect should be drawn for style
1044     @property override const(uint[]) focusRectColors() const {
1045         if (_focusRectColors)
1046             return _focusRectColors;
1047         return null;
1048     }
1049 
1050     /// create new named style or get existing
1051     override Style createSubstyle(string id) {
1052         if (id !is null && id in _byId)
1053             return _byId[id]; // already exists
1054         Style style = new Style(this, id);
1055         if (id !is null)
1056             _byId[id] = style;
1057         style._parentStyle = this; // as initial value, use theme as parent
1058         return style;
1059     }
1060 
1061     /// to track unknown styles refernced from code
1062     int[string] unknownStyleIds;
1063     /// find style by id, returns theme if not style with specified ID is not found
1064     @property Style get(string id) {
1065         if (id is null)
1066             return this;
1067         if (id in _byId)
1068             return _byId[id];
1069         // track unknown style ID references
1070         if (id in unknownStyleIds)
1071             unknownStyleIds[id] = unknownStyleIds[id] + 1;
1072         else {
1073             Log.e("Unknown style ID requested: ", id);
1074             unknownStyleIds[id] = 1;
1075         }
1076         return this;
1077     }
1078     
1079     /// find substyle based on widget state (e.g. focused, pressed, ...)
1080     override const(Style) forState(uint state) const {
1081         return this;
1082     }
1083 
1084     /// find substyle based on widget state (e.g. focused, pressed, ...)
1085     override Style forState(uint state) {
1086         return this;
1087     }
1088 
1089     void dumpStats() {
1090         Log.d("Theme ", _id, ": children:", _children.length, ", substates:", _substates.length, ", mapsize:", _byId.length);
1091     }
1092 }
1093 
1094 /// to access current theme
1095 private __gshared Theme _currentTheme;
1096 /// current theme accessor
1097 @property Theme currentTheme() { return _currentTheme; }
1098 /// set new current theme
1099 @property void currentTheme(Theme theme) { 
1100     if (_currentTheme !is null) {
1101         destroy(_currentTheme);
1102     }
1103     _currentTheme = theme; 
1104 }
1105 
1106 immutable ATTR_SCROLLBAR_BUTTON_UP = "scrollbar_button_up";
1107 immutable ATTR_SCROLLBAR_BUTTON_DOWN = "scrollbar_button_down";
1108 immutable ATTR_SCROLLBAR_BUTTON_LEFT = "scrollbar_button_left";
1109 immutable ATTR_SCROLLBAR_BUTTON_RIGHT = "scrollbar_button_right";
1110 immutable ATTR_SCROLLBAR_INDICATOR_VERTICAL = "scrollbar_indicator_vertical";
1111 immutable ATTR_SCROLLBAR_INDICATOR_HORIZONTAL = "scrollbar_indicator_horizontal";
1112 
1113 Theme createDefaultTheme() {
1114     Log.d("Creating default theme");
1115     Theme res = new Theme("default");
1116     //res.fontSize(14);
1117     version (Windows) {
1118         res.fontFace = "Verdana";
1119     }
1120     //res.fontFace = "Arial Narrow";
1121     static if (BACKEND_CONSOLE) {
1122         res.fontSize = 1;
1123         res.textColor = 0xFFFFFF;
1124         Style button = res.createSubstyle(STYLE_BUTTON).backgroundColor(0x808080).alignment(Align.Center).setMargins(0, 0, 0, 0).textColor(0x000000);
1125         //button.createState(State.Selected, State.Selected).backgroundColor(0xFFFFFF);
1126         button.createState(State.Pressed, State.Pressed).backgroundColor(0xFFFF00);
1127         button.createState(State.Focused|State.Hovered, State.Focused|State.Hovered).textColor(0x800000).backgroundColor(0xFFFFFF);
1128         button.createState(State.Focused, State.Focused).backgroundColor(0xFFFFFF).textColor(0x000080);
1129         button.createState(State.Hovered, State.Hovered).textColor(0x800000);
1130         Style buttonLabel = res.createSubstyle(STYLE_BUTTON_LABEL).layoutWidth(FILL_PARENT).alignment(Align.Left|Align.VCenter);
1131         //buttonLabel.createState(State.Hovered, State.Hovered).textColor(0x800000);
1132         //buttonLabel.createState(State.Focused, State.Focused).textColor(0x000080);
1133         res.createSubstyle(STYLE_BUTTON_TRANSPARENT).backgroundImageId("btn_background_transparent").alignment(Align.Center);
1134         res.createSubstyle(STYLE_BUTTON_IMAGE).alignment(Align.Center).textColor(0x000000);
1135         res.createSubstyle(STYLE_TEXT).setMargins(0, 0, 0, 0).setPadding(0, 0, 0, 0);
1136         res.createSubstyle(STYLE_HSPACER).layoutWidth(FILL_PARENT).minWidth(5).layoutWeight(100);
1137         res.createSubstyle(STYLE_VSPACER).layoutHeight(FILL_PARENT).minHeight(5).layoutWeight(100);
1138         res.createSubstyle(STYLE_BUTTON_NOMARGINS).alignment(Align.Center); // .setMargins(5,5,5,5)
1139         //button.createState(State.Enabled | State.Focused, State.Focused).backgroundImageId("btn_default_small_normal_disable_focused");
1140         //button.createState(State.Enabled, 0).backgroundImageId("btn_default_small_normal_disable");
1141         //button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed");
1142         //button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected");
1143         //button.createState(State.Hovered, State.Hovered).backgroundImageId("btn_default_small_normal_hover");
1144         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_UP, "scrollbar_btn_up");
1145         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_DOWN, "scrollbar_btn_down");
1146         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_LEFT, "scrollbar_btn_left");
1147         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_RIGHT, "scrollbar_btn_right");
1148         res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_VERTICAL, "scrollbar_indicator_vertical");
1149         res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_HORIZONTAL, "scrollbar_indicator_horizontal");
1150 
1151         Style scrollbar = res.createSubstyle(STYLE_SCROLLBAR);
1152         scrollbar.backgroundColor(0xC0808080);
1153         Style scrollbarButton = button.createSubstyle(STYLE_SCROLLBAR_BUTTON);
1154         Style scrollbarSlider = res.createSubstyle(STYLE_SLIDER);
1155         Style scrollbarPage = res.createSubstyle(STYLE_PAGE_SCROLL).backgroundColor(COLOR_TRANSPARENT);
1156         scrollbarPage.createState(State.Pressed, State.Pressed).backgroundColor(0xC0404080);
1157         scrollbarPage.createState(State.Hovered, State.Hovered).backgroundColor(0xF0404080);
1158 
1159         Style tabUp = res.createSubstyle(STYLE_TAB_UP);
1160         tabUp.backgroundImageId("tab_up_background");
1161         tabUp.layoutWidth(FILL_PARENT);
1162         tabUp.createState(State.Selected, State.Selected).backgroundImageId("tab_up_backgrond_selected");
1163         Style tabUpButtonText = res.createSubstyle(STYLE_TAB_UP_BUTTON_TEXT);
1164         tabUpButtonText.textColor(0x000000).alignment(Align.Center);
1165         tabUpButtonText.createState(State.Selected, State.Selected).textColor(0x000000);
1166         tabUpButtonText.createState(State.Selected|State.Focused, State.Selected|State.Focused).textColor(0x000000);
1167         tabUpButtonText.createState(State.Focused, State.Focused).textColor(0x000000);
1168         tabUpButtonText.createState(State.Hovered, State.Hovered).textColor(0xFFE0E0);
1169         Style tabUpButton = res.createSubstyle(STYLE_TAB_UP_BUTTON);
1170         tabUpButton.backgroundImageId("tab_btn_up");
1171         //tabUpButton.backgroundImageId("tab_btn_up_normal");
1172         //tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected");
1173         //tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected");
1174         //tabUpButton.createState(State.Focused, State.Focused).backgroundImageId("tab_btn_up_focused");
1175         //tabUpButton.createState(State.Hovered, State.Hovered).backgroundImageId("tab_btn_up_hover");
1176         Style tabHost = res.createSubstyle(STYLE_TAB_HOST);
1177         tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
1178         tabHost.backgroundColor(0xF0F0F0);
1179         Style tabWidget = res.createSubstyle(STYLE_TAB_WIDGET);
1180         tabWidget.setPadding(3,3,3,3).backgroundColor(0xEEEEEE);
1181         //tabWidget.backgroundImageId("frame_blue");
1182         //res.dumpStats();
1183 
1184         Style mainMenu = res.createSubstyle(STYLE_MAIN_MENU).backgroundColor(0xEFEFF2).layoutWidth(FILL_PARENT);
1185         Style mainMenuItem = res.createSubstyle(STYLE_MAIN_MENU_ITEM).setPadding(4,2,4,2).backgroundImageId("main_menu_item_background").textFlags(TEXT_FLAGS_USE_PARENT);
1186         Style menuItem = res.createSubstyle(STYLE_MENU_ITEM).setPadding(4,2,4,2); //.backgroundColor(0xE0E080)   ;
1187         menuItem.createState(State.Focused, State.Focused).backgroundColor(0x40C0C000);
1188         menuItem.createState(State.Pressed, State.Pressed).backgroundColor(0x4080C000);
1189         menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
1190         menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00);
1191         res.createSubstyle(STYLE_MENU_ICON).setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).alpha(0xA0);
1192         res.createSubstyle(STYLE_MENU_LABEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys).createState(State.Enabled,0).textColor(0x80404040);
1193         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);
1194         res.createSubstyle(STYLE_MENU_ACCEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).textColor(0x80404040);
1195 
1196         Style transparentButtonBackground = res.createSubstyle(STYLE_TRANSPARENT_BUTTON_BACKGROUND).backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080)   ;
1197         //transparentButtonBackground.createState(State.Focused, State.Focused).backgroundColor(0xC0C0C000);
1198         //transparentButtonBackground.createState(State.Pressed, State.Pressed).backgroundColor(0x4080C000);
1199         //transparentButtonBackground.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
1200         //transparentButtonBackground.createState(State.Hovered, State.Hovered).backgroundColor(0xD0FFFF00);
1201 
1202         Style poopupMenu = res.createSubstyle(STYLE_POPUP_MENU).backgroundImageId("popup_menu_background_normal");
1203 
1204         Style listItem = res.createSubstyle(STYLE_LIST_ITEM).backgroundImageId("list_item_background");
1205         //listItem.createState(State.Selected, State.Selected).backgroundColor(0xC04040FF).textColor(0x000000);
1206         //listItem.createState(State.Enabled, 0).textColor(0x80000000); // half transparent text for disabled item
1207 
1208         Style editLine = res.createSubstyle(STYLE_EDIT_LINE).backgroundImageId(q{
1209                 {
1210                     text: [
1211                        "╔═╗",
1212                        "║ ║",
1213                        "╚═╝"],
1214                     backgroundColor: [0x000080],
1215                     textColor: [0xFF0000],
1216                     ninepatch: [1,1,1,1]
1217                 }
1218             })
1219             .setPadding(0,0,0,0).setMargins(0,0,0,0).minWidth(20)
1220             .fontFace("Arial").fontFamily(FontFamily.SansSerif).fontSize(1);
1221         Style editBox = res.createSubstyle(STYLE_EDIT_BOX).backgroundImageId("editbox_background")
1222             .setPadding(0,0,0,0).setMargins(0,0,0,0).minWidth(30).minHeight(8).layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT)
1223             .fontFace("Courier New").fontFamily(FontFamily.MonoSpace).fontSize(1);
1224     } else {
1225         res.fontSize = 15; // TODO: choose based on DPI
1226         Style button = res.createSubstyle(STYLE_BUTTON).backgroundImageId("btn_background").alignment(Align.Center).setMargins(5,5,5,5);
1227         res.createSubstyle(STYLE_BUTTON_TRANSPARENT).backgroundImageId("btn_background_transparent").alignment(Align.Center);
1228         res.createSubstyle(STYLE_BUTTON_LABEL).layoutWidth(FILL_PARENT).alignment(Align.Left|Align.VCenter);
1229         res.createSubstyle(STYLE_BUTTON_IMAGE).alignment(Align.Center);
1230         res.createSubstyle(STYLE_TEXT).setMargins(2,2,2,2).setPadding(1,1,1,1);
1231         res.createSubstyle(STYLE_HSPACER).layoutWidth(FILL_PARENT).minWidth(5).layoutWeight(100);
1232         res.createSubstyle(STYLE_VSPACER).layoutHeight(FILL_PARENT).minHeight(5).layoutWeight(100);
1233         res.createSubstyle(STYLE_BUTTON_NOMARGINS).backgroundImageId("btn_background").alignment(Align.Center); // .setMargins(5,5,5,5)
1234         //button.createState(State.Enabled | State.Focused, State.Focused).backgroundImageId("btn_default_small_normal_disable_focused");
1235         //button.createState(State.Enabled, 0).backgroundImageId("btn_default_small_normal_disable");
1236         //button.createState(State.Pressed, State.Pressed).backgroundImageId("btn_default_small_pressed");
1237         //button.createState(State.Focused, State.Focused).backgroundImageId("btn_default_small_selected");
1238         //button.createState(State.Hovered, State.Hovered).backgroundImageId("btn_default_small_normal_hover");
1239         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_UP, "scrollbar_btn_up");
1240         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_DOWN, "scrollbar_btn_down");
1241         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_LEFT, "scrollbar_btn_left");
1242         res.setCustomDrawable(ATTR_SCROLLBAR_BUTTON_RIGHT, "scrollbar_btn_right");
1243         res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_VERTICAL, "scrollbar_indicator_vertical");
1244         res.setCustomDrawable(ATTR_SCROLLBAR_INDICATOR_HORIZONTAL, "scrollbar_indicator_horizontal");
1245 
1246         Style scrollbar = res.createSubstyle(STYLE_SCROLLBAR);
1247         scrollbar.backgroundColor(0xC0808080);
1248         Style scrollbarButton = button.createSubstyle(STYLE_SCROLLBAR_BUTTON);
1249         Style scrollbarSlider = res.createSubstyle(STYLE_SLIDER);
1250         Style scrollbarPage = res.createSubstyle(STYLE_PAGE_SCROLL).backgroundColor(COLOR_TRANSPARENT);
1251         scrollbarPage.createState(State.Pressed, State.Pressed).backgroundColor(0xC0404080);
1252         scrollbarPage.createState(State.Hovered, State.Hovered).backgroundColor(0xF0404080);
1253 
1254         Style tabUp = res.createSubstyle(STYLE_TAB_UP);
1255         tabUp.backgroundImageId("tab_up_background");
1256         tabUp.layoutWidth(FILL_PARENT);
1257         tabUp.createState(State.Selected, State.Selected).backgroundImageId("tab_up_backgrond_selected");
1258         Style tabUpButtonText = res.createSubstyle(STYLE_TAB_UP_BUTTON_TEXT);
1259         tabUpButtonText.textColor(0x000000).fontSize(12).alignment(Align.Center);
1260         tabUpButtonText.createState(State.Selected, State.Selected).textColor(0x000000);
1261         tabUpButtonText.createState(State.Selected|State.Focused, State.Selected|State.Focused).textColor(0x000000);
1262         tabUpButtonText.createState(State.Focused, State.Focused).textColor(0x000000);
1263         tabUpButtonText.createState(State.Hovered, State.Hovered).textColor(0xFFE0E0);
1264         Style tabUpButton = res.createSubstyle(STYLE_TAB_UP_BUTTON);
1265         tabUpButton.backgroundImageId("tab_btn_up");
1266         //tabUpButton.backgroundImageId("tab_btn_up_normal");
1267         //tabUpButton.createState(State.Selected, State.Selected).backgroundImageId("tab_btn_up_selected");
1268         //tabUpButton.createState(State.Selected|State.Focused, State.Selected|State.Focused).backgroundImageId("tab_btn_up_focused_selected");
1269         //tabUpButton.createState(State.Focused, State.Focused).backgroundImageId("tab_btn_up_focused");
1270         //tabUpButton.createState(State.Hovered, State.Hovered).backgroundImageId("tab_btn_up_hover");
1271         Style tabHost = res.createSubstyle(STYLE_TAB_HOST);
1272         tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
1273         tabHost.backgroundColor(0xF0F0F0);
1274         Style tabWidget = res.createSubstyle(STYLE_TAB_WIDGET);
1275         tabWidget.setPadding(3,3,3,3).backgroundColor(0xEEEEEE);
1276         //tabWidget.backgroundImageId("frame_blue");
1277         //res.dumpStats();
1278 
1279         Style mainMenu = res.createSubstyle(STYLE_MAIN_MENU).backgroundColor(0xEFEFF2).layoutWidth(FILL_PARENT);
1280         Style mainMenuItem = res.createSubstyle(STYLE_MAIN_MENU_ITEM).setPadding(4,2,4,2).backgroundImageId("main_menu_item_background").textFlags(TEXT_FLAGS_USE_PARENT);
1281         Style menuItem = res.createSubstyle(STYLE_MENU_ITEM).setPadding(4,2,4,2); //.backgroundColor(0xE0E080)   ;
1282         menuItem.createState(State.Focused, State.Focused).backgroundColor(0x40C0C000);
1283         menuItem.createState(State.Pressed, State.Pressed).backgroundColor(0x4080C000);
1284         menuItem.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
1285         menuItem.createState(State.Hovered, State.Hovered).backgroundColor(0xC0FFFF00);
1286         res.createSubstyle(STYLE_MENU_ICON).setMargins(2,2,2,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).alpha(0xA0);
1287         res.createSubstyle(STYLE_MENU_LABEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).textFlags(TextFlag.UnderlineHotKeys).createState(State.Enabled,0).textColor(0x80404040);
1288         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);
1289         res.createSubstyle(STYLE_MENU_ACCEL).setMargins(4,2,4,2).alignment(Align.VCenter|Align.Left).createState(State.Enabled,0).textColor(0x80404040);
1290 
1291         Style transparentButtonBackground = res.createSubstyle(STYLE_TRANSPARENT_BUTTON_BACKGROUND).backgroundImageId("transparent_button_background").setPadding(4,2,4,2); //.backgroundColor(0xE0E080)   ;
1292         //transparentButtonBackground.createState(State.Focused, State.Focused).backgroundColor(0xC0C0C000);
1293         //transparentButtonBackground.createState(State.Pressed, State.Pressed).backgroundColor(0x4080C000);
1294         //transparentButtonBackground.createState(State.Selected, State.Selected).backgroundColor(0x00F8F9Fa);
1295         //transparentButtonBackground.createState(State.Hovered, State.Hovered).backgroundColor(0xD0FFFF00);
1296 
1297         Style poopupMenu = res.createSubstyle(STYLE_POPUP_MENU).backgroundImageId("popup_menu_background_normal");
1298 
1299         Style listItem = res.createSubstyle(STYLE_LIST_ITEM).backgroundImageId("list_item_background");
1300         //listItem.createState(State.Selected, State.Selected).backgroundColor(0xC04040FF).textColor(0x000000);
1301         //listItem.createState(State.Enabled, 0).textColor(0x80000000); // half transparent text for disabled item
1302 
1303         Style editLine = res.createSubstyle(STYLE_EDIT_LINE).backgroundImageId("editbox_background")
1304             .setPadding(5,6,5,6).setMargins(2,2,2,2).minWidth(40)
1305             .fontFace("Arial").fontFamily(FontFamily.SansSerif).fontSize(16);
1306         Style editBox = res.createSubstyle(STYLE_EDIT_BOX).backgroundImageId("editbox_background")
1307             .setPadding(5,6,5,6).setMargins(2,2,2,2).minWidth(100).minHeight(60).layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT)
1308             .fontFace("Courier New").fontFamily(FontFamily.MonoSpace).fontSize(16);
1309     }
1310 
1311     return res;
1312 }
1313 
1314 /// decode comma delimited dimension list or single value - and put to Rect
1315 Rect decodeRect(string s) {
1316     uint[6] values;
1317     int valueCount = 0;
1318     int start = 0;
1319     for (int i = 0; i <= s.length; i++) {
1320         if (i == s.length || s[i] == ',') {
1321             if (i > start) {
1322                 string item = s[start .. i];
1323                 values[valueCount++] = decodeDimension(item);
1324                 if (valueCount >= 6)
1325                     break;
1326             }
1327             start = i + 1;
1328         }
1329     }
1330     if (valueCount == 1) // same value for all dimensions
1331         return Rect(values[0], values[0], values[0], values[0]);
1332     else if (valueCount == 2) // one value of horizontal, and one for vertical
1333         return Rect(values[0], values[1], values[0], values[1]);
1334     else if (valueCount == 4) // separate left, top, right, bottom
1335         return Rect(values[0], values[1], values[2], values[3]);
1336     Log.e("Invalid rect attribute value ", s);
1337     return Rect(0,0,0,0);
1338 }
1339 
1340 private import std.array : split;
1341 
1342 /// Decode color list attribute, e.g.: "#84A, #99FFFF" -> [0x8844aa, 0x99ffff]
1343 uint[] decodeFocusRectColors(string s) {
1344     if (s.equal("@null"))
1345         return [COLOR_UNSPECIFIED];
1346     string[] colors = split(s, ",");
1347     if (colors.length < 1)
1348         return null;
1349     uint[] res = new uint[colors.length];
1350     for (int i = 0; i < colors.length; i++) {
1351         uint cl = decodeHexColor(colors[i], COLOR_UNSPECIFIED);
1352         if (cl == COLOR_UNSPECIFIED)
1353             return null;
1354         res[i] = cl;
1355     }
1356     return res;
1357 }
1358 
1359 /// parses string like "Left|VCenter" to bit set of Align flags
1360 ubyte decodeAlignment(string s) {
1361     ubyte res = 0;
1362     int start = 0;
1363     for (int i = 0; i <= s.length; i++) {
1364         if (i == s.length || s[i] == '|') {
1365             if (i > start) {
1366                 string item = s[start .. i];
1367                 if (item.equal("Left"))
1368                     res |= Align.Left;
1369                 else if (item.equal("Right"))
1370                     res |= Align.Right;
1371                 else if (item.equal("Top"))
1372                     res |= Align.Top;
1373                 else if (item.equal("Bottom"))
1374                     res |= Align.Bottom;
1375                 else if (item.equal("HCenter"))
1376                     res |= Align.HCenter;
1377                 else if (item.equal("VCenter"))
1378                     res |= Align.VCenter;
1379                 else if (item.equal("Center"))
1380                     res |= Align.Center;
1381                 else if (item.equal("TopLeft"))
1382                     res |= Align.TopLeft;
1383                 else
1384                     Log.e("unknown Align value: ", item);
1385             }
1386             start = i + 1;
1387         }
1388     }
1389     return res;
1390 }
1391 
1392 /// parses string like "HotKeys|UnderlineHotKeysWhenAltPressed" to bit set of TextFlag flags
1393 uint decodeTextFlags(string s) {
1394     uint res = 0;
1395     int start = 0;
1396     for (int i = 0; i <= s.length; i++) {
1397         if (i == s.length || s[i] == '|') {
1398             if (i > start) {
1399                 string item = s[start .. i];
1400                 if (item.equal("HotKeys"))
1401                     res |= TextFlag.HotKeys;
1402                 else if (item.equal("UnderlineHotKeys"))
1403                     res |= TextFlag.UnderlineHotKeys;
1404                 else if (item.equal("UnderlineHotKeysWhenAltPressed"))
1405                     res |= TextFlag.UnderlineHotKeysWhenAltPressed;
1406                 else if (item.equal("Underline"))
1407                     res |= TextFlag.Underline;
1408                 else if (item.equal("Unspecified"))
1409                     res = TEXT_FLAGS_UNSPECIFIED;
1410                 else if (item.equal("Parent"))
1411                     res = TEXT_FLAGS_USE_PARENT;
1412                 else
1413                     Log.e("unknown text flag value: ", item);
1414             }
1415             start = i + 1;
1416         }
1417     }
1418     return res;
1419 }
1420 
1421 /// decode FontFamily item name to value
1422 FontFamily decodeFontFamily(string s) {
1423     if (s.equal("SansSerif"))
1424         return FontFamily.SansSerif;
1425     if (s.equal("Serif"))
1426         return FontFamily.Serif;
1427     if (s.equal("Cursive"))
1428         return FontFamily.Cursive;
1429     if (s.equal("Fantasy"))
1430         return FontFamily.Fantasy;
1431     if (s.equal("MonoSpace"))
1432         return FontFamily.MonoSpace;
1433     if (s.equal("Unspecified"))
1434         return FontFamily.Unspecified;
1435     Log.e("unknown font family ", s);
1436     return FontFamily.SansSerif;
1437 }
1438 
1439 /// decode FontWeight item name to value
1440 FontWeight decodeFontWeight(string s) {
1441     if (s.equal("bold"))
1442         return FontWeight.Bold;
1443     if (s.equal("normal"))
1444         return FontWeight.Normal;
1445     Log.e("unknown font weight ", s);
1446     return FontWeight.Normal;
1447 }
1448 
1449 /// decode layout dimension (FILL_PARENT, WRAP_CONTENT, or just size)
1450 int decodeLayoutDimension(string s) {
1451     if (s.equal("FILL_PARENT") || s.equal("fill"))
1452         return FILL_PARENT;
1453     if (s.equal("WRAP_CONTENT") || s.equal("wrap"))
1454         return WRAP_CONTENT;
1455     return decodeDimension(s);
1456 }
1457 
1458 /// load style attributes from XML element
1459 bool loadStyleAttributes(Style style, Element elem, bool allowStates) {
1460     //Log.d("Theme: loadStyleAttributes ", style.id, " ", elem.tag.attr);
1461     if ("backgroundImageId" in elem.tag.attr)
1462         style.backgroundImageId = elem.tag.attr["backgroundImageId"];
1463     if ("backgroundColor" in elem.tag.attr) {
1464         uint col = decodeHexColor(elem.tag.attr["backgroundColor"]);
1465         style.backgroundColor = col;
1466         //Log.d("    background color=", col);
1467     } else {
1468         //Log.d("    no background color attr");
1469     }
1470     if ("textColor" in elem.tag.attr)
1471         style.textColor = decodeHexColor(elem.tag.attr["textColor"]);
1472     if ("margins" in elem.tag.attr)
1473         style.margins = decodeRect(elem.tag.attr["margins"]);
1474     if ("padding" in elem.tag.attr)
1475         style.padding = decodeRect(elem.tag.attr["padding"]);
1476     if ("align" in elem.tag.attr)
1477         style.alignment = decodeAlignment(elem.tag.attr["align"]);
1478     if ("minWidth" in elem.tag.attr)
1479         style.minWidth = decodeDimension(elem.tag.attr["minWidth"]);
1480     if ("maxWidth" in elem.tag.attr)
1481         style.maxWidth = decodeDimension(elem.tag.attr["maxWidth"]);
1482     if ("minHeight" in elem.tag.attr)
1483         style.minHeight = decodeDimension(elem.tag.attr["minHeight"]);
1484     if ("maxHeight" in elem.tag.attr)
1485         style.maxHeight = decodeDimension(elem.tag.attr["maxHeight"]);
1486     if ("maxLines" in elem.tag.attr)
1487         style.maxLines = decodeDimension(elem.tag.attr["maxLines"]);
1488     if ("fontFace" in elem.tag.attr)
1489         style.fontFace = elem.tag.attr["fontFace"];
1490     if ("fontFamily" in elem.tag.attr)
1491         style.fontFamily = decodeFontFamily(elem.tag.attr["fontFamily"]);
1492     if ("fontSize" in elem.tag.attr)
1493         style.fontSize = cast(int)decodeDimension(elem.tag.attr["fontSize"]);
1494     if ("fontWeight" in elem.tag.attr)
1495         style.fontWeight = cast(ushort)decodeFontWeight(elem.tag.attr["fontWeight"]);
1496     if ("layoutWidth" in elem.tag.attr)
1497         style.layoutWidth = decodeLayoutDimension(elem.tag.attr["layoutWidth"]);
1498     if ("layoutHeight" in elem.tag.attr)
1499         style.layoutHeight = decodeLayoutDimension(elem.tag.attr["layoutHeight"]);
1500     if ("alpha" in elem.tag.attr)
1501         style.alpha = decodeDimension(elem.tag.attr["alpha"]);
1502     if ("textFlags" in elem.tag.attr)
1503         style.textFlags = decodeTextFlags(elem.tag.attr["textFlags"]);
1504     if ("focusRectColors" in elem.tag.attr)
1505         style.focusRectColors = decodeFocusRectColors(elem.tag.attr["focusRectColors"]);
1506     foreach(item; elem.elements) {
1507         if (allowStates && item.tag.name.equal("state")) {
1508             uint stateMask = 0;
1509             uint stateValue = 0;
1510             extractStateFlags(item.tag.attr, stateMask, stateValue);
1511             if (stateMask) {
1512                 Style state = style.getOrCreateState(stateMask, stateValue);
1513                 loadStyleAttributes(state, item, false);
1514             }
1515         } else if (item.tag.name.equal("drawable")) {
1516             // <drawable id="scrollbar_button_up" value="scrollbar_btn_up"/>
1517             string drawableid = attrValue(item, "id");
1518             string drawablevalue = attrValue(item, "value");
1519             if (drawableid)
1520                 style.setCustomDrawable(drawableid, drawablevalue);
1521         } else if (item.tag.name.equal("color")) {
1522             // <color id="buttons_panel_color" value="#303080"/>
1523             string colorid = attrValue(item, "id");
1524             string colorvalue = attrValue(item, "value");
1525             uint color = decodeHexColor(colorvalue, COLOR_TRANSPARENT);
1526             if (colorid)
1527                 style.setCustomColor(colorid, color);
1528         } else if (item.tag.name.equal("length")) {
1529             // <color id="buttons_panel_color" value="#303080"/>
1530             string lenid = attrValue(item, "id");
1531             string lenvalue = attrValue(item, "value");
1532             uint len = decodeDimension(lenvalue);
1533             if (lenid.length > 0 && len > 0)
1534                 style.setCustomLength(lenid, len);
1535         }
1536     }
1537     return true;
1538 }
1539 
1540 /** 
1541  * load theme from XML document
1542  * 
1543  * Sample:
1544  * ---
1545  * <?xml version="1.0" encoding="utf-8"?>
1546  * <theme id="theme_custom" parent="theme_default">
1547  *       <style id="BUTTON" 
1548  *             backgroundImageId="btn_background"
1549  *          >
1550  *       </style>
1551  * </theme>
1552  * ---
1553  * 
1554  */
1555 bool loadTheme(Theme theme, Element doc, int level = 0) {
1556     if (!doc.tag.name.equal("theme")) {
1557         Log.e("<theme> element should be main in theme file!");
1558         return false;
1559     }
1560     // <theme>
1561     string id = attrValue(doc, "id");
1562     string parent = attrValue(doc, "parent");
1563     theme.id = id;
1564     if (parent.length > 0) {
1565         // load base theme
1566         if (level < 3) // to prevent infinite recursion
1567             loadTheme(theme, parent, level + 1);
1568     }
1569     loadStyleAttributes(theme, doc, false);
1570     foreach(styleitem; doc.elements) {
1571         if (styleitem.tag.name.equal("style")) {
1572             // load <style>
1573             string styleid = attrValue(styleitem, "id");
1574             string styleparent = attrValue(styleitem, "parent");
1575             if (styleid.length) {
1576                 // create new style
1577                 Style parentStyle = null;
1578                 parentStyle = theme.get(styleparent);
1579                 Style style = parentStyle.createSubstyle(styleid);
1580                 loadStyleAttributes(style, styleitem, true);
1581             } else {
1582                 Log.e("style without ID in theme file");
1583             }
1584         }
1585     }
1586     return true;
1587 }
1588 
1589 /// load theme from file
1590 bool loadTheme(Theme theme, string resourceId, int level = 0) {
1591 
1592     string filename;
1593     try {
1594         filename = drawableCache.findResource(BACKEND_CONSOLE ? "console_" ~ resourceId : resourceId);
1595         if (!filename || !filename.endsWith(".xml"))
1596             return false;
1597         string s = cast(string)loadResourceBytes(filename);
1598         if (!s) {
1599             Log.e("Cannot read XML resource ", resourceId, " from file ", filename);
1600             return false;
1601         }
1602         
1603         // Check for well-formedness
1604         //check(s);
1605     
1606         // Make a DOM tree
1607         auto doc = new Document(s);
1608         
1609         return loadTheme(theme, doc);
1610     } catch (CheckException e) {
1611         Log.e("Invalid XML resource ", resourceId);
1612         return false;
1613     }
1614 }
1615 
1616 /// load theme from XML file (null if failed)
1617 Theme loadTheme(string resourceId) {
1618     Theme res = new Theme(resourceId);
1619     if (loadTheme(res, resourceId)) {
1620         res.id = resourceId;
1621         return res;
1622     }
1623     destroy(res);
1624     return null;
1625 }
1626 
1627 /// custom drawable attribute container for styles
1628 class DrawableAttribute {
1629 protected:
1630     string _id;
1631     string _drawableId;
1632     DrawableRef _drawable;
1633     bool _initialized;
1634     
1635 public:
1636     this(string id, string drawableId) {
1637         _id = id;
1638         _drawableId = drawableId;
1639     }
1640     ~this() {
1641         clear();
1642     }
1643     @property string id() const { return _id; }
1644     @property string drawableId() const { return _drawableId; }
1645     @property void drawableId(string newDrawable) { _drawableId = newDrawable; clear(); }
1646     @property ref DrawableRef drawable() const {
1647         if (!_drawable.isNull)
1648             return (cast(DrawableAttribute)this)._drawable;
1649         (cast(DrawableAttribute)this)._drawable = drawableCache.get(_drawableId);
1650         (cast(DrawableAttribute)this)._initialized = true;
1651         return (cast(DrawableAttribute)this)._drawable;
1652     }
1653     void clear() {
1654         _drawable.clear();
1655         _initialized = false;
1656     }
1657 }
1658 
1659 /// returns custom drawable replacement id for specified id from current theme, or returns passed value if not found or no current theme
1660 string overrideCustomDrawableId(string id) {
1661     string res = currentTheme ? currentTheme.customDrawableId(id) : id;
1662     return !res ? id : res;
1663 }
1664 
1665 shared static ~this() {
1666     currentTheme = null;
1667 }