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) if (op == "+") {
54 return Point(x + v.x, y + v.y);
55 }
56 Point opBinary(string op)(Point v) if (op == "-") {
57 return Point(x - v.x, y - v.y);
58 }
59 int opCmp(ref const Point b) const {
60 if (x == b.x) return y - b.y;
61 return x - b.x;
62 }
63 }
64
65 /** 2D rectangle
66 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
67 */
68 struct Rect {
69 /// x coordinate of top left corner
70 int left;
71 /// y coordinate of top left corner
72 int top;
73 /// x coordinate of bottom right corner (non-inclusive)
74 int right;
75 /// y coordinate of bottom right corner (non-inclusive)
76 int bottom;
77 /// returns average of left, right
78 @property int middlex() { return (left + right) / 2; }
79 /// returns average of top, bottom
80 @property int middley() { return (top + bottom) / 2; }
81 /// returns middle point
82 @property Point middle() { return Point(middlex, middley); }
83
84 /// returns top left point of rectangle
85 @property Point topLeft() { return Point(left, top); }
86 /// returns bottom right point of rectangle
87 @property Point bottomRight() { return Point(right, bottom); }
88
89 /// returns size (width, height) in Point
90 @property Point size() { return Point(right - left, bottom - top); }
91
92 /// add offset to horizontal and vertical coordinates
93 void offset(int dx, int dy) {
94 left += dx;
95 right += dx;
96 top += dy;
97 bottom += dy;
98 }
99 /// expand rectangle dimensions
100 void expand(int dx, int dy) {
101 left -= dx;
102 right += dx;
103 top -= dy;
104 bottom += dy;
105 }
106 /// shrink rectangle dimensions
107 void shrink(int dx, int dy) {
108 left += dx;
109 right -= dx;
110 top += dy;
111 bottom -= dy;
112 }
113 /// for all fields, sets this.field to rc.field if rc.field > this.field
114 void setMax(Rect rc) {
115 if (left < rc.left)
116 left = rc.left;
117 if (right < rc.right)
118 right = rc.right;
119 if (top < rc.top)
120 top = rc.top;
121 if (bottom < rc.bottom)
122 bottom = rc.bottom;
123 }
124 /// returns width of rectangle (right - left)
125 @property int width() { return right - left; }
126 /// returns height of rectangle (bottom - top)
127 @property int height() { return bottom - top; }
128 /// constructs rectangle using left, top, right, bottom coordinates
129 this(int x0, int y0, int x1, int y1) {
130 left = x0;
131 top = y0;
132 right = x1;
133 bottom = y1;
134 }
135 /// constructs rectangle using two points - (left, top), (right, bottom) coordinates
136 this(Point pt0, Point pt1) {
137 left = pt0.x;
138 top = pt0.y;
139 right = pt1.x;
140 bottom = pt1.y;
141 }
142 /// returns true if rectangle is empty (right <= left || bottom <= top)
143 @property bool empty() {
144 return right <= left || bottom <= top;
145 }
146 /// translate rectangle coordinates by (x,y) - add deltax to x coordinates, and deltay to y coordinates
147 alias moveBy = offset;
148 /// moves this rect to fit rc bounds, retaining the same size
149 void moveToFit(ref Rect rc) {
150 if (right > rc.right)
151 moveBy(rc.right - right, 0);
152 if (bottom > rc.bottom)
153 moveBy(0, rc.bottom - bottom);
154 if (left < rc.left)
155 moveBy(rc.left - left, 0);
156 if (top < rc.top)
157 moveBy(0, rc.top - top);
158
159 }
160 /// updates this rect to intersection with rc, returns true if result is non empty
161 bool intersect(Rect rc) {
162 if (left < rc.left)
163 left = rc.left;
164 if (top < rc.top)
165 top = rc.top;
166 if (right > rc.right)
167 right = rc.right;
168 if (bottom > rc.bottom)
169 bottom = rc.bottom;
170 return right > left && bottom > top;
171 }
172 /// returns true if this rect has nonempty intersection with rc
173 bool intersects(Rect rc) {
174 if (rc.left >= right || rc.top >= bottom || rc.right <= left || rc.bottom <= top)
175 return false;
176 return true;
177 }
178 /// returns true if point is inside of this rectangle
179 bool isPointInside(Point pt) {
180 return pt.x >= left && pt.x < right && pt.y >= top && pt.y < bottom;
181 }
182 /// returns true if point is inside of this rectangle
183 bool isPointInside(int x, int y) {
184 return x >= left && x < right && y >= top && y < bottom;
185 }
186 /// this rectangle is completely inside rc
187 bool isInsideOf(Rect rc) {
188 return left >= rc.left && right <= rc.right && top >= rc.top && bottom <= rc.bottom;
189 }
190
191 bool opEquals(Rect rc) const {
192 return left == rc.left && right == rc.right && top == rc.top && bottom == rc.bottom;
193 }
194 }
195
196 /// constant acting as "rectangle not set" value
197 immutable Rect RECT_VALUE_IS_NOT_SET = Rect(int.min, int.min, int.min, int.min);
198
199 /// widget state bit flags
200 enum State : uint {
201 /// state not specified / normal
202 Normal = 4 | 256, // Normal is Enabled
203 /// pressed (e.g. clicked by mouse)
204 Pressed = 1,
205 /// widget has focus
206 Focused = 2,
207 /// widget can process mouse and key events
208 Enabled = 4,
209 /// mouse pointer is over this widget
210 Hovered = 8, // mouse pointer is over control, buttons not pressed
211 /// widget is selected
212 Selected = 16,
213 /// widget can be checked
214 Checkable = 32,
215 /// widget is checked
216 Checked = 64,
217 /// widget is activated
218 Activated = 128,
219 /// window is focused
220 WindowFocused = 256,
221 /// widget is default control for form (should be focused when window gains focus first time)
222 Default = 512, // widget is default for form (e.g. default button will be focused on show)
223 /// widget has been focused by keyboard navigation
224 KeyboardFocused = 1024,
225 /// return state of parent instead of widget's state when requested
226 Parent = 0x10000, // use parent's state
227 }
228
229
230 // Layout size constants
231 /// layout option, to occupy all available place
232 enum int FILL_PARENT = 0x4000_0000;
233 /// layout option, for size based on content
234 enum int WRAP_CONTENT = 0x2000_0000;
235 /// use as widget.layout() param to avoid applying of parent size
236 enum int SIZE_UNSPECIFIED = 0x6000_0000;
237
238 /// use in styles to specify size in points (1/72 inch)
239 enum int SIZE_IN_POINTS_FLAG = 0x1000_0000;
240 /// (RESERVED) use in styles to specify size in percents * 100 (e.g. 0 == 0%, 10000 == 100%, 100 = 1%)
241 enum int SIZE_IN_PERCENTS_FLAG = 0x0800_0000;
242
243
244 /// convert custom size to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set)
245 int toPixels(int sz) {
246 if (sz > 0 && (sz & SIZE_IN_POINTS_FLAG) != 0) {
247 return pointsToPixels(sz ^ SIZE_IN_POINTS_FLAG);
248 }
249 return sz;
250 }
251
252 /// convert custom size Point to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set)
253 Point toPixels(const Point sz) {
254 return Point(toPixels(sz.x), toPixels(sz.y));
255 }
256
257 /// convert custom size Rect to pixels (sz can be either pixels, or points if SIZE_IN_POINTS_FLAG bit set)
258 Rect toPixels(const Rect sz) {
259 return Rect(toPixels(sz.left), toPixels(sz.top), toPixels(sz.right), toPixels(sz.bottom));
260 }
261
262 /// make size value with SIZE_IN_POINTS_FLAG set
263 int makePointSize(int pt) {
264 return pt | SIZE_IN_POINTS_FLAG;
265 }
266
267 /// make size value with SIZE_IN_PERCENTS_FLAG set
268 int makePercentSize(int percent) {
269 return (percent * 100) | SIZE_IN_PERCENTS_FLAG;
270 }
271
272 /// make size value with SIZE_IN_PERCENTS_FLAG set
273 int makePercentSize(double percent) {
274 return cast(int)(percent * 100) | SIZE_IN_PERCENTS_FLAG;
275 }
276
277 /// returns true for WRAP_CONTENT, WRAP_CONTENT, SIZE_UNSPECIFIED
278 bool isSpecialSize(int sz) {
279 // don't forget to update if more special constants added
280 return (sz & (WRAP_CONTENT | FILL_PARENT | SIZE_UNSPECIFIED)) != 0;
281 }
282
283 /// returns true if size has SIZE_IN_PERCENTS_FLAG bit set
284 bool isPercentSize(int size) {
285 return (size & SIZE_IN_PERCENTS_FLAG) != 0;
286 }
287
288 /// if size has SIZE_IN_PERCENTS_FLAG bit set, returns percent of baseSize, otherwise returns size unchanged
289 int fromPercentSize(int size, int baseSize) {
290 if (isPercentSize(size))
291 return cast(int)(cast(long)(size & ~SIZE_IN_PERCENTS_FLAG) * baseSize / 10000);
292 return size;
293 }
294
295 /// screen dots per inch
296 private __gshared int PRIVATE_SCREEN_DPI = 96;
297
298 @property int SCREEN_DPI() {
299 return PRIVATE_SCREEN_DPI;
300 }
301
302 @property void SCREEN_DPI(int dpi) {
303 static if (BACKEND_CONSOLE) {
304 PRIVATE_SCREEN_DPI = dpi;
305 } else {
306 if (dpi >= 72 && dpi <= 500) {
307 if (PRIVATE_SCREEN_DPI != dpi) {
308 // changed DPI
309 PRIVATE_SCREEN_DPI = dpi;
310 }
311 }
312 }
313 }
314
315 /// one point is 1/72 of inch
316 enum POINTS_PER_INCH = 72;
317
318 /// convert length in points (1/72in units) to pixels according to SCREEN_DPI
319 int pointsToPixels(int pt) {
320 return pt * SCREEN_DPI / POINTS_PER_INCH;
321 }
322
323 /// rectangle coordinates in points (1/72in units) to pixels according to SCREEN_DPI
324 Rect pointsToPixels(Rect rc) {
325 return Rect(rc.left.pointsToPixels, rc.top.pointsToPixels, rc.right.pointsToPixels, rc.bottom.pointsToPixels);
326 }
327
328 /// convert points (1/72in units) to pixels according to SCREEN_DPI
329 int pixelsToPoints(int px) {
330 return px * POINTS_PER_INCH / SCREEN_DPI;
331 }
332
333 /// Subpixel rendering mode for fonts (aka ClearType)
334 enum SubpixelRenderingMode : ubyte {
335 /// no sub
336 None,
337 /// subpixel rendering on, subpixel order on device: B,G,R
338 BGR,
339 /// subpixel rendering on, subpixel order on device: R,G,B
340 RGB,
341 }
342
343 /**
344 Character glyph.
345
346 Holder for glyph metrics as well as image.
347 */
348 align(1)
349 struct Glyph
350 {
351 /// 0: width of glyph black box
352 ushort blackBoxX;
353
354 @property ushort correctedBlackBoxX() { return subpixelMode ? blackBoxX / 3 : blackBoxX; }
355
356 /// 2: height of glyph black box
357 ubyte blackBoxY;
358 /// 3: X origin for glyph
359 byte originX;
360 /// 4: Y origin for glyph
361 byte originY;
362
363 /// 5: full width of glyph
364 ubyte width;
365 /// 6: subpixel rendering mode - if !=SubpixelRenderingMode.None, glyph data contains 3 bytes per pixel instead of 1
366 SubpixelRenderingMode subpixelMode;
367 /// 7: usage flag, to handle cleanup of unused glyphs
368 ubyte lastUsage;
369 static if (ENABLE_OPENGL) {
370 /// 8: unique id of glyph (for drawing in hardware accelerated scenes)
371 uint id;
372 }
373
374 ///< 12: glyph data, arbitrary size (blackBoxX * blackBoxY)
375 ubyte[] glyph;
376 }
377
378 /**
379 Base class for reference counted objects, maintains reference counter inplace.
380
381 If some class is not inherited from RefCountedObject, additional object will be required to hold counters.
382 */
383 class RefCountedObject {
384 /// count of references to this object from Ref
385 protected int _refCount;
386 /// returns current value of reference counter
387 @property int refCount() const { return _refCount; }
388 /// increments reference counter
389 void addRef() {
390 _refCount++;
391 }
392 /// decrement reference counter, destroy object if no more references left
393 void releaseRef() {
394 if (--_refCount == 0)
395 destroy(this);
396 }
397 ~this() {}
398 }
399
400 /**
401 Reference counting support.
402
403 Implemented for case when T is RefCountedObject.
404 Similar to shared_ptr in C++.
405 Allows to share object, destroying it when no more references left.
406
407 Useful for automatic destroy of objects.
408 */
409 struct Ref(T) { // if (T is RefCountedObject)
410 private T _data;
411 alias get this;
412 /// returns true if object is not assigned
413 @property bool isNull() const { return _data is null; }
414 /// returns counter of references
415 @property int refCount() const { return _data !is null ? _data.refCount : 0; }
416 /// init from T
417 this(T data) {
418 _data = data;
419 if (_data !is null)
420 _data.addRef();
421 }
422 /// after blit
423 this(this) {
424 if (_data !is null)
425 _data.addRef();
426 }
427 /// assign from another refcount by reference
428 ref Ref opAssign(ref Ref data) {
429 if (data._data == _data)
430 return this;
431 if (_data !is null)
432 _data.releaseRef();
433 _data = data._data;
434 if (_data !is null)
435 _data.addRef();
436 return this;
437 }
438 /// assign from another refcount by value
439 ref Ref opAssign(Ref data) {
440 if (data._data == _data)
441 return this;
442 if (_data !is null)
443 _data.releaseRef();
444 _data = data._data;
445 if (_data !is null)
446 _data.addRef();
447 return this;
448 }
449 /// assign object
450 ref Ref opAssign(T data) {
451 if (data == _data)
452 return this;
453 if (_data !is null)
454 _data.releaseRef();
455 _data = data;
456 if (_data !is null)
457 _data.addRef();
458 return this;
459 }
460 /// clears reference
461 void clear() {
462 if (_data !is null) {
463 _data.releaseRef();
464 _data = null;
465 }
466 }
467 /// returns object reference (null if not assigned)
468 @property T get() {
469 return _data;
470 }
471 /// returns const reference from const object
472 @property const(T) get() const {
473 return _data;
474 }
475 /// decreases counter, and destroys object if no more references left
476 ~this() {
477 if (_data !is null)
478 _data.releaseRef();
479 }
480 }
481
482
483 //================================================================================
484 // some utility functions
485
486 /** conversion from wchar z-string */
487 wstring fromWStringz(const(wchar[]) s) {
488 if (s is null)
489 return null;
490 int i = 0;
491 while(s[i])
492 i++;
493 return cast(wstring)(s[0..i].dup);
494 }
495
496 /** conversion from wchar z-string */
497 wstring fromWStringz(const(wchar) * s) {
498 if (s is null)
499 return null;
500 int i = 0;
501 while(s[i])
502 i++;
503 return cast(wstring)(s[0..i].dup);
504 }
505
506 /** Deprecated: use std.uni.toUpper instead.
507 Uppercase unicode character.
508 */
509 deprecated dchar dcharToUpper(dchar ch) {
510 static import std.uni;
511 return std.uni.toUpper(ch);
512 }
513
514 /// decodes hex digit (0..9, a..f, A..F), returns uint.max if invalid
515 uint parseHexDigit(T)(T ch) pure nothrow {
516 if (ch >= '0' && ch <= '9')
517 return ch - '0';
518 else if (ch >= 'a' && ch <= 'f')
519 return ch - 'a' + 10;
520 else if (ch >= 'A' && ch <= 'F')
521 return ch - 'A' + 10;
522 return uint.max;
523 }
524
525 /// replacement of deprecated std.utf.toUTF8
526 string toUTF8(dstring str) {
527 import std.utf : encode, codeLength, byUTF;
528 char[] buf;
529 buf.length = codeLength!char(str);
530 int pos = 0;
531 foreach(ch; str.byUTF!char) {
532 buf.ptr[pos++] = ch;
533 }
534 return cast(string)buf;
535 }