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