cosmetix
[amper.git] / egfx / base.d
blob851503bcae25df8cace93844a3abf6cac193491b
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module egfx.base;
19 private:
21 import arsd.simpledisplay;
23 import iv.bclamp;
24 import iv.cmdcon;
27 // ////////////////////////////////////////////////////////////////////////// //
28 public __gshared int VBufWidth = 2;//740*2;
29 public __gshared int VBufHeight = 2;//520*2;
30 public __gshared ubyte vbufEffScale = 1; // effective (current) window scale
32 public __gshared uint* vglTexBuf; // OpenGL texture buffer
35 // ////////////////////////////////////////////////////////////////////////// //
36 public @property int winWidth () nothrow @trusted @nogc { pragma(inline, true); return VBufWidth; }
37 public @property int winHeight () nothrow @trusted @nogc { pragma(inline, true); return VBufHeight; }
39 public @property int winWidthScaled () nothrow @trusted @nogc { pragma(inline, true); return VBufWidth*vbufEffScale; }
40 public @property int winHeightScaled () nothrow @trusted @nogc { pragma(inline, true); return VBufHeight*vbufEffScale; }
43 // ////////////////////////////////////////////////////////////////////////// //
44 public struct GxPoint {
45 public:
46 int x, y; ///
48 pure nothrow @safe @nogc:
49 this() (in auto ref GxPoint p) { pragma(inline, true); x = p.x; y = p.y; } ///
50 this (int ax, int ay) { pragma(inline, true); x = ax; y = ay; } ///
51 void opAssign() (in auto ref GxPoint p) { pragma(inline, true); x = p.x; y = p.y; } ///
52 bool opEquals() (in auto ref GxPoint p) const { pragma(inline, true); return (p.x == x && p.y == y); } ///
53 ///
54 int opCmp() (in auto ref GxPoint p) const {
55 pragma(inline, true);
56 if (auto d0 = y-p.y) return (d0 < 0 ? -1 : 1);
57 else if (auto d1 = x-p.x) return (d1 < 0 ? -1 : 1);
58 else return 0;
62 public struct GxRect {
63 public:
64 int x0, y0; ///
65 int width = -1; // <0: invalid rect
66 int height = -1; // <0: invalid rect
68 alias left = x0; ///
69 alias top = y0; ///
70 alias right = x1; ///
71 alias bottom = y1; ///
73 ///
74 string toString () const @trusted nothrow {
75 if (valid) {
76 import core.stdc.stdio : snprintf;
77 char[128] buf = void;
78 return buf[0..snprintf(buf.ptr, buf.length, "(%d,%d)-(%d,%d)", x0, y0, x0+width-1, y0+height-1)].idup;
79 } else {
80 return "(invalid-rect)";
84 pure nothrow @safe @nogc:
85 ///
86 this() (in auto ref GxRect rc) { pragma(inline, true); x0 = rc.x0; y0 = rc.y0; width = rc.width; height = rc.height; } ///
88 ///
89 this (int ax0, int ay0, int awidth, int aheight) {
90 //pragma(inline, true);
91 x0 = ax0;
92 y0 = ay0;
93 width = awidth;
94 height = aheight;
97 ///
98 this() (in auto ref GxPoint xy0, int awidth, int aheight) {
99 pragma(inline, true);
100 x0 = xy0.x;
101 y0 = xy0.y;
102 width = awidth;
103 height = aheight;
107 this() (in auto ref GxPoint xy0, in auto ref GxPoint xy1) {
108 pragma(inline, true);
109 x0 = xy0.x;
110 y0 = xy0.y;
111 width = xy1.x-xy0.x+1;
112 height = xy1.y-xy0.y+1;
115 void opAssign() (in auto ref GxRect rc) { pragma(inline, true); x0 = rc.x0; y0 = rc.y0; width = rc.width; height = rc.height; } ///
116 bool opEquals() (in auto ref GxRect rc) const { pragma(inline, true); return (rc.x0 == x0 && rc.y0 == y0 && rc.width == width && rc.height == height); } ///
118 int opCmp() (in auto ref GxRect p) const {
119 if (auto d0 = y0-rc.y0) return (d0 < 0 ? -1 : 1);
120 if (auto d1 = x0-rc.x0) return (d1 < 0 ? -1 : 1);
121 if (auto d2 = width*height-rc.width*rc.height) return (d2 < 0 ? -1 : 1);
122 return 0;
125 @property bool valid () const { pragma(inline, true); return (width >= 0 && height >= 0); } ///
126 @property bool invalid () const { pragma(inline, true); return (width < 0 || height < 0); } ///
127 @property bool empty () const { pragma(inline, true); return (width <= 0 || height <= 0); } /// invalid rects are empty
129 void invalidate () { pragma(inline, true); width = height = -1; } ///
131 @property GxPoint lefttop () const { pragma(inline, true); return GxPoint(x0, y0); } ///
132 @property GxPoint righttop () const { pragma(inline, true); return GxPoint(x0+width-1, y0); } ///
133 @property GxPoint leftbottom () const { pragma(inline, true); return GxPoint(x0, y0+height-1); } ///
134 @property GxPoint rightbottom () const { pragma(inline, true); return GxPoint(x0+width-1, y0+height-1); } ///
136 alias topleft = lefttop; ///
137 alias topright = righttop; ///
138 alias bottomleft = leftbottom; ///
139 alias bottomright = rightbottom; ///
141 @property int x1 () const { pragma(inline, true); return (width > 0 ? x0+width-1 : x0-1); } ///
142 @property int y1 () const { pragma(inline, true); return (height > 0 ? y0+height-1 : y0-1); } ///
144 @property void x1 (in int val) { pragma(inline, true); width = val-x0+1; } ///
145 @property void y1 (in int val) { pragma(inline, true); height = val-y0+1; } ///
147 GxPoint translateToGlobal() (in auto ref GxPoint lpt) const {
148 pragma(inline, true);
149 return GxPoint(lpt.x+x0, lpt.y+y0);
152 GxRect translateToGlobal() (in auto ref GxRect lrc) const {
153 pragma(inline, true);
154 return GxRect(lrc.x0+x0, lrc.y0+y0, lrc.width, lrc.height);
158 bool inside() (in auto ref GxPoint p) const {
159 pragma(inline, true);
160 return (width > 0 && height > 0 ? (p.x >= x0 && p.y >= y0 && p.x < x0+width && p.y < y0+height) : false);
163 /// ditto
164 bool inside (in int ax, in int ay) const {
165 pragma(inline, true);
166 return (width > 0 && height > 0 ? (ax >= x0 && ay >= y0 && ax < x0+width && ay < y0+height) : false);
169 /// is `r` inside `this`?
170 bool contains() (in auto ref GxRect r) const {
171 pragma(inline, true);
172 return
173 width > 0 && height > 0 &&
174 r.width > 0 && r.height > 0 &&
175 r.x0 >= x0 && r.y0 >= y0 &&
176 r.x0+r.width <= x0+width && r.y0+r.height <= y0+height;
179 /// is `r` and `this` overlaps?
180 bool overlaps() (in auto ref GxRect r) const {
181 pragma(inline, true);
182 return
183 width > 0 && height > 0 &&
184 r.width > 0 && r.height > 0 &&
185 x0 < r.x0+r.width && r.x0 < x0+width &&
186 y0 < r.y0+r.height && r.y0 < y0+height;
189 /// extend `this` so it will include `r`
190 void include() (in auto ref GxRect r) {
191 pragma(inline, true);
192 if (!r.empty) {
193 if (empty) {
194 x0 = r.x;
195 y0 = r.y;
196 width = r.width;
197 height = r.height;
198 } else {
199 if (r.x < x0) x0 = r.x0;
200 if (r.y < y0) y0 = r.y0;
201 if (r.x1 > x1) x1 = r.x1;
202 if (r.y1 > y1) y1 = r.y1;
207 /// clip `this` so it will not be larger than `r`
208 bool intersect() (in auto ref GxRect r) {
209 if (r.invalid || invalid) { width = height = -1; return false; }
210 if (r.empty || empty) { width = height = 0; return false; }
211 if (r.y1 < y0 || r.x1 < x0 || r.x0 > x1 || r.y0 > y1) { width = height = 0; return false; }
212 // rc is at least partially inside this rect
213 if (x0 < r.x0) x0 = r.x0;
214 if (y0 < r.y0) y0 = r.y0;
215 if (x1 > r.x1) x1 = r.x1;
216 if (y1 > r.y1) y1 = r.y1;
217 assert(!empty); // yeah, always
218 return true;
222 void shrinkBy (int dx, int dy) {
223 pragma(inline, true);
224 if ((dx || dy) && valid) {
225 x0 += dx;
226 y0 += dy;
227 width -= dx*2;
228 height -= dy*2;
233 void growBy (int dx, int dy) {
234 pragma(inline, true);
235 if ((dx || dy) && valid) {
236 x0 -= dx;
237 y0 -= dy;
238 width += dx*2;
239 height += dy*2;
244 void set (int ax0, int ay0, int awidth, int aheight) {
245 pragma(inline, true);
246 x0 = ax0;
247 y0 = ay0;
248 width = awidth;
249 height = aheight;
253 void moveLeftTopBy (int dx, int dy) {
254 pragma(inline, true);
255 x0 += dx;
256 y0 += dy;
257 width -= dx;
258 height -= dy;
261 alias moveTopLeftBy = moveLeftTopBy; /// ditto
264 void moveRightBottomBy (int dx, int dy) {
265 pragma(inline, true);
266 width += dx;
267 height += dy;
270 alias moveBottomRightBy = moveRightBottomBy; /// ditto
273 void moveBy (int dx, int dy) {
274 pragma(inline, true);
275 x0 += dx;
276 y0 += dy;
280 void moveTo (int nx, int ny) {
281 pragma(inline, true);
282 x0 = nx;
283 y0 = ny;
287 * clip (x,y,len) stripe to this rect
289 * Params:
290 * x = stripe start (not relative to rect)
291 * y = stripe start (not relative to rect)
292 * len = stripe length
294 * Returns:
295 * x = fixed x (invalid if result is false)
296 * len = fixed length (invalid if result is false)
297 * leftSkip = how much cells skipped at the left side (invalid if result is false)
298 * result = false if stripe is completely clipped out
300 * TODO:
301 * overflows
303 bool clipHStripe (ref int x, int y, ref int len, int* leftSkip=null) const @trusted {
304 if (empty) return false;
305 if (len <= 0 || y < y0 || y >= y0+height || x >= x0+width) return false;
306 if (x < x0) {
307 // left clip
308 if (x+len <= x0) return false;
309 immutable int dx = x0-x;
310 if (leftSkip !is null) *leftSkip = dx;
311 len -= dx;
312 x = x0;
313 assert(len > 0); // yeah, always
315 if (x+len > x0+width) {
316 // right clip
317 len = x0+width-x;
318 assert(len > 0); // yeah, always
320 return true;
324 * clip (x,y,hgt) stripe to this rect
326 * Params:
327 * x = stripe start (not relative to rect)
328 * y = stripe start (not relative to rect)
329 * hgt = stripe length
331 * Returns:
332 * y = fixed y (invalid if result is false)
333 * hgt = fixed length (invalid if result is false)
334 * topSkip = how much cells skipped at the top side (invalid if result is false)
335 * result = false if stripe is completely clipped out
337 * TODO:
338 * overflows
340 bool clipVStripe (int x, ref int y, ref int hgt, int* topSkip=null) const @trusted {
341 if (empty) return false;
342 if (hgt <= 0 || x < x0 || x >= x0+width || y >= y0+height) return false;
343 if (y < y0) {
344 // top clip
345 if (y+hgt <= y0) return false;
346 immutable int dy = y0-y;
347 if (topSkip !is null) *topSkip = dy;
348 hgt -= dy;
349 y = y0;
350 assert(hgt > 0); // yeah, always
352 if (y+hgt > y0+height) {
353 // bottom clip
354 hgt = y0+height-y;
355 assert(hgt > 0); // yeah, always
357 return true;
361 bool clipHVStripes (ref int x, ref int y, ref int wdt, ref int hgt, int* leftSkip=null, int* topSkip=null) const @trusted {
362 if (empty || wdt <= 0 || hgt <= 0) return false;
363 if (y >= y0+height || x >= x0+width) return false;
364 if (x < x0) {
365 // left clip
366 if (x+wdt <= x0) return false;
367 immutable int dx = x0-x;
368 if (leftSkip !is null) *leftSkip = dx;
369 wdt -= dx;
370 x = x0;
371 assert(wdt > 0); // yeah, always
373 if (x+wdt > x0+width) {
374 // right clip
375 wdt = x0+width-x;
376 assert(wdt > 0); // yeah, always
379 if (y < y0) {
380 // top clip
381 if (y+hgt <= y0) return false;
382 immutable int dy = y0-y;
383 if (topSkip !is null) *topSkip = dy;
384 hgt -= dy;
385 y = y0;
386 assert(hgt > 0); // yeah, always
388 if (y+hgt > y0+height) {
389 // bottom clip
390 hgt = y0+height-y;
391 assert(hgt > 0); // yeah, always
394 return true;
399 // ////////////////////////////////////////////////////////////////////////// //
400 // mix dc with ARGB (or ABGR) col; dc A is ignored (removed)
401 enum GxColMixMixin(string destvar, string dcvar, string colvar) =
402 "{immutable uint col_ = "~colvar~";\n"~
403 " immutable uint dc_ = ("~dcvar~")&0xffffff;\n"~
404 " immutable uint a_ = 256-(col_>>24); /* to not loose bits */\n"~
405 " immutable uint srb_ = (col_&0xff00ff);\n"~
406 " immutable uint sg_ = (col_&0x00ff00);\n"~
407 " immutable uint drb_ = (dc_&0xff00ff);\n"~
408 " immutable uint dg_ = (dc_&0x00ff00);\n"~
409 " immutable uint orb_ = (drb_+(((srb_-drb_)*a_+0x800080)>>8))&0xff00ff;\n"~
410 " immutable uint og_ = (dg_+(((sg_-dg_)*a_+0x008000)>>8))&0x00ff00;\n"~
411 " ("~destvar~") = orb_|og_;}";
413 public uint gxColMix (uint dc, uint col) pure nothrow @trusted @nogc {
414 pragma(inline, true);
415 immutable uint a = 256-(col>>24); // to not loose bits
416 //immutable uint dc = (da)&0xffffff;
417 dc &= 0xffffff;
418 immutable uint srb = (col&0xff00ff);
419 immutable uint sg = (col&0x00ff00);
420 immutable uint drb = (dc&0xff00ff);
421 immutable uint dg = (dc&0x00ff00);
422 immutable uint orb = (drb+(((srb-drb)*a+0x800080)>>8))&0xff00ff;
423 immutable uint og = (dg+(((sg-dg)*a+0x008000)>>8))&0x00ff00;
424 return orb|og;
428 // ////////////////////////////////////////////////////////////////////////// //
429 public bool gxIsTransparent (uint clr) pure nothrow @safe @nogc { pragma(inline, true); return ((clr&0xff000000) == 0xff000000); }
430 public bool gxIsSolid (uint clr) pure nothrow @safe @nogc { pragma(inline, true); return ((clr&0xff000000) == 0x00_000000); }
432 public enum gxTransparent = 0xff000000;
435 // ////////////////////////////////////////////////////////////////////////// //
436 private template isGoodRGBInt(T) {
437 import std.traits : Unqual;
438 alias TT = Unqual!T;
439 enum isGoodRGBInt =
440 is(TT == ubyte) ||
441 is(TT == short) || is(TT == ushort) ||
442 is(TT == int) || is(TT == uint) ||
443 is(TT == long) || is(TT == ulong);
447 // ////////////////////////////////////////////////////////////////////////// //
448 public uint gxrgb(T0, T1, T2) (T0 r, T1 g, T2 b) pure nothrow @trusted @nogc if (isGoodRGBInt!T0 && isGoodRGBInt!T1 && isGoodRGBInt!T2) {
449 pragma(inline, true);
450 return (clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b);
454 public template gxRGB(int r, int g, int b) {
455 enum gxRGB = (clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b);
458 public template gxRGBA(int r, int g, int b, int a) {
459 enum gxRGBA = (clampToByte(a)<<24)|(clampToByte(r)<<16)|(clampToByte(g)<<8)|clampToByte(b);
463 // ////////////////////////////////////////////////////////////////////////// //
464 public __gshared GxRect gxClipRect = GxRect(0, 0, 65535, 65535);
466 private struct ClipSave {
467 GxRect rc;
468 ~this () const nothrow @trusted @nogc {
469 pragma(inline, true);
470 gxClipRect = rc;
474 public ClipSave gxClipSave () nothrow @trusted @nogc {
475 pragma(inline, true);
476 return ClipSave(gxClipRect);
479 public void gxClipRestore() (in auto ref ClipSave cs) nothrow @trusted @nogc {
480 pragma(inline, true);
481 gxClipRect = cs.rc;
484 public void gxClipReset () nothrow @trusted @nogc {
485 pragma(inline, true);
486 gxClipRect = GxRect(0, 0, 65535, 65535);
490 // ////////////////////////////////////////////////////////////////////////// //
491 public void gxClearScreen (uint clr) nothrow @trusted @nogc {
492 pragma(inline, true);
493 vglTexBuf[0..VBufWidth*VBufHeight] = clr;
497 public void gxPutPixel (int x, int y, uint c) nothrow @trusted @nogc {
498 pragma(inline, true);
499 if (x >= 0 && y >= 0 && x < VBufWidth && y < VBufHeight && (c&0xff000000) != 0xff000000 && gxClipRect.inside(x, y)) {
500 uint* dp = cast(uint*)(cast(ubyte*)vglTexBuf)+y*VBufWidth+x;
501 *dp = gxColMix(*dp, c);
506 public void gxPutPixel() (in auto ref GxPoint p, uint c) nothrow @trusted @nogc {
507 pragma(inline, true);
508 if (p.x >= 0 && p.y >= 0 && p.x < VBufWidth && p.y < VBufHeight && (c&0xff000000) != 0xff000000 && gxClipRect.inside(p)) {
509 uint* dp = cast(uint*)(cast(ubyte*)vglTexBuf)+p.y*VBufWidth+p.x;
510 *dp = ((c&0xff000000) == 0 ? c : gxColMix(*dp, c));
515 public void gxSetPixel (int x, int y, uint c) nothrow @trusted @nogc {
516 pragma(inline, true);
517 if (x >= 0 && y >= 0 && x < VBufWidth && y < VBufHeight && (c&0xff000000) != 0xff000000 && gxClipRect.inside(x, y)) {
518 uint* dp = cast(uint*)(cast(ubyte*)vglTexBuf)+y*VBufWidth+x;
519 *dp = c&0xffffff;
524 public void gxSetPixel() (in auto ref GxPoint p, uint c) nothrow @trusted @nogc {
525 pragma(inline, true);
526 if (p.x >= 0 && p.y >= 0 && p.x < VBufWidth && p.y < VBufHeight && (c&0xff000000) != 0xff000000 && gxClipRect.inside(p)) {
527 uint* dp = cast(uint*)(cast(ubyte*)vglTexBuf)+p.y*VBufWidth+p.x;
528 *dp = c&0xffffff;
533 // ////////////////////////////////////////////////////////////////////////// //
534 public void gxHLine (int x, int y, int w, uint clr) nothrow @trusted @nogc {
535 if (gxIsTransparent(clr)) return;
536 if (!gxClipRect.clipHStripe(x, y, w)) return;
537 if (!GxRect(0, 0, VBufWidth, VBufHeight).clipHStripe(x, y, w)) return;
538 if (gxIsSolid(clr)) {
539 immutable uint addr = y*VBufWidth+x;
540 vglTexBuf[addr..addr+w] = clr;
541 } else {
542 //while (w-- > 0) gxPutPixel(x++, y, clr);
543 uint* dptr = vglTexBuf+y*VBufWidth+x;
544 while (w-- > 0) {
545 mixin(GxColMixMixin!("*dptr++", "*dptr", "clr"));
550 public void gxHLine() (in auto ref GxPoint p, int w, uint clr) nothrow @trusted @nogc { gxHLine(p.x, p.y, w, clr); }
552 public void gxVLine (int x, int y, int h, uint clr) nothrow @trusted @nogc {
553 if (gxIsTransparent(clr)) return;
554 if (!gxClipRect.clipVStripe(x, y, h)) return;
555 if (!GxRect(0, 0, VBufWidth, VBufHeight).clipVStripe(x, y, h)) return;
556 uint* dptr = vglTexBuf+y*VBufWidth+x;
557 if (gxIsSolid(clr)) {
558 while (h-- > 0) { *dptr = clr; dptr += VBufWidth; }
559 } else {
560 while (h-- > 0) { mixin(GxColMixMixin!("*dptr", "*dptr", "clr")); dptr += VBufWidth; }
564 public void gxVLine() (in auto ref GxPoint p, int h, uint clr) nothrow @trusted @nogc { gxVLine(p.x, p.y, h, clr); }
567 // ////////////////////////////////////////////////////////////////////////// //
568 public void gxFillRect (int x, int y, int w, int h, uint clr) nothrow @trusted @nogc {
569 if (gxIsTransparent(clr)) return;
570 if (!gxClipRect.clipHVStripes(x, y, w, h)) return;
571 if (!GxRect(0, 0, VBufWidth, VBufHeight).clipHVStripes(x, y, w, h)) return;
572 if (gxIsSolid(clr)) {
573 uint addr = y*VBufWidth+x;
574 while (h-- > 0) {
575 vglTexBuf[addr..addr+w] = clr;
576 addr += VBufWidth;
578 } else {
579 //while (h-- > 0) gxHLine(x, y++, w, clr);
580 uint* dptr = vglTexBuf+y*VBufWidth+x;
581 immutable uint dinc = VBufWidth-w;
582 while (h-- > 0) {
583 foreach (immutable _; 0..w) {
584 mixin(GxColMixMixin!("*dptr++", "*dptr", "clr"));
586 dptr += dinc;
591 public void gxFillRect() (in auto ref GxRect rc, uint clr) nothrow @trusted @nogc {
592 gxFillRect(rc.x0, rc.y0, rc.width, rc.height, clr);
595 public void gxDrawRect (int x, int y, int w, int h, uint clr) nothrow @trusted @nogc {
596 //if (w < 1 || h < 1 || x >= VBufWidth || y >= VBufHeight) return;
598 if (gxIsTransparent(clr)) return;
599 if (!gxClipRect.clipHVStripes(x, y, w, h)) return;
600 if (!GxRect(0, 0, VBufWidth, VBufHeight).clipHVStripes(x, y, w, h)) return;
602 gxHLine(x, y, w, clr);
603 gxHLine(x, y+h-1, w, clr);
604 gxVLine(x, y+1, h-2, clr);
605 gxVLine(x+w-1, y+1, h-2, clr);
608 public void gxDrawRect() (in auto ref GxRect rc, uint clr) nothrow @trusted @nogc {
609 gxDrawRect(rc.x0, rc.y0, rc.width, rc.height, clr);
613 // ////////////////////////////////////////////////////////////////////////// //
614 // use clip region as boundaries
615 public void gxDrawShadow () nothrow @trusted @nogc {
616 auto sv = gxClipSave();
617 gxClipReset();
618 gxFillRect(sv.rc.x1+1, sv.rc.y0+8, 8, sv.rc.height-8, gxRGBA!(0, 0, 0, 127));
619 gxFillRect(sv.rc.x0+8, sv.rc.y1+1, sv.rc.width, 8, gxRGBA!(0, 0, 0, 127));
623 public void gxDrawWindow (const(char)[] title, uint framecolor, uint titlecolor, uint titlebackcolor, uint windowcolor) nothrow @trusted {
624 import egfx.text;
626 gxDrawShadow();
628 gxFillRect(gxClipRect, windowcolor);
629 gxDrawRect(gxClipRect, framecolor);
631 if (title !is null) {
632 auto sv = gxClipSave();
633 int hgt = (gxTextHeightUtf < 10 ? 10 : gxTextHeightUtf+1);
634 gxClipRect.intersect(GxRect(sv.rc.x0+1, sv.rc.y0+1, sv.rc.width-2, hgt));
635 if (!gxClipRect.empty) {
636 gxFillRect(gxClipRect, titlebackcolor);
637 gxDrawTextUtf(gxClipRect.x0+(gxClipRect.width-gxTextWidthUtf(title))/2, gxClipRect.y0+(hgt-gxTextHeightUtf)/2, title, titlecolor);
643 // ////////////////////////////////////////////////////////////////////////// //
644 public void gxDrawScrollBar() (in auto ref GxRect r, int max, int value) nothrow @trusted @nogc { gxDrawScrollBar(r, 0, max, value); }
646 public void gxDrawScrollBar() (in auto ref GxRect r, int min, int max, int value) nothrow @trusted @nogc {
647 enum FrameColor = gxRGB!(220, 220, 220);
648 enum EmptyColor = gxRGB!(0, 0, 0);
649 enum FullColor = gxRGB!(160, 160, 160);
650 if (r.empty) return;
651 if (max <= min) min = max = value = 0;
652 // move it to 0
653 //conwriteln("00: min=", min, "; max=", max, "; value=", value);
654 max -= min;
655 value -= min;
656 if (value < 0) value = 0; else if (value > max) value = max;
657 //conwriteln("01: min=", min, "; max=", max, "; value=", value);
658 int sx0 = r.x0+1;
659 int sy0 = r.y0+1;
660 int wdt = r.width-2;
661 int hgt = r.height-2;
662 bool vert = (r.width < r.height);
663 // frame
664 if ((vert && wdt > 1) || (!vert && hgt > 1)) {
665 gxHLine(r.x0+1, r.y0+0, wdt, FrameColor);
666 gxVLine(r.x0+0, r.y0+1, hgt, FrameColor);
667 gxVLine(r.x1+0, r.y0+1, hgt, FrameColor);
668 gxHLine(r.x0+1, r.y1+0, wdt, FrameColor);
669 } else {
670 sx0 -= 1;
671 sy0 -= 1;
672 wdt += 2;
673 hgt += 2;
675 if (max <= 0) {
676 gxFillRect(sx0, sy0, wdt, hgt, FullColor);
677 return;
679 if (vert) {
680 int pix = hgt*value/max;
681 if (pix > hgt) pix = hgt; // just in case
682 gxFillRect(sx0, sy0, wdt, pix, FullColor);
683 gxFillRect(sx0, sy0+pix, wdt, hgt-pix, EmptyColor);
684 } else {
685 int pix = wdt*value/max;
686 if (pix > wdt) pix = wdt; // just in case
687 gxFillRect(sx0, sy0, pix, hgt, FullColor);
688 gxFillRect(sx0+pix, sy0, wdt-pix, hgt, EmptyColor);