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