iv.egra: low-level gfx and high-level GUI library
[iv.d.git] / egra / gfx / base.d
blobf6aad29c45e5630f08a54015854348cee142769e
1 /*
2 * Simple Framebuffer Gfx/GUI lib
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv.egra.gfx.base /*is aliced*/;
20 private:
22 import iv.alice;
23 import iv.bclamp;
24 import iv.cmdcon;
26 import iv.egra.gfx.config;
29 // ////////////////////////////////////////////////////////////////////////// //
30 package __gshared int VBufWidth = 740*2;
31 package __gshared int VBufHeight = 520*2;
32 package __gshared ubyte vbufEffScale = 1; // effective (current) window scale
34 package __gshared uint* vglTexBuf; // OpenGL texture buffer
37 // ////////////////////////////////////////////////////////////////////////// //
38 public @property int screenEffScale () nothrow @trusted @nogc { pragma(inline, true); return vbufEffScale; }
39 public @property void screenEffScale (int scale) nothrow @trusted @nogc { pragma(inline, true); if (scale < 1) scale = 1; if (scale > 32) scale = 32; vbufEffScale = cast(ubyte)scale; }
41 public @property int screenWidth () nothrow @trusted @nogc { pragma(inline, true); return VBufWidth; }
42 public @property int screenHeight () nothrow @trusted @nogc { pragma(inline, true); return VBufHeight; }
44 public @property int screenWidthScaled () nothrow @trusted @nogc { pragma(inline, true); return VBufWidth*vbufEffScale; }
45 public @property int screenHeightScaled () nothrow @trusted @nogc { pragma(inline, true); return VBufHeight*vbufEffScale; }
48 // ////////////////////////////////////////////////////////////////////////// //
49 public enum GxDir {
50 Horiz,
51 Vert,
55 // ////////////////////////////////////////////////////////////////////////// //
56 public struct GxPoint {
57 public:
58 int x, y;
60 nothrow @safe @nogc:
61 this() (in auto ref GxPoint p) pure { pragma(inline, true); x = p.x; y = p.y; }
62 this (in int ax, in int ay) pure { pragma(inline, true); x = ax; y = ay; }
64 bool inside() (in auto ref GxRect rc) pure const { pragma(inline, true); return rc.inside(this); }
65 bool inside() (in auto ref GxSize sz) pure const { pragma(inline, true); return sz.inside(this); }
67 bool opEquals() (in auto ref GxPoint p) pure const { pragma(inline, true); return ((p.x^x)|(p.y^y)); }
69 int opCmp() (in auto ref GxPoint p) pure const {
70 pragma(inline, true);
71 return
72 y < p.y ? -1 :
73 y > p.y ? +1 :
74 x < p.x ? -1 :
75 x > p.x ? +1 :
79 void opAssign() (in auto ref GxPoint p) { pragma(inline, true); x = p.x; y = p.y; }
81 void opOpAssign(string op) (in int v) if (op == "+" || op == "-" || op == "*" || op == "/") {
82 pragma(inline, true);
83 mixin("x"~op~"=v; y"~op~"=v;");
86 void opOpAssign(string op) (in auto ref GxPoint pt) if (op == "+" || op == "-") {
87 pragma(inline, true);
88 mixin("x"~op~"=pt.x; y"~op~"=pt.y;");
91 GxPoint opBinary(string op) (in auto ref GxPoint pt) pure const if (op == "+" || op == "-") {
92 pragma(inline, true);
93 mixin("return GxPoint(x"~op~"pt.x, y"~op~"pt.y);");
96 GxPoint opBinary(string op) (in auto ref GxSize sz) pure const if (op == "+" || op == "-") {
97 pragma(inline, true);
98 mixin("return GxPoint(x"~op~"sz.w, y"~op~"sz.h);");
101 GxPoint opBinary(string op) (in int v) pure const if (op == "+" || op == "-") {
102 pragma(inline, true);
103 mixin("return GxPoint(x"~op~"v, y"~op~"v);");
106 int opIndex (in GxDir dir) pure const { pragma(inline, true); return (dir == GxDir.Horiz ? x : y); }
108 void opIndexAssign (in int v, in GxDir dir) { pragma(inline, true); if (dir == GxDir.Horiz) x = v; else y = v; }
110 void opIndexOpAssign(string op) (in int v, in GxDir dir) if (op == "+" || op == "-") {
111 pragma(inline, true);
112 if (dir == GxDir.Horiz) mixin("x "~op~"= v;"); else mixin("y "~op~"= v;");
117 // ////////////////////////////////////////////////////////////////////////// //
118 public struct GxSize {
119 public:
120 int w, h;
122 nothrow @safe @nogc:
123 @property bool valid () pure const { pragma(inline, true); return (w >= 0 && h >= 0); }
124 @property bool empty () pure const { pragma(inline, true); return (w < 1 || h < 1); }
126 bool inside() (in auto ref GxPoint pt) pure const { pragma(inline, true); return (pt.x >= 0 && pt.y >= 0 && pt.x < w && pt.y < h); }
128 void sanitize () { pragma(inline, true); if (w < 0) w = 0; if (h < 0) h = 0; }
130 void opOpAssign(string op) (in int v) if (op == "+" || op == "-" || op == "*" || op == "/") {
131 pragma(inline, true);
132 mixin("w"~op~"=v; h"~op~"=v;");
135 void opOpAssign(string op) (in auto ref GxSize sz) if (op == "+" || op == "-") {
136 pragma(inline, true);
137 mixin("w"~op~"=sz.w; h"~op~"=sz.h;");
140 GxSize opBinary(string op) (in auto ref GxSize sz) pure const if (op == "+" || op == "-") {
141 pragma(inline, true);
142 mixin("return GxSize(w"~op~"sz.w, h"~op~"sz.h);");
145 int opIndex (in GxDir dir) pure const { pragma(inline, true); return (dir == GxDir.Horiz ? w : h); }
147 void opIndexAssign (in int v, in GxDir dir) { pragma(inline, true); if (dir == GxDir.Horiz) w = v; else h = v; }
149 //FIXME: overflow!
150 void opIndexOpAssign(string op) (in int v, in GxDir dir) if (op == "+" || op == "-") {
151 pragma(inline, true);
152 if (dir == GxDir.Horiz) {
153 mixin("w "~op~"= v;");
154 if (w < 0) w = 0;
155 } else {
156 mixin("h "~op~"= v;");
157 if (h < 0) h = 0;
163 // ////////////////////////////////////////////////////////////////////////// //
164 public struct GxRect {
165 public:
166 GxPoint pos;
167 GxSize size = GxSize(-1, -1); // <0: invalid rect
169 string toString () const @trusted nothrow {
170 if (valid) {
171 import core.stdc.stdio : snprintf;
172 char[128] buf = void;
173 return buf[0..snprintf(buf.ptr, buf.length, "(%d,%d)-(%d,%d)", pos.x, pos.y, pos.x+size.w-1, pos.y+size.h-1)].idup;
174 } else {
175 return "(invalid-rect)";
179 nothrow @safe @nogc:
180 this() (in auto ref GxRect rc) pure {
181 pragma(inline, true);
182 pos = rc.pos;
183 size = rc.size;
186 this (in int ax0, in int ay0, in int awidth, in int aheight) pure {
187 pragma(inline, true);
188 pos.x = ax0;
189 pos.y = ay0;
190 size.w = awidth;
191 size.h = aheight;
194 this() (in int ax0, in int ay0, in auto ref GxSize asize) pure {
195 pragma(inline, true);
196 pos.x = ax0;
197 pos.y = ay0;
198 size = asize;
201 this() (in auto ref GxPoint xy0, in int awidth, in int aheight) pure {
202 pragma(inline, true);
203 pos = xy0;
204 size.w = awidth;
205 size.h = aheight;
208 this() (in auto ref GxPoint xy0, in auto ref GxSize asize) pure {
209 pragma(inline, true);
210 pos = xy0;
211 size = asize;
214 this() (in auto ref GxPoint xy0, in auto ref GxPoint xy1) pure {
215 pragma(inline, true);
216 pos = xy0;
217 size.w = xy1.x-xy0.x+1;
218 size.h = xy1.y-xy0.y+1;
221 this (in int awidth, in int aheight) pure {
222 pragma(inline, true);
223 pos.x = pos.y = 0;
224 size.w = awidth;
225 size.h = aheight;
228 this() (in auto ref GxSize asize) pure {
229 pragma(inline, true);
230 pos.x = pos.y = 0;
231 size = asize;
234 void setCoords(bool doSwap=true) (in int ax0, in int ay0, in int ax1, in int ay1) {
235 pragma(inline, true);
236 static if (doSwap) {
237 pos.x = (ax0 < ax1 ? ax0 : ax1);
238 pos.y = (ay0 < ay1 ? ay0 : ay1);
239 size.w = (ax0 < ax1 ? ax1 : ax0)-pos.x+1;
240 size.h = (ay0 < ay1 ? ay1 : ay0)-pos.y+1;
241 } else {
242 pos.x = ax0;
243 pos.y = ay0;
244 size.w = ax1-ax0+1;
245 size.h = ay1-ay0+1;
249 void setCoords(bool doSwap=true) (in auto ref GxPoint p0, in int ax1, in int ay1) { pragma(inline, true); setCoords!doSwap(p0.x, p0.y, ax1, ay1); }
250 void setCoords(bool doSwap=true) (in int ax0, in int ay0, in auto ref GxPoint p1) { pragma(inline, true); setCoords!doSwap(ax0, ay0, p1.x, p1.y); }
251 void setCoords(bool doSwap=true) (in auto ref GxPoint p0, in auto ref GxPoint p1) { pragma(inline, true); setCoords!doSwap(p0.x, p0.y, p1.x, p1.y); }
253 void opAssign() (in auto ref GxRect rc) { pragma(inline, true); pos = rc.pos; size = rc.size; }
255 bool opEquals() (in auto ref GxRect rc) pure const { pragma(inline, true); return (pos == rc.pos && size == rc.size); }
257 int opCmp() (in auto ref GxRect p) pure const { pragma(inline, true); return pos.opCmp(p.pos); }
259 @property bool valid () pure const { pragma(inline, true); return size.valid; }
260 @property bool invalid () pure const { pragma(inline, true); return !size.valid; }
261 @property bool empty () pure const { pragma(inline, true); return size.empty; } // invalid rects are empty
263 void invalidate () { pragma(inline, true); size.w = size.h = -1; }
265 @property int left () pure const { pragma(inline, true); return pos.x; }
266 @property void left (in int v) { pragma(inline, true); pos.x = v; }
268 @property int top () pure const { pragma(inline, true); return pos.y; }
269 @property void top (in int v) { pragma(inline, true); pos.y = v; }
271 @property int right () pure const { pragma(inline, true); return pos.x+size.w-1; }
272 @property void right (in int v) { pragma(inline, true); size.w = v-pos.x+1; }
274 @property int bottom () pure const { pragma(inline, true); return pos.y+size.h-1; }
275 @property void bottom (in int v) { pragma(inline, true); size.h = v-pos.y+1; }
277 @property GxPoint lefttop () pure const { pragma(inline, true); return pos; }
278 @property GxPoint righttop () pure const { pragma(inline, true); return pos+GxSize(size.w-1, 0); }
279 @property GxPoint leftbottom () pure const { pragma(inline, true); return pos+GxSize(0, size.h-1); }
280 @property GxPoint rightbottom () pure const { pragma(inline, true); return pos+size-1; }
282 @property void lefttop() (in auto ref GxPoint p) { pragma(inline, true); setCoords!false(p, rightbottom); }
283 @property void rightbottom() (in auto ref GxPoint p) { pragma(inline, true); setCoords!false(lefttop, p); }
285 alias topleft = lefttop;
286 alias topright = righttop;
287 alias bottomleft = leftbottom;
288 alias bottomright = rightbottom;
290 @property int x0 () pure const { pragma(inline, true); return pos.x; }
291 @property int y0 () pure const { pragma(inline, true); return pos.y; }
293 @property void x0 (in int val) { pragma(inline, true); setCoords!false(val, pos.y, rightbottom); }
294 @property void y0 (in int val) { pragma(inline, true); setCoords!false(pos.x, val, rightbottom); }
296 @property int x1 () pure const { pragma(inline, true); return (width > 0 ? x0+width-1 : x0-1); }
297 @property int y1 () pure const { pragma(inline, true); return (height > 0 ? y0+height-1 : y0-1); }
299 @property void x1 (in int val) { pragma(inline, true); width = val-x0+1; }
300 @property void y1 (in int val) { pragma(inline, true); height = val-y0+1; }
302 @property int width () pure const { pragma(inline, true); return size.w; }
303 @property int height () pure const { pragma(inline, true); return size.h; }
305 @property void width (in int val) { pragma(inline, true); size.w = val; }
306 @property void height (in int val) { pragma(inline, true); size.h = val; }
308 // is point inside this rect?
309 bool inside() (in auto ref GxPoint p) pure const {
310 pragma(inline, true);
311 return (width > 0 && height > 0 ? (p.x >= x0 && p.y >= y0 && p.x < x0+width && p.y < y0+height) : false);
314 // is point inside this rect?
315 bool inside (in int ax, in int ay) pure const {
316 pragma(inline, true);
317 return (width > 0 && height > 0 ? (ax >= x0 && ay >= y0 && ax < x0+width && ay < y0+height) : false);
320 // is `r` inside `this`?
321 bool contains() (in auto ref GxRect r) pure const {
322 pragma(inline, true);
323 return
324 width > 0 && height > 0 &&
325 r.width > 0 && r.height > 0 &&
326 r.x0 >= x0 && r.y0 >= y0 &&
327 r.x0+r.width <= x0+width && r.y0+r.height <= y0+height;
330 // does `r` and `this` overlap?
331 bool overlaps() (in auto ref GxRect r) pure const {
332 pragma(inline, true);
333 return
334 width > 0 && height > 0 &&
335 r.width > 0 && r.height > 0 &&
336 x0 < r.x0+r.width && r.x0 < x0+width &&
337 y0 < r.y0+r.height && r.y0 < y0+height;
340 // extend `this` so it will include `p`
341 void include() (in auto ref GxPoint p) {
342 pragma(inline, true);
343 if (empty) {
344 x0 = p.x;
345 y0 = p.y;
346 width = 1;
347 height = 1;
348 } else {
349 if (p.x < x0) x0 = p.x0;
350 if (p.y < y0) y0 = p.y0;
351 if (p.x1 > x1) x1 = p.x1;
352 if (p.y1 > y1) y1 = p.y1;
356 // extend `this` so it will include `r`
357 void include() (in auto ref GxRect r) {
358 pragma(inline, true);
359 if (!r.empty) {
360 if (empty) {
361 x0 = r.x;
362 y0 = r.y;
363 width = r.width;
364 height = r.height;
365 } else {
366 if (r.x < x0) x0 = r.x0;
367 if (r.y < y0) y0 = r.y0;
368 if (r.x1 > x1) x1 = r.x1;
369 if (r.y1 > y1) y1 = r.y1;
374 // clip `this` so it will not be larger than `r`
375 // returns `false` if the resulting rect (this) is empty or invalid
376 bool intersect (in int rx0, in int ry0, in int rwdt, in int rhgt) {
377 if (rwdt < 0 || rhgt < 0 || invalid) { size.w = size.h = -1; return false; }
378 if (rwdt == 0 || rhgt == 0 || empty) { size.w = size.h = 0; return false; }
379 immutable int rx1 = rx0+rwdt-1;
380 immutable int ry1 = ry0+rhgt-1;
381 if (ry1 < y0 || rx1 < x0 || rx0 > x1 || ry0 > y1) { size.w = size.h = 0; return false; }
382 // rc is at least partially inside this rect
383 if (x0 < rx0) x0 = rx0;
384 if (y0 < ry0) y0 = ry0;
385 if (x1 > rx1) x1 = rx1;
386 if (y1 > ry1) y1 = ry1;
387 assert(!empty); // yeah, always
388 return true;
391 // clip `this` so it will not be larger than `r`
392 // returns `false` if the resulting rect (this) is empty or invalid
393 bool intersect (in int rwdt, in int rhgt) {
394 pragma(inline, true);
395 return intersect(0, 0, rwdt, rhgt);
398 // clip `this` so it will not be larger than `r`
399 // returns `false` if the resulting rect (this) is empty or invalid
400 bool intersect() (in auto ref GxRect r) {
401 pragma(inline, true);
402 return intersect(r.x0, r.y0, r.width, r.height);
405 void shrinkBy (in int dx, in int dy) {
406 pragma(inline, true);
407 if ((dx|dy) && valid) {
408 pos.x += dx;
409 pos.y += dy;
410 size.w -= dx<<1;
411 size.h -= dy<<1;
415 void shrinkBy() (in auto ref GxSize sz) { pragma(inline, true); shrinkBy(sz.w, sz.h); }
417 void growBy (in int dx, in int dy) {
418 pragma(inline, true);
419 if ((dx|dy) && valid) {
420 pos.x -= dx;
421 pos.y -= dy;
422 size.w += dx<<1;
423 size.h += dy<<1;
427 void growBy() (in auto ref GxSize sz) { pragma(inline, true); growBy(sz.w, sz.h); }
429 void set (in int ax0, in int ay0, in int awidth, in int aheight) {
430 pragma(inline, true);
431 x0 = ax0;
432 y0 = ay0;
433 width = awidth;
434 height = aheight;
437 void set() (in auto ref GxPoint p0, in int awidth, in int aheight) { pragma(inline, true); set(p0.x, p0.y, awidth, aheight); }
438 void set() (in auto ref GxPoint p0, in auto ref GxSize asize) { pragma(inline, true); set(p0.x, p0.y, asize.w, asize.h); }
439 void set() (in int ax0, in int ay0, in auto ref GxSize asize) { pragma(inline, true); set(ax0, ay0, asize.w, asize.h); }
441 void moveLeftTopBy (in int dx, in int dy) {
442 pragma(inline, true);
443 pos.x += dx;
444 pos.y += dy;
445 size.w -= dx;
446 size.h -= dy;
449 void moveLeftTopBy() (in auto ref GxPoint p) { pragma(inline, true); moveLeftTopBy(p.x, p.y); }
451 alias moveTopLeftBy = moveLeftTopBy;
453 void moveRightBottomBy (in int dx, in int dy) {
454 pragma(inline, true);
455 size.w += dx;
456 size.h += dy;
459 void moveRightBottomBy() (in auto ref GxPoint p) { pragma(inline, true); moveRightBottomBy(p.x, p.y); }
461 alias moveBottomRightBy = moveRightBottomBy;
463 void moveBy (in int dx, in int dy) {
464 pragma(inline, true);
465 pos.x += dx;
466 pos.y += dy;
469 void moveBy() (in auto ref GxPoint p) { pragma(inline, true); moveBy(p.x, p.y); }
471 void moveTo (in int nx, in int ny) {
472 pragma(inline, true);
473 x0 = nx;
474 y0 = ny;
477 void moveTo() (in auto ref GxPoint p) { pragma(inline, true); moveTo(p.x, p.y); }
480 * clip (x,y,wdt) stripe to this rect
482 * Params:
483 * x = stripe start (not relative to rect)
484 * y = stripe start (not relative to rect)
485 * wdt = stripe length
487 * Returns:
488 * x = fixed x (invalid if result is false)
489 * wdt = fixed length (invalid if result is false)
490 * leftSkip = how much cells skipped at the left side (invalid if result is false)
491 * result = false if stripe is completely clipped out
493 bool clipHStripe (ref int x, int y, ref int wdt, int* leftSkip=null) const @trusted {
494 if (empty) return false;
495 if (wdt <= 0 || y < y0 || y >= y0+height || x >= x0+width) return false;
496 if (x < x0) {
497 // left clip
498 immutable int dx = x0-x;
499 if (dx >= wdt) return false; // avoid overflow
500 if (leftSkip !is null) *leftSkip = dx;
501 wdt -= dx;
502 x = x0;
503 assert(wdt > 0); // yeah, always
505 if (wdt > width) wdt = width; // avoid overflow
506 if (x+wdt > x0+width) {
507 // right clip
508 wdt = x0+width-x;
509 assert(wdt > 0); // yeah, always
511 return true;
514 bool clipHStripe (ref GxPoint p, ref int wdt, int* leftSkip=null) const @trusted {
515 pragma(inline, true);
516 return clipHStripe(ref p.x, p.y, ref wdt, leftSkip);
520 * clip (x,y,hgt) stripe to this rect
522 * Params:
523 * x = stripe start (not relative to rect)
524 * y = stripe start (not relative to rect)
525 * hgt = stripe length
527 * Returns:
528 * y = fixed y (invalid if result is false)
529 * hgt = fixed length (invalid if result is false)
530 * topSkip = how much cells skipped at the top side (invalid if result is false)
531 * result = false if stripe is completely clipped out
533 bool clipVStripe (int x, ref int y, ref int hgt, int* topSkip=null) const @trusted {
534 if (empty) return false;
535 if (hgt <= 0 || x < x0 || x >= x0+width || y >= y0+height) return false;
536 if (y < y0) {
537 // top clip
538 immutable int dy = y0-y;
539 if (dy >= hgt) return false; // avoid overflow
540 if (topSkip !is null) *topSkip = dy;
541 hgt -= dy;
542 y = y0;
543 assert(hgt > 0); // yeah, always
545 if (hgt > height) hgt = height; // avoid overflow
546 if (y+hgt > y0+height) {
547 // bottom clip
548 hgt = y0+height-y;
549 assert(hgt > 0); // yeah, always
551 return true;
554 bool clipVStripe (ref GxPoint p, ref int hgt, int* topSkip=null) const @trusted {
555 pragma(inline, true);
556 return clipVStripe(p.x, ref p.y, ref hgt, topSkip);
559 bool clipHVStripes (ref int x, ref int y, ref int wdt, ref int hgt, int* leftSkip=null, int* topSkip=null) const @trusted {
560 if (empty || wdt <= 0 || hgt <= 0) return false;
561 if (y >= y0+height || x >= x0+width) return false;
562 // use dummy `x` and `y` for horizontal and vertical clippers, because they are only checked for validity there
563 if (!clipHStripe(ref x, y0, ref wdt, leftSkip)) return false;
564 return clipVStripe(x0, ref y, ref hgt, topSkip);
567 bool clipHVStripes (ref GxPoint p, ref int wdt, ref int hgt, int* leftSkip=null, int* topSkip=null) const @trusted {
568 pragma(inline, true);
569 return clipHVStripes(ref p.x, ref p.y, ref wdt, ref hgt, leftSkip, topSkip);
572 bool clipHVStripes (ref GxPoint p, ref GxSize sz, int* leftSkip=null, int* topSkip=null) const @trusted {
573 pragma(inline, true);
574 return clipHVStripes(ref p.x, ref p.y, ref sz.w, ref sz.h, leftSkip, topSkip);
577 bool clipHVStripes (ref int x, ref int y, ref GxSize sz, int* leftSkip=null, int* topSkip=null) const @trusted {
578 pragma(inline, true);
579 return clipHVStripes(ref x, ref y, ref sz.w, ref sz.h, leftSkip, topSkip);
584 // ////////////////////////////////////////////////////////////////////////// //
585 public bool gxIsTransparent (in uint clr) pure nothrow @safe @nogc { pragma(inline, true); return ((clr&0xff000000u) == 0x00000000u); }
586 public bool gxIsSolid (in uint clr) pure nothrow @safe @nogc { pragma(inline, true); return ((clr&0xff000000u) == 0xff000000u); }
588 public bool gxIsSolidBlack (in uint clr) pure nothrow @safe @nogc { pragma(inline, true); return (clr == 0xff000000u); }
590 public ubyte gxGetRed (in uint clr) pure nothrow @safe @nogc { pragma(inline, true); return cast(ubyte)(clr>>16); }
591 public ubyte gxGetGreen (in uint clr) pure nothrow @safe @nogc { pragma(inline, true); return cast(ubyte)(clr>>8); }
592 public ubyte gxGetBlue (in uint clr) pure nothrow @safe @nogc { pragma(inline, true); return cast(ubyte)clr; }
593 public ubyte gxGetAlpha (in uint clr) pure nothrow @safe @nogc { pragma(inline, true); return cast(ubyte)(clr>>24); }
595 public enum gxSolidBlack = 0xff000000u;
596 public enum gxSolidWhite = 0xffffffffu;
598 public enum gxTransparent = 0x00000000u;
599 public enum gxColorMask = 0x00ffffffu;
601 public enum gxUnknown = 0x00010203u;
604 // ////////////////////////////////////////////////////////////////////////// //
605 // mix dc with ARGB (or ABGR) clr; dc A is ignored (removed)
606 // main code never calls this with solid or transparent `colvar`
607 enum GxColMixMixin(string destvar, string dcvar, string colvar) = `{
608 immutable uint col_ = `~colvar~`;
609 immutable uint dc_ = (`~dcvar~`)&0xffffff;
610 /*immutable uint a_ = 256-(col_>>24);*/ /* to not loose bits */
611 immutable uint a_ = (col_>>24);
612 immutable uint srb_ = (col_&0xff00ff);
613 immutable uint sg_ = (col_&0x00ff00);
614 immutable uint drb_ = (dc_&0xff00ff);
615 immutable uint dg_ = (dc_&0x00ff00);
616 immutable uint orb_ = (drb_+(((srb_-drb_)*a_+0x800080)>>8))&0xff00ff;
617 immutable uint og_ = (dg_+(((sg_-dg_)*a_+0x008000)>>8))&0x00ff00;
618 (`~destvar~`) = orb_|og_;
621 public uint gxColMix (in uint dc, in uint clr) pure nothrow @trusted @nogc {
622 pragma(inline, true);
623 if (gxIsSolid(clr)) return clr;
624 else if (gxIsTransparent(clr)) return dc;
625 else {
626 uint res = void;
627 mixin(GxColMixMixin!("res", "dc", "clr"));
628 return res;
633 // ////////////////////////////////////////////////////////////////////////// //
634 private template isGoodRGBInt(T) {
635 import std.traits : Unqual;
636 alias TT = Unqual!T;
637 enum isGoodRGBInt =
638 is(TT == ubyte) ||
639 is(TT == short) || is(TT == ushort) ||
640 is(TT == int) || is(TT == uint) ||
641 is(TT == long) || is(TT == ulong);
645 // ////////////////////////////////////////////////////////////////////////// //
646 public uint gxrgb(T0, T1, T2) (T0 r, T1 g, T2 b) pure nothrow @trusted @nogc
647 if (isGoodRGBInt!T0 && isGoodRGBInt!T1 && isGoodRGBInt!T2)
649 pragma(inline, true);
650 return (clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b)|0xff000000u;
653 public uint gxrgba(T0, T1, T2, T3) (T0 r, T1 g, T2 b, T3 a) pure nothrow @trusted @nogc
654 if (isGoodRGBInt!T0 && isGoodRGBInt!T1 && isGoodRGBInt!T2 && isGoodRGBInt!T3)
656 pragma(inline, true);
657 return (clampToByte(a)<<24)|(clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b);
661 public enum gxRGB(int r, int g, int b) = (clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b)|0xff000000u;
662 public enum gxRGBA(int r, int g, int b, int a) = (clampToByte(a)<<24)|(clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b);
665 // ////////////////////////////////////////////////////////////////////////// //
666 // current clip rect
667 public __gshared GxRect gxClipRect = GxRect(65535, 65535);
669 public void gxWithSavedClip(DG) (scope DG dg)
670 if (is(typeof((inout int=0) { DG dg = void; dg(); })))
672 pragma(inline, true);
673 if (dg !is null) {
674 immutable rc = gxClipRect;
675 scope(exit) gxClipRect = rc;
676 dg();
680 public void gxClipReset () nothrow @trusted @nogc {
681 pragma(inline, true);
682 gxClipRect = GxRect(65535, 65535);
686 // ////////////////////////////////////////////////////////////////////////// //
687 public void gxClearScreen (uint clr) nothrow @trusted @nogc {
688 clr &= gxColorMask; // only solid color matters here anyway
689 if (clr == 0u) {
690 import core.stdc.string : memset;
691 memset(vglTexBuf, 0, (VBufWidth*VBufHeight)<<2);
692 } else {
693 vglTexBuf[0..VBufWidth*VBufHeight] = clr;
698 public void gxPutPixel (in int x, in int y, in uint c) nothrow @trusted @nogc {
699 pragma(inline, true);
700 if (x >= 0 && y >= 0 && x < VBufWidth && y < VBufHeight && !gxIsTransparent(c) && gxClipRect.inside(x, y)) {
701 uint* dp = cast(uint*)(cast(ubyte*)vglTexBuf)+y*VBufWidth+x;
702 *dp = gxColMix(*dp, c);
707 public void gxPutPixel() (in auto ref GxPoint p, in uint c) nothrow @trusted @nogc {
708 pragma(inline, true);
709 if (p.x >= 0 && p.y >= 0 && p.x < VBufWidth && p.y < VBufHeight && !gxIsTransparent(c) && gxClipRect.inside(p)) {
710 uint* dp = cast(uint*)(cast(ubyte*)vglTexBuf)+p.y*VBufWidth+p.x;
711 *dp = (gxIsSolid(c) ? (c&gxColorMask) : gxColMix(*dp, c));
716 public void gxSetPixel (in int x, in int y, in uint c) nothrow @trusted @nogc {
717 pragma(inline, true);
718 if (x >= 0 && y >= 0 && x < VBufWidth && y < VBufHeight && !gxIsTransparent(c) && gxClipRect.inside(x, y)) {
719 *(cast(uint*)(cast(ubyte*)vglTexBuf)+y*VBufWidth+x) = c&gxColorMask;
724 public void gxSetPixel() (in auto ref GxPoint p, in uint c) nothrow @trusted @nogc {
725 pragma(inline, true);
726 if (p.x >= 0 && p.y >= 0 && p.x < VBufWidth && p.y < VBufHeight && !gxIsTransparent(c) && gxClipRect.inside(p)) {
727 *(cast(uint*)(cast(ubyte*)vglTexBuf)+p.y*VBufWidth+p.x) = c&gxColorMask;
732 // ////////////////////////////////////////////////////////////////////////// //
733 public void gxHLine (int x, int y, int w, in uint clr) nothrow @trusted @nogc {
734 if (gxIsTransparent(clr)) return;
735 if (!gxClipRect.clipHStripe(x, y, w)) return;
736 if (!GxRect(VBufWidth, VBufHeight).clipHStripe(x, y, w)) return;
737 if (gxIsSolid(clr)) {
738 immutable uint addr = y*VBufWidth+x;
739 if (gxIsSolidBlack(clr)) {
740 import core.stdc.string : memset;
741 memset(vglTexBuf+addr, 0, w<<2);
742 } else {
743 vglTexBuf[addr..addr+w] = clr;
745 } else {
746 uint* dptr = vglTexBuf+y*VBufWidth+x;
747 while (w-- > 0) {
748 mixin(GxColMixMixin!("*dptr++", "*dptr", "clr"));
753 public void gxHLine() (in auto ref GxPoint p, in int w, in uint clr) nothrow @trusted @nogc { pragma(inline, true); gxHLine(p.x, p.y, w, clr); }
755 public void gxVLine (int x, int y, int h, in uint clr) nothrow @trusted @nogc {
756 if (gxIsTransparent(clr)) return;
757 if (!gxClipRect.clipVStripe(x, y, h)) return;
758 if (!GxRect(VBufWidth, VBufHeight).clipVStripe(x, y, h)) return;
759 uint* dptr = vglTexBuf+y*VBufWidth+x;
760 if (gxIsSolid(clr)) {
761 while (h-- > 0) { *dptr = clr; dptr += VBufWidth; }
762 } else {
763 while (h-- > 0) { mixin(GxColMixMixin!("*dptr", "*dptr", "clr")); dptr += VBufWidth; }
767 public void gxVLine() (in auto ref GxPoint p, in int h, in uint clr) nothrow @trusted @nogc { pragma(inline, true); gxVLine(p.x, p.y, h, clr); }
770 // ////////////////////////////////////////////////////////////////////////// //
771 public void gxFillRect (int x, int y, int w, int h, in uint clr) nothrow @trusted @nogc {
772 if (gxIsTransparent(clr)) return;
773 if (!gxClipRect.clipHVStripes(x, y, w, h)) return;
774 if (!GxRect(VBufWidth, VBufHeight).clipHVStripes(x, y, w, h)) return;
775 if (gxIsSolid(clr)) {
776 uint addr = y*VBufWidth+x;
777 if (gxIsSolidBlack(clr)) {
778 import core.stdc.string : memset;
779 while (h-- > 0) {
780 memset(vglTexBuf+addr, 0, w<<2);
781 addr += VBufWidth;
783 } else {
784 while (h-- > 0) {
785 vglTexBuf[addr..addr+w] = clr;
786 addr += VBufWidth;
789 } else {
790 uint* dptr = vglTexBuf+y*VBufWidth+x;
791 immutable uint dinc = VBufWidth-w;
792 while (h-- > 0) {
793 foreach (immutable _; 0..w) {
794 mixin(GxColMixMixin!("*dptr++", "*dptr", "clr"));
796 dptr += dinc;
801 public void gxFillRect() (in auto ref GxRect rc, in uint clr) nothrow @trusted @nogc {
802 pragma(inline, true);
803 gxFillRect(rc.x0, rc.y0, rc.width, rc.height, clr);
806 public void gxDrawRect (in int x, in int y, in int w, in int h, in uint clr) nothrow @trusted @nogc {
807 if (w < 1 || h < 1 || gxIsTransparent(clr)) return;
808 gxHLine(x, y, w, clr);
809 if (h > 1) gxHLine(x, y+h-1, w, clr);
810 if (h > 2) {
811 gxVLine(x, y+1, h-2, clr);
812 if (w > 1) gxVLine(x+w-1, y+1, h-2, clr);
816 public void gxDrawRect() (in auto ref GxRect rc, in uint clr) nothrow @trusted @nogc {
817 pragma(inline, true);
818 gxDrawRect(rc.x0, rc.y0, rc.width, rc.height, clr);
822 // ////////////////////////////////////////////////////////////////////////// //
823 // use clip region as boundaries
824 public void gxDrawShadow (in GxRect winrect) nothrow @trusted @nogc {
825 if (winrect.empty) return;
826 gxWithSavedClip{
827 //immutable GxRect rc = gxClipRect;
828 gxClipReset();
829 gxFillRect(winrect.x1+1, winrect.y0+8, 8, winrect.height-8, gxRGBA!(0, 0, 0, 127));
830 gxFillRect(winrect.x0+8, winrect.y1+1, winrect.width, 8, gxRGBA!(0, 0, 0, 127));
835 public void gxDrawWindow (in GxRect winrect,
836 const(char)[] title, in uint framecolor, in uint titlecolor,
837 in uint titlebackcolor, in uint windowcolor) nothrow @trusted
839 import iv.egra.gfx.text;
841 if (winrect.empty) return;
842 gxDrawShadow(winrect);
844 gxFillRect(winrect, windowcolor);
845 gxDrawRect(winrect, framecolor);
847 if (title is null) return;
848 if (winrect.width <= 2 || winrect.height <= 2) return;
849 gxWithSavedClip{
850 immutable int hgt = (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+1);
851 if (gxClipRect.intersect(winrect.x0+1, winrect.y0+1, winrect.width-2, hgt)) {
852 gxFillRect(gxClipRect, titlebackcolor);
853 gxDrawTextUtf(winrect.x0+1+(winrect.width-2-gxTextWidthUtf(title))/2, winrect.y0+1+(hgt-gxTextHeightUtf)/2, title, titlecolor);
859 // ////////////////////////////////////////////////////////////////////////// //
860 public void gxDrawScrollBar() (in auto ref GxRect r, in int max, in int value) nothrow @trusted @nogc { pragma(inline, true); gxDrawScrollBar(r, 0, max, value); }
862 public void gxDrawScrollBar() (in auto ref GxRect r, int min, int max, int value) nothrow @trusted @nogc {
863 enum FrameColor = gxRGB!(220, 220, 220);
864 enum EmptyColor = gxRGB!(0, 0, 0);
865 enum FullColor = gxRGB!(160, 160, 160);
866 if (r.empty) return;
867 if (max <= min) min = max = value = 0;
868 // move it to 0
869 //conwriteln("00: min=", min, "; max=", max, "; value=", value);
870 max -= min;
871 value -= min;
872 if (value < 0) value = 0; else if (value > max) value = max;
873 //conwriteln("01: min=", min, "; max=", max, "; value=", value);
874 int sx0 = r.x0+1;
875 int sy0 = r.y0+1;
876 int wdt = r.width-2;
877 int hgt = r.height-2;
878 bool vert = (r.width < r.height);
879 // frame
880 if ((vert && wdt > 1) || (!vert && hgt > 1)) {
881 gxHLine(r.x0+1, r.y0+0, wdt, FrameColor);
882 gxVLine(r.x0+0, r.y0+1, hgt, FrameColor);
883 gxVLine(r.x1+0, r.y0+1, hgt, FrameColor);
884 gxHLine(r.x0+1, r.y1+0, wdt, FrameColor);
885 } else {
886 sx0 -= 1;
887 sy0 -= 1;
888 wdt += 2;
889 hgt += 2;
891 if (max <= 0) {
892 gxFillRect(sx0, sy0, wdt, hgt, FullColor);
893 return;
895 if (vert) {
896 int pix = hgt*value/max;
897 if (pix > hgt) pix = hgt; // just in case
898 gxFillRect(sx0, sy0, wdt, pix, FullColor);
899 gxFillRect(sx0, sy0+pix, wdt, hgt-pix, EmptyColor);
900 } else {
901 int pix = wdt*value/max;
902 if (pix > wdt) pix = wdt; // just in case
903 gxFillRect(sx0, sy0, pix, hgt, FullColor);
904 gxFillRect(sx0+pix, sy0, wdt-pix, hgt, EmptyColor);
909 // ////////////////////////////////////////////////////////////////////////// //
910 private int abs (int a) pure nothrow @safe @nogc { pragma(inline, true); return (a < 0 ? -a : a); }
912 public void gxCircle (in int cx, in int cy, in int radius, in uint clr) nothrow @trusted @nogc {
913 static void plot4points (in int cx, in int cy, in int x, in int y, in uint clr) nothrow @trusted @nogc {
914 pragma(inline, true);
915 gxPutPixel(cx+x, cy+y, clr);
916 if (x) gxPutPixel(cx-x, cy+y, clr);
917 if (y) gxPutPixel(cx+x, cy-y, clr);
918 gxPutPixel(cx-x, cy-y, clr);
921 if (radius <= 0 || gxIsTransparent(clr)) return;
922 if (radius == 1) { gxPutPixel(cx, cy, clr); return; }
923 int error = -radius, x = radius, y = 0;
924 while (x > y) {
925 plot4points(cx, cy, x, y, clr);
926 plot4points(cx, cy, y, x, clr);
927 error += y*2+1;
928 ++y;
929 if (error >= 0) { --x; error -= x*2; }
931 plot4points(cx, cy, x, y, clr);
934 public void gxCircle() (in auto ref GxPoint c, in int radius, in uint clr) nothrow @trusted @nogc { pragma(inline, true); gxCircle(c.x, c.y, radius, clr); }
937 public void gxFillCircle (in int cx, in int cy, in int radius, in uint clr) nothrow @trusted @nogc {
938 if (radius <= 0 || gxIsTransparent(clr)) return;
939 if (radius == 1) { gxPutPixel(cx, cy, clr); return; }
940 int error = -radius, x = radius, y = 0;
941 while (x >= y) {
942 int last_y = y;
943 error += y;
944 ++y;
945 error += y;
946 gxHLine(cx-x, cy+last_y, 2*x+1, clr);
947 if (x != 0 && last_y != 0) gxHLine(cx-x, cy-last_y, 2*x+1, clr);
948 if (error >= 0) {
949 if (x != last_y) {
950 gxHLine(cx-last_y, cy+x, 2*last_y+1, clr);
951 if (last_y != 0 && x != 0) gxHLine(cx-last_y, cy-x, 2*last_y+1, clr);
953 error -= x;
954 --x;
955 error -= x;
960 public void gxFillCircle() (in auto ref GxPoint c, in int radius, in uint clr) nothrow @trusted @nogc { pragma(inline, true); gxFillCircle(c.x, c.y, radius, clr); }
963 public void gxEllipse (int x0, int y0, int x1, int y1, in uint clr) nothrow @trusted @nogc {
964 if (gxIsTransparent(clr)) return;
965 if (y0 == y1) { gxHLine(x0, y0, x1-x0+1, clr); return; }
966 if (x0 == x1) { gxVLine(x0, y0, y1-y0+1, clr); return; }
967 int a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; // values of diameter
968 long dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; // error increment
969 long err = dx+dy+b1*a*a; // error of 1.step
970 if (x0 > x1) { x0 = x1; x1 += a; } // if called with swapped points...
971 if (y0 > y1) y0 = y1; // ...exchange them
972 y0 += (b+1)/2; y1 = y0-b1; // starting pixel
973 a *= 8*a; b1 = 8*b*b;
974 do {
975 long e2;
976 gxPutPixel(x1, y0, clr); // I. Quadrant
977 gxPutPixel(x0, y0, clr); // II. Quadrant
978 gxPutPixel(x0, y1, clr); // III. Quadrant
979 gxPutPixel(x1, y1, clr); // IV. Quadrant
980 e2 = 2*err;
981 if (e2 >= dx) { ++x0; --x1; err += dx += b1; } // x step
982 if (e2 <= dy) { ++y0; --y1; err += dy += a; } // y step
983 } while (x0 <= x1);
984 while (y0-y1 < b) {
985 // too early stop of flat ellipses a=1
986 gxPutPixel(x0-1, ++y0, clr); // complete tip of ellipse
987 gxPutPixel(x0-1, --y1, clr);
991 public void gxEllipse() (in auto ref GxRect rc, in int radius, in uint clr) nothrow @trusted @nogc { pragma(inline, true); gxEllipse(rc.x0, rc.y0, rc.x1, rc.y1, clr); }
994 public void gxFillEllipse (int x0, int y0, int x1, int y1, in uint clr) nothrow @trusted @nogc {
995 if (gxIsTransparent(clr)) return;
996 if (y0 == y1) { gxHLine(x0, y0, x1-x0+1, clr); return; }
997 if (x0 == x1) { gxVLine(x0, y0, y1-y0+1, clr); return; }
998 int a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; // values of diameter
999 long dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; // error increment
1000 long err = dx+dy+b1*a*a; // error of 1.step
1001 int prev_y0 = -1, prev_y1 = -1;
1002 if (x0 > x1) { x0 = x1; x1 += a; } // if called with swapped points...
1003 if (y0 > y1) y0 = y1; // ...exchange them
1004 y0 += (b+1)/2; y1 = y0-b1; // starting pixel
1005 a *= 8*a; b1 = 8*b*b;
1006 do {
1007 long e2;
1008 if (y0 != prev_y0) { gxHLine(x0, y0, x1-x0+1, clr); prev_y0 = y0; }
1009 if (y1 != y0 && y1 != prev_y1) { gxHLine(x0, y1, x1-x0+1, clr); prev_y1 = y1; }
1010 e2 = 2*err;
1011 if (e2 >= dx) { ++x0; --x1; err += dx += b1; } // x step
1012 if (e2 <= dy) { ++y0; --y1; err += dy += a; } // y step
1013 } while (x0 <= x1);
1014 while (y0-y1 < b) {
1015 // too early stop of flat ellipses a=1
1016 gxPutPixel(x0-1, ++y0, clr); // complete tip of ellipse
1017 gxPutPixel(x0-1, --y1, clr);
1021 public void gxFillEllipse() (in auto ref GxRect rc, in int radius, in uint clr) nothrow @trusted @nogc { pragma(inline, true); gxFillEllipse(rc.x0, rc.y0, rc.x1, rc.y1, clr); }
1024 // ////////////////////////////////////////////////////////////////////////// //
1025 public void gxDrawRoundedRect (int x0, int y0, int wdt, int hgt, int radius, in uint clr) nothrow @trusted @nogc {
1026 static void gxArcs (int cx, int cy, int wdt, int hgt, in int radius, in uint clr) nothrow @trusted @nogc {
1027 static void plot4points (in int radius, in int wdt, in int hgt, in int cx, in int cy, in int x, in int y, in uint clr) nothrow @trusted @nogc {
1028 pragma(inline, true);
1029 gxPutPixel(cx+wdt-1+x, cy+hgt-1+y, clr);
1030 gxPutPixel(cx-x, cy+hgt-1+y, clr);
1031 gxPutPixel(cx+wdt-1+x, cy-y, clr);
1032 gxPutPixel(cx-x, cy-y, clr);
1035 wdt -= (radius<<1); if (wdt <= 0) return;
1036 hgt -= (radius<<1); if (hgt <= 0) return;
1037 cx += radius; cy += radius;
1038 int error = -radius, x = radius, y = 0;
1039 while (x > y) {
1040 plot4points(radius, wdt, hgt, cx, cy, x, y, clr);
1041 plot4points(radius, wdt, hgt, cx, cy, y, x, clr);
1042 error += y*2+1;
1043 ++y;
1044 if (error >= 0) { --x; error -= x*2; }
1046 if (x || y != radius) plot4points(radius, wdt, hgt, cx, cy, x, y, clr);
1050 if (wdt < 1 || hgt < 1) return;
1051 if (radius < 1) { gxDrawRect(x0, y0, wdt, hgt, clr); return; }
1052 if (gxIsTransparent(clr)) return;
1053 if (hgt == 1) { gxHLine(x0, y0, wdt, clr); return; }
1054 if (wdt == 1) { gxVLine(x0, y0, hgt, clr); return; }
1055 // fix radius
1056 immutable int minsz = (wdt < hgt ? wdt : hgt);
1057 if (radius >= (minsz>>1)) {
1058 radius = (minsz>>1)-1;
1059 if (radius < 1) { gxDrawRect(x0, y0, wdt, hgt, clr); return; }
1061 // draw the parts of the rect
1062 gxHLine(x0+radius+1, y0, wdt-(radius<<1)-2, clr); // top
1063 gxHLine(x0+radius+1, y0+hgt-1, wdt-(radius<<1)-2, clr); // bottom
1064 gxVLine(x0, y0+radius+1, hgt-(radius<<1)-2, clr); // left
1065 gxVLine(x0+wdt-1, y0+radius+1, hgt-(radius<<1)-2, clr); // right
1066 // left arc
1067 gxArcs(x0, y0, wdt, hgt, radius, clr);
1070 public void gxDrawRoundedRect() (in auto ref GxRect rc, in int radius, in uint clr) nothrow @trusted @nogc {
1071 pragma(inline, true);
1072 gxDrawRoundedRect(rc.x0, rc.y0, rc.width, rc.height, radius, clr);
1076 // ////////////////////////////////////////////////////////////////////////// //
1077 __gshared usize frectXCoords; // array of ints
1078 __gshared usize frectXCSize; // in items
1081 /* cyclic dependency
1082 shared static ~this () {
1083 if (frectXCoords) {
1084 import core.stdc.stdlib : free;
1085 free(cast(void*)frectXCoords);
1086 frectXCoords = 0;
1092 int* ensureXCoords (int radius) nothrow @trusted @nogc {
1093 if (radius < 1) return null;
1094 if (radius > 1024*1024) return null;
1095 if (cast(usize)radius > frectXCSize) {
1096 import core.stdc.stdlib : realloc;
1097 immutable usize newsz = (cast(usize)radius|0x7fu)+1;
1098 void* np = realloc(cast(void*)frectXCoords, newsz*int.sizeof);
1099 if (np is null) return null; // out of memory
1100 frectXCSize = newsz;
1101 frectXCoords = cast(usize)np;
1103 return cast(int*)frectXCoords;
1107 // this is wrong, but i'm ok with it for now
1108 public void gxFillRoundedRect (int x0, int y0, int wdt, int hgt, int radius, in uint clr) nothrow @trusted @nogc {
1109 if (wdt < 1 || hgt < 1) return;
1110 if (radius < 1) { gxFillRect(x0, y0, wdt, hgt, clr); return; }
1111 if (gxIsTransparent(clr)) return;
1112 if (hgt == 1) { gxHLine(x0, y0, wdt, clr); return; }
1113 if (wdt == 1) { gxVLine(x0, y0, hgt, clr); return; }
1114 // fix radius
1115 immutable int minsz = (wdt < hgt ? wdt : hgt);
1116 if (radius >= (minsz>>1)) {
1117 radius = (minsz>>1)-1;
1118 if (radius < 1) { gxFillRect(x0, y0, wdt, hgt, clr); return; }
1121 // create border coords
1122 auto xpt = ensureXCoords(radius+1);
1123 if (xpt is null) { gxFillRect(x0, y0, wdt, hgt, clr); return; } // do at least something
1124 xpt[0..radius+1] = int.min;
1126 // create coords
1128 int error = -radius, x = radius, y = 0;
1129 while (x > y) {
1130 if (y <= radius && xpt[y] < x) xpt[y] = x;
1131 if (x >= 0 && x <= radius && xpt[x] < y) xpt[x] = y;
1132 error += y*2+1;
1133 ++y;
1134 if (error >= 0) { --x; error -= x*2; }
1136 if (y <= radius && xpt[y] < x) xpt[y] = x;
1137 if (x >= 0 && x <= radius && xpt[x] < y) xpt[x] = y;
1140 // draw the filled rect
1141 gxFillRect(x0, y0+radius+1, wdt, hgt-(radius<<1)-2, clr);
1143 // draw arc
1144 foreach (immutable dy; 0..radius+1) {
1145 if (xpt[dy] == int.min) continue;
1146 immutable topy = y0+radius-dy;
1147 immutable topx0 = x0+radius-xpt[dy];
1148 immutable topx1 = x0+wdt-radius-1+xpt[dy];
1149 //gxPutPixel(topx0, topy, clr);
1150 //gxPutPixel(topx1, topy, clr);
1151 gxHLine(topx0, topy, topx1-topx0+1, clr);
1152 immutable boty = y0+hgt-radius+dy-1;
1153 //gxPutPixel(topx0, boty, clr);
1154 //gxPutPixel(topx1, boty, clr);
1155 gxHLine(topx0, boty, topx1-topx0+1, clr);
1159 public void gxFillRoundedRect() (in auto ref GxRect rc, in int radius, in uint clr) nothrow @trusted @nogc {
1160 pragma(inline, true);
1161 gxFillRoundedRect(rc.x0, rc.y0, rc.width, rc.height, radius, clr);
1165 // ////////////////////////////////////////////////////////////////////////// //
1166 // bresenham with clipping
1167 // the idea is that we can simply skip the right number of steps
1168 // if the point is off the drawing area
1169 public void gxDrawLine (int x0, int y0, int x1, int y1, in uint clr, bool lastPoint=true) nothrow @trusted @nogc {
1170 enum swap(string a, string b) = "{immutable int tmp_="~a~";"~a~"="~b~";"~b~"=tmp_;}";
1172 if (gxIsTransparent(clr)) return;
1174 GxRect realClip = gxClipRect;
1175 if (!realClip.intersect(VBufWidth, VBufHeight)) return;
1177 // just a point?
1178 if (x0 == x1 && y0 == y1) {
1179 if (lastPoint) gxPutPixel(x0, y0, clr);
1180 return;
1183 // horizontal line?
1184 if (y0 == y1) {
1185 if (x0 > x1) {
1186 gxHLine(x1+(lastPoint ? 0 : 1), y0, x0-x1+(lastPoint ? 1 : 0), clr);
1187 } else {
1188 gxHLine(x0, y0, x1-x0+(lastPoint ? 1 : 0), clr);
1190 return;
1193 // clip rectange
1194 int wx0 = realClip.x0, wy0 = realClip.y0, wx1 = realClip.x1, wy1 = realClip.y1;
1195 if (wx0 > wx1 || wy0 > wy1) return; // this should not happen, but...
1197 // vertical setup; always go from top to bottom, so we'll draw the same line regardless of the starting point
1198 bool skipFirst = false;
1199 if (y0 > y1) {
1200 // swap endpoints
1201 if (!lastPoint) skipFirst = lastPoint = true;
1202 mixin(swap!("x0", "x1"));
1203 mixin(swap!("y0", "y1"));
1205 if (y0 > wy1 || y1 < wy0) return; // out of clip rectange
1206 int sty = 1; // "step sign" for x axis; we still need the var, because there is a possible swap down there
1208 // horizontal setup
1209 int stx = void; // "step sign" for x axis
1210 if (x0 < x1) {
1211 // from left to right
1212 if (x0 > wx1 || x1 < wx0) return; // out of clip rectange
1213 stx = 1; // going right
1214 } else {
1215 // from right to left
1216 if (x1 > wx1 || x0 < wx0) return; // out of clip rectange
1217 stx = -1; // going left
1218 x0 = -x0;
1219 x1 = -x1;
1220 wx0 = -wx0;
1221 wx1 = -wx1;
1222 mixin(swap!("wx0", "wx1"));
1225 int dsx = x1-x0; // "length" for x axis
1226 int dsy = y1-y0; // "length" for y axis
1227 int xd = void, yd = void; // current coord
1228 bool xyswapped = false; // if `true`, `xd` and `yd` are swapped
1229 if (dsx < dsy) {
1230 xyswapped = true;
1231 mixin(swap!("x0", "y0"));
1232 mixin(swap!("x1", "y1"));
1233 mixin(swap!("dsx", "dsy"));
1234 mixin(swap!("wx0", "wy0"));
1235 mixin(swap!("wx1", "wy1"));
1236 mixin(swap!("stx", "sty"));
1238 xd = x0;
1239 yd = y0;
1240 int dx2 = 2*dsx; // "double length" for x axis
1241 int dy2 = 2*dsy; // "double length" for y axis
1242 int e = 2*dsy-dsx; // "error" (as in bresenham algo)
1243 int term = x1; // termination point
1244 bool xfixed = false; // will be set if we properly fixed x0 coord while fixing the y0
1245 // note that clipping can overflow for insane coords
1246 // if you are completely sure that it can't happen, you can use `int` instead of `long`
1247 if (y0 < wy0) {
1248 // clip at top
1249 immutable long temp = cast(long)dx2*(wy0-y0)-dsx;
1250 xd += cast(int)(temp/dy2);
1251 if (xd > wx1) return; // x is moved out of clipping rect, nothing to do
1252 immutable int rem = cast(int)(temp%dy2);
1253 if (xd+(rem > 0 ? 1 : 0) >= wx0) {
1254 xfixed = true; // startx is inside the clipping rect, no need to perform left clip
1255 yd = wy0;
1256 e -= rem+dsx;
1257 if (rem > 0) { ++xd; e += dy2; }
1260 if (!xfixed && x0 < wx0) {
1261 // clip at left
1262 immutable long temp = cast(long)dy2*(wx0-x0);
1263 yd += cast(int)(temp/dx2);
1264 immutable int rem = cast(int)(temp%dx2);
1265 if (yd > wy1 || (yd == wy1 && rem >= dsx)) return; // y is moved out of clipping rect, nothing to do
1266 xd = wx0;
1267 e += rem;
1268 if (rem >= dsx) { ++yd; e -= dx2; }
1270 if (y1 > wy1) {
1271 // clip at bottom
1272 immutable long temp = cast(long)dx2*(wy1-y0)+dsx;
1273 term = x0+cast(int)(temp/dy2);
1274 // it should be safe to decrement here
1275 if (cast(int)(temp%dy2) == 0) --term;
1277 if (term > wx1) term = wx1; // clip at right
1279 if (sty == -1) yd = -yd;
1280 if (stx == -1) { xd = -xd; term = -term; }
1281 dx2 -= dy2;
1283 if (lastPoint) term += stx;
1284 if (skipFirst) {
1285 if (term == xd) return;
1286 if (e >= 0) { yd += sty; e -= dx2; } else { e += dy2; }
1287 xd += stx;
1290 // draw it; `putPixel()` can omit checks
1291 if (gxIsSolid(clr)) {
1292 while (xd != term) {
1293 // inlined putpixel
1294 *cast(uint*)((cast(ubyte*)vglTexBuf)+(xyswapped ? xd*VBufWidth+yd : yd*VBufWidth+xd)) = clr;
1295 // done drawing, move coords
1296 if (e >= 0) { yd += sty; e -= dx2; } else { e += dy2; }
1297 xd += stx;
1299 } else {
1300 while (xd != term) {
1301 // inlined putpixel
1302 uint* dp = cast(uint*)((cast(ubyte*)vglTexBuf)+(xyswapped ? xd*VBufWidth+yd : yd*VBufWidth+xd));
1303 mixin(GxColMixMixin!("*dp", "*dp", "clr"));
1304 // done drawing, move coords
1305 if (e >= 0) { yd += sty; e -= dx2; } else { e += dy2; }
1306 xd += stx;
1311 public void gxDrawLine() (in auto ref GxPoint p0, in int x1, in int y1, in uint clr, in bool lastPoint=true) { pragma(inline, true); gxDrawLine(p0.x, p0.y, x1, y1, clr, lastPoint); }
1312 public void gxDrawLine() (in int x0, in int y0, auto ref GxPoint p1, in uint clr, in bool lastPoint=true) { pragma(inline, true); gxDrawLine(x0, y0, p1.x, p1.y, clr, lastPoint); }
1313 public void gxDrawLine() (in auto ref GxPoint p0, auto ref GxPoint p1, in uint clr, in bool lastPoint=true) { pragma(inline, true); gxDrawLine(p0.x, p0.y, p1.x, p1.y, clr, lastPoint); }