egra: checkbox fixes
[iv.d.git] / egtui / tty.d
blob3cc82153512a89a06256907a7d300f5ba5f4dee5
1 /* Invisible Vector Library
2 * simple FlexBox-based TUI engine
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // virtual console with doublebuffering, to awoid alot of overdraw
17 module iv.egtui.tty /*is aliced*/;
18 private:
20 import iv.alice;
21 import iv.rawtty;
22 import iv.strex;
23 import iv.utfutil;
26 // ////////////////////////////////////////////////////////////////////////// //
27 public __gshared int TtyDefaultEscWait = 50; // -1: forever
28 private __gshared int ttywIntr, ttyhIntr; // DO NOT CHANGE!
29 __gshared bool weAreFucked = false; // utfucked?
30 __gshared string ttyzTitleSuffix;
32 public @property int ttyw () nothrow @trusted @nogc { pragma(inline, true); return ttywIntr; }
33 public @property int ttyh () nothrow @trusted @nogc { pragma(inline, true); return ttyhIntr; }
35 public @property string ttyTitleSuffix () nothrow @trusted @nogc { return ttyzTitleSuffix; }
36 public @property void ttyTitleSuffix(T : const(char)[]) (T s) nothrow {
37 static if (is(T == typeof(null))) {
38 ttyzTitleSuffix = null;
39 } else static if (is(T == string)) {
40 ttyzTitleSuffix = s;
41 } else {
42 ttyzTitleSuffix = s.idup;
47 // ////////////////////////////////////////////////////////////////////////// //
48 public enum XtColorFB(ubyte fg, ubyte bg) = cast(uint)((fg<<8)|bg);
51 // ////////////////////////////////////////////////////////////////////////// //
52 // global scissors
53 public align(1) struct XtScissor {
54 align(1) nothrow @trusted @nogc:
55 private:
56 ushort mX0, mY0, mW, mH;
58 public:
59 this (int ax0, int ay0, int aw, int ah) {
60 if (ay0 >= ttyhIntr || ax0 >= ttywIntr || aw < 1 || ah < 1) return;
61 if (ax0 < 0) {
62 if (ax0 <= -aw) return;
63 aw += ax0;
64 ax0 = 0;
66 if (ay0 < 0) {
67 if (ay0 <= -ah) return;
68 ah += ay0;
69 ay0 = 0;
71 if (aw > ttywIntr) aw = ttywIntr;
72 if (ah > ttyhIntr) ah = ttyhIntr;
73 if (ttywIntr-ax0 > aw) aw = ttywIntr-ax0;
74 if (ttyhIntr-ay0 > ah) ah = ttyhIntr-ay0;
75 if (aw > 0 && ah > 0) {
76 mX0 = cast(ushort)ax0;
77 mY0 = cast(ushort)ay0;
78 mW = cast(ushort)aw;
79 mH = cast(ushort)ah;
83 static XtScissor fullscreen () { return XtScissor(0, 0, ttywIntr, ttyhIntr); }
85 const @safe:
86 // crop this scissor with another scissor
87 XtScissor crop (int x, int y, int w, int h) { return crop(XtScissor(x, y, w, h)); }
88 pure:
89 XtScissor crop (in XtScissor s) {
90 import std.algorithm : max, min;
91 XtScissor res = void;
92 res.mX0 = this.mX0;
93 res.mY0 = this.mY0;
94 res.mW = this.mW;
95 res.mH = this.mH;
96 if (res.visible && s.visible) {
97 int rx0 = max(mX0, s.mX0);
98 int ry0 = max(mY0, s.mY0);
99 int rx1 = min(x1, s.x1);
100 int ry1 = min(y1, s.y1);
101 if (rx1 < rx0 || ry1 < ry0) {
102 res.mW = res.mH = 0;
103 } else {
104 res.mX0 = cast(ushort)rx0;
105 res.mY0 = cast(ushort)ry0;
106 res.mW = cast(ushort)(rx1-rx0+1);
107 res.mH = cast(ushort)(ry1-ry0+1);
110 return res;
113 @property:
114 bool visible () { pragma(inline, true); return (mW > 0 && mH > 0); }
115 bool empty () { pragma(inline, true); return (mW == 0 && mH == 0); }
116 int x0 () { pragma(inline, true); return mX0; }
117 int y0 () { pragma(inline, true); return mY0; }
118 int x1 () { pragma(inline, true); return mX0+mW-1; } // may be negative for empty scissor
119 int y1 () { pragma(inline, true); return mY0+mH-1; } // may be negative for empty scissor
120 int width () { pragma(inline, true); return mW; }
121 int height () { pragma(inline, true); return mH; }
124 __gshared XtScissor ttyzScissor;
127 public @property XtScissor ttyScissor () nothrow @trusted @nogc { pragma(inline, true); return ttyzScissor; }
128 public @property void ttyScissor (in XtScissor sc) nothrow @trusted @nogc { pragma(inline, true); ttyzScissor = sc; }
131 // ////////////////////////////////////////////////////////////////////////// //
132 enum SIGWINCH = 28;
134 __gshared bool winSizeChanged = false;
135 __gshared bool winChSet = false;
137 extern(C) void sigwinchHandler (int sig) {
138 winSizeChanged = true;
141 void setupWinch () {
142 import core.sys.posix.signal;
143 if (winChSet) return;
144 winChSet = true;
145 sigaction_t sa;
146 sigemptyset(&sa.sa_mask);
147 sa.sa_flags = 0;
148 sa.sa_handler = &sigwinchHandler;
149 if (sigaction(SIGWINCH, &sa, null) == -1) return;
153 // ////////////////////////////////////////////////////////////////////////// //
154 align(1) struct Glyph {
155 align(1):
156 enum Flag : ubyte {
157 G1 = 0b1000_0000u, // only this will be checked in refresh
158 Mask = 0b1000_0000u, // refresh compare mask
159 GraphMask = 0b0000_1111u,
160 GraphUp = 0b0000_0001u,
161 GraphDown = 0b0000_0010u,
162 GraphLeft = 0b0000_0100u,
163 GraphRight = 0b0000_1000u,
165 ubyte fg = 7; // foreground color
166 ubyte bg = 0; // background color
167 char ch = 0; // char
168 ubyte flags = 0; // see Flag enum
169 // 0: not a graphic char
170 @property char g1char () const pure nothrow @trusted @nogc {
171 static immutable char[16] transTbl = [
172 0x20, // ....
173 0x78, // ...U
174 0x78, // ..D.
175 0x78, // ..DU
176 0x71, // .L..
177 0x6A, // .L.U
178 0x6B, // .LD.
179 0x75, // .LDU
180 0x71, // R...
181 0x6D, // R..U
182 0x6C, // R.D.
183 0x74, // R.DU
184 0x71, // RL..
185 0x76, // RL.U
186 0x77, // RLD.
187 0x6E, // RLDU
189 return (flags&Flag.GraphMask ? transTbl.ptr[flags&Flag.GraphMask] : 0);
191 void g1line(bool setattr) (ubyte gf, ubyte afg, ubyte abg) nothrow @trusted @nogc {
192 if (gf&Flag.GraphMask) {
193 if (flags&Flag.G1) {
194 if ((flags&Flag.GraphMask) == 0) {
195 // check if we have some line drawing char here
196 switch (ch) {
197 case 0x6A: flags |= Flag.GraphLeft|Flag.GraphUp; break;
198 case 0x6B: flags |= Flag.GraphLeft|Flag.GraphDown; break;
199 case 0x6C: flags |= Flag.GraphRight|Flag.GraphDown; break;
200 case 0x6D: flags |= Flag.GraphRight|Flag.GraphUp; break;
201 case 0x6E: flags |= Flag.GraphLeft|Flag.GraphRight|Flag.GraphUp|Flag.GraphDown; break;
202 case 0x71: flags |= Flag.GraphLeft|Flag.GraphRight; break;
203 case 0x74: flags |= Flag.GraphUp|Flag.GraphDown|Flag.GraphRight; break;
204 case 0x75: flags |= Flag.GraphUp|Flag.GraphDown|Flag.GraphLeft; break;
205 case 0x76: flags |= Flag.GraphLeft|Flag.GraphRight|Flag.GraphUp; break;
206 case 0x77: flags |= Flag.GraphLeft|Flag.GraphRight|Flag.GraphDown; break;
207 case 0x78: flags |= Flag.GraphUp|Flag.GraphDown; break;
208 default:
211 } else {
212 flags &= ~(Flag.GraphMask);
214 flags |= (gf&Flag.GraphMask)|Flag.G1;
215 ch = g1char;
216 } else if (flags&Flag.G1) {
217 flags &= ~(Flag.GraphMask|Flag.G1); // reset graphics
218 ch = ' ';
220 static if (setattr) {
221 fg = afg;
222 bg = abg;
226 static assert(Glyph.sizeof == 4);
229 // ////////////////////////////////////////////////////////////////////////// //
230 __gshared int ttycx, ttycy; // current cursor position (0-based)
231 __gshared Glyph[] ttywb; // working buffer
232 __gshared Glyph[] ttybc; // previous buffer
233 __gshared ubyte curFG = 7, curBG = 0;
234 __gshared bool ttzFullRefresh = true;
237 // ////////////////////////////////////////////////////////////////////////// //
238 __gshared ubyte* ttySavedBufs;
239 __gshared uint ttySBUsed;
240 __gshared uint ttySBSize;
242 private align(1) struct TxSaveInfo {
243 align(1):
244 int cx, cy; // cursor position
245 ubyte fg, bg; // current color
246 int x, y, w, h; // saved data (if any)
247 Glyph[0] data;
250 shared static ~this () {
251 import core.stdc.stdlib : free;
252 if (ttySavedBufs !is null) free(ttySavedBufs);
256 private ubyte* ttzSBAlloc (uint size) {
257 import core.stdc.stdlib : realloc;
258 import core.stdc.string : memset;
259 assert(size > 0);
260 if (size >= 4*1024*1024) assert(0, "wtf?!");
261 uint nsz = ttySBUsed+size;
262 if (nsz > ttySBSize) {
263 if (nsz&0xfff) nsz = (nsz|0xfff)+1;
264 auto nb = cast(ubyte*)realloc(ttySavedBufs, nsz);
265 if (nb is null) assert(0, "out of memory"); //FIXME
266 ttySavedBufs = nb;
267 ttySBSize = nsz;
269 assert(ttySBSize-ttySBUsed >= size);
270 auto res = ttySavedBufs+ttySBUsed;
271 ttySBUsed += size;
272 memset(res, 0, size);
273 return res;
277 // push area contents and cursor position (unaffected by scissors)
278 public void xtPushArea (int x, int y, int w, int h) {
279 if (w < 1 || h < 1 || x >= ttywIntr || y >= ttyhIntr) { x = y = w = h = 0; }
280 if (w > 0) {
281 int x0 = x, y0 = y, x1 = x+w-1, y1 = y+h-1;
282 if (x0 < 0) x0 = 0; else if (x0 >= ttywIntr) x0 = ttywIntr-1;
283 if (x1 < 0) x1 = 0; else if (x1 >= ttywIntr) x1 = ttywIntr-1;
284 if (y0 < 0) y0 = 0; else if (y0 >= ttyhIntr) y0 = ttyhIntr-1;
285 if (y1 < 0) y1 = 0; else if (y1 >= ttyhIntr) y1 = ttyhIntr-1;
286 if (x0 <= x1 && y0 <= y1) {
287 x = x0;
288 y = y0;
289 w = x1-x0+1;
290 h = y1-y0+1;
291 } else {
292 x = y = w = h = 0;
295 if (w < 1 || h < 1) { x = y = w = h = 0; }
296 uint sz = cast(uint)(TxSaveInfo.sizeof+Glyph.sizeof*w*h);
297 auto buf = ttzSBAlloc(sz+4);
298 auto st = cast(TxSaveInfo*)buf;
299 st.cx = ttycx;
300 st.cy = ttycy;
301 st.fg = curFG;
302 st.bg = curBG;
303 st.x = x;
304 st.y = y;
305 st.w = w;
306 st.h = h;
307 if (w > 0 && h > 0) {
308 assert(x >= 0 && y >= 0 && x < ttywIntr && y < ttyhIntr && x+w <= ttywIntr && y+h <= ttyhIntr);
309 import core.stdc.string : memcpy;
310 auto src = ttywb.ptr+y*ttywIntr+x;
311 auto dst = st.data.ptr;
312 foreach (immutable _; 0..h) {
313 memcpy(dst, src, Glyph.sizeof*w);
314 src += ttywIntr;
315 dst += w;
318 buf += sz;
319 *cast(uint*)buf = sz;
323 // pop (restore) area contents and cursor position (unaffected by scissors)
324 public void xtPopArea () {
325 if (ttySBUsed == 0) return;
326 assert(ttySBUsed >= 4);
327 auto sz = *cast(uint*)(ttySavedBufs+ttySBUsed-4);
328 ttySBUsed -= sz+4;
329 auto st = cast(TxSaveInfo*)(ttySavedBufs+ttySBUsed);
330 ttycx = st.cx;
331 ttycy = st.cy;
332 curFG = st.fg;
333 curBG = st.bg;
334 auto x = st.x;
335 auto y = st.y;
336 auto w = st.w;
337 auto h = st.h;
338 if (w > 0 && h > 0) {
339 assert(x >= 0 && y >= 0 && x < ttywIntr && y < ttyhIntr && x+w <= ttywIntr && y+h <= ttyhIntr);
340 import core.stdc.string : memcpy;
341 auto src = st.data.ptr;
342 auto dst = ttywb.ptr+y*ttywIntr+x;
343 foreach (immutable _; 0..h) {
344 memcpy(dst, src, Glyph.sizeof*w);
345 src += w;
346 dst += ttywIntr;
352 // ////////////////////////////////////////////////////////////////////////// //
353 // initialize system, allocate buffers, clear screen, etc
354 public void xtInit () {
355 import core.sys.posix.unistd : write;
356 weAreFucked = ttyIsUtfucked;
357 setupWinch();
358 ttywIntr = ttyWidth;
359 ttyhIntr = ttyHeight;
360 ttywb.length = ttywIntr*ttyhIntr;
361 ttybc.length = ttywIntr*ttyhIntr;
362 ttywb[] = Glyph.init;
363 ttybc[] = Glyph.init;
364 ttycx = ttycy = 0;
365 ttyzScissor = XtScissor.fullscreen;
366 // clear screen
367 enum initStr =
368 "\x1b[?1034l"~ // xterm: disable "eightBitInput"
369 "\x1b[?1036h"~ // xterm: enable "metaSendsEscape"
370 "\x1b[?1039h"~ // xterm: enable "altSendsEscape"
371 // disable various mouse reports
372 "\x1b[?1000l"~
373 "\x1b[?1001l"~
374 "\x1b[?1002l"~
375 "\x1b[?1003l"~
376 "\x1b[?1004l"~ // don't send focus events
377 "\x1b[?1005l"~
378 "\x1b[?1006l"~
379 "\x1b[?1015l"~
380 "\x1b[?1000l";
381 write(1, initStr.ptr, initStr.length);
382 xtFullRefresh();
386 public bool xtNeedReinit () {
387 //return (ttywIntr != ttyWidth || ttyhIntr != ttyHeight);
388 return winSizeChanged;
392 // this will reset scissors
393 public void xtReinit () {
394 ttyzScissor = XtScissor.fullscreen;
395 if (ttywIntr != ttyWidth || ttyhIntr != ttyHeight) {
396 winSizeChanged = false;
397 ttywIntr = ttyWidth;
398 ttyhIntr = ttyHeight;
399 ttywb.length = ttywIntr*ttyhIntr;
400 ttybc.length = ttywIntr*ttyhIntr;
401 ttywb[] = Glyph.init;
402 ttybc[] = Glyph.init;
403 ttycx = ttycy = 0;
404 // clear screen
405 //enum initStr = "\x1b[H\x1b[0;37;40m\x1b[2J";
406 //write(1, initStr.ptr, initStr.length);
407 xtFullRefresh();
412 // ////////////////////////////////////////////////////////////////////////// //
413 __gshared char[128*1024] ttytbuf = 0;
414 __gshared int ttytpos;
415 __gshared int lastx, lasty;
418 private void ttzFlush () nothrow @nogc {
419 if (ttytpos > 0) {
420 import core.sys.posix.unistd : write;
421 write(1, ttytbuf.ptr, ttytpos);
422 ttytpos = 0;
427 private void ttzPut (const(char)[] str...) nothrow @nogc {
428 import core.stdc.string : memcpy;
429 while (str.length > 0) {
430 uint left = cast(uint)ttytbuf.length-ttytpos;
431 if (left == 0) { ttzFlush(); left = cast(uint)ttytbuf.length; }
432 if (left > str.length) left = cast(uint)str.length;
433 memcpy(ttytbuf.ptr+ttytpos, str.ptr, left);
434 ttytpos += left;
435 str = str[left..$];
440 private void ttzPutUInt (uint n) nothrow @nogc {
441 import core.stdc.string : memcpy;
442 char[64] ttbuf = void;
443 uint tbpos = cast(uint)ttbuf.length;
444 do {
445 ttbuf[--tbpos] = cast(char)(n%10+'0');
446 } while ((n /= 10) != 0);
447 ttzPut(ttbuf[tbpos..$]);
451 private void ttzPutUHex (uint n) nothrow @nogc {
452 char[8] ttbuf = void;
453 foreach_reverse (ref char ch; ttbuf) {
454 int d = n%16;
455 if (d < 10) d += '0'; else d += 'a'-10;
456 ch = cast(char)d;
457 n /= 16;
459 ttzPut(ttbuf[]);
463 // ////////////////////////////////////////////////////////////////////////// //
464 public void xtSetTerminalTitle (const(char)[] title) {
465 import core.sys.posix.unistd : write;
466 if (title.length > 500) title = title[0..500];
467 enum titStart = "\x1b]2;";
468 enum titEnd = "\x07";
469 //enum suffix = " -- egedit";
470 bool good = true;
471 foreach (char ch; title) if (ch < ' ' || ch == 127) { good = false; break; }
472 write(1, titStart.ptr, titStart.length);
473 if (good) {
474 if (title.length) write(1, title.ptr, title.length);
475 } else {
476 foreach (char ch; title) write(1, &ch, 1);
478 if (ttyzTitleSuffix.length) write(1, ttyzTitleSuffix.ptr, ttyzTitleSuffix.length);
479 write(1, titEnd.ptr, titEnd.length);
483 // ////////////////////////////////////////////////////////////////////////// //
484 // redraw the whole screen; doesn't flush it yet
485 public void xtFullRefresh () nothrow @trusted @nogc {
486 ttzFullRefresh = true;
490 // ////////////////////////////////////////////////////////////////////////// //
491 // this will reset scissors
492 public void xtFlush () /*nothrow @nogc*/ {
493 void gotoXY (int x, int y) {
494 if (x == lastx && y == lasty) return;
495 //debug { import iv.vfs.io; stderr.writeln("x=", x, "; y=", y, "; last=", lastx, "; lasty=", lasty); }
496 if (y == lasty) {
497 // move horizontally
498 if (x == 0) { ttzPut("\r"); lastx = 0; return; }
499 ttzPut("\x1b[");
500 if (x < lastx) {
501 // move cursor left
502 ttzPutUInt(lastx-x);
503 ttzPut("D");
504 } else if (x > lastx) {
505 // move cursor right
506 ttzPutUInt(x-lastx);
507 ttzPut("C");
509 lastx = x;
510 } else if (x == lastx) {
511 // move vertically
512 ttzPut("\x1b[");
513 if (y < lasty) {
514 // move cursor up
515 ttzPutUInt(lasty-y);
516 ttzPut("A");
517 } else if (y > lasty) {
518 // move cursor down
519 ttzPutUInt(y-lasty);
520 ttzPut("B");
522 lasty = y;
523 } else {
524 // move in both directions
525 //TODO: optimize this too
526 ttzPut("\x1b[");
527 ttzPutUInt(y+1);
528 ttzPut(";");
529 ttzPutUInt(x+1);
530 ttzPut("H");
531 lastx = x;
532 lasty = y;
536 ttyzScissor = XtScissor.fullscreen;
538 ubyte inG0G1 = 0;
539 ttytpos = 0; // reset output position
540 if (ttzFullRefresh) {
541 ttzPut("\x1b[H");
542 lastx = lasty = 0;
544 ttzPut("\x1b[0;38;5;7;48;5;0m");
545 ubyte lastFG = 7, lastBG = 0;
546 int tsz = ttywIntr*ttyhIntr;
547 // fix glyph chars
548 auto tsrc = ttywb.ptr; // source buffer
549 auto tdst = ttybc.ptr; // destination buffer
550 //immutable doctrans = (termType == TermType.linux);
551 enum doctrans = false;
552 for (uint pos = 0; pos < tsz; *tdst++ = *tsrc++, ++pos) {
553 if (tsrc.ch == '\t') { tsrc.ch = '\x62'; tsrc.flags = Glyph.Flag.G1; }
554 if (tsrc.ch == '\v') { tsrc.ch = '\x69'; tsrc.flags = Glyph.Flag.G1; }
555 if (tsrc.ch == '\n') { tsrc.ch = '\x65'; tsrc.flags = Glyph.Flag.G1; }
556 if (tsrc.ch == '\r') { tsrc.ch = '\x64'; tsrc.flags = Glyph.Flag.G1; }
557 else if (tsrc.ch == 0) { tsrc.ch = ' '; tsrc.flags = 0; }
558 else if (tsrc.ch < ' ' || tsrc.ch == 127) { tsrc.ch = '\x7e'; tsrc.flags = Glyph.Flag.G1; }
559 // skip things that doesn't need to be updated
560 if (!ttzFullRefresh) {
561 if (((tsrc.flags^tdst.flags)&Glyph.Flag.Mask) == 0 && tsrc.ch == tdst.ch && tsrc.bg == tdst.bg) {
562 // same char, different attrs? for spaces, it is enough to compare only bg color
563 // actually, some terminals may draw different colored cursor on different colored
564 // spaces, but i don't care: fix your terminal!
565 if (/*tsrc.ch == ' ' ||*/ tsrc.fg == tdst.fg) continue;
568 gotoXY(pos%ttywIntr, pos/ttywIntr);
569 if (inG0G1 != (tsrc.flags&Glyph.Flag.G1)) {
570 if ((inG0G1 = (tsrc.flags&Glyph.Flag.G1)) != 0) ttzPut('\x0e'); else ttzPut('\x0f');
572 // new attrs?
573 if (tsrc.bg != lastBG || (/*tsrc.ch != ' ' &&*/ tsrc.fg != lastFG)) {
574 ttzPut("\x1b[");
575 if (!doctrans) {
576 bool needSC = false;
577 if (tsrc.fg != lastFG) {
578 lastFG = tsrc.fg;
579 ttzPut("38;5;");
580 ttzPutUInt(lastFG);
581 needSC = true;
583 if (tsrc.bg != lastBG) {
584 lastBG = tsrc.bg;
585 if (needSC) ttzPut(';');
586 ttzPut("48;5;");
587 ttzPutUInt(lastBG);
589 } else {
590 lastFG = tsrc.fg;
591 lastBG = tsrc.bg;
592 ttzPut("0;");
593 auto c0 = tty2linux(lastFG);
594 auto c1 = tty2linux(lastBG);
595 if (c0 > 7) ttzPut("1;");
596 ttzPut("3");
597 ttzPutUInt(c0);
598 ttzPut(";4");
599 ttzPutUInt(c1);
601 ttzPut('m');
603 // draw char
604 if (inG0G1) {
605 ttzPut(tsrc.ch < 128 ? tsrc.ch : ' ');
606 } else if (!weAreFucked || tsrc.ch < 128) {
607 ttzPut(tsrc.ch);
608 } else {
609 char[8] ubuf = void;
610 dchar dch = koi2uni(cast(char)tsrc.ch);
611 auto len = utf8Encode(ubuf[], dch);
612 if (len < 1) { ubuf[0] = '?'; len = 1; }
613 ttzPut(ubuf[0..len]);
615 // adjust cursor position
616 if (++lastx == ttywIntr) {
617 lastx = 0;
618 ttzPut("\r");
621 // switch back to G0
622 if (inG0G1) ttzPut('\x0f');
623 // position cursor
624 gotoXY(ttycx, ttycy);
625 // done
626 ttzFlush();
627 ttzFullRefresh = false;
631 // ////////////////////////////////////////////////////////////////////////// //
632 public struct XtWindow {
633 private:
634 int x, y, w, h;
635 ushort fgbg;
637 public: // what idiot marked this whole thing as nothrow-nogc?!
638 // temporarily change color and call delegate
639 void withFB (ubyte fg, ubyte bg, scope void delegate () dg) {
640 if (dg is null) return;
641 auto oldcolor = fgbg;
642 scope(exit) fgbg = oldcolor;
643 fb(fg, bg);
644 dg();
647 // temporarily change color and call delegate
648 void withFG (ubyte cfg, scope void delegate () dg) {
649 if (dg is null) return;
650 auto oldcolor = fgbg;
651 scope(exit) fgbg = oldcolor;
652 fg = cfg;
653 dg();
656 // temporarily change color and call delegate
657 void withBG (ubyte cbg, scope void delegate () dg) {
658 if (dg is null) return;
659 auto oldcolor = fgbg;
660 scope(exit) fgbg = oldcolor;
661 bg = cbg;
662 dg();
665 nothrow @safe @nogc:
666 public:
667 this (int ax, int ay, int aw, int ah) @trusted {
668 x = ax;
669 y = ay;
670 w = aw;
671 h = ah;
672 fgbg = cast(ushort)((curFG<<8)|curBG); // with current color
675 static XtWindow fullscreen () @trusted { pragma(inline, true); return XtWindow(0, 0, ttywIntr, ttyhIntr); }
677 @property bool valid () const pure { pragma(inline, true); return (w > 0 && h > 0); }
678 // invalid windows are invisible ;-)
679 @property bool visible () const @trusted {
680 pragma(inline, true);
681 return
682 w > 0 && h > 0 && // valid
683 x < ttywIntr && y < ttyhIntr && // not too right/bottom
684 x+w > 0 && y+h > 0; // not too left/top
687 // clip this window to another window
688 //FIXME: completely untested (and unused!)
690 void clipBy() (in auto ref XtWindow ww) {
691 if (empty || ww.empty) { w = h = 0; return; }
692 if (x+w <= ww.x || y+h <= ww.y || x >= ww.x+ww.w || y >= ww.y+ww.h) { w = h = 0; return; }
693 // we are at least partially inside ww
694 if (x < ww.x) x = ww.x; // clip left
695 if (y < ww.y) y = ww.y; // clip top
696 if (x+w > ww.x+ww.w) w = ww.x+ww.w-x; // clip right
697 if (y+h > ww.y+ww.h) y = ww.y+ww.h-y; // clip bottom
701 @property int x0 () const pure { pragma(inline, true); return x; }
702 @property int y0 () const pure { pragma(inline, true); return y; }
703 @property int width () const pure { pragma(inline, true); return (w > 0 ? w : 0); }
704 @property int height () const pure { pragma(inline, true); return (h > 0 ? h : 0); }
706 @property void x0 (int v) pure { pragma(inline, true); x = v; }
707 @property void y0 (int v) pure { pragma(inline, true); y = v; }
708 @property void width (int v) pure { pragma(inline, true); w = v; }
709 @property void height (int v) pure { pragma(inline, true); h = v; }
711 @property ubyte fg () const pure { pragma(inline, true); return cast(ubyte)(fgbg>>8); }
712 @property ubyte bg () const pure { pragma(inline, true); return cast(ubyte)(fgbg&0xff); }
714 @property void fg (ubyte v) pure { pragma(inline, true); fgbg = cast(ushort)((fgbg&0x00ff)|(v<<8)); }
715 @property void bg (ubyte v) pure { pragma(inline, true); fgbg = cast(ushort)((fgbg&0xff00)|v); }
717 @property uint color () const pure { pragma(inline, true); return fgbg; }
718 @property void color (uint v) pure { pragma(inline, true); fgbg = cast(ushort)(v&0xffff); }
720 @property void fb (ubyte fg, ubyte bg) pure { pragma(inline, true); fgbg = cast(ushort)((fg<<8)|bg); }
722 void gotoXY (int x, int y) @trusted {
723 x += this.x;
724 y += this.y;
725 if (x >= this.x+this.w) x = this.x+this.w-1;
726 if (y >= this.y+this.h) y = this.y+this.h-1;
727 if (x < this.x) x = this.x;
728 if (y < this.y) y = this.y;
729 // now global
730 if (x < 0) x = 0;
731 if (y < 0) y = 0;
732 if (x >= ttywIntr) x = ttywIntr-1;
733 if (y > ttyhIntr) y = ttyhIntr-1;
734 // set new coords
735 ttycx = x;
736 ttycy = y;
739 // returns new length (can be 0, and both `x` and `y` are undefined in this case)
740 // ofs: bytes to skip (if x is "before" window
741 // x, y: will be translated to global coords
742 int normXYLen (ref int x, ref int y, int len, out int ofs) const @trusted {
743 if (len < 1 || w < 1 || h < 1 || x >= w || y < 0 || y >= h || !visible || !ttyzScissor.visible) return 0; // nothing to do
744 // crop to window
745 if (x < 0) {
746 if (x <= -len) return 0;
747 len += x;
748 ofs = -x;
749 x = 0;
751 int left = w-x;
752 if (left < len) len = left;
753 if (len < 1) return 0; // just in case
754 // crop to global space
755 x += this.x;
756 y += this.y;
757 if (x+len <= ttyzScissor.mX0 || x > ttyzScissor.x1 || y < ttyzScissor.mY0 || y > ttyzScissor.y1) return 0;
758 if (x < 0) {
759 if (x <= -len) return 0;
760 len += x;
761 ofs = -x;
762 x = 0;
764 if (x < ttyzScissor.mX0) {
765 auto z = ttyzScissor.mX0-x;
766 if (z >= len) return 0;
767 x += z;
768 len -= z;
770 if ((left = ttyzScissor.x1+1-x) < 1) return 0;
771 if (left < len) len = left;
772 return len;
775 int normXYLen (ref int x, ref int y, int len) const {
776 int ofs;
777 return normXYLen(x, y, len, ofs);
780 @trusted const:
781 void writeStrAt(bool g1=false) (int x, int y, const(char)[] str...) {
782 if (str.length > int.max) str = str[0..int.max];
783 int ofs;
784 auto len = normXYLen(x, y, cast(int)str.length, ofs);
785 if (len < 1) return;
786 immutable f = fg, b = bg;
787 auto src = cast(const(char)*)str.ptr+ofs;
788 auto dst = ttywb.ptr+y*ttywIntr+x;
789 while (len-- > 0) {
790 dst.fg = f;
791 dst.bg = b;
792 dst.ch = *src++;
793 static if (g1) {
794 dst.flags &= ~Glyph.Flag.GraphMask;
795 dst.flags |= Glyph.Flag.G1;
796 } else {
797 dst.flags &= ~(Glyph.Flag.GraphMask|Glyph.Flag.G1);
799 ++dst;
803 void writeCharsAt(bool g1=false) (int x, int y, int count, char ch) {
804 auto len = normXYLen(x, y, count);
805 if (len < 1) return;
806 immutable f = fg, b = bg;
807 auto dst = ttywb.ptr+y*ttywIntr+x;
808 while (len-- > 0) {
809 dst.fg = f;
810 dst.bg = b;
811 dst.ch = ch;
812 static if (g1) {
813 dst.flags &= ~(Glyph.Flag.GraphMask);
814 dst.flags |= Glyph.Flag.G1;
815 } else {
816 dst.flags &= ~(Glyph.Flag.GraphMask|Glyph.Flag.G1);
818 ++dst;
822 void writeUIntAt (int x, int y, uint n) {
823 char[64] ttbuf = void;
824 uint tbpos = cast(uint)ttbuf.length;
825 do {
826 ttbuf[--tbpos] = cast(char)(n%10+'0');
827 } while ((n /= 10) != 0);
828 writeStrAt(x, y, ttbuf[tbpos..$]);
831 void writeUHexAt (int x, int y, uint n) {
832 char[8] ttbuf = void;
833 foreach_reverse (ref char ch; ttbuf) {
834 int d = n%16;
835 if (d < 10) d += '0'; else d += 'a'-10;
836 ch = cast(char)d;
837 n /= 16;
839 writeStrAt(x, y, ttbuf[]);
842 void hline(bool setattr=true) (int x, int y, int len) {
843 auto nlen = normXYLen(x, y, len);
844 if (nlen < 1) return;
845 immutable f = fg, b = bg;
846 if (nlen == 1) {
847 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphLeft|Glyph.Flag.GraphRight, f, b);
848 } else {
849 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphRight, f, b);
850 foreach (ref gl; ttywb.ptr[y*ttywIntr+x+1..y*ttywIntr+x+nlen-1]) gl.g1line!setattr(Glyph.Flag.GraphLeft|Glyph.Flag.GraphRight, f, b);
851 ttywb.ptr[y*ttywIntr+x+nlen-1].g1line!setattr(Glyph.Flag.GraphLeft, f, b);
855 void vline(bool setattr=true) (int x, int y, int len) {
856 if (len < 1 || y >= h) return;
857 if (y < 0) {
858 if (y <= -len) return;
859 len += y;
860 y = 0;
862 if (len > h-y) len = h-y;
863 if (len < 1) return; // just in case
864 if (normXYLen(x, y, 1) != 1) return;
865 immutable f = fg, b = bg;
866 if (len == 1) {
867 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp|Glyph.Flag.GraphDown, f, b);
868 } else {
869 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphDown, f, b);
870 foreach (int sy; y+1..y+len-1) ttywb.ptr[sy*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp|Glyph.Flag.GraphDown, f, b);
871 ttywb.ptr[(y+len-1)*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp, f, b);
875 void frame(bool filled=false) (int x, int y, int w, int h) {
876 if (w < 1 || h < 1) return;
877 // erase lines first
878 writeCharsAt(x, y, w, ' ');
879 foreach (immutable sy; y..y+h) {
880 writeCharsAt(x, sy, 1, ' ');
881 writeCharsAt(x+w-1, sy, 1, ' ');
883 writeCharsAt(x, y+h-1, w, ' ');
884 // draw lines
885 if (h == 1) { hline(x, y, w); return; }
886 if (w == 1) { vline(x, y, h); return; }
887 hline(x, y, w); // top
888 hline(x, y+h-1, w); // bottom
889 vline(x, y, h); // left
890 vline(x+w-1, y, h); // right
891 static if (filled) {
892 foreach (immutable sy; y+1..y+h-1) writeCharsAt(x+1, sy, w-2, ' ');
896 void hshadow (int x, int y, int len) {
897 static ubyte shadowColor (ubyte clr) nothrow @trusted @nogc {
898 ubyte r, g, b;
899 ttyColor2rgb(clr, r, g, b);
900 return ttyRgb2Color(r/3, g/3, b/3);
903 len = normXYLen(x, y, len);
904 if (len < 1) return;
905 auto dst = ttywb.ptr+y*ttywIntr+x;
906 while (len-- > 0) {
907 dst.fg = shadowColor(dst.fg);
908 dst.bg = shadowColor(dst.bg);
909 ++dst;
913 void shadowBox (int x, int y, int w, int h) {
914 if (w < 1) return;
915 while (h-- > 0) hshadow(x, y++, w);
918 void frameShadowed(bool filled=false) (int x, int y, int w, int h) {
919 if (w < 1 || h < 1) return;
920 frame!filled(x, y, w, h);
921 shadowBox(x+w, y+1, 2, h-1);
922 hshadow(x+2, y+h, w);
925 void fill(bool g1=false) (int x, int y, int w, int h, char ch=' ') {
926 foreach (immutable sy; y..y+h) writeCharsAt!g1(x, sy, w, ch);
931 // ////////////////////////////////////////////////////////////////////////// //
932 private __gshared int screenSwapped = 0;
935 public void altScreen () nothrow @nogc {
936 if (++screenSwapped == 1) {
937 import core.sys.posix.unistd : write;
938 enum initStr =
939 "\x1b[?1048h"~ // save cursor position
940 "\x1b[?1047h"~ // set alternate screen
941 "\x1b[?7l"~ // turn off autowrapping
942 "\x1b[?25h"~ // make cursor visible
943 "\x1b(B"~ // G0 is ASCII
944 "\x1b)0"~ // G1 is graphics
945 "\x1b[?2004h"~ // enable bracketed paste
946 "\x1b[?1000h\x1b[?1006h\x1b[?1002h"~ // SGR mouse reports
948 write(1, initStr.ptr, initStr.length);
949 xtFullRefresh();
954 public void normalScreen () @trusted nothrow @nogc {
955 if (--screenSwapped == 0) {
956 import core.sys.posix.unistd : write;
957 enum deinitStr =
958 "\x1b[?1047l"~ // set normal screen
959 "\x1b[?7h"~ // turn on autowrapping
960 "\x1b[0;37;40m"~ // set 'normal' attributes
961 "\x1b[?1048l"~ // restore cursor position
962 "\x1b[?25h"~ // make cursor visible
963 "\x1b[?2004l"~ // disable bracketed paste
964 "\x1b[?1002l\x1b[?1006l\x1b[?1000l"~ // disable mouse reports
966 write(1, deinitStr.ptr, deinitStr.length);
967 xtFullRefresh();
972 // ////////////////////////////////////////////////////////////////////////// //
973 private extern(C) void ttyzAtExit () {
974 if (screenSwapped) {
975 screenSwapped = 1;
976 normalScreen();
980 shared static this () {
981 import core.stdc.stdlib : atexit;
982 atexit(&ttyzAtExit);