some updates
[iv.d.git] / egtui / tty.d
blob0541fc00dd241c36b06d8d06cc9b0459d45df598
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 import core.stdc.stdio : fprintf, fflush, fopen, fclose, FILE;
139 winSizeChanged = true;
140 version(none) {
141 FILE *fl = fopen("/home/ketmar/back/D/prj/miri/z_errlog.log", "a");
142 if (fl != null) {
143 fprintf(fl, "WINCH!\n");
144 fclose(fl);
149 void setupWinch () {
150 import core.sys.posix.signal;
151 if (winChSet) return;
152 winChSet = true;
153 sigaction_t sa;
154 sigemptyset(&sa.sa_mask);
155 sa.sa_flags = 0;
156 sa.sa_handler = &sigwinchHandler;
157 if (sigaction(SIGWINCH, &sa, null) == -1) return;
161 // ////////////////////////////////////////////////////////////////////////// //
162 align(1) struct Glyph {
163 align(1):
164 enum Flag : ubyte {
165 G1 = 0b1000_0000u, // only this will be checked in refresh
166 Mask = 0b1000_0000u, // refresh compare mask
167 GraphMask = 0b0000_1111u,
168 GraphUp = 0b0000_0001u,
169 GraphDown = 0b0000_0010u,
170 GraphLeft = 0b0000_0100u,
171 GraphRight = 0b0000_1000u,
173 ubyte fg = 7; // foreground color
174 ubyte bg = 0; // background color
175 char ch = 0; // char
176 ubyte flags = 0; // see Flag enum
177 // 0: not a graphic char
178 @property char g1char () const pure nothrow @trusted @nogc {
179 static immutable char[16] transTbl = [
180 0x20, // ....
181 0x78, // ...U
182 0x78, // ..D.
183 0x78, // ..DU
184 0x71, // .L..
185 0x6A, // .L.U
186 0x6B, // .LD.
187 0x75, // .LDU
188 0x71, // R...
189 0x6D, // R..U
190 0x6C, // R.D.
191 0x74, // R.DU
192 0x71, // RL..
193 0x76, // RL.U
194 0x77, // RLD.
195 0x6E, // RLDU
197 return (flags&Flag.GraphMask ? transTbl.ptr[flags&Flag.GraphMask] : 0);
199 void g1line(bool setattr) (ubyte gf, ubyte afg, ubyte abg) nothrow @trusted @nogc {
200 if (gf&Flag.GraphMask) {
201 if (flags&Flag.G1) {
202 if ((flags&Flag.GraphMask) == 0) {
203 // check if we have some line drawing char here
204 switch (ch) {
205 case 0x6A: flags |= Flag.GraphLeft|Flag.GraphUp; break;
206 case 0x6B: flags |= Flag.GraphLeft|Flag.GraphDown; break;
207 case 0x6C: flags |= Flag.GraphRight|Flag.GraphDown; break;
208 case 0x6D: flags |= Flag.GraphRight|Flag.GraphUp; break;
209 case 0x6E: flags |= Flag.GraphLeft|Flag.GraphRight|Flag.GraphUp|Flag.GraphDown; break;
210 case 0x71: flags |= Flag.GraphLeft|Flag.GraphRight; break;
211 case 0x74: flags |= Flag.GraphUp|Flag.GraphDown|Flag.GraphRight; break;
212 case 0x75: flags |= Flag.GraphUp|Flag.GraphDown|Flag.GraphLeft; break;
213 case 0x76: flags |= Flag.GraphLeft|Flag.GraphRight|Flag.GraphUp; break;
214 case 0x77: flags |= Flag.GraphLeft|Flag.GraphRight|Flag.GraphDown; break;
215 case 0x78: flags |= Flag.GraphUp|Flag.GraphDown; break;
216 default:
219 } else {
220 flags &= ~(Flag.GraphMask);
222 flags |= (gf&Flag.GraphMask)|Flag.G1;
223 ch = g1char;
224 } else if (flags&Flag.G1) {
225 flags &= ~(Flag.GraphMask|Flag.G1); // reset graphics
226 ch = ' ';
228 static if (setattr) {
229 fg = afg;
230 bg = abg;
234 static assert(Glyph.sizeof == 4);
237 // ////////////////////////////////////////////////////////////////////////// //
238 __gshared int ttycx, ttycy; // current cursor position (0-based)
239 __gshared Glyph[] ttywb; // working buffer
240 __gshared Glyph[] ttybc; // previous buffer
241 __gshared ubyte curFG = 7, curBG = 0;
242 __gshared bool ttzFullRefresh = true;
245 // ////////////////////////////////////////////////////////////////////////// //
246 __gshared ubyte* ttySavedBufs;
247 __gshared uint ttySBUsed;
248 __gshared uint ttySBSize;
250 private align(1) struct TxSaveInfo {
251 align(1):
252 int cx, cy; // cursor position
253 ubyte fg, bg; // current color
254 int x, y, w, h; // saved data (if any)
255 Glyph[0] data;
258 shared static ~this () {
259 import core.stdc.stdlib : free;
260 if (ttySavedBufs !is null) free(ttySavedBufs);
264 private ubyte* ttzSBAlloc (uint size) {
265 import core.stdc.stdlib : realloc;
266 import core.stdc.string : memset;
267 assert(size > 0);
268 if (size >= 4*1024*1024) assert(0, "wtf?!");
269 uint nsz = ttySBUsed+size;
270 if (nsz > ttySBSize) {
271 if (nsz&0xfff) nsz = (nsz|0xfff)+1;
272 auto nb = cast(ubyte*)realloc(ttySavedBufs, nsz);
273 if (nb is null) assert(0, "out of memory"); //FIXME
274 ttySavedBufs = nb;
275 ttySBSize = nsz;
277 assert(ttySBSize-ttySBUsed >= size);
278 auto res = ttySavedBufs+ttySBUsed;
279 ttySBUsed += size;
280 memset(res, 0, size);
281 return res;
285 // push area contents and cursor position (unaffected by scissors)
286 public void xtPushArea (int x, int y, int w, int h) {
287 if (w < 1 || h < 1 || x >= ttywIntr || y >= ttyhIntr) { x = y = w = h = 0; }
288 if (w > 0) {
289 int x0 = x, y0 = y, x1 = x+w-1, y1 = y+h-1;
290 if (x0 < 0) x0 = 0; else if (x0 >= ttywIntr) x0 = ttywIntr-1;
291 if (x1 < 0) x1 = 0; else if (x1 >= ttywIntr) x1 = ttywIntr-1;
292 if (y0 < 0) y0 = 0; else if (y0 >= ttyhIntr) y0 = ttyhIntr-1;
293 if (y1 < 0) y1 = 0; else if (y1 >= ttyhIntr) y1 = ttyhIntr-1;
294 if (x0 <= x1 && y0 <= y1) {
295 x = x0;
296 y = y0;
297 w = x1-x0+1;
298 h = y1-y0+1;
299 } else {
300 x = y = w = h = 0;
303 if (w < 1 || h < 1) { x = y = w = h = 0; }
304 uint sz = cast(uint)(TxSaveInfo.sizeof+Glyph.sizeof*w*h);
305 auto buf = ttzSBAlloc(sz+4);
306 auto st = cast(TxSaveInfo*)buf;
307 st.cx = ttycx;
308 st.cy = ttycy;
309 st.fg = curFG;
310 st.bg = curBG;
311 st.x = x;
312 st.y = y;
313 st.w = w;
314 st.h = h;
315 if (w > 0 && h > 0) {
316 assert(x >= 0 && y >= 0 && x < ttywIntr && y < ttyhIntr && x+w <= ttywIntr && y+h <= ttyhIntr);
317 import core.stdc.string : memcpy;
318 auto src = ttywb.ptr+y*ttywIntr+x;
319 auto dst = st.data.ptr;
320 foreach (immutable _; 0..h) {
321 memcpy(dst, src, Glyph.sizeof*w);
322 src += ttywIntr;
323 dst += w;
326 buf += sz;
327 *cast(uint*)buf = sz;
331 // pop (restore) area contents and cursor position (unaffected by scissors)
332 public void xtPopArea () {
333 if (ttySBUsed == 0) return;
334 assert(ttySBUsed >= 4);
335 auto sz = *cast(uint*)(ttySavedBufs+ttySBUsed-4);
336 ttySBUsed -= sz+4;
337 auto st = cast(TxSaveInfo*)(ttySavedBufs+ttySBUsed);
338 ttycx = st.cx;
339 ttycy = st.cy;
340 curFG = st.fg;
341 curBG = st.bg;
342 auto x = st.x;
343 auto y = st.y;
344 auto w = st.w;
345 auto h = st.h;
346 if (w > 0 && h > 0) {
347 assert(x >= 0 && y >= 0 && x < ttywIntr && y < ttyhIntr && x+w <= ttywIntr && y+h <= ttyhIntr);
348 import core.stdc.string : memcpy;
349 auto src = st.data.ptr;
350 auto dst = ttywb.ptr+y*ttywIntr+x;
351 foreach (immutable _; 0..h) {
352 memcpy(dst, src, Glyph.sizeof*w);
353 src += w;
354 dst += ttywIntr;
360 // ////////////////////////////////////////////////////////////////////////// //
361 // initialize system, allocate buffers, clear screen, etc
362 public void xtInit () {
363 import core.sys.posix.unistd : write;
364 weAreFucked = ttyIsUtfucked;
365 setupWinch();
366 ttywIntr = ttyWidth;
367 ttyhIntr = ttyHeight;
368 ttywb.length = ttywIntr*ttyhIntr;
369 ttybc.length = ttywIntr*ttyhIntr;
370 ttywb[] = Glyph.init;
371 ttybc[] = Glyph.init;
372 ttycx = ttycy = 0;
373 ttyzScissor = XtScissor.fullscreen;
374 // clear screen
375 enum initStr =
376 "\x1b[?1034l"~ // xterm: disable "eightBitInput"
377 "\x1b[?1036h"~ // xterm: enable "metaSendsEscape"
378 "\x1b[?1039h"~ // xterm: enable "altSendsEscape"
379 // disable various mouse reports
380 "\x1b[?1000l"~
381 "\x1b[?1001l"~
382 "\x1b[?1002l"~
383 "\x1b[?1003l"~
384 "\x1b[?1004l"~ // don't send focus events
385 "\x1b[?1005l"~
386 "\x1b[?1006l"~
387 "\x1b[?1015l"~
388 "\x1b[?1000l";
389 write(1, initStr.ptr, initStr.length);
390 xtFullRefresh();
394 public bool xtNeedReinit () {
395 //return (ttywIntr != ttyWidth || ttyhIntr != ttyHeight);
396 return winSizeChanged;
400 // this will reset scissors
401 public void xtReinit () {
402 int nw, nh;
403 nw = ttyWidth; nh = ttyHeight;
404 ttyzScissor = XtScissor.fullscreen;
405 if (ttywIntr != nw || ttyhIntr != nh) {
406 winSizeChanged = false;
407 ttywIntr = nw; ttyhIntr = nh;
408 ttywb.length = ttywIntr*ttyhIntr;
409 ttybc.length = ttywIntr*ttyhIntr;
410 ttywb[] = Glyph.init;
411 ttybc[] = Glyph.init;
412 ttycx = ttycy = 0;
413 // clear screen
414 //enum initStr = "\x1b[H\x1b[0;37;40m\x1b[2J";
415 //write(1, initStr.ptr, initStr.length);
416 xtFullRefresh();
421 // ////////////////////////////////////////////////////////////////////////// //
422 __gshared char[128*1024] ttytbuf = 0;
423 __gshared int ttytpos;
424 __gshared int lastx, lasty;
427 private void ttzFlush () nothrow @nogc {
428 if (ttytpos > 0) {
429 import core.sys.posix.unistd : write;
430 write(1, ttytbuf.ptr, ttytpos);
431 ttytpos = 0;
436 private void ttzPut (const(char)[] str...) nothrow @nogc {
437 import core.stdc.string : memcpy;
438 while (str.length > 0) {
439 uint left = cast(uint)ttytbuf.length-ttytpos;
440 if (left == 0) { ttzFlush(); left = cast(uint)ttytbuf.length; }
441 if (left > str.length) left = cast(uint)str.length;
442 memcpy(ttytbuf.ptr+ttytpos, str.ptr, left);
443 ttytpos += left;
444 str = str[left..$];
449 private void ttzPutUInt (uint n) nothrow @nogc {
450 import core.stdc.string : memcpy;
451 char[64] ttbuf = void;
452 uint tbpos = cast(uint)ttbuf.length;
453 do {
454 ttbuf[--tbpos] = cast(char)(n%10+'0');
455 } while ((n /= 10) != 0);
456 ttzPut(ttbuf[tbpos..$]);
460 private void ttzPutUHex (uint n) nothrow @nogc {
461 char[8] ttbuf = void;
462 foreach_reverse (ref char ch; ttbuf) {
463 int d = n%16;
464 if (d < 10) d += '0'; else d += 'a'-10;
465 ch = cast(char)d;
466 n /= 16;
468 ttzPut(ttbuf[]);
472 // ////////////////////////////////////////////////////////////////////////// //
473 public void xtSetTerminalTitle (const(char)[] title) {
474 import core.sys.posix.unistd : write;
475 if (title.length > 500) title = title[0..500];
476 enum titStart = "\x1b]2;";
477 enum titEnd = "\x07";
478 //enum suffix = " -- egedit";
479 bool good = true;
480 foreach (char ch; title) if (ch < ' ' || ch == 127) { good = false; break; }
481 write(1, titStart.ptr, titStart.length);
482 if (good) {
483 if (title.length) write(1, title.ptr, title.length);
484 } else {
485 foreach (char ch; title) write(1, &ch, 1);
487 if (ttyzTitleSuffix.length) write(1, ttyzTitleSuffix.ptr, ttyzTitleSuffix.length);
488 write(1, titEnd.ptr, titEnd.length);
492 // ////////////////////////////////////////////////////////////////////////// //
493 // redraw the whole screen; doesn't flush it yet
494 public void xtFullRefresh () nothrow @trusted @nogc {
495 ttzFullRefresh = true;
499 // ////////////////////////////////////////////////////////////////////////// //
500 // this will reset scissors
501 public void xtFlush () /*nothrow @nogc*/ {
502 void gotoXY (int x, int y) {
503 if (x == lastx && y == lasty) return;
504 //debug { import iv.vfs.io; stderr.writeln("x=", x, "; y=", y, "; last=", lastx, "; lasty=", lasty); }
505 if (y == lasty) {
506 // move horizontally
507 if (x == 0) { ttzPut("\r"); lastx = 0; return; }
508 ttzPut("\x1b[");
509 if (x < lastx) {
510 // move cursor left
511 ttzPutUInt(lastx-x);
512 ttzPut("D");
513 } else if (x > lastx) {
514 // move cursor right
515 ttzPutUInt(x-lastx);
516 ttzPut("C");
518 lastx = x;
519 } else if (x == lastx) {
520 // move vertically
521 ttzPut("\x1b[");
522 if (y < lasty) {
523 // move cursor up
524 ttzPutUInt(lasty-y);
525 ttzPut("A");
526 } else if (y > lasty) {
527 // move cursor down
528 ttzPutUInt(y-lasty);
529 ttzPut("B");
531 lasty = y;
532 } else {
533 // move in both directions
534 //TODO: optimize this too
535 ttzPut("\x1b[");
536 ttzPutUInt(y+1);
537 ttzPut(";");
538 ttzPutUInt(x+1);
539 ttzPut("H");
540 lastx = x;
541 lasty = y;
545 ttyzScissor = XtScissor.fullscreen;
547 ubyte inG0G1 = 0;
548 ttytpos = 0; // reset output position
549 if (ttzFullRefresh) {
550 ttzPut("\x1b[H");
551 lastx = lasty = 0;
553 ttzPut("\x1b[0;38;5;7;48;5;0m");
554 ubyte lastFG = 7, lastBG = 0;
555 int tsz = ttywIntr*ttyhIntr;
556 // fix glyph chars
557 auto tsrc = ttywb.ptr; // source buffer
558 auto tdst = ttybc.ptr; // destination buffer
559 //immutable doctrans = (termType == TermType.linux);
560 enum doctrans = false;
561 for (uint pos = 0; pos < tsz; *tdst++ = *tsrc++, ++pos) {
562 if (tsrc.ch == '\t') { tsrc.ch = '\x62'; tsrc.flags = Glyph.Flag.G1; }
563 if (tsrc.ch == '\v') { tsrc.ch = '\x69'; tsrc.flags = Glyph.Flag.G1; }
564 if (tsrc.ch == '\n') { tsrc.ch = '\x65'; tsrc.flags = Glyph.Flag.G1; }
565 if (tsrc.ch == '\r') { tsrc.ch = '\x64'; tsrc.flags = Glyph.Flag.G1; }
566 else if (tsrc.ch == 0) { tsrc.ch = ' '; tsrc.flags = 0; }
567 else if (tsrc.ch < ' ' || tsrc.ch == 127) { tsrc.ch = '\x7e'; tsrc.flags = Glyph.Flag.G1; }
568 // skip things that doesn't need to be updated
569 if (!ttzFullRefresh) {
570 if (((tsrc.flags^tdst.flags)&Glyph.Flag.Mask) == 0 && tsrc.ch == tdst.ch && tsrc.bg == tdst.bg) {
571 // same char, different attrs? for spaces, it is enough to compare only bg color
572 // actually, some terminals may draw different colored cursor on different colored
573 // spaces, but i don't care: fix your terminal!
574 if (/*tsrc.ch == ' ' ||*/ tsrc.fg == tdst.fg) continue;
577 gotoXY(pos%ttywIntr, pos/ttywIntr);
578 if (inG0G1 != (tsrc.flags&Glyph.Flag.G1)) {
579 if ((inG0G1 = (tsrc.flags&Glyph.Flag.G1)) != 0) ttzPut('\x0e'); else ttzPut('\x0f');
581 // new attrs?
582 if (tsrc.bg != lastBG || (/*tsrc.ch != ' ' &&*/ tsrc.fg != lastFG)) {
583 ttzPut("\x1b[");
584 if (!doctrans) {
585 bool needSC = false;
586 if (tsrc.fg != lastFG) {
587 lastFG = tsrc.fg;
588 ttzPut("38;5;");
589 ttzPutUInt(lastFG);
590 needSC = true;
592 if (tsrc.bg != lastBG) {
593 lastBG = tsrc.bg;
594 if (needSC) ttzPut(';');
595 ttzPut("48;5;");
596 ttzPutUInt(lastBG);
598 } else {
599 lastFG = tsrc.fg;
600 lastBG = tsrc.bg;
601 ttzPut("0;");
602 auto c0 = tty2linux(lastFG);
603 auto c1 = tty2linux(lastBG);
604 if (c0 > 7) ttzPut("1;");
605 ttzPut("3");
606 ttzPutUInt(c0);
607 ttzPut(";4");
608 ttzPutUInt(c1);
610 ttzPut('m');
612 // draw char
613 if (inG0G1) {
614 ttzPut(tsrc.ch < 128 ? tsrc.ch : ' ');
615 } else if (!weAreFucked || tsrc.ch < 128) {
616 ttzPut(tsrc.ch);
617 } else {
618 char[8] ubuf = void;
619 dchar dch = koi2uni(cast(char)tsrc.ch);
620 auto len = utf8Encode(ubuf[], dch);
621 if (len < 1) { ubuf[0] = '?'; len = 1; }
622 ttzPut(ubuf[0..len]);
624 // adjust cursor position
625 if (++lastx == ttywIntr) {
626 lastx = 0;
627 ttzPut("\r");
630 // switch back to G0
631 if (inG0G1) ttzPut('\x0f');
632 // position cursor
633 gotoXY(ttycx, ttycy);
634 // done
635 ttzFlush();
636 ttzFullRefresh = false;
640 // ////////////////////////////////////////////////////////////////////////// //
641 public struct XtWindow {
642 private:
643 int x, y, w, h;
644 ushort fgbg;
646 public: // what idiot marked this whole thing as nothrow-nogc?!
647 // temporarily change color and call delegate
648 void withFB (ubyte fg, ubyte bg, scope void delegate () dg) {
649 if (dg is null) return;
650 auto oldcolor = fgbg;
651 scope(exit) fgbg = oldcolor;
652 fb(fg, bg);
653 dg();
656 // temporarily change color and call delegate
657 void withFG (ubyte cfg, scope void delegate () dg) {
658 if (dg is null) return;
659 auto oldcolor = fgbg;
660 scope(exit) fgbg = oldcolor;
661 fg = cfg;
662 dg();
665 // temporarily change color and call delegate
666 void withBG (ubyte cbg, scope void delegate () dg) {
667 if (dg is null) return;
668 auto oldcolor = fgbg;
669 scope(exit) fgbg = oldcolor;
670 bg = cbg;
671 dg();
674 nothrow @safe @nogc:
675 public:
676 this (int ax, int ay, int aw, int ah) @trusted {
677 x = ax;
678 y = ay;
679 w = aw;
680 h = ah;
681 fgbg = cast(ushort)((curFG<<8)|curBG); // with current color
684 static XtWindow fullscreen () @trusted { pragma(inline, true); return XtWindow(0, 0, ttywIntr, ttyhIntr); }
686 @property bool valid () const pure { pragma(inline, true); return (w > 0 && h > 0); }
687 // invalid windows are invisible ;-)
688 @property bool visible () const @trusted {
689 pragma(inline, true);
690 return
691 w > 0 && h > 0 && // valid
692 x < ttywIntr && y < ttyhIntr && // not too right/bottom
693 x+w > 0 && y+h > 0; // not too left/top
696 // clip this window to another window
697 //FIXME: completely untested (and unused!)
699 void clipBy() (in auto ref XtWindow ww) {
700 if (empty || ww.empty) { w = h = 0; return; }
701 if (x+w <= ww.x || y+h <= ww.y || x >= ww.x+ww.w || y >= ww.y+ww.h) { w = h = 0; return; }
702 // we are at least partially inside ww
703 if (x < ww.x) x = ww.x; // clip left
704 if (y < ww.y) y = ww.y; // clip top
705 if (x+w > ww.x+ww.w) w = ww.x+ww.w-x; // clip right
706 if (y+h > ww.y+ww.h) y = ww.y+ww.h-y; // clip bottom
710 @property int x0 () const pure { pragma(inline, true); return x; }
711 @property int y0 () const pure { pragma(inline, true); return y; }
712 @property int width () const pure { pragma(inline, true); return (w > 0 ? w : 0); }
713 @property int height () const pure { pragma(inline, true); return (h > 0 ? h : 0); }
715 @property void x0 (int v) pure { pragma(inline, true); x = v; }
716 @property void y0 (int v) pure { pragma(inline, true); y = v; }
717 @property void width (int v) pure { pragma(inline, true); w = v; }
718 @property void height (int v) pure { pragma(inline, true); h = v; }
720 @property ubyte fg () const pure { pragma(inline, true); return cast(ubyte)(fgbg>>8); }
721 @property ubyte bg () const pure { pragma(inline, true); return cast(ubyte)(fgbg&0xff); }
723 @property void fg (ubyte v) pure { pragma(inline, true); fgbg = cast(ushort)((fgbg&0x00ff)|(v<<8)); }
724 @property void bg (ubyte v) pure { pragma(inline, true); fgbg = cast(ushort)((fgbg&0xff00)|v); }
726 @property uint color () const pure { pragma(inline, true); return fgbg; }
727 @property void color (uint v) pure { pragma(inline, true); fgbg = cast(ushort)(v&0xffff); }
729 @property void fb (ubyte fg, ubyte bg) pure { pragma(inline, true); fgbg = cast(ushort)((fg<<8)|bg); }
731 void gotoXY (int x, int y) @trusted {
732 x += this.x;
733 y += this.y;
734 if (x >= this.x+this.w) x = this.x+this.w-1;
735 if (y >= this.y+this.h) y = this.y+this.h-1;
736 if (x < this.x) x = this.x;
737 if (y < this.y) y = this.y;
738 // now global
739 if (x < 0) x = 0;
740 if (y < 0) y = 0;
741 if (x >= ttywIntr) x = ttywIntr-1;
742 if (y > ttyhIntr) y = ttyhIntr-1;
743 // set new coords
744 ttycx = x;
745 ttycy = y;
748 // returns new length (can be 0, and both `x` and `y` are undefined in this case)
749 // ofs: bytes to skip (if x is "before" window
750 // x, y: will be translated to global coords
751 int normXYLen (ref int x, ref int y, int len, out int ofs) const @trusted {
752 if (len < 1 || w < 1 || h < 1 || x >= w || y < 0 || y >= h || !visible || !ttyzScissor.visible) return 0; // nothing to do
753 // crop to window
754 if (x < 0) {
755 if (x <= -len) return 0;
756 len += x;
757 ofs = -x;
758 x = 0;
760 int left = w-x;
761 if (left < len) len = left;
762 if (len < 1) return 0; // just in case
763 // crop to global space
764 x += this.x;
765 y += this.y;
766 if (x+len <= ttyzScissor.mX0 || x > ttyzScissor.x1 || y < ttyzScissor.mY0 || y > ttyzScissor.y1) return 0;
767 if (x < 0) {
768 if (x <= -len) return 0;
769 len += x;
770 ofs = -x;
771 x = 0;
773 if (x < ttyzScissor.mX0) {
774 auto z = ttyzScissor.mX0-x;
775 if (z >= len) return 0;
776 x += z;
777 len -= z;
779 if ((left = ttyzScissor.x1+1-x) < 1) return 0;
780 if (left < len) len = left;
781 return len;
784 int normXYLen (ref int x, ref int y, int len) const {
785 int ofs;
786 return normXYLen(x, y, len, ofs);
789 @trusted const:
790 void writeStrAt(bool g1=false) (int x, int y, const(char)[] str...) {
791 if (str.length > int.max) str = str[0..int.max];
792 int ofs;
793 auto len = normXYLen(x, y, cast(int)str.length, ofs);
794 if (len < 1) return;
795 immutable f = fg, b = bg;
796 auto src = cast(const(char)*)str.ptr+ofs;
797 auto dst = ttywb.ptr+y*ttywIntr+x;
798 while (len-- > 0) {
799 dst.fg = f;
800 dst.bg = b;
801 dst.ch = *src++;
802 static if (g1) {
803 dst.flags &= ~Glyph.Flag.GraphMask;
804 dst.flags |= Glyph.Flag.G1;
805 } else {
806 dst.flags &= ~(Glyph.Flag.GraphMask|Glyph.Flag.G1);
808 ++dst;
812 void writeCharsAt(bool g1=false) (int x, int y, int count, char ch) {
813 auto len = normXYLen(x, y, count);
814 if (len < 1) return;
815 immutable f = fg, b = bg;
816 auto dst = ttywb.ptr+y*ttywIntr+x;
817 while (len-- > 0) {
818 dst.fg = f;
819 dst.bg = b;
820 dst.ch = ch;
821 static if (g1) {
822 dst.flags &= ~(Glyph.Flag.GraphMask);
823 dst.flags |= Glyph.Flag.G1;
824 } else {
825 dst.flags &= ~(Glyph.Flag.GraphMask|Glyph.Flag.G1);
827 ++dst;
831 void writeUIntAt (int x, int y, uint n) {
832 char[64] ttbuf = void;
833 uint tbpos = cast(uint)ttbuf.length;
834 do {
835 ttbuf[--tbpos] = cast(char)(n%10+'0');
836 } while ((n /= 10) != 0);
837 writeStrAt(x, y, ttbuf[tbpos..$]);
840 void writeUHexAt (int x, int y, uint n) {
841 char[8] ttbuf = void;
842 foreach_reverse (ref char ch; ttbuf) {
843 int d = n%16;
844 if (d < 10) d += '0'; else d += 'a'-10;
845 ch = cast(char)d;
846 n /= 16;
848 writeStrAt(x, y, ttbuf[]);
851 void hline(bool setattr=true) (int x, int y, int len) {
852 auto nlen = normXYLen(x, y, len);
853 if (nlen < 1) return;
854 immutable f = fg, b = bg;
855 if (nlen == 1) {
856 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphLeft|Glyph.Flag.GraphRight, f, b);
857 } else {
858 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphRight, f, b);
859 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);
860 ttywb.ptr[y*ttywIntr+x+nlen-1].g1line!setattr(Glyph.Flag.GraphLeft, f, b);
864 void vline(bool setattr=true) (int x, int y, int len) {
865 if (len < 1 || y >= h) return;
866 if (y < 0) {
867 if (y <= -len) return;
868 len += y;
869 y = 0;
871 if (len > h-y) len = h-y;
872 if (len < 1) return; // just in case
873 if (normXYLen(x, y, 1) != 1) return;
874 immutable f = fg, b = bg;
875 if (len == 1) {
876 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp|Glyph.Flag.GraphDown, f, b);
877 } else {
878 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphDown, f, b);
879 foreach (int sy; y+1..y+len-1) ttywb.ptr[sy*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp|Glyph.Flag.GraphDown, f, b);
880 ttywb.ptr[(y+len-1)*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp, f, b);
884 void frame(bool filled=false) (int x, int y, int w, int h) {
885 if (w < 1 || h < 1) return;
886 // erase lines first
887 writeCharsAt(x, y, w, ' ');
888 foreach (immutable sy; y..y+h) {
889 writeCharsAt(x, sy, 1, ' ');
890 writeCharsAt(x+w-1, sy, 1, ' ');
892 writeCharsAt(x, y+h-1, w, ' ');
893 // draw lines
894 if (h == 1) { hline(x, y, w); return; }
895 if (w == 1) { vline(x, y, h); return; }
896 hline(x, y, w); // top
897 hline(x, y+h-1, w); // bottom
898 vline(x, y, h); // left
899 vline(x+w-1, y, h); // right
900 static if (filled) {
901 foreach (immutable sy; y+1..y+h-1) writeCharsAt(x+1, sy, w-2, ' ');
905 void hshadow (int x, int y, int len) {
906 static ubyte shadowColor (ubyte clr) nothrow @trusted @nogc {
907 ubyte r, g, b;
908 ttyColor2rgb(clr, r, g, b);
909 return ttyRgb2Color(r/3, g/3, b/3);
912 len = normXYLen(x, y, len);
913 if (len < 1) return;
914 auto dst = ttywb.ptr+y*ttywIntr+x;
915 while (len-- > 0) {
916 dst.fg = shadowColor(dst.fg);
917 dst.bg = shadowColor(dst.bg);
918 ++dst;
922 void shadowBox (int x, int y, int w, int h) {
923 if (w < 1) return;
924 while (h-- > 0) hshadow(x, y++, w);
927 void frameShadowed(bool filled=false) (int x, int y, int w, int h) {
928 if (w < 1 || h < 1) return;
929 frame!filled(x, y, w, h);
930 shadowBox(x+w, y+1, 2, h-1);
931 hshadow(x+2, y+h, w);
934 void fill(bool g1=false) (int x, int y, int w, int h, char ch=' ') {
935 foreach (immutable sy; y..y+h) writeCharsAt!g1(x, sy, w, ch);
940 // ////////////////////////////////////////////////////////////////////////// //
941 private __gshared int screenSwapped = 0;
944 public void altScreen () nothrow @nogc {
945 if (++screenSwapped == 1) {
946 import core.sys.posix.unistd : write;
947 enum initStr =
948 "\x1b[?1048h"~ // save cursor position
949 "\x1b[?1047h"~ // set alternate screen
950 "\x1b[?7l"~ // turn off autowrapping
951 "\x1b[?25h"~ // make cursor visible
952 "\x1b(B"~ // G0 is ASCII
953 "\x1b)0"~ // G1 is graphics
954 "\x1b[?2004h"~ // enable bracketed paste
955 "\x1b[?1000h\x1b[?1006h\x1b[?1002h"~ // SGR mouse reports
957 write(1, initStr.ptr, initStr.length);
958 xtFullRefresh();
963 public void normalScreen () @trusted nothrow @nogc {
964 if (--screenSwapped == 0) {
965 import core.sys.posix.unistd : write;
966 enum deinitStr =
967 "\x1b[?1047l"~ // set normal screen
968 "\x1b[?7h"~ // turn on autowrapping
969 "\x1b[0;37;40m"~ // set 'normal' attributes
970 "\x1b[?1048l"~ // restore cursor position
971 "\x1b[?25h"~ // make cursor visible
972 "\x1b[?2004l"~ // disable bracketed paste
973 "\x1b[?1002l\x1b[?1006l\x1b[?1000l"~ // disable mouse reports
975 write(1, deinitStr.ptr, deinitStr.length);
976 xtFullRefresh();
981 // ////////////////////////////////////////////////////////////////////////// //
982 private extern(C) void ttyzAtExit () {
983 if (screenSwapped) {
984 screenSwapped = 1;
985 normalScreen();
989 shared static this () {
990 import core.stdc.stdlib : atexit;
991 atexit(&ttyzAtExit);