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 }