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 }