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*/;
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
)) {
42 ttyzTitleSuffix
= s
.idup
;
47 // ////////////////////////////////////////////////////////////////////////// //
48 public enum XtColorFB(ubyte fg
, ubyte bg
) = cast(uint)((fg
<<8)|bg
);
51 // ////////////////////////////////////////////////////////////////////////// //
53 public align(1) struct XtScissor
{
54 align(1) nothrow @trusted @nogc:
56 ushort mX0
, mY0
, mW
, mH
;
59 this (int ax0
, int ay0
, int aw
, int ah
) {
60 if (ay0
>= ttyhIntr || ax0
>= ttywIntr || aw
< 1 || ah
< 1) return;
62 if (ax0
<= -aw
) return;
67 if (ay0
<= -ah
) return;
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
;
83 static XtScissor
fullscreen () { return XtScissor(0, 0, ttywIntr
, ttyhIntr
); }
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
)); }
89 XtScissor
crop (in XtScissor s
) {
90 import std
.algorithm
: max
, min
;
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
) {
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);
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 // ////////////////////////////////////////////////////////////////////////// //
134 __gshared
bool winSizeChanged
= false;
135 __gshared
bool winChSet
= false;
137 extern(C
) void sigwinchHandler (int sig
) {
138 winSizeChanged
= true;
142 import core
.sys
.posix
.signal
;
143 if (winChSet
) return;
146 sigemptyset(&sa
.sa_mask
);
148 sa
.sa_handler
= &sigwinchHandler
;
149 if (sigaction(SIGWINCH
, &sa
, null) == -1) return;
153 // ////////////////////////////////////////////////////////////////////////// //
154 align(1) struct Glyph
{
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
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
= [
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
) {
194 if ((flags
&Flag
.GraphMask
) == 0) {
195 // check if we have some line drawing char here
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;
212 flags
&= ~(Flag
.GraphMask
);
214 flags |
= (gf
&Flag
.GraphMask
)|Flag
.G1
;
216 } else if (flags
&Flag
.G1
) {
217 flags
&= ~(Flag
.GraphMask|Flag
.G1
); // reset graphics
220 static if (setattr
) {
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
{
244 int cx
, cy
; // cursor position
245 ubyte fg
, bg
; // current color
246 int x
, y
, w
, h
; // saved data (if any)
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
;
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
269 assert(ttySBSize
-ttySBUsed
>= size
);
270 auto res
= ttySavedBufs
+ttySBUsed
;
272 memset(res
, 0, size
);
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; }
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
) {
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
;
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
);
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);
329 auto st
= cast(TxSaveInfo
*)(ttySavedBufs
+ttySBUsed
);
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
);
352 // ////////////////////////////////////////////////////////////////////////// //
353 // initialize system, allocate buffers, clear screen, etc
354 public void xtInit () {
355 import core
.sys
.posix
.unistd
: write
;
356 weAreFucked
= ttyIsUtfucked
;
359 ttyhIntr
= ttyHeight
;
360 ttywb
.length
= ttywIntr
*ttyhIntr
;
361 ttybc
.length
= ttywIntr
*ttyhIntr
;
362 ttywb
[] = Glyph
.init
;
363 ttybc
[] = Glyph
.init
;
365 ttyzScissor
= XtScissor
.fullscreen
;
368 "\x1b[?1034l"~ // xterm: disable "eightBitInput"
369 "\x1b[?1036h"~ // xterm: enable "metaSendsEscape"
370 "\x1b[?1039h"~ // xterm: enable "altSendsEscape"
371 // disable various mouse reports
376 "\x1b[?1004l"~ // don't send focus events
381 write(1, initStr
.ptr
, initStr
.length
);
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;
398 ttyhIntr
= ttyHeight
;
399 ttywb
.length
= ttywIntr
*ttyhIntr
;
400 ttybc
.length
= ttywIntr
*ttyhIntr
;
401 ttywb
[] = Glyph
.init
;
402 ttybc
[] = Glyph
.init
;
405 //enum initStr = "\x1b[H\x1b[0;37;40m\x1b[2J";
406 //write(1, initStr.ptr, initStr.length);
412 // ////////////////////////////////////////////////////////////////////////// //
413 __gshared
char[128*1024] ttytbuf
= 0;
414 __gshared
int ttytpos
;
415 __gshared
int lastx
, lasty
;
418 private void ttzFlush () nothrow @nogc {
420 import core
.sys
.posix
.unistd
: write
;
421 write(1, ttytbuf
.ptr
, ttytpos
);
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
);
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
;
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
) {
455 if (d
< 10) d
+= '0'; else d
+= 'a'-10;
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";
471 foreach (char ch
; title
) if (ch
< ' ' || ch
== 127) { good
= false; break; }
472 write(1, titStart
.ptr
, titStart
.length
);
474 if (title
.length
) write(1, title
.ptr
, title
.length
);
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); }
498 if (x
== 0) { ttzPut("\r"); lastx
= 0; return; }
504 } else if (x
> lastx
) {
510 } else if (x
== lastx
) {
517 } else if (y
> lasty
) {
524 // move in both directions
525 //TODO: optimize this too
536 ttyzScissor
= XtScissor
.fullscreen
;
539 ttytpos
= 0; // reset output position
540 if (ttzFullRefresh
) {
544 ttzPut("\x1b[0;38;5;7;48;5;0m");
545 ubyte lastFG
= 7, lastBG
= 0;
546 int tsz
= ttywIntr
*ttyhIntr
;
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');
573 if (tsrc
.bg
!= lastBG ||
(/*tsrc.ch != ' ' &&*/ tsrc
.fg
!= lastFG
)) {
577 if (tsrc
.fg
!= lastFG
) {
583 if (tsrc
.bg
!= lastBG
) {
585 if (needSC
) ttzPut(';');
593 auto c0
= tty2linux(lastFG
);
594 auto c1
= tty2linux(lastBG
);
595 if (c0
> 7) ttzPut("1;");
605 ttzPut(tsrc
.ch
< 128 ? tsrc
.ch
: ' ');
606 } else if (!weAreFucked || tsrc
.ch
< 128) {
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
) {
622 if (inG0G1
) ttzPut('\x0f');
624 gotoXY(ttycx
, ttycy
);
627 ttzFullRefresh
= false;
631 // ////////////////////////////////////////////////////////////////////////// //
632 public struct XtWindow
{
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
;
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
;
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
;
667 this (int ax
, int ay
, int aw
, int ah
) @trusted {
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);
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 {
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
;
732 if (x
>= ttywIntr
) x
= ttywIntr
-1;
733 if (y
> ttyhIntr
) y
= ttyhIntr
-1;
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
746 if (x
<= -len
) return 0;
752 if (left
< len
) len
= left
;
753 if (len
< 1) return 0; // just in case
754 // crop to global space
757 if (x
+len
<= ttyzScissor
.mX0 || x
> ttyzScissor
.x1 || y
< ttyzScissor
.mY0 || y
> ttyzScissor
.y1
) return 0;
759 if (x
<= -len
) return 0;
764 if (x
< ttyzScissor
.mX0
) {
765 auto z
= ttyzScissor
.mX0
-x
;
766 if (z
>= len
) return 0;
770 if ((left
= ttyzScissor
.x1
+1-x
) < 1) return 0;
771 if (left
< len
) len
= left
;
775 int normXYLen (ref int x
, ref int y
, int len
) const {
777 return normXYLen(x
, y
, len
, ofs
);
781 void writeStrAt(bool g1
=false) (int x
, int y
, const(char)[] str...) {
782 if (str.length
> int.max
) str = str[0..int.max
];
784 auto len
= normXYLen(x
, y
, cast(int)str.length
, ofs
);
786 immutable f
= fg
, b
= bg
;
787 auto src
= cast(const(char)*)str.ptr
+ofs
;
788 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
794 dst
.flags
&= ~Glyph
.Flag
.GraphMask
;
795 dst
.flags |
= Glyph
.Flag
.G1
;
797 dst
.flags
&= ~(Glyph
.Flag
.GraphMask|Glyph
.Flag
.G1
);
803 void writeCharsAt(bool g1
=false) (int x
, int y
, int count
, char ch
) {
804 auto len
= normXYLen(x
, y
, count
);
806 immutable f
= fg
, b
= bg
;
807 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
813 dst
.flags
&= ~(Glyph
.Flag
.GraphMask
);
814 dst
.flags |
= Glyph
.Flag
.G1
;
816 dst
.flags
&= ~(Glyph
.Flag
.GraphMask|Glyph
.Flag
.G1
);
822 void writeUIntAt (int x
, int y
, uint n
) {
823 char[64] ttbuf
= void;
824 uint tbpos
= cast(uint)ttbuf
.length
;
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
) {
835 if (d
< 10) d
+= '0'; else d
+= 'a'-10;
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
;
847 ttywb
.ptr
[y
*ttywIntr
+x
].g1line
!setattr(Glyph
.Flag
.GraphLeft|Glyph
.Flag
.GraphRight
, f
, b
);
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;
858 if (y
<= -len
) return;
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
;
867 ttywb
.ptr
[y
*ttywIntr
+x
].g1line
!setattr(Glyph
.Flag
.GraphUp|Glyph
.Flag
.GraphDown
, f
, b
);
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;
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
, ' ');
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
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 {
899 ttyColor2rgb(clr
, r
, g
, b
);
900 return ttyRgb2Color(r
/3, g
/3, b
/3);
903 len
= normXYLen(x
, y
, len
);
905 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
907 dst
.fg
= shadowColor(dst
.fg
);
908 dst
.bg
= shadowColor(dst
.bg
);
913 void shadowBox (int x
, int y
, int w
, int h
) {
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
;
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
);
954 public void normalScreen () @trusted nothrow @nogc {
955 if (--screenSwapped
== 0) {
956 import core
.sys
.posix
.unistd
: write
;
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
);
972 // ////////////////////////////////////////////////////////////////////////// //
973 private extern(C
) void ttyzAtExit () {
980 shared static this () {
981 import core
.stdc
.stdlib
: atexit
;