1 // Written in the D programming language. 2 3 /** 4 5 6 This module contains simple controls widgets implementation. 7 8 9 TextWidget - static text 10 11 ImageWidget - image 12 13 Button - button with only text 14 15 ImageButton - button with only image 16 17 ImageTextButton - button with text and image 18 19 SwitchButton - switch widget 20 21 RadioButton - radio button 22 23 CheckBox - button with check mark 24 25 UrlImageTextButton - URL link button 26 27 CanvasWidget - for drawing arbitrary graphics 28 29 30 Note: ScrollBar and SliderWidget are moved to dlangui.widgets.scrollbar 31 32 Synopsis: 33 34 ---- 35 import dlangui.widgets.controls; 36 37 ---- 38 39 Copyright: Vadim Lopatin, 2014 40 License: Boost License 1.0 41 Authors: Vadim Lopatin, coolreader.org@gmail.com 42 */ 43 module dlangui.widgets.controls; 44 45 import dlangui.widgets.widget; 46 import dlangui.widgets.layouts; 47 import dlangui.core.stdaction; 48 49 private import std.algorithm; 50 private import std.conv : to; 51 private import std.utf : toUTF32; 52 53 /// vertical spacer to fill empty space in vertical layouts 54 class VSpacer : Widget { 55 this() { 56 styleId = STYLE_VSPACER; 57 } 58 //override void measure(int parentWidth, int parentHeight) { 59 // measuredContent(parentWidth, parentHeight, 8, 8); 60 //} 61 } 62 63 /// horizontal spacer to fill empty space in horizontal layouts 64 class HSpacer : Widget { 65 this() { 66 styleId = STYLE_HSPACER; 67 } 68 //override void measure(int parentWidth, int parentHeight) { 69 // measuredContent(parentWidth, parentHeight, 8, 8); 70 //} 71 } 72 73 /// static text widget 74 class TextWidget : Widget { 75 this(string ID = null, string textResourceId = null) { 76 super(ID); 77 styleId = STYLE_TEXT; 78 _text.id = textResourceId; 79 } 80 this(string ID, dstring rawText) { 81 super(ID); 82 styleId = STYLE_TEXT; 83 _text.value = rawText; 84 } 85 this(string ID, UIString uitext) { 86 super(ID); 87 styleId = STYLE_TEXT; 88 _text = uitext; 89 } 90 91 /// max lines to show 92 @property int maxLines() { return style.maxLines; } 93 /// set max lines to show 94 @property TextWidget maxLines(int n) { ownStyle.maxLines = n; return this; } 95 96 protected UIString _text; 97 /// get widget text 98 override @property dstring text() { return _text; } 99 /// set text to show 100 override @property Widget text(dstring s) { 101 _text = s; 102 requestLayout(); 103 return this; 104 } 105 /// set text to show 106 override @property Widget text(UIString s) { 107 _text = s; 108 requestLayout(); 109 return this; 110 } 111 /// set text resource ID to show 112 @property Widget textResource(string s) { 113 _text = s; 114 requestLayout(); 115 return this; 116 } 117 118 override void measure(int parentWidth, int parentHeight) { 119 FontRef font = font(); 120 //auto measureStart = std.datetime.Clock.currAppTick; 121 Point sz; 122 if (maxLines == 1) { 123 sz = font.textSize(text, MAX_WIDTH_UNSPECIFIED, 4, 0, textFlags); 124 } else { 125 sz = font.measureMultilineText(text,maxLines,parentWidth-margins.left-margins.right-padding.left-padding.right, 4, 0, textFlags); 126 } 127 //auto measureEnd = std.datetime.Clock.currAppTick; 128 //auto duration = measureEnd - measureStart; 129 //if (duration.length > 10) 130 // Log.d("TextWidget measureText took ", duration.length, " ticks"); 131 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 132 } 133 134 override void onDraw(DrawBuf buf) { 135 if (visibility != Visibility.Visible) 136 return; 137 super.onDraw(buf); 138 Rect rc = _pos; 139 applyMargins(rc); 140 auto saver = ClipRectSaver(buf, rc, alpha); 141 applyPadding(rc); 142 143 FontRef font = font(); 144 if (maxLines == 1) { 145 Point sz = font.textSize(text); 146 applyAlign(rc, sz); 147 font.drawText(buf, rc.left, rc.top, text, textColor, 4, 0, textFlags); 148 } else { 149 SimpleTextFormatter fmt; 150 Point sz = fmt.format(text, font, maxLines, rc.width, 4, 0, textFlags); 151 applyAlign(rc, sz); 152 // TODO: apply align to alignment lines 153 fmt.draw(buf, rc.left, rc.top, font, textColor); 154 } 155 } 156 } 157 158 /// static text widget with multiline text 159 class MultilineTextWidget : TextWidget { 160 this(string ID = null, string textResourceId = null) { 161 super(ID, textResourceId); 162 styleId = STYLE_MULTILINE_TEXT; 163 } 164 this(string ID, dstring rawText) { 165 super(ID, rawText); 166 styleId = STYLE_MULTILINE_TEXT; 167 } 168 this(string ID, UIString uitext) { 169 super(ID, uitext); 170 styleId = STYLE_MULTILINE_TEXT; 171 } 172 } 173 174 /// Switch (on/off) widget 175 class SwitchButton : Widget { 176 this(string ID = null) { 177 super(ID); 178 styleId = STYLE_SWITCH; 179 clickable = true; 180 focusable = true; 181 trackHover = true; 182 } 183 // called to process click and notify listeners 184 override protected bool handleClick() { 185 checked = !checked; 186 return super.handleClick(); 187 } 188 override void measure(int parentWidth, int parentHeight) { 189 DrawableRef img = backgroundDrawable; 190 int w = 0; 191 int h = 0; 192 if (!img.isNull) { 193 w = img.width; 194 h = img.height; 195 } 196 measuredContent(parentWidth, parentHeight, w, h); 197 } 198 199 override void onDraw(DrawBuf buf) { 200 if (visibility != Visibility.Visible) 201 return; 202 Rect rc = _pos; 203 applyMargins(rc); 204 auto saver = ClipRectSaver(buf, rc, alpha); 205 DrawableRef img = backgroundDrawable; 206 if (!img.isNull) { 207 Point sz; 208 sz.x = img.width; 209 sz.y = img.height; 210 applyAlign(rc, sz); 211 uint st = state; 212 img.drawTo(buf, rc, st); 213 } 214 _needDraw = false; 215 } 216 } 217 218 /// static image widget 219 class ImageWidget : Widget { 220 221 protected string _drawableId; 222 protected DrawableRef _drawable; 223 224 this(string ID = null, string drawableId = null) { 225 super(ID); 226 _drawableId = drawableId; 227 } 228 229 ~this() { 230 _drawable.clear(); 231 } 232 233 /// get drawable image id 234 @property string drawableId() { return _drawableId; } 235 /// set drawable image id 236 @property ImageWidget drawableId(string id) { 237 _drawableId = id; 238 _drawable.clear(); 239 requestLayout(); 240 return this; 241 } 242 /// get drawable 243 @property ref DrawableRef drawable() { 244 if (!_drawable.isNull) 245 return _drawable; 246 if (_drawableId !is null) 247 _drawable = drawableCache.get(overrideCustomDrawableId(_drawableId)); 248 return _drawable; 249 } 250 /// set custom drawable (not one from resources) 251 @property ImageWidget drawable(DrawableRef img) { 252 _drawable = img; 253 _drawableId = null; 254 return this; 255 } 256 /// set custom drawable (not one from resources) 257 @property ImageWidget drawable(string drawableId) { 258 if (_drawableId.equal(drawableId)) 259 return this; 260 _drawableId = drawableId; 261 _drawable.clear(); 262 requestLayout(); 263 return this; 264 } 265 266 /// set string property value, for ML loaders 267 mixin(generatePropertySettersMethodOverride("setStringProperty", "string", 268 "drawableId")); 269 270 /// handle theme change: e.g. reload some themed resources 271 override void onThemeChanged() { 272 super.onThemeChanged(); 273 if (_drawableId !is null) 274 _drawable.clear(); // remove cached drawable 275 } 276 277 override void measure(int parentWidth, int parentHeight) { 278 DrawableRef img = drawable; 279 int w = 0; 280 int h = 0; 281 if (!img.isNull) { 282 w = img.width; 283 h = img.height; 284 } 285 measuredContent(parentWidth, parentHeight, w, h); 286 } 287 288 override void onDraw(DrawBuf buf) { 289 if (visibility != Visibility.Visible) 290 return; 291 super.onDraw(buf); 292 Rect rc = _pos; 293 applyMargins(rc); 294 auto saver = ClipRectSaver(buf, rc, alpha); 295 applyPadding(rc); 296 DrawableRef img = drawable; 297 if (!img.isNull) { 298 Point sz; 299 sz.x = img.width; 300 sz.y = img.height; 301 applyAlign(rc, sz); 302 uint st = state; 303 img.drawTo(buf, rc, st); 304 } 305 } 306 } 307 308 /// button with image only 309 class ImageButton : ImageWidget { 310 /// constructor by id and icon resource id 311 this(string ID = null, string drawableId = null) { 312 super(ID, drawableId); 313 styleId = STYLE_BUTTON; 314 _drawableId = drawableId; 315 clickable = true; 316 focusable = true; 317 trackHover = true; 318 } 319 /// constructor from action 320 this(const Action a) { 321 this("imagebutton-action" ~ to!string(a.id), a.iconId); 322 action = a; 323 } 324 } 325 326 /// button with image working as trigger: check / uncheck occurs when pressing 327 class ImageCheckButton : ImageButton { 328 /// constructor by id and icon resource id 329 this(string ID = null, string drawableId = null) { 330 super(ID, drawableId); 331 styleId = "BUTTON_CHECK_TRANSPARENT"; 332 } 333 /// constructor from action 334 this(const Action a) { 335 super(a); 336 styleId = "BUTTON_CHECK_TRANSPARENT"; 337 } 338 339 // called to process click and notify listeners 340 override protected bool handleClick() { 341 checked = !checked; 342 return super.handleClick(); 343 } 344 } 345 346 /// button with image and text 347 class ImageTextButton : HorizontalLayout { 348 protected ImageWidget _icon; 349 protected TextWidget _label; 350 351 /// Get label text 352 override @property dstring text() { return _label.text; } 353 /// Set label plain unicode string 354 override @property Widget text(dstring s) { _label.text = s; requestLayout(); return this; } 355 /// Set label string resource Id 356 override @property Widget text(UIString s) { _label.text = s; requestLayout(); return this; } 357 358 /// Returns orientation: Vertical - image top, Horizontal - image left" 359 override @property Orientation orientation() { 360 return super.orientation(); 361 } 362 363 /// Sets orientation: Vertical - image top, Horizontal - image left" 364 override @property LinearLayout orientation(Orientation value) { 365 if (!_icon || !_label) 366 return super.orientation(value); 367 if (value != orientation) { 368 super.orientation(value); 369 if (value == Orientation.Horizontal) { 370 _icon.alignment = Align.Left | Align.VCenter; 371 _label.alignment = Align.Right | Align.VCenter; 372 } else { 373 _icon.alignment = Align.Top | Align.HCenter; 374 _label.alignment = Align.Bottom | Align.HCenter; 375 } 376 } 377 return this; 378 } 379 380 protected void initialize(string drawableId, UIString caption) { 381 styleId = STYLE_BUTTON; 382 _icon = new ImageWidget("icon", drawableId); 383 _icon.styleId = STYLE_BUTTON_IMAGE; 384 _label = new TextWidget("label", caption); 385 _label.styleId = STYLE_BUTTON_LABEL; 386 _icon.state = State.Parent; 387 _label.state = State.Parent; 388 addChild(_icon); 389 addChild(_label); 390 clickable = true; 391 focusable = true; 392 trackHover = true; 393 } 394 395 this(string ID = null, string drawableId = null, string textResourceId = null) { 396 super(ID); 397 initialize(drawableId, UIString.fromId(textResourceId)); 398 } 399 400 this(string ID, string drawableId, dstring rawText) { 401 super(ID); 402 initialize(drawableId, UIString.fromRaw(rawText)); 403 } 404 405 /// constructor from action 406 this(const Action a) { 407 super("imagetextbutton-action" ~ to!string(a.id)); 408 initialize(a.iconId, a.labelValue); 409 action = a; 410 } 411 412 } 413 414 /// button - url 415 class UrlImageTextButton : ImageTextButton { 416 this(string ID, dstring labelText, string url, string icon = "applications-internet") { 417 super(ID, icon, labelText); 418 Action a = ACTION_OPEN_URL.clone(); 419 a.label = labelText; 420 a.stringParam = url; 421 _action = a; 422 styleId = null; 423 //_icon.styleId = STYLE_BUTTON_IMAGE; 424 //_label.styleId = STYLE_BUTTON_LABEL; 425 //_label.textFlags(TextFlag.Underline); 426 _label.styleId = "BUTTON_LABEL_LINK"; 427 static if (BACKEND_GUI) padding(Rect(3,3,3,3)); 428 } 429 } 430 431 /// button looking like URL, executing specified action 432 class LinkButton : ImageTextButton { 433 this(Action a) { 434 super(a); 435 styleId = null; 436 _label.styleId = "BUTTON_LABEL_LINK"; 437 static if (BACKEND_GUI) padding(Rect(3,3,3,3)); 438 } 439 } 440 441 442 /// checkbox 443 class CheckBox : ImageTextButton { 444 this(string ID = null, string textResourceId = null) { 445 super(ID, "btn_check", textResourceId); 446 } 447 this(string ID, dstring labelText) { 448 super(ID, "btn_check", labelText); 449 } 450 this(string ID, UIString label) { 451 super(ID, "btn_check", label); 452 } 453 override protected void initialize(string drawableId, UIString caption) { 454 super.initialize(drawableId, caption); 455 styleId = STYLE_CHECKBOX; 456 if (_icon) 457 _icon.styleId = STYLE_CHECKBOX_IMAGE; 458 if (_label) 459 _label.styleId = STYLE_CHECKBOX_LABEL; 460 checkable = true; 461 } 462 // called to process click and notify listeners 463 override protected bool handleClick() { 464 checked = !checked; 465 return super.handleClick(); 466 } 467 } 468 469 /// radio button 470 class RadioButton : ImageTextButton { 471 this(string ID = null, string textResourceId = null) { 472 super(ID, "btn_radio", textResourceId); 473 } 474 this(string ID, dstring labelText) { 475 super(ID, "btn_radio", labelText); 476 } 477 override protected void initialize(string drawableId, UIString caption) { 478 super.initialize(drawableId, caption); 479 styleId = STYLE_RADIOBUTTON; 480 if (_icon) 481 _icon.styleId = STYLE_RADIOBUTTON_IMAGE; 482 if (_label) 483 _label.styleId = STYLE_RADIOBUTTON_LABEL; 484 checkable = true; 485 } 486 487 private bool blockUnchecking = false; 488 489 void uncheckSiblings() { 490 Widget p = parent; 491 if (!p) 492 return; 493 for (int i = 0; i < p.childCount; i++) { 494 Widget child = p.child(i); 495 if (child is this) 496 continue; 497 RadioButton rb = cast(RadioButton)child; 498 if (rb) { 499 rb.blockUnchecking = true; 500 scope(exit) rb.blockUnchecking = false; 501 rb.checked = false; 502 } 503 } 504 } 505 506 // called to process click and notify listeners 507 override protected bool handleClick() { 508 uncheckSiblings(); 509 checked = true; 510 511 return super.handleClick(); 512 } 513 514 override protected void handleCheckChange(bool checked) { 515 if (!blockUnchecking) 516 uncheckSiblings(); 517 invalidate(); 518 checkChange(this, checked); 519 } 520 521 } 522 523 /// Text only button 524 class Button : Widget { 525 protected UIString _text; 526 override @property dstring text() { return _text; } 527 override @property Widget text(dstring s) { _text = s; requestLayout(); return this; } 528 override @property Widget text(UIString s) { _text = s; requestLayout(); return this; } 529 @property Widget textResource(string s) { _text = s; requestLayout(); return this; } 530 /// empty parameter list constructor - for usage by factory 531 this() { 532 super(null); 533 initialize(UIString()); 534 } 535 536 private void initialize(UIString label) { 537 styleId = STYLE_BUTTON; 538 _text = label; 539 clickable = true; 540 focusable = true; 541 trackHover = true; 542 } 543 544 /// create with ID parameter 545 this(string ID) { 546 super(ID); 547 initialize(UIString()); 548 } 549 this(string ID, UIString label) { 550 super(ID); 551 initialize(label); 552 } 553 this(string ID, dstring label) { 554 super(ID); 555 initialize(UIString.fromRaw(label)); 556 } 557 this(string ID, string labelResourceId) { 558 super(ID); 559 initialize(UIString.fromId(labelResourceId)); 560 } 561 /// constructor from action 562 this(const Action a) { 563 this("button-action" ~ to!string(a.id), a.labelValue); 564 action = a; 565 } 566 567 override void measure(int parentWidth, int parentHeight) { 568 FontRef font = font(); 569 Point sz = font.textSize(text); 570 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 571 } 572 573 override void onDraw(DrawBuf buf) { 574 if (visibility != Visibility.Visible) 575 return; 576 super.onDraw(buf); 577 Rect rc = _pos; 578 applyMargins(rc); 579 //buf.fillRect(_pos, backgroundColor); 580 applyPadding(rc); 581 auto saver = ClipRectSaver(buf, rc, alpha); 582 FontRef font = font(); 583 Point sz = font.textSize(text); 584 applyAlign(rc, sz); 585 font.drawText(buf, rc.left, rc.top, text, textColor, 4, 0, textFlags); 586 } 587 588 } 589 590 591 /// interface - slot for onClick 592 interface OnDrawHandler { 593 void doDraw(CanvasWidget canvas, DrawBuf buf, Rect rc); 594 } 595 596 /// canvas widget - draw on it either by overriding of doDraw() or by assigning of onDrawListener 597 class CanvasWidget : Widget { 598 599 Listener!OnDrawHandler onDrawListener; 600 601 this(string ID = null) { 602 super(ID); 603 } 604 605 override void measure(int parentWidth, int parentHeight) { 606 measuredContent(parentWidth, parentHeight, 0, 0); 607 } 608 609 void doDraw(DrawBuf buf, Rect rc) { 610 if (onDrawListener.assigned) 611 onDrawListener(this, buf, rc); 612 } 613 614 override void onDraw(DrawBuf buf) { 615 if (visibility != Visibility.Visible) 616 return; 617 super.onDraw(buf); 618 Rect rc = _pos; 619 applyMargins(rc); 620 auto saver = ClipRectSaver(buf, rc, alpha); 621 applyPadding(rc); 622 doDraw(buf, rc); 623 } 624 } 625 626 //import dlangui.widgets.metadata; 627 //mixin(registerWidgets!(Widget, TextWidget, MultilineTextWidget, Button, ImageWidget, ImageButton, ImageCheckButton, ImageTextButton, RadioButton, CheckBox, ScrollBar, HSpacer, VSpacer, CanvasWidget)());