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 }