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