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