1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
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 module iv
.vt100
.scrbuf
;
25 // ////////////////////////////////////////////////////////////////////////// //
26 public align(1) struct X11ModState
{
31 public pure nothrow @safe @nogc:
33 modstate
= astate
&(Mod1Mask|Mod4Mask|ControlMask|ShiftMask
);
36 @property bool meta () const { pragma(inline
, true); return ((modstate
&Mod1Mask
) != 0); }
37 @property bool hyper () const { pragma(inline
, true); return ((modstate
&Mod4Mask
) != 0); }
38 @property bool ctrl () const { pragma(inline
, true); return ((modstate
&ControlMask
) != 0); }
39 @property bool shift () const { pragma(inline
, true); return ((modstate
&ShiftMask
) != 0); }
41 @property void meta (bool v
) { pragma(inline
, true); if (v
) modstate |
= Mod1Mask
; else modstate
&= ~cast(uint)Mod1Mask
; }
42 @property void hyper (bool v
) { pragma(inline
, true); if (v
) modstate |
= Mod4Mask
; else modstate
&= ~cast(uint)Mod4Mask
; }
43 @property void ctrl (bool v
) { pragma(inline
, true); if (v
) modstate |
= ControlMask
; else modstate
&= ~cast(uint)ControlMask
; }
44 @property void shift (bool v
) { pragma(inline
, true); if (v
) modstate |
= ShiftMask
; else modstate
&= ~cast(uint)ShiftMask
; }
46 bool opEquals (const(char)[] s
) const {
48 foreach (char ch
; s
) {
49 //if (ch >= 'a' && ch <= 'z') ch -= 32; // poor man's `toupper()`
50 if (ch
< ' ') continue;
52 case 'C': case 'c': mask |
= ControlMask
; break;
53 case 'S': case 's': mask |
= ShiftMask
; break;
54 case 'M': case 'm': mask |
= Mod1Mask
; break;
55 case 'H': case 'h': mask |
= Mod4Mask
; break;
56 default: return false;
59 return (modstate
== mask
);
64 // ////////////////////////////////////////////////////////////////////////// //
65 public enum MinBufferWidth
= 80;
66 public enum MinBufferHeight
= 24;
67 public enum MaxBufferWidth
= 8192;
68 public enum MaxBufferHeight
= 8192;
71 // ////////////////////////////////////////////////////////////////////// //
72 public align(1) struct Attr
{
81 Dirty
= 0x40U
, // we need this in alot of other modules, so let it be here too
82 AutoWrap
= 0x80U
, // has meaning only for last glyph in line; means "this line was autowrapped"
86 uint attr
= (DefaultBG|DefaultFG|Dirty
)<<FlagsShift
; // by bytes: bg, fg, flags, dummy
88 enum ColorDefault
= -1;
90 enum ColorUnderline
= 257;
91 enum ColorBoldUnderline
= 258;
92 enum ColorUnderlineBold
= ColorBoldUnderline
;
94 pure nothrow @safe @nogc:
95 //this (ubyte afg, ubyte abg) { pragma(inline, true); attr = (abg<<BGShift)|afg|((DefaultBG|DefaultFG)<<FlagsShift); }
96 this (ubyte afg
, ubyte abg
) { pragma(inline
, true); attr
= (abg
<<BGShift
)|afg
; }
97 this (ubyte afg
, ubyte abg
, ushort aflags
) { pragma(inline
, true); attr
= (abg
<<BGShift
)|afg|
(aflags
<<FlagsShift
); }
99 @property ubyte fg () const { pragma(inline
, true); return (attr
&0xff); }
100 @property ubyte bg () const { pragma(inline
, true); return ((attr
>>BGShift
)&0xff); }
101 @property ushort flags () const { pragma(inline
, true); return ((attr
>>FlagsShift
)&0xffff); }
103 @property void fg (ubyte v
) { pragma(inline
, true); attr
= (attr
&~0xffU
)|v
; }
104 @property void bg (ubyte v
) { pragma(inline
, true); attr
= (attr
&~0xff00U
)|
(v
<<BGShift
); }
105 @property void flags (ushort v
) { pragma(inline
, true); attr
= (attr
&~0xffff0000U
)|
(v
<<FlagsShift
); }
107 private static template GenSG(string pname
) {
108 private static template up1(string s
) { enum up1
= ""~cast(char)(s
[0]-32)~s
[1..$]; }
110 "@property bool "~pname
~" (bool v) {
111 /*pragma(inline, true);*/
112 if (v) attr |= "~up1
!pname
~"<<FlagsShift; else attr &= ~(cast(uint)("~up1
!pname
~"<<FlagsShift));
115 @property bool "~pname
~" () const pure { pragma(inline, true); return ((attr&("~up1
!pname
~"<<FlagsShift)) != 0); }";
118 mixin(GenSG
!"defaultBG");
119 mixin(GenSG
!"defaultFG");
120 mixin(GenSG
!"underline");
122 mixin(GenSG
!"blink");
123 mixin(GenSG
!"reversed");
124 mixin(GenSG
!"dirty");
125 mixin(GenSG
!"autoWrap");
127 // see ColorXXX special constants
128 @property int realFG () const {
129 pragma(inline
, true);
131 (attr
&(Reversed
<<FlagsShift
) ?
132 // reversed: get background color
133 (attr
&(DefaultBG
<<FlagsShift
) ? ColorDefault
: (attr
>>BGShift
)&0xff) :
134 // normal: get foreground color
135 (attr
&(DefaultFG
<<FlagsShift
) ?
136 (attr
&((Underline|Bold
)<<FlagsShift
) ? ColorBoldUnderline
:
137 attr
&(Underline
<<FlagsShift
) ? ColorUnderline
:
138 attr
&(Bold
<<FlagsShift
) ? ColorBold
:
139 ColorDefault
) : attr
&0xff)
143 // see ColorXXX special constants
144 @property int realBG () const {
145 pragma(inline
, true);
147 (attr
&(Reversed
<<FlagsShift
) ?
148 // reversed: get foreground color
149 (attr
&(DefaultFG
<<FlagsShift
) ?
150 (attr
&((Underline|Bold
)<<FlagsShift
) ? ColorBoldUnderline
:
151 attr
&(Underline
<<FlagsShift
) ? ColorUnderline
:
152 attr
&(Bold
<<FlagsShift
) ? ColorBold
:
153 ColorDefault
) : attr
&0xff) :
154 // normal: get background color
155 (attr
&(DefaultBG
<<FlagsShift
) ? ColorDefault
: (attr
>>BGShift
)&0xff)
159 bool opEquals() (in Attr a
) const {
160 pragma(inline
, true);
161 return ((flags
&(Underline|Bold|Reversed
)) == (a
.flags
&(Underline|Bold|Reversed
)) && a
.realBG
== realBG
&& a
.realFG
== realFG
);
164 static assert(Attr
.sizeof
== 4);
165 static assert(Attr(0, 5, Attr
.DefaultBG|Attr
.DefaultFG
) == Attr(5, 0, Attr
.DefaultBG|Attr
.DefaultFG
));
168 // ////////////////////////////////////////////////////////////////////// //
169 // only 0x0000..0xffff chars are allowed
170 public align(1) struct Glyph
{
176 @property bool dirty () const pure { pragma(inline
, true); return mAttr
.dirty
; }
177 @property void dirty (bool v
) pure { pragma(inline
, true); mAttr
.dirty
= v
; }
179 @property wchar ch () const pure { pragma(inline
, true); return mChar
; }
180 @property void ch (wchar v
) pure { pragma(inline
, true); if (mChar
!= v
) { mChar
= v
; mAttr
.dirty
= true; } }
181 //@property void ch (char v) pure { pragma(inline, true); if (mChar != koi2uni(v)) { mChar = koi2uni(v); mAttr.dirty = true; } }
183 @property Attr
attr () const pure { pragma(inline
, true); return mAttr
; }
184 @property void attr (Attr v
) pure { pragma(inline
, true); if (v
.dirty || v
!= mAttr
) { v
.dirty
= true; mAttr
= v
; } }
186 void set (wchar ach
, Attr aa
) pure {
187 pragma(inline
, true);
188 if (mChar
!= ach || aa
.dirty || aa
!= mAttr
) {
195 bool opEquals() (auto ref const Glyph g
) const pure {
196 pragma(inline
, true);
197 // for spaces only background matters
198 return (mChar
== g
.mChar
&& mAttr
.realBG
== g
.mAttr
.realBG
&& (mChar
== ' ' || mChar
== 0 || mAttr
.realFG
== g
.mAttr
.realFG
));
201 static assert(Glyph(' ', Attr(5, 0, Attr
.DefaultBG|Attr
.DefaultFG
)) == Glyph(' ', Attr(0, 5, Attr
.DefaultBG|Attr
.DefaultFG
)));
202 static assert(Glyph(' ', Attr(5, 0, 0)) != Glyph(' ', Attr(5, 1, 0)));
203 static assert(Glyph('!', Attr(5, 0, 0)) != Glyph(' ', Attr(5, 0, 0)));
204 static assert(Glyph('!', Attr(5, 0, 0)) != Glyph(' ', Attr(5, 1, 0)));
207 // ////////////////////////////////////////////////////////////////////////// //
208 public class ScreenBuffer
{
210 static T
min(T
, T0
, T1
) (T0 a
, T1 b
) { pragma(inline
, true); return cast(T
)(a
< b ? a
: b
); }
211 static T
max(T
, T0
, T1
) (T0 a
, T1 b
) { pragma(inline
, true); return cast(T
)(a
> b ? a
: b
); }
212 static T
between(T
, T0
, T1
, T2
) (T0 lo
, T1 hi
, T2 val
) { pragma(inline
, true); return cast(T
)(val
< lo ? lo
: val
> hi ? hi
: val
); }
214 static T
min(T
, T1
) (T a
, T1 b
) { pragma(inline
, true); return cast(T
)(a
< b ? a
: b
); }
215 static T
max(T
, T1
) (T a
, T1 b
) { pragma(inline
, true); return cast(T
)(a
> b ? a
: b
); }
217 static wchar filterDC (dchar ch
) pure nothrow @safe @nogc {
218 pragma(inline
, true);
220 ((ch
>= 0x02B0 && ch
<= 0x036F) ||
221 (ch
>= 0x20D0 && ch
<= 0x20FF) ||
222 (ch
>= 0xD800 && ch
<= 0xDBFF) ||
223 (ch
>= 0xDC00 && ch
<= 0xF8FF) ||
224 (ch
>= 0xFE20 && ch
<= 0xFE2F) ||
225 (ch
>= 0xFEFF && ch
<= 0xFFEF) ||
226 (ch
>= 0xFFF0) ?
'?' : cast(wchar)ch
);
229 static wchar filterWC (wchar ch
) pure nothrow @safe @nogc {
230 pragma(inline
, true);
232 ((ch
>= 0x02B0 && ch
<= 0x036F) ||
233 (ch
>= 0x20D0 && ch
<= 0x20FF) ||
234 (ch
>= 0xD800 && ch
<= 0xDBFF) ||
235 (ch
>= 0xDC00 && ch
<= 0xF8FF) ||
236 (ch
>= 0xFE20 && ch
<= 0xFE2F) ||
237 (ch
>= 0xFEFF && ch
<= 0xFFEF) ||
238 (ch
>= 0xFFF0) ?
'?' : ch
);
256 static immutable wchar[6][2] FrameChars
= [
257 "\u250c\u2510\u2514\u2518\u2500\u2502"w
,
258 "\u2554\u2557\u255a\u255d\u2550\u2551"w
,
264 int mCurX
= 0; // can be == mWidth, that means "do wrap on next output"
270 public final inout(Glyph
)[] scrbuf () inout pure nothrow @safe @nogc { pragma(inline
, true); return mGBuf
; }
273 // called after scrolling up
274 void delegate (ScreenBuffer self
, int y0
, int y1
, int count
, bool wasDirty
) nothrow onScrollUp
;
275 // called after scrolling down
276 void delegate (ScreenBuffer self
, int y0
, int y1
, int count
, bool wasDirty
) nothrow onScrollDown
;
278 void delegate (ScreenBuffer self
) nothrow @safe @nogc onBell
;
279 // new title was set; we don't check if it's the same as old title
280 void delegate (ScreenBuffer self
, const(char)[] title
) nothrow onNewTitleEvent
;
281 // inverse mode changed; it should be in effect immediately
282 void delegate (ScreenBuffer self
) nothrow @safe @nogc onReverseEvent
;
283 // this will be called when screen buffer is scrolled, and owner should save history line
284 // check `autoWrap` property on last line glyph to see if this line is autowrapped
285 // never called by `ScreenBuffer`, but can be called by VT-100 Emulator, for example
286 void delegate (const(Glyph
)[] aline
) nothrow onAppendHistory
;
289 static final doKeyTrans (ref KeySym ksym
) nothrow @safe @nogc {
291 case XK_KP_Home
: ksym
= XK_Home
; break;
292 case XK_KP_Left
: ksym
= XK_Left
; break;
293 case XK_KP_Up
: ksym
= XK_Up
; break;
294 case XK_KP_Right
: ksym
= XK_Right
; break;
295 case XK_KP_Down
: ksym
= XK_Down
; break;
296 case XK_KP_Prior
: ksym
= XK_Prior
; break;
297 case XK_KP_Next
: ksym
= XK_Next
; break;
298 case XK_KP_End
: ksym
= XK_End
; break;
299 case XK_KP_Begin
: ksym
= XK_Begin
; break;
300 case XK_KP_Insert
: ksym
= XK_Insert
; break;
301 case XK_KP_Delete
: ksym
= XK_Delete
; break;
302 case XK_KP_Enter
: ksym
= XK_Return
; break;
303 case XK_ISO_Left_Tab
: ksym
= XK_Tab
; break; // x11 is fucked
309 this (int aw
, int ah
) nothrow @safe {
310 if (aw
< 1 || ah
< 1 || aw
> MaxBufferWidth || ah
> MaxBufferHeight
) assert(0, "invalid screen buffer size");
311 mGBuf
.length
= aw
*ah
;
314 foreach (ref Glyph g
; mGBuf
) g
.dirty
= true;
318 void intrClear () nothrow @trusted {
320 mWidth
= mHeight
= 0;
326 final @property bool isDirty () const pure nothrow @safe @nogc { pragma(inline
, true); return (mDirtyCount
!= 0); }
328 final @property Attr
curAttr () const pure nothrow @safe @nogc { pragma(inline
, true); return mCurAttr
; }
329 final @property void curAttr (Attr v
) pure nothrow @safe @nogc { pragma(inline
, true); mCurAttr
= v
; }
331 final @property int width () const pure nothrow @safe @nogc { pragma(inline
, true); return mWidth
; }
332 final @property int height () const pure nothrow @safe @nogc { pragma(inline
, true); return mHeight
; }
334 final @property int curX () const pure nothrow @safe @nogc { /*pragma(inline, true);*/ return (mCurX
== mWidth ? mWidth
-1 : mCurX
); }
335 final @property int curY () const pure nothrow @safe @nogc { /*pragma(inline, true);*/ return mCurY
; }
336 final @property bool curVisible () const pure nothrow @safe @nogc { pragma(inline
, true); return mCurVis
; }
338 final void gotoXYSetVis (int ax
, int ay
, bool avis
) pure nothrow @trusted @nogc {
339 if (ax
!= mCurX || ay
!= mCurY || avis
!= mCurVis
) {
340 // mark old and new cursor positions as dirty
341 if (mCurVis
&& mCurX
>= 0 && mCurY
>= 0 && mCurX
< mWidth
&& mCurY
< mHeight
&& !mGBuf
.ptr
[mCurY
*mWidth
+mCurX
].dirty
) {
342 // old cursor is visible and not dirty
343 mGBuf
.ptr
[mCurY
*mWidth
+mCurX
].dirty
= true;
346 if (avis
&& ax
>= 0 && ay
>= 0 && ax
< mWidth
&& ay
< mHeight
&& !mGBuf
.ptr
[ay
*mWidth
+ax
].dirty
) {
347 // new cursor is visible and not dirty
348 mGBuf
.ptr
[ay
*mWidth
+ax
].dirty
= true;
357 final void gotoXY (int ax
, int ay
) pure nothrow @trusted @nogc { pragma(inline
, true); gotoXYSetVis(ax
, ay
, mCurVis
); }
359 final @property void curX (int v
) pure nothrow @trusted @nogc { pragma(inline
, true); gotoXYSetVis(v
, mCurY
, mCurVis
); }
360 final @property void curY (int v
) pure nothrow @trusted @nogc { pragma(inline
, true); gotoXYSetVis(mCurX
, v
, mCurVis
); }
361 final @property void curVisible (bool v
) pure nothrow @trusted @nogc { pragma(inline
, true); gotoXYSetVis(mCurX
, mCurY
, v
); }
363 //TODO: send cutted lines to history buffer
364 protected final void resizeBuf (ref Glyph
[] buf
, int aw
, int ah
) nothrow @trusted {
367 buf
[$-1].mAttr
.autoWrap
= false;
369 buf
.assumeSafeAppend
;
370 if (ah
> mHeight
) foreach (ref Glyph g
; buf
[ah
*mWidth
..$]) g
.dirty
= true;
374 scope(exit
) { foreach (ref arr
; lines
) delete arr
; delete lines
; }
375 lines
.reserve(mHeight
);
377 while (pos
< buf
.length
) {
378 // find line end (rough)
379 int epos
= pos
+mWidth
;
380 assert(epos
<= buf
.length
);
381 while (epos
< buf
.length
&& buf
[epos
-1].attr
.autoWrap
) epos
+= mWidth
;
383 auto line
= new Glyph
[](epos
-pos
);
384 line
[] = buf
[pos
..epos
];
385 // remove spaces, 'cause why not
386 while (line
.length
&& line
[$-1].ch
<= ' ') line
= line
[0..$-1];
390 // remove empty lines, 'cause why should we keep 'em?
391 while (lines
.length
&& lines
[$-1].length
== 0) lines
= lines
[0..$-1];
392 // redistribute lines, starting from the last one
393 auto newbuf
= new Glyph
[](aw
*ah
);
395 int srcline
= cast(int)lines
.length
-1;
397 while (srcline
>= 0 && desty
>= 0) {
398 auto ln
= lines
[srcline
--];
399 int lc
= cast(int)(ln
.length
/aw
+(ln
.length
%aw ?
1 : 0));
400 //{ import core.stdc.stdio; stderr.fprintf("srcline=%d; lc=%d\n", srcline+1, lc); }
402 foreach (immutable dy
; desty
-lc
+1..desty
+1) {
403 int xlen
= cast(int)ln
.length
;
404 if (xlen
> aw
) xlen
= aw
;
405 if (dy
>= 0 && dy
< ah
) {
406 newbuf
[dy
*aw
..dy
*aw
+xlen
] = ln
[0..xlen
];
407 bool awrap
= (xlen
== aw
&& xlen
+1 < ln
.length
);
408 newbuf
[(dy
+1)*aw
-1].mAttr
.autoWrap
= (xlen
+1 < ln
.length
);
423 protected final void resizeBufSimple (ref Glyph
[] buf
, int aw
, int ah
) nothrow @trusted {
424 auto newbuf
= new Glyph
[](aw
*ah
);
425 foreach (immutable y
; 0..min(ah
, mHeight
)) {
426 foreach (immutable x
; 0..min(aw
, mWidth
)) {
427 newbuf
[y
*aw
+x
] = buf
[y
*mWidth
+x
];
434 void resize (int aw
, int ah
) nothrow @trusted {
435 if (aw
< 1 || ah
< 1 || aw
> MaxBufferWidth || ah
> MaxBufferHeight
) assert(0, "invalid screen buffer size");
436 if (aw
== mWidth
&& ah
== mHeight
) return;
437 resizeBuf(mGBuf
, aw
, ah
);
442 final Glyph
opIndex (int x
, int y
) const nothrow @trusted @nogc {
443 pragma(inline
, true);
444 return (x
>= 0 && y
>= 0 && x
< mWidth
&& y
< mHeight ? mGBuf
.ptr
[y
*mWidth
+x
] : Glyph
.init
);
447 final void opIndexAssign (Glyph g
, int x
, int y
) nothrow @trusted @nogc {
448 if (x
>= 0 && y
>= 0 && x
< mWidth
&& y
< mHeight
) {
449 if (g
!= mGBuf
.ptr
[y
*mWidth
+x
]) {
450 if (!mGBuf
.ptr
[y
*mWidth
+x
].dirty
) ++mDirtyCount
;
452 mGBuf
.ptr
[y
*mWidth
+x
] = g
;
457 final bool isDirtyAt (int x
, int y
) const nothrow @trusted @nogc {
458 pragma(inline
, true);
459 return (x
>= 0 && y
>= 0 && x
< mWidth
&& y
< mHeight ? mGBuf
.ptr
[y
*mWidth
+x
].dirty
: false);
462 final void setDirtyAt (int x
, int y
, bool v
) nothrow @trusted @nogc {
463 pragma(inline
, true);
464 if (x
>= 0 && y
>= 0 && x
< mWidth
&& y
< mHeight
&& mGBuf
.ptr
[y
*mWidth
+x
].dirty
!= v
) {
465 mDirtyCount
+= (v ?
1 : -1);
466 mGBuf
.ptr
[y
*mWidth
+x
].dirty
= v
;
470 final bool isDirtyLine (int y
) const nothrow @trusted @nogc {
471 if (y
>= 0 && y
< mHeight
) {
472 foreach (const ref Glyph g
; mGBuf
.ptr
[y
*mWidth
..(y
+1)*mWidth
]) if (g
.dirty
) return true;
477 final void resetDirtyLine (int y
) nothrow @trusted @nogc {
478 if (y
>= 0 && y
< mHeight
) {
479 foreach (ref Glyph g
; mGBuf
.ptr
[y
*mWidth
..(y
+1)*mWidth
]) {
480 if (g
.dirty
) --mDirtyCount
;
486 final void setDirtyLine (int y
) nothrow @trusted @nogc {
487 if (y
>= 0 && y
< mHeight
) {
488 foreach (ref Glyph g
; mGBuf
.ptr
[y
*mWidth
..(y
+1)*mWidth
]) {
489 if (!g
.dirty
) ++mDirtyCount
;
495 final void setFullDirty () nothrow @trusted @nogc {
496 foreach (ref Glyph g
; mGBuf
) g
.dirty
= true;
497 mDirtyCount
= mWidth
*mHeight
;
500 final void resetFullDirty () nothrow @trusted @nogc {
501 foreach (ref Glyph g
; mGBuf
) g
.dirty
= false;
505 void scrollUp () nothrow {
506 bool wasDirty
= (mDirtyCount
!= 0);
508 auto gbp
= mGBuf
.ptr
;
510 foreach (immutable pos
; mh
..mGBuf
.length
) {
511 if (gbp
[pos
-mh
] != gbp
[pos
]) {
512 gbp
[pos
-mh
] = gbp
[pos
];
513 gbp
[pos
-mh
].dirty
= true;
517 auto defg
= Glyph(' ', mCurAttr
);
518 foreach (ref Glyph g
; mGBuf
[$-mWidth
..$]) {
519 if (g
!= defg
) { g
= defg
; g
.dirty
= true; }
521 // recalculate dirty counter
523 foreach (const ref Glyph g
; mGBuf
) if (g
.dirty
) ++mDirtyCount
;
524 if (onScrollUp
!is null) onScrollUp(this, 0, mh
-1, 1, wasDirty
);
527 // at current cursor position, with current attrs; interprets some control codes
528 void writeStr (const(char)[] s
...) {
529 if (s
.length
== 0) return;
531 foreach (immutable char ch
; s
) {
532 if (!dc
.decodeSafe(ch
)) continue;
533 wchar wc
= filterDC(dc
.codepoint
);
536 if (mCurY
>= 0 && mCurY
< mHeight
) mGBuf
.ptr
[mCurY
*mWidth
+mWidth
-1].mAttr
.autoWrap
= false; // no autowrap
542 if (mCurY
>= 0 && mCurY
< mHeight
) mGBuf
.ptr
[mCurY
*mWidth
+mWidth
-1].mAttr
.autoWrap
= false; // no autowrap
545 if (mCurY
>= mHeight
) { mCurY
= mHeight
-1; scrollUp(); }
550 if (mCurX
> 0) --mCurX
;
554 if (wc
== 8) continue;
555 // tab and other chars
556 int count
= 1; // for tab
561 if (count
< 0) count
= -count
; else count
= 8;
562 } else if (mCurX
>= mWidth
) {
567 wc
= ' '; // put spaces instead of tabs
569 if (wc
== 0) wc
= ' '; //HACK
571 foreach (immutable _
; 0..count
) {
573 if (mCurX
>= mWidth
&& mCurY
>= 0 && mCurY
< mHeight
) mGBuf
.ptr
[mCurY
*mWidth
+mWidth
-1].mAttr
.autoWrap
= true; // autowrap
575 if (mCurX
>= mWidth
) {
578 if (mCurY
>= mHeight
) { mCurY
= mHeight
-1; scrollUp(); }
581 if (mCurX
>= 0 && mCurX
< mWidth
&& mCurY
>= 0 && mCurY
< mHeight
) {
582 auto ng
= Glyph(wc
, mCurAttr
);
584 if (mGBuf
.ptr
[mCurY
*mWidth
+mCurX
] != ng
) {
585 if (!mGBuf
.ptr
[mCurY
*mWidth
+mCurX
].dirty
) ++mDirtyCount
;
586 mGBuf
.ptr
[mCurY
*mWidth
+mCurX
] = ng
;
589 // move to next position
595 void writeCharsAt (int x
, int y
, int count
, dchar dch
, Attr a
) nothrow @trusted @nogc {
596 if (y
< 0 || count
< 1 || y
>= mHeight || x
>= mWidth || dch
>= dchar.max
) return;
598 if (x
== int.min
) return;
600 if (count
< 1) return;
603 wchar wc
= filterDC(dch
);
604 auto ng
= Glyph(wc
, a
);
606 Glyph
* g
= mGBuf
.ptr
+y
*mWidth
+x
;
607 foreach (immutable _
; 0..count
) {
609 if (!g
.dirty
) ++mDirtyCount
;
613 if (++x
>= mWidth
) return;
617 void writeStrAt (int x
, int y
, const(char)[] s
, Attr a
) nothrow @trusted @nogc {
618 if (y
< 0 || y
>= mHeight || x
>= mWidth || s
.length
== 0) return;
620 if (x
== int.min
) return;
621 if (s
.length
<= -x
) return;
624 Glyph
* g
= mGBuf
.ptr
+y
*mWidth
+(x
> 0 ? x
: 0);
625 foreach (immutable char ch
; s
) {
626 if (!dc
.decodeSafe(ch
)) continue;
627 wchar wc
= filterDC(dc
.codepoint
);
629 auto ng
= Glyph(wc
, a
);
632 if (!g
.dirty
) ++mDirtyCount
;
637 if (++x
>= mWidth
) return;
641 void writeStrAt (int x
, int y
, const(wchar)[] s
, Attr a
) nothrow @trusted @nogc {
642 if (y
< 0 || y
>= mHeight || x
>= mWidth || s
.length
== 0) return;
644 if (x
== int.min
) return;
646 if (s
.length
<= x
) return;
650 Glyph
* g
= mGBuf
.ptr
+y
*mWidth
+x
;
651 foreach (immutable wchar wc
; s
) {
652 auto ng
= Glyph(filterWC(wc
), a
);
655 if (!g
.dirty
) ++mDirtyCount
;
659 if (++x
>= mWidth
) return;
663 void writeStrAt (int x
, int y
, const(dchar)[] s
, Attr a
) nothrow @trusted @nogc {
664 if (y
< 0 || y
>= mHeight || x
>= mWidth || s
.length
== 0) return;
666 if (x
== int.min
) return;
668 if (s
.length
<= x
) return;
672 Glyph
* g
= mGBuf
.ptr
+y
*mWidth
+x
;
673 foreach (immutable dchar dc
; s
) {
674 auto ng
= Glyph(filterDC(dc
), a
);
677 if (!g
.dirty
) ++mDirtyCount
;
681 if (++x
>= mWidth
) return;
685 void fillRect (int x
, int y
, int w
, int h
, Attr a
) nothrow @trusted @nogc {
686 if (w
< 1 || h
< 1 || x
>= mWidth || y
>= mHeight
) return;
687 foreach (immutable sy
; y
..y
+h
) writeCharsAt(x
, sy
, w
, ' ', a
);
690 void drawFrame (int x
, int y
, int w
, int h
, Attr a
, FrameType ft
=FrameType
.Single
) nothrow @trusted @nogc {
691 if (w
< 1 || h
< 1) return;
694 foreach (immutable sx
; x
..x
+w
) writeCharsAt(sx
, y
, 1, FrameChars
[ft
][HLine
], a
);
697 foreach (immutable sy
; y
..y
+h
) writeCharsAt(x
, sy
, 1, FrameChars
[ft
][VLine
], a
);
700 writeCharsAt(x
+1, y
, w
-2, FrameChars
[ft
][HLine
], a
);
701 writeCharsAt(x
+1, y
+h
-1, w
-2, FrameChars
[ft
][HLine
], a
);
703 foreach (immutable sy
; y
+1..y
+h
-1) {
704 writeCharsAt(x
, sy
, 1, FrameChars
[ft
][VLine
], a
);
705 writeCharsAt(x
+w
-1, sy
, 1, FrameChars
[ft
][VLine
], a
);
708 writeCharsAt(x
, y
, 1, FrameChars
[ft
][CornerLU
], a
);
709 writeCharsAt(x
+w
-1, y
, 1, FrameChars
[ft
][CornerRU
], a
);
710 writeCharsAt(x
, y
+h
-1, 1, FrameChars
[ft
][CornerLD
], a
);
711 writeCharsAt(x
+w
-1, y
+h
-1, 1, FrameChars
[ft
][CornerRD
], a
);
715 // ////////////////////////////////////////////////////////////////////// //
730 // button: 1=left, 2=middle, 3=right
731 void doMouseReport (uint x
, uint y
, MouseEvent event
, ubyte button
, uint mods
) {
735 bool keypressEvent (dchar dch
, KeySym ksym
, X11ModState modstate
) {
739 // ////////////////////////////////////////////////////////////////////// //
740 void onBlurFocus (bool focused
) nothrow {}
742 // should mark dirty areas if necessary
743 void resetSelection () nothrow {}
745 // should mark dirty areas if necessary
746 void doneSelection () nothrow {}
748 // should mark dirty areas if necessary
749 // this called by mouse handler, with cell coords
750 // when mouse button released, `doneSelection()` will be called
751 void selectionChanged (int x
, int y
) nothrow {}
753 bool isInSelection (int x
, int y
) nothrow @trusted @nogc { return false; }
755 bool lineHasSelection (int y
) nothrow @trusted @nogc { return false; }
757 string
getSelectionText () nothrow { return null; }
759 // ////////////////////////////////////////////////////////////////////// //
761 final void blitTo (ScreenBuffer dest
, int x0
, int y0
) nothrow @trusted @nogc {
762 if (dest
is null) return;
763 Glyph
* g
= mGBuf
.ptr
;
764 foreach (immutable y
; 0..mHeight
) {
765 foreach (immutable x
; 0..mWidth
) {
766 dest
[x
+x0
, y
+y0
] = *g
;