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 }