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
.tuing
.tty
/*is aliced*/;
21 public import iv
.rawtty
;
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
{
639 this (int ax
, int ay
, int aw
, int ah
) @trusted {
644 fgbg
= cast(ushort)((curFG
<<8)|curBG
); // with current color
647 static XtWindow
fullscreen () @trusted { pragma(inline
, true); return XtWindow(0, 0, ttywIntr
, ttyhIntr
); }
649 @property bool valid () const pure { pragma(inline
, true); return (w
> 0 && h
> 0); }
650 // invalid windows are invisible ;-)
651 @property bool visible () const @trusted {
652 pragma(inline
, true);
654 w
> 0 && h
> 0 && // valid
655 x
< ttywIntr
&& y
< ttyhIntr
&& // not too right/bottom
656 x
+w
> 0 && y
+h
> 0; // not too left/top
659 // clip this window to another window
660 //FIXME: completely untested (and unused!)
662 void clipBy() (in auto ref XtWindow ww) {
663 if (empty || ww.empty) { w = h = 0; return; }
664 if (x+w <= ww.x || y+h <= ww.y || x >= ww.x+ww.w || y >= ww.y+ww.h) { w = h = 0; return; }
665 // we are at least partially inside ww
666 if (x < ww.x) x = ww.x; // clip left
667 if (y < ww.y) y = ww.y; // clip top
668 if (x+w > ww.x+ww.w) w = ww.x+ww.w-x; // clip right
669 if (y+h > ww.y+ww.h) y = ww.y+ww.h-y; // clip bottom
673 @property int x0 () const pure { pragma(inline
, true); return x
; }
674 @property int y0 () const pure { pragma(inline
, true); return y
; }
675 @property int x1 () const pure { pragma(inline
, true); return x
+w
-1; }
676 @property int y1 () const pure { pragma(inline
, true); return y
+h
-1; }
677 @property int width () const pure { pragma(inline
, true); return (w
> 0 ? w
: 0); }
678 @property int height () const pure { pragma(inline
, true); return (h
> 0 ? h
: 0); }
680 @property void x0 (int v
) pure { pragma(inline
, true); x
= v
; }
681 @property void y0 (int v
) pure { pragma(inline
, true); y
= v
; }
682 @property void x1 (int v
) pure { pragma(inline
, true); w
= (v
-x0
+1 > 0 ? v
-x0
+1 : 0); }
683 @property void y1 (int v
) pure { pragma(inline
, true); h
= (v
-y0
+1 > 0 ? v
-y0
+1 : 0); }
684 @property void width (int v
) pure { pragma(inline
, true); w
= v
; }
685 @property void height (int v
) pure { pragma(inline
, true); h
= v
; }
687 @property ubyte fg () const pure { pragma(inline
, true); return cast(ubyte)(fgbg
>>8); }
688 @property ubyte bg () const pure { pragma(inline
, true); return cast(ubyte)(fgbg
&0xff); }
690 @property void fg (ubyte v
) pure { pragma(inline
, true); fgbg
= cast(ushort)((fgbg
&0x00ff)|
(v
<<8)); }
691 @property void bg (ubyte v
) pure { pragma(inline
, true); fgbg
= cast(ushort)((fgbg
&0xff00)|v
); }
693 @property uint color () const pure { pragma(inline
, true); return fgbg
; }
694 @property void color (uint v
) pure { pragma(inline
, true); fgbg
= cast(ushort)(v
&0xffff); }
696 @property void fb (ubyte fg
, ubyte bg
) pure { pragma(inline
, true); fgbg
= cast(ushort)((fg
<<8)|bg
); }
698 void gotoXY (int x
, int y
) @trusted {
701 if (x
>= this.x
+this.w
) x
= this.x
+this.w
-1;
702 if (y
>= this.y
+this.h
) y
= this.y
+this.h
-1;
703 if (x
< this.x
) x
= this.x
;
704 if (y
< this.y
) y
= this.y
;
708 if (x
>= ttywIntr
) x
= ttywIntr
-1;
709 if (y
> ttyhIntr
) y
= ttyhIntr
-1;
715 // returns new length (can be 0, and both `x` and `y` are undefined in this case)
716 // ofs: bytes to skip (if x is "before" window
717 // x, y: will be translated to global coords
718 int normXYLen (ref int x
, ref int y
, int len
, out int ofs
) const @trusted {
719 if (len
< 1 || w
< 1 || h
< 1 || x
>= w || y
< 0 || y
>= h ||
!visible ||
!ttyzScissor
.visible
) return 0; // nothing to do
722 if (x
<= -len
) return 0;
728 if (left
< len
) len
= left
;
729 if (len
< 1) return 0; // just in case
730 // crop to global space
733 if (x
+len
<= ttyzScissor
.mX0 || x
> ttyzScissor
.x1 || y
< ttyzScissor
.mY0 || y
> ttyzScissor
.y1
) return 0;
735 if (x
<= -len
) return 0;
740 if (x
< ttyzScissor
.mX0
) {
741 auto z
= ttyzScissor
.mX0
-x
;
742 if (z
>= len
) return 0;
746 if ((left
= ttyzScissor
.x1
+1-x
) < 1) return 0;
747 if (left
< len
) len
= left
;
751 int normXYLen (ref int x
, ref int y
, int len
) const {
753 return normXYLen(x
, y
, len
, ofs
);
757 void writeHotStrAt (int x
, int y
, int w
, const(char)[] s
, uint hotattr
, Align defalign
=Align
.Left
, bool dohot
=true, bool setcur
=false) {
759 if (s
.length
&& s
.ptr
[0] >= '\x01' && s
.ptr
[0] <= '\x03') {
760 defalign
= cast(Align
)s
.ptr
[0];
763 if (s
.length
== 0) return;
764 if (s
.length
> int.max
/32) s
= s
[0..int.max
/32];
765 if (defalign
< 1 || defalign
> 3) defalign
= Align
.Left
;
766 final switch (defalign
) {
767 case Align
.Left
: break;
768 case Align
.Right
: x
+= w
-hotStrLen(s
, dohot
); break;
769 case Align
.Center
: x
+= (w
-hotStrLen(s
, dohot
))/2; break;
771 if (setcur
) gotoXY(x
, y
);
775 import iv
.strex
: indexOf
;
776 auto ampos
= s
.indexOf('&');
777 if (ampos
< 0 || s
.length
-ampos
< 2) { writeStrAt(x
, y
, s
); break; }
778 if (ampos
> 0) { writeStrAt(x
, y
, s
[0..ampos
]); x
+= cast(int)ampos
; }
779 if (s
.ptr
[ampos
+1] != '&') {
780 if (setcur
) { gotoXY(x
, y
); setcur
= false; }
782 fgbg
= hotattr
&0xffff;
783 writeCharsAt(x
, y
, 1, s
.ptr
[ampos
+1]);
786 writeCharsAt(x
, y
, 1, '&');
798 void writeStrAt(bool g1
=false) (int x
, int y
, const(char)[] str...) {
799 if (str.length
> int.max
) str = str[0..int.max
];
801 auto len
= normXYLen(x
, y
, cast(int)str.length
, ofs
);
803 immutable f
= fg
, b
= bg
;
804 auto src
= cast(const(char)*)str.ptr
+ofs
;
805 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
811 dst
.flags
&= ~Glyph
.Flag
.GraphMask
;
812 dst
.flags |
= Glyph
.Flag
.G1
;
814 dst
.flags
&= ~(Glyph
.Flag
.GraphMask|Glyph
.Flag
.G1
);
820 void writeCharAt(bool g1
=false) (int x
, int y
, char ch
) { writeCharsAt
!g1(x
, y
, 1, ch
); }
822 void writeCharsAt(bool g1
=false) (int x
, int y
, int count
, char ch
) {
823 auto len
= normXYLen(x
, y
, count
);
825 immutable f
= fg
, b
= bg
;
826 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
832 dst
.flags
&= ~Glyph
.Flag
.GraphMask
;
833 dst
.flags |
= Glyph
.Flag
.G1
;
835 dst
.flags
&= ~(Glyph
.Flag
.GraphMask|Glyph
.Flag
.G1
);
841 void writeUIntAt (int x
, int y
, uint n
) {
842 char[64] ttbuf
= void;
843 uint tbpos
= cast(uint)ttbuf
.length
;
845 ttbuf
[--tbpos
] = cast(char)(n
%10+'0');
846 } while ((n
/= 10) != 0);
847 writeStrAt(x
, y
, ttbuf
[tbpos
..$]);
850 void writeUHexAt (int x
, int y
, uint n
) {
851 char[8] ttbuf
= void;
852 foreach_reverse (ref char ch
; ttbuf
) {
854 if (d
< 10) d
+= '0'; else d
+= 'a'-10;
858 writeStrAt(x
, y
, ttbuf
[]);
861 void hline(bool setattr
=true) (int x
, int y
, int len
) {
862 auto nlen
= normXYLen(x
, y
, len
);
863 if (nlen
< 1) return;
864 immutable f
= fg
, b
= bg
;
866 ttywb
.ptr
[y
*ttywIntr
+x
].g1line
!setattr(Glyph
.Flag
.GraphLeft|Glyph
.Flag
.GraphRight
, f
, b
);
868 ttywb
.ptr
[y
*ttywIntr
+x
].g1line
!setattr(Glyph
.Flag
.GraphRight
, f
, b
);
869 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
);
870 ttywb
.ptr
[y
*ttywIntr
+x
+nlen
-1].g1line
!setattr(Glyph
.Flag
.GraphLeft
, f
, b
);
874 void vline(bool setattr
=true) (int x
, int y
, int len
) {
875 if (x
< 0 || x
>= w || len
< 1 || y
>= h || w
< 1 || h
< 1) return;
880 if (y
<= -len
) return;
884 if (len
> h
-y
) { last
= false; len
= h
-y
; }
885 if (len
< 1) return; // just in case
886 immutable f
= fg
, b
= bg
;
888 if (normXYLen(x, y, 1) != 1) return;
890 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp|Glyph.Flag.GraphDown, f, b);
892 ttywb.ptr[y*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphDown, f, b);
893 foreach (int sy; y+1..y+len-1) ttywb.ptr[sy*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp|Glyph.Flag.GraphDown, f, b);
894 ttywb.ptr[(y+len-1)*ttywIntr+x].g1line!setattr(Glyph.Flag.GraphUp, f, b);
898 if (normXYLen(x
, y
, 1) != 1) return;
899 ttywb
.ptr
[y
*ttywIntr
+x
].g1line
!setattr(Glyph
.Flag
.GraphUp|Glyph
.Flag
.GraphDown
, f
, b
);
904 if (normXYLen(xx
, yy
, 1) == 1) {
907 ttywb
.ptr
[yy
*ttywIntr
+xx
].g1line
!setattr(Glyph
.Flag
.GraphDown
, f
, b
);
908 } else if (last
&& len
== 0) {
909 ttywb
.ptr
[yy
*ttywIntr
+xx
].g1line
!setattr(Glyph
.Flag
.GraphUp
, f
, b
);
911 ttywb
.ptr
[yy
*ttywIntr
+xx
].g1line
!setattr(Glyph
.Flag
.GraphUp|Glyph
.Flag
.GraphDown
, f
, b
);
918 void frame(bool filled
=false) (int x
, int y
, int w
, int h
) {
919 if (w
< 1 || h
< 1) return;
921 writeCharsAt(x
, y
, w
, ' ');
922 foreach (immutable sy
; y
..y
+h
) {
923 writeCharsAt(x
, sy
, 1, ' ');
924 writeCharsAt(x
+w
-1, sy
, 1, ' ');
926 writeCharsAt(x
, y
+h
-1, w
, ' ');
928 if (h
== 1) { hline(x
, y
, w
); return; }
929 if (w
== 1) { vline(x
, y
, h
); return; }
930 hline(x
, y
, w
); // top
931 hline(x
, y
+h
-1, w
); // bottom
932 vline(x
, y
, h
); // left
933 vline(x
+w
-1, y
, h
); // right
935 foreach (immutable sy
; y
+1..y
+h
-1) writeCharsAt(x
+1, sy
, w
-2, ' ');
939 void hshadow (int x
, int y
, int len
) {
940 static ubyte shadowColor (ubyte clr
) nothrow @trusted @nogc {
942 ttyColor2rgb(clr
, r
, g
, b
);
943 return ttyRgb2Color(r
/3, g
/3, b
/3);
946 len
= normXYLen(x
, y
, len
);
948 auto dst
= ttywb
.ptr
+y
*ttywIntr
+x
;
950 dst
.fg
= shadowColor(dst
.fg
);
951 dst
.bg
= shadowColor(dst
.bg
);
956 void shadowBox (int x
, int y
, int w
, int h
) {
958 while (h
-- > 0) hshadow(x
, y
++, w
);
961 void frameShadowed(bool filled
=false) (int x
, int y
, int w
, int h
) {
962 if (w
< 1 || h
< 1) return;
963 frame
!filled(x
, y
, w
, h
);
964 shadowBox(x
+w
, y
+1, 2, h
-1);
965 hshadow(x
+2, y
+h
, w
);
968 void fill(bool g1
=false) (int x
, int y
, int w
, int h
, char ch
=' ') {
969 foreach (immutable sy
; y
..y
+h
) writeCharsAt
!g1(x
, sy
, w
, ch
);
978 static int hotStrLen (const(char)[] s
, bool dohot
=true) nothrow @trusted @nogc {
979 if (s
.length
> int.max
) s
= s
[0..int.max
];
980 int res
= cast(int)s
.length
;
981 if (res
&& s
.ptr
[0] >= '\x01' && s
.ptr
[0] <= '\x03') --res
; // left
984 import iv
.strex
: indexOf
;
985 auto ampos
= s
.indexOf('&');
986 if (ampos
< 0 || s
.length
-ampos
< 2) break;
994 static char hotChar (const(char)[] s
, bool dohot
=true) nothrow @trusted @nogc {
996 if (s
.length
&& s
.ptr
[0] >= '\x01' && s
.ptr
[0] <= '\x03') s
= s
[1..$];
998 import iv
.strex
: indexOf
;
999 auto ampos
= s
.indexOf('&');
1000 if (ampos
< 0 || s
.length
-ampos
< 2) return 0;
1001 if (s
.ptr
[ampos
+1] != '&') return s
.ptr
[ampos
+1];
1009 static int hotCharOfs (const(char)[] s, bool dohot=true) nothrow @trusted @nogc {
1011 if (s.length && s.ptr[0] >= '\x01' && s.ptr[0] <= '\x03') s = s[1..$];
1014 import iv.strex : indexOf;
1015 auto ampos = s.indexOf('&');
1016 if (ampos < 0 || s.length-ampos < 2) return 0;
1017 if (s.ptr[ampos+1] != '&') return ofs+cast(int)ampos;
1027 // ////////////////////////////////////////////////////////////////////////// //
1028 private __gshared
int screenSwapped
= 0;
1031 public void altScreen () nothrow @nogc {
1032 if (++screenSwapped
== 1) {
1033 import core
.sys
.posix
.unistd
: write
;
1035 "\x1b[?1048h"~ // save cursor position
1036 "\x1b[?1047h"~ // set alternate screen
1037 "\x1b[?7l"~ // turn off autowrapping
1038 "\x1b[?25h"~ // make cursor visible
1039 "\x1b(B"~ // G0 is ASCII
1040 "\x1b)0"~ // G1 is graphics
1041 "\x1b[?2004h"~ // enable bracketed paste
1042 "\x1b[?1000h\x1b[?1006h\x1b[?1002h"~ // SGR mouse reports
1044 write(1, initStr
.ptr
, initStr
.length
);
1050 public void normalScreen () @trusted nothrow @nogc {
1051 if (--screenSwapped
== 0) {
1052 import core
.sys
.posix
.unistd
: write
;
1054 "\x1b[?1047l"~ // set normal screen
1055 "\x1b[?7h"~ // turn on autowrapping
1056 "\x1b[0;37;40m"~ // set 'normal' attributes
1057 "\x1b[?1048l"~ // restore cursor position
1058 "\x1b[?25h"~ // make cursor visible
1059 "\x1b[?2004l"~ // disable bracketed paste
1060 "\x1b[?1002l\x1b[?1006l\x1b[?1000l"~ // disable mouse reports
1062 write(1, deinitStr
.ptr
, deinitStr
.length
);
1068 // ////////////////////////////////////////////////////////////////////////// //
1069 private extern(C
) void ttyzAtExit () {
1070 if (screenSwapped
) {
1076 shared static this () {
1077 import core
.stdc
.stdlib
: atexit
;
1078 atexit(&ttyzAtExit
);