1 // Written in the D programming language. 2 3 /** 4 This module declares basic data types for usage in dlangui library. 5 6 Contains reference counting support, point and rect structures, character glyph structure, misc utility functions. 7 8 Synopsis: 9 10 ---- 11 import dlangui.core.types; 12 13 // points 14 Point p(5, 10); 15 16 // rectangles 17 Rect r(5, 13, 120, 200); 18 writeln(r); 19 20 // reference counted objects, useful for RAII / resource management. 21 class Foo : RefCountedObject { 22 int[] resource; 23 ~this() { 24 writeln("freeing Foo resources"); 25 } 26 } 27 { 28 Ref!Foo ref1; 29 { 30 Ref!Foo fooRef = new RefCountedObject(); 31 ref1 = fooRef; 32 } 33 // RAII: will destroy object when no more references 34 } 35 36 ---- 37 38 Copyright: Vadim Lopatin, 2014 39 License: Boost License 1.0 40 Authors: Vadim Lopatin, coolreader.org@gmail.com 41 */ 42 module dlangui.core.types; 43 44 public import dlangui.core.config; 45 46 import std.algorithm; 47 48 /// 2D point 49 struct Point { 50 int x; 51 int y; 52 53 @safe @nogc nothrow: 54 Point opBinary(string op)(Point v) const if (op == "+") { 55 return Point(x + v.x, y + v.y); 56 } 57 Point opBinary(string op)(int n) const if (op == "*") { 58 return Point(x * n, y * n); 59 } 60 Point opBinary(string op)(Point v) const if (op == "-") { 61 return Point(x - v.x, y - v.y); 62 } 63 Point opUnary(string op)() const if (op == "-") { 64 return Point(-x, -y); 65 } 66 int opCmp(ref const Point b) const { 67 if (x == b.x) return y - b.y; 68 return x - b.x; 69 } 70 int opCmp(const Point b) const { 71 if (x == b.x) return y - b.y; 72 return x - b.x; 73 } 74 } 75 76 // Point unittest 77 @safe @nogc unittest 78 { 79 immutable testA = Point(100, 100); 80 immutable testB = Point(50, 50); 81 assert(testA + testA == Point(200, 200)); 82 assert(testA * 2 == Point(200, 200)); 83 assert(testA - testA == Point(0, 0)); 84 assert(-testA == Point(-100, -100)); 85 assert(testA > Point(50, 50)); 86 assert(testA > testB); 87 } 88 89 /** 2D rectangle 90 Note: Rect(0,0,20,10) size is 20x10 -- right and bottom sides are non-inclusive -- if you draw such rect, rightmost drawn pixel will be x=19 and bottom pixel y=9 91 */ 92 struct Rect { 93 /// x coordinate of top left corner 94 int left; 95 /// y coordinate of top left corner 96 int top; 97 /// x coordinate of bottom right corner (non-inclusive) 98 int right; 99 /// y coordinate of bottom right corner (non-inclusive) 100 int bottom; 101 102 @safe @nogc nothrow: 103 /// returns average of left, right 104 @property int middlex() const { return (left + right) / 2; } 105 /// returns average of top, bottom 106 @property int middley() const { return (top + bottom) / 2; } 107 /// returns middle point 108 @property Point middle() const { return Point(middlex, middley); } 109 110 /// returns top left point of rectangle 111 @property Point topLeft() const { return Point(left, top); } 112 /// returns bottom right point of rectangle 113 @property Point bottomRight() const { return Point(right, bottom); } 114 /// returns top right point of rectangel 115 @property Point topRight() const { return Point(right, top); } 116 /// returns bottom left point of rectangle 117 @property Point bottomLeft() const { return Point(left, bottom); } 118 119 /// returns size (width, height) in Point 120 @property Point size() const { return Point(right - left, bottom - top); } 121 122 /// add offset to horizontal and vertical coordinates 123 void offset(int dx, int dy) { 124 left += dx; 125 right += dx; 126 top += dy; 127 bottom += dy; 128 } 129 /// expand rectangle dimensions 130 void expand(int dx, int dy) { 131 left -= dx; 132 right += dx; 133 top -= dy; 134 bottom += dy; 135 } 136 /// shrink rectangle dimensions 137 void shrink(int dx, int dy) { 138 left += dx; 139 right -= dx; 140 top += dy; 141 bottom -= dy; 142 } 143 /// for all fields, sets this.field to rc.field if rc.field > this.field 144 void setMax(Rect rc) { 145 if (left < rc.left) 146 left = rc.left; 147 if (right < rc.right) 148 right = rc.right; 149 if (top < rc.top) 150 top = rc.top; 151 if (bottom < rc.bottom) 152 bottom = rc.bottom; 153 } 154 /// returns width of rectangle (right - left) 155 @property int width() const { return right - left; } 156 /// returns height of rectangle (bottom - top) 157 @property int height() const { return bottom - top; } 158 /// constructs rectangle using left, top, right, bottom coordinates 159 this(int x0, int y0, int x1, int y1) { 160 left = x0; 161 top = y0; 162 right = x1; 163 bottom = y1; 164 } 165 /// constructs rectangle using two points - (left, top), (right, bottom) coordinates 166 this(Point pt0, Point pt1) { 167 this(pt0.x, pt0.y, pt1.x, pt1.y); 168 } 169 /// returns true if rectangle is empty (right <= left || bottom <= top) 170 @property bool empty() const { 171 return right <= left || bottom <= top; 172 } 173 /// translate rectangle coordinates by (x,y) - add deltax to x coordinates, and deltay to y coordinates 174 alias moveBy = offset; 175 /// moves this rect to fit rc bounds, retaining the same size 176 void moveToFit(ref Rect rc) { 177 if (right > rc.right) 178 moveBy(rc.right - right, 0); 179 if (bottom > rc.bottom) 180 moveBy(0, rc.bottom - bottom); 181 if (left < rc.left) 182 moveBy(rc.left - left, 0); 183 if (top < rc.top) 184 moveBy(0, rc.top - top); 185 186 } 187 /// updates this rect to intersection with rc, returns true if result is non empty 188 bool intersect(Rect rc) { 189 if (left < rc.left) 190 left = rc.left; 191 if (top < rc.top) 192 top = rc.top; 193 if (right > rc.right) 194 right = rc.right; 195 if (bottom > rc.bottom) 196 bottom = rc.bottom; 197 return right > left && bottom > top; 198 } 199 /// returns true if this rect has nonempty intersection with rc 200 bool intersects(Rect rc) const { 201 if (rc.left >= right || rc.top >= bottom || rc.right <= left || rc.bottom <= top) 202 return false; 203 return true; 204 } 205 /// returns true if point is inside of this rectangle 206 bool isPointInside(Point pt) const { 207 return pt.x >= left && pt.x < right && pt.y >= top && pt.y < bottom; 208 } 209 /// returns true if point is inside of this rectangle 210 bool isPointInside(int x, int y) const { 211 return x >= left && x < right && y >= top && y < bottom; 212 } 213 /// this rectangle is completely inside rc 214 bool isInsideOf(Rect rc) const { 215 return left >= rc.left && right <= rc.right && top >= rc.top && bottom <= rc.bottom; 216 } 217 218 bool opEquals(Rect rc) const { 219 return left == rc.left && right == rc.right && top == rc.top && bottom == rc.bottom; 220 } 221 } 222 223 // Rect unittests 224 @safe @nogc unittest 225 { 226 immutable testA = Rect(0, 0, 100, 100); 227 assert(testA.width == 100); 228 assert(testA.height == 100); 229 assert(testA.middlex == 50); 230 assert(testA.middley == 50); 231 assert(testA.middle == Point(50, 50)); 232 assert(testA == Rect(0, 0, 100, 100)); 233 assert(testA == Rect(Point(0, 0), Point(100, 100))); 234 assert(testA.topLeft == Point(0, 0)); 235 assert(testA.topRight == Point(100, 0)); 236 assert(testA.bottomLeft == Point(0, 100)); 237 assert(testA.bottomRight == Point(100, 100)); 238 assert(testA.size == Point(100, 100)); 239 assert(!testA.empty); 240 } 241 242 /// constant acting as "rectangle not set" value 243 immutable Rect RECT_VALUE_IS_NOT_SET = Rect(int.min, int.min, int.min, int.min); 244 245 /// widget state bit flags 246 enum State : uint { 247 /// state not specified / normal 248 Normal = 4 | 256, // Normal is Enabled 249 /// pressed (e.g. clicked by mouse) 250 Pressed = 1, 251 /// widget has focus 252 Focused = 2, 253 /// widget can process mouse and key events 254 Enabled = 4, 255 /// mouse pointer is over this widget 256 Hovered = 8, // mouse pointer is over control, buttons not pressed 257 /// widget is selected 258 Selected = 16, 259 /// widget can be checked 260 Checkable = 32, 261 /// widget is checked 262 Checked = 64, 263 /// widget is activated 264 Activated = 128, 265 /// window is focused 266 WindowFocused = 256, 267 /// widget is default control for form (should be focused when window gains focus first time) 268 Default = 512, // widget is default for form (e.g. default button will be focused on show) 269 /// widget has been focused by keyboard navigation 270 KeyboardFocused = 1024, 271 /// return state of parent instead of widget's state when requested 272 Parent = 0x10000, // use parent's state 273 } 274 275 276 // Layout size constants 277 /// layout option, to occupy all available place 278 enum int FILL_PARENT = 0x4000_0000; 279 /// layout option, for size based on content 280 enum int WRAP_CONTENT = 0x2000_0000; 281 /// use as widget.layout() param to avoid applying of parent size 282 enum int SIZE_UNSPECIFIED = 0x6000_0000; 283 284 /// use in styles to specify size in points (1/72 inch) 285 enum int SIZE_IN_POINTS_FLAG = 0x1000_0000; 286 /// (RESERVED) use in styles to specify size in percents * 100 (e.g. 0 == 0%, 10000 == 100%, 100 = 1%) 287 enum int SIZE_IN_PERCENTS_FLAG = 0x0800_0000; 288 289 290 /// convert custom size to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set) 291 int toPixels(int sz) { 292 if (sz > 0 && (sz & SIZE_IN_POINTS_FLAG) != 0) { 293 return pointsToPixels(sz ^ SIZE_IN_POINTS_FLAG); 294 } 295 return sz; 296 } 297 298 /// convert custom size Point to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set) 299 Point toPixels(const Point sz) { 300 return Point(toPixels(sz.x), toPixels(sz.y)); 301 } 302 303 /// convert custom size Rect to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set) 304 Rect toPixels(const Rect sz) { 305 return Rect(toPixels(sz.left), toPixels(sz.top), toPixels(sz.right), toPixels(sz.bottom)); 306 } 307 308 /// make size value with SIZE_IN_POINTS_FLAG set 309 int makePointSize(int pt) { 310 return pt | SIZE_IN_POINTS_FLAG; 311 } 312 313 /// make size value with SIZE_IN_PERCENTS_FLAG set 314 int makePercentSize(int percent) { 315 return (percent * 100) | SIZE_IN_PERCENTS_FLAG; 316 } 317 318 /// make size value with SIZE_IN_PERCENTS_FLAG set 319 int makePercentSize(double percent) { 320 return cast(int)(percent * 100) | SIZE_IN_PERCENTS_FLAG; 321 } 322 323 /// returns true for WRAP_CONTENT, WRAP_CONTENT, SIZE_UNSPECIFIED 324 bool isSpecialSize(int sz) { 325 // don't forget to update if more special constants added 326 return (sz & (WRAP_CONTENT | FILL_PARENT | SIZE_UNSPECIFIED)) != 0; 327 } 328 329 /// returns true if size has SIZE_IN_PERCENTS_FLAG bit set 330 bool isPercentSize(int size) { 331 return (size & SIZE_IN_PERCENTS_FLAG) != 0; 332 } 333 334 /// if size has SIZE_IN_PERCENTS_FLAG bit set, returns percent of baseSize, otherwise returns size unchanged 335 int fromPercentSize(int size, int baseSize) { 336 if (isPercentSize(size)) 337 return cast(int)(cast(long)(size & ~SIZE_IN_PERCENTS_FLAG) * baseSize / 10000); 338 return size; 339 } 340 341 /// screen dots per inch 342 private __gshared int PRIVATE_SCREEN_DPI = 96; 343 /// value to override detected system DPI, 0 to disable overriding 344 private __gshared int PRIVATE_SCREEN_DPI_OVERRIDE = 0; 345 346 /// get current screen DPI used for scaling while drawing 347 @property int SCREEN_DPI() { 348 return PRIVATE_SCREEN_DPI_OVERRIDE ? PRIVATE_SCREEN_DPI_OVERRIDE : PRIVATE_SCREEN_DPI; 349 } 350 351 /// get screen DPI detection override value, if non 0 - this value is used instead of DPI detected by platform, if 0, value detected by platform will be used) 352 @property int overrideScreenDPI() { 353 return PRIVATE_SCREEN_DPI_OVERRIDE; 354 } 355 356 /// call to disable automatic screen DPI detection, use provided one instead (pass 0 to disable override and use value detected by platform) 357 @property void overrideScreenDPI(int dpi = 96) { 358 static if (WIDGET_STYLE_CONSOLE) { 359 } else { 360 if ((dpi >= 72 && dpi <= 500) || dpi == 0) 361 PRIVATE_SCREEN_DPI_OVERRIDE = dpi; 362 } 363 } 364 365 /// set screen DPI detected by platform 366 @property void SCREEN_DPI(int dpi) { 367 static if (WIDGET_STYLE_CONSOLE) { 368 PRIVATE_SCREEN_DPI = dpi; 369 } else { 370 if (dpi >= 72 && dpi <= 500) { 371 if (PRIVATE_SCREEN_DPI != dpi) { 372 // changed DPI 373 PRIVATE_SCREEN_DPI = dpi; 374 } 375 } 376 } 377 } 378 379 /// returns DPI detected by platform w/o override 380 @property int systemScreenDPI() { 381 return PRIVATE_SCREEN_DPI; 382 } 383 384 /// one point is 1/72 of inch 385 enum POINTS_PER_INCH = 72; 386 387 /// convert length in points (1/72in units) to pixels according to SCREEN_DPI 388 int pointsToPixels(int pt) { 389 return pt * SCREEN_DPI / POINTS_PER_INCH; 390 } 391 392 /// rectangle coordinates in points (1/72in units) to pixels according to SCREEN_DPI 393 Rect pointsToPixels(Rect rc) { 394 return Rect(rc.left.pointsToPixels, rc.top.pointsToPixels, rc.right.pointsToPixels, rc.bottom.pointsToPixels); 395 } 396 397 /// convert points (1/72in units) to pixels according to SCREEN_DPI 398 int pixelsToPoints(int px) { 399 return px * POINTS_PER_INCH / SCREEN_DPI; 400 } 401 402 /// Subpixel rendering mode for fonts (aka ClearType) 403 enum SubpixelRenderingMode : ubyte { 404 /// no sub 405 None, 406 /// subpixel rendering on, subpixel order on device: B,G,R 407 BGR, 408 /// subpixel rendering on, subpixel order on device: R,G,B 409 RGB, 410 } 411 412 /** 413 Character glyph. 414 415 Holder for glyph metrics as well as image. 416 */ 417 align(1) 418 struct Glyph 419 { 420 static if (ENABLE_OPENGL) { 421 /// 0: unique id of glyph (for drawing in hardware accelerated scenes) 422 uint id; 423 } 424 425 /// 0: width of glyph black box 426 ushort blackBoxX; 427 428 @property ushort correctedBlackBoxX() { return subpixelMode ? (blackBoxX + 2) / 3 : blackBoxX; } 429 430 431 /// 2: height of glyph black box 432 ubyte blackBoxY; 433 /// 3: X origin for glyph 434 byte originX; 435 /// 4: Y origin for glyph 436 byte originY; 437 438 /// 5: full width of glyph 439 ubyte widthPixels; 440 /// 6: full width of glyph scaled * 64 441 ushort widthScaled; 442 /// 8: subpixel rendering mode - if !=SubpixelRenderingMode.None, glyph data contains 3 bytes per pixel instead of 1 443 SubpixelRenderingMode subpixelMode; 444 /// 9: usage flag, to handle cleanup of unused glyphs 445 ubyte lastUsage; 446 447 ///< 10: glyph data, arbitrary size (blackBoxX * blackBoxY) 448 ubyte[] glyph; 449 } 450 451 /** 452 Base class for reference counted objects, maintains reference counter inplace. 453 454 If some class is not inherited from RefCountedObject, additional object will be required to hold counters. 455 */ 456 class RefCountedObject { 457 /// count of references to this object from Ref 458 protected int _refCount; 459 /// returns current value of reference counter 460 @property int refCount() const { return _refCount; } 461 /// increments reference counter 462 void addRef() { 463 _refCount++; 464 } 465 /// decrement reference counter, destroy object if no more references left 466 void releaseRef() { 467 if (--_refCount == 0) 468 destroy(this); 469 } 470 ~this() {} 471 } 472 473 /** 474 Reference counting support. 475 476 Implemented for case when T is RefCountedObject. 477 Similar to shared_ptr in C++. 478 Allows to share object, destroying it when no more references left. 479 480 Useful for automatic destroy of objects. 481 */ 482 struct Ref(T) { // if (T is RefCountedObject) 483 private T _data; 484 alias get this; 485 /// returns true if object is not assigned 486 @property bool isNull() const { return _data is null; } 487 /// returns counter of references 488 @property int refCount() const { return _data !is null ? _data.refCount : 0; } 489 /// init from T 490 this(T data) { 491 _data = data; 492 if (_data !is null) 493 _data.addRef(); 494 } 495 /// after blit 496 this(this) { 497 if (_data !is null) 498 _data.addRef(); 499 } 500 /// assign from another refcount by reference 501 ref Ref opAssign(ref Ref data) { 502 if (data._data == _data) 503 return this; 504 if (_data !is null) 505 _data.releaseRef(); 506 _data = data._data; 507 if (_data !is null) 508 _data.addRef(); 509 return this; 510 } 511 /// assign from another refcount by value 512 ref Ref opAssign(Ref data) { 513 if (data._data == _data) 514 return this; 515 if (_data !is null) 516 _data.releaseRef(); 517 _data = data._data; 518 if (_data !is null) 519 _data.addRef(); 520 return this; 521 } 522 /// assign object 523 ref Ref opAssign(T data) { 524 if (data == _data) 525 return this; 526 if (_data !is null) 527 _data.releaseRef(); 528 _data = data; 529 if (_data !is null) 530 _data.addRef(); 531 return this; 532 } 533 /// clears reference 534 void clear() { 535 if (_data !is null) { 536 _data.releaseRef(); 537 _data = null; 538 } 539 } 540 /// returns object reference (null if not assigned) 541 @property T get() { 542 return _data; 543 } 544 /// returns const reference from const object 545 @property const(T) get() const { 546 return _data; 547 } 548 /// decreases counter, and destroys object if no more references left 549 ~this() { 550 if (_data !is null) 551 _data.releaseRef(); 552 } 553 } 554 555 556 /** 557 This struct allows to not execute some code if some variables was not changed since the last check. 558 Used for optimizations. 559 560 Reference types, arrays and pointers are compared by reference. 561 */ 562 struct CalcSaver(Params...) { 563 import std.typecons : Tuple; 564 Tuple!Params values; 565 566 bool check(Params args) { 567 bool changed; 568 foreach (i, arg; args) { 569 if (values[i] !is arg) { 570 values[i] = arg; 571 changed = true; 572 } 573 } 574 return changed; 575 } 576 } 577 578 /// 579 unittest { 580 581 class A { } 582 583 CalcSaver!(uint, double[], A) saver; 584 585 uint x = 5; 586 double[] arr = [1, 2, 3]; 587 A a = new A(); 588 589 assert(saver.check(x, arr, a)); 590 // values are not changing 591 assert(!saver.check(x, arr, a)); 592 assert(!saver.check(x, arr, a)); 593 assert(!saver.check(x, arr, a)); 594 assert(!saver.check(x, arr, a)); 595 596 x = 8; 597 arr ~= 25; 598 a = new A(); 599 // values are changed 600 assert(saver.check(x, arr, a)); 601 assert(!saver.check(x, arr, a)); 602 } 603 604 605 //================================================================================ 606 // some utility functions 607 608 /** conversion from wchar z-string */ 609 wstring fromWStringz(const(wchar[]) s) { 610 if (s is null) 611 return null; 612 int i = 0; 613 while(s[i]) 614 i++; 615 return cast(wstring)(s[0..i].dup); 616 } 617 618 /** conversion from wchar z-string */ 619 wstring fromWStringz(const(wchar) * s) { 620 if (s is null) 621 return null; 622 int i = 0; 623 while(s[i]) 624 i++; 625 return cast(wstring)(s[0..i].dup); 626 } 627 628 /** Deprecated: use std.uni.toUpper instead. 629 Uppercase unicode character. 630 */ 631 deprecated dchar dcharToUpper(dchar ch) { 632 static import std.uni; 633 return std.uni.toUpper(ch); 634 } 635 636 /// decodes hex digit (0..9, a..f, A..F), returns uint.max if invalid 637 uint parseHexDigit(T)(T ch) pure nothrow { 638 if (ch >= '0' && ch <= '9') 639 return ch - '0'; 640 else if (ch >= 'a' && ch <= 'f') 641 return ch - 'a' + 10; 642 else if (ch >= 'A' && ch <= 'F') 643 return ch - 'A' + 10; 644 return uint.max; 645 } 646 647 /// replacement of deprecated std.utf.toUTF8 648 string toUTF8(dstring str) { 649 import std.utf : encode, codeLength, byUTF; 650 char[] buf; 651 buf.length = codeLength!char(str); 652 int pos = 0; 653 foreach(ch; str.byUTF!char) { 654 buf.ptr[pos++] = ch; 655 } 656 return cast(string)buf; 657 } 658 659 /// normalize end of line style - convert to '\n' 660 dstring normalizeEndOfLineCharacters(dstring s) { 661 bool crFound = false; 662 foreach(ch; s) { 663 if (ch == '\r') { 664 crFound = true; 665 break; 666 } 667 } 668 if (!crFound) 669 return s; 670 dchar[] res; 671 res.reserve(s.length); 672 dchar prevCh = 0; 673 foreach(ch; s) { 674 if (ch == '\r') { 675 res ~= '\n'; 676 } else if (ch == '\n') { 677 if (prevCh != '\r') 678 res ~= '\n'; 679 } else { 680 res ~= ch; 681 } 682 prevCh = ch; 683 } 684 return cast(dstring)res; 685 } 686 687 /// C malloc allocated array wrapper 688 struct MallocBuf(T) { 689 import core.stdc.stdlib : realloc, free; 690 private T * _allocated; 691 private uint _allocatedSize; 692 private uint _length; 693 /// get pointer 694 @property T * ptr() { return _allocated; } 695 /// get length 696 @property uint length() { return _length; } 697 /// set new length 698 @property void length(uint len) { 699 if (len > _allocatedSize) { 700 reserve(_allocatedSize ? len * 2 : len); 701 } 702 _length = len; 703 } 704 /// const array[index]; 705 T opIndex(uint index) const { 706 assert(index < _length); 707 return _allocated[index]; 708 } 709 /// ref array[index]; 710 ref T opIndex(uint index) { 711 assert(index < _length); 712 return _allocated[index]; 713 } 714 /// array[index] = value; 715 void opIndexAssign(uint index, T value) { 716 assert(index < _length); 717 _allocated[index] = value; 718 } 719 /// array[index] = value; 720 void opIndexAssign(uint index, T[] values) { 721 assert(index + values.length < _length); 722 _allocated[index .. index + values.length] = values[]; 723 } 724 /// array[a..b] 725 T[] opSlice(uint a, uint b) { 726 assert(a <= b && b <= _length); 727 return _allocated[a .. b]; 728 } 729 /// array[] 730 T[] opSlice() { 731 return _allocated ? _allocated[0 .. _length] : null; 732 } 733 /// array[$] 734 uint opDollar() { return _length; } 735 ~this() { 736 clear(); 737 } 738 /// free allocated memory, set length to 0 739 void clear() { 740 if (_allocated) 741 free(_allocated); 742 _allocatedSize = 0; 743 _length = 0; 744 } 745 /// make sure buffer capacity is at least (size) items 746 void reserve(uint size) { 747 if (_allocatedSize < size) { 748 _allocated = cast(T*)realloc(_allocated, T.sizeof * size); 749 _allocatedSize = size; 750 } 751 } 752 /// fill buffer with specified value 753 void fill(T value) { 754 if (_length) { 755 _allocated[0 .. _length] = value; 756 } 757 } 758 }