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 }