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 import core
.stdc
.stdio
: fprintf
, fflush
, fopen
, fclose
, FILE
;
139 winSizeChanged
= true;
141 FILE
*fl
= fopen("/home/ketmar/back/D/prj/miri/z_errlog.log", "a");
143 fprintf(fl
, "WINCH!\n");
150 import core
.sys
.posix
.signal
;
151 if (winChSet
) return;
154 sigemptyset(&sa
.sa_mask
);
156 sa
.sa_handler
= &sigwinchHandler
;
157 if (sigaction(SIGWINCH
, &sa
, null) == -1) return;
161 // ////////////////////////////////////////////////////////////////////////// //
162 align(1) struct Glyph
{
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
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
= [
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
) {
202 if ((flags
&Flag
.GraphMask
) == 0) {
203 // check if we have some line drawing char here
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;
220 flags
&= ~(Flag
.GraphMask
);
222 flags |
= (gf
&Flag
.GraphMask
)|Flag
.G1
;
224 } else if (flags
&Flag
.G1
) {
225 flags
&= ~(Flag
.GraphMask|Flag
.G1
); // reset graphics
228 static if (setattr
) {
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
{
252 int cx
, cy
; // cursor position
253 ubyte fg
, bg
; // current color
254 int x
, y
, w
, h
; // saved data (if any)
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
;
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
277 assert(ttySBSize
-ttySBUsed
>= size
);
278 auto res
= ttySavedBufs
+ttySBUsed
;
280 memset(res
, 0, size
);
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; }
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
) {
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
;
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
);
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);
337 auto st
= cast(TxSaveInfo
*)(ttySavedBufs
+ttySBUsed
);
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
);
360 // ////////////////////////////////////////////////////////////////////////// //
361 // initialize system, allocate buffers, clear screen, etc
362 public void xtInit () {
363 import core
.sys
.posix
.unistd
: write
;
364 weAreFucked
= ttyIsUtfucked
;
367 ttyhIntr
= ttyHeight
;
368 ttywb
.length
= ttywIntr
*ttyhIntr
;
369 ttybc
.length
= ttywIntr
*ttyhIntr
;
370 ttywb
[] = Glyph
.init
;
371 ttybc
[] = Glyph
.init
;
373 ttyzScissor
= XtScissor
.fullscreen
;
376 "\x1b[?1034l"~ // xterm: disable "eightBitInput"
377 "\x1b[?1036h"~ // xterm: enable "metaSendsEscape"
378 "\x1b[?1039h"~ // xterm: enable "altSendsEscape"
379 // disable various mouse reports
384 "\x1b[?1004l"~ // don't send focus events
389 write(1, initStr
.ptr
, initStr
.length
);
394 public bool xtNeedReinit () {
395 //return (ttywIntr != ttyWidth || ttyhIntr != ttyHeight);
396 return winSizeChanged
;
400 // this will reset scissors
401 public void xtReinit () {
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
;
414 //enum initStr = "\x1b[H\x1b[0;37;40m\x1b[2J";
415 //write(1, initStr.ptr, initStr.length);
421 // ////////////////////////////////////////////////////////////////////////// //
422 __gshared
char[128*1024] ttytbuf
= 0;
423 __gshared
int ttytpos
;
424 __gshared
int lastx
, lasty
;
427 private void ttzFlush () nothrow @nogc {
429 import core
.sys
.posix
.unistd
: write
;
430 write(1, ttytbuf
.ptr
, ttytpos
);
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
);
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
;
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
) {
464 if (d
< 10) d
+= '0'; else d
+= 'a'-10;
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";
480 foreach (char ch
; title
) if (ch
< ' ' || ch
== 127) { good
= false; break; }
481 write(1, titStart
.ptr
, titStart
.length
);
483 if (title
.length
) write(1, title
.ptr
, title
.length
);
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); }
507 if (x
== 0) { ttzPut("\r"); lastx
= 0; return; }
513 } else if (x
> lastx
) {
519 } else if (x
== lastx
) {
526 } else if (y
> lasty
) {
533 // move in both directions
534 //TODO: optimize this too
545 ttyzScissor
= XtScissor
.fullscreen
;
548 ttytpos
= 0; // reset output position
549 if (ttzFullRefresh
) {
553 ttzPut("\x1b[0;38;5;7;48;5;0m");
554 ubyte lastFG
= 7, lastBG
= 0;
555 int tsz
= ttywIntr
*ttyhIntr
;
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');
582 if (tsrc
.bg
!= lastBG ||
(/*tsrc.ch != ' ' &&*/ tsrc
.fg
!= lastFG
)) {
586 if (tsrc
.fg
!= lastFG
) {
592 if (tsrc
.bg
!= lastBG
) {
594 if (needSC
) ttzPut(';');
602 auto c0
= tty2linux(lastFG
);
603 auto c1
= tty2linux(lastBG
);
604 if (c0
> 7) ttzPut("1;");
614 ttzPut(tsrc
.ch
< 128 ? tsrc
.ch
: ' ');
615 } else if (!weAreFucked || tsrc
.ch
< 128) {
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
) {
631 if (inG0G1
) ttzPut('\x0f');
633 gotoXY(ttycx
, ttycy
);
636 ttzFullRefresh
= false;
640 // ////////////////////////////////////////////////////////////////////////// //
641 public struct XtWindow
{
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
;
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
;
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
;
676 this (int ax
, int ay
, int aw
, int ah
) @trusted {
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);
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 {
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
;
741 if (x
>= ttywIntr
) x
= ttywIntr
-1;
742 if (y
> ttyhIntr
) y
= ttyhIntr
-1;
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
755 if (x
<= -len
) return 0;
761 if (left
< len
) len
= left
;
762 if (len
< 1) return 0; // just in case
763 // crop to global space
766 if (x
+len
<= ttyzScissor
.mX0 || x
> ttyzScissor
.x1 || y
< ttyzScissor
.mY0 || y
> ttyzScissor
.y1
) return 0;
768 if (x
<= -len
) return 0;
773 if (x
< ttyzScissor
.mX0
) {
774 auto z
= ttyzScissor
.mX0
-x
;
775 if (z
>= len
) return 0;
779 if ((left
= ttyzScissor
.x1
+1-x
) < 1) return 0;
780 if (left
< len
) len
= left
;
784 int normXYLen (ref int x
, ref int y
, int len
) const {
786 return normXYLen(x
, y
, len
, ofs
);
790 void writeStrAt(bool g1
=false) (int x
, int y
, const(char)[] str...) {
791 if (str.length
> int.max
) str = str[0..int.max
];
793 auto len
= normXYLen(x
, y
, cast(int)str.length
, ofs
);
795 immutable f
= fg
, b
= bg
;
796 auto src
= cast(const(char)*)str.ptr
+ofs
;
797 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
803 dst
.flags
&= ~Glyph
.Flag
.GraphMask
;
804 dst
.flags |
= Glyph
.Flag
.G1
;
806 dst
.flags
&= ~(Glyph
.Flag
.GraphMask|Glyph
.Flag
.G1
);
812 void writeCharsAt(bool g1
=false) (int x
, int y
, int count
, char ch
) {
813 auto len
= normXYLen(x
, y
, count
);
815 immutable f
= fg
, b
= bg
;
816 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
822 dst
.flags
&= ~(Glyph
.Flag
.GraphMask
);
823 dst
.flags |
= Glyph
.Flag
.G1
;
825 dst
.flags
&= ~(Glyph
.Flag
.GraphMask|Glyph
.Flag
.G1
);
831 void writeUIntAt (int x
, int y
, uint n
) {
832 char[64] ttbuf
= void;
833 uint tbpos
= cast(uint)ttbuf
.length
;
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
) {
844 if (d
< 10) d
+= '0'; else d
+= 'a'-10;
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
;
856 ttywb
.ptr
[y
*ttywIntr
+x
].g1line
!setattr(Glyph
.Flag
.GraphLeft|Glyph
.Flag
.GraphRight
, f
, b
);
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;
867 if (y
<= -len
) return;
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
;
876 ttywb
.ptr
[y
*ttywIntr
+x
].g1line
!setattr(Glyph
.Flag
.GraphUp|Glyph
.Flag
.GraphDown
, f
, b
);
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;
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
, ' ');
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
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 {
908 ttyColor2rgb(clr
, r
, g
, b
);
909 return ttyRgb2Color(r
/3, g
/3, b
/3);
912 len
= normXYLen(x
, y
, len
);
914 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
916 dst
.fg
= shadowColor(dst
.fg
);
917 dst
.bg
= shadowColor(dst
.bg
);
922 void shadowBox (int x
, int y
, int w
, int h
) {
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
;
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
);
963 public void normalScreen () @trusted nothrow @nogc {
964 if (--screenSwapped
== 0) {
965 import core
.sys
.posix
.unistd
: write
;
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
);
981 // ////////////////////////////////////////////////////////////////////////// //
982 private extern(C
) void ttyzAtExit () {
989 shared static this () {
990 import core
.stdc
.stdlib
: atexit
;