egra: don't use ENTER/LEAVE (because intel sux, and they are slower than the correspo...
[iv.d.git] / vt100 / scrbuf.d
blob63bae6d255b518bfd24027108efe5d497359fabf
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;
17 private:
19 import iv.alice;
20 import iv.strex;
21 import iv.utfutil;
22 import iv.x11;
25 // ////////////////////////////////////////////////////////////////////////// //
26 public align(1) struct X11ModState {
27 align(1):
28 public:
29 uint modstate;
31 public pure nothrow @safe @nogc:
32 this (uint astate) {
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 {
47 uint mask = 0;
48 foreach (char ch; s) {
49 //if (ch >= 'a' && ch <= 'z') ch -= 32; // poor man's `toupper()`
50 if (ch < ' ') continue;
51 switch (ch) {
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 {
73 align(1):
74 enum : uint {
75 DefaultBG = 0x01U,
76 DefaultFG = 0x02U,
77 Underline = 0x04U,
78 Bold = 0x08U,
79 Blink = 0x10U,
80 Reversed = 0x20U,
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"
84 enum BGShift = 8;
85 enum FlagsShift = 16;
86 uint attr = (DefaultBG|DefaultFG|Dirty)<<FlagsShift; // by bytes: bg, fg, flags, dummy
88 enum ColorDefault = -1;
89 enum ColorBold = 256;
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..$]; }
109 enum GenSG =
110 "@property bool "~pname~" (bool v) {
111 /*pragma(inline, true);*/
112 if (v) attr |= "~up1!pname~"<<FlagsShift; else attr &= ~(cast(uint)("~up1!pname~"<<FlagsShift));
113 return v;
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");
121 mixin(GenSG!"bold");
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);
130 return
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);
146 return
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 {
171 align(1):
172 wchar mChar = ' ';
173 Attr mAttr;
175 nothrow @safe @nogc:
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) {
189 aa.dirty = true;
190 mChar = ach;
191 mAttr = aa;
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 {
209 public:
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);
219 return
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);
231 return
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);
241 public:
242 enum {
243 CornerLU,
244 CornerRU,
245 CornerLD,
246 CornerRD,
247 HLine,
248 VLine,
251 enum FrameType {
252 Single,
253 Double,
256 static immutable wchar[6][2] FrameChars = [
257 "\u250c\u2510\u2514\u2518\u2500\u2502"w,
258 "\u2554\u2557\u255a\u255d\u2550\u2551"w,
261 protected:
262 Glyph[] mGBuf;
263 int mWidth, mHeight;
264 int mCurX = 0; // can be == mWidth, that means "do wrap on next output"
265 int mCurY = 0;
266 bool mCurVis = true;
267 int mDirtyCount = 0;
268 Attr mCurAttr;
270 public final inout(Glyph)[] scrbuf () inout pure nothrow @safe @nogc { pragma(inline, true); return mGBuf; }
272 public:
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;
277 // ring a bell
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;
288 public:
289 static final doKeyTrans (ref KeySym ksym) nothrow @safe @nogc {
290 switch (ksym) {
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
304 default: break;
308 public:
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;
312 mWidth = aw;
313 mHeight = ah;
314 foreach (ref Glyph g; mGBuf) g.dirty = true;
315 mDirtyCount = aw*ah;
318 void intrClear () nothrow @trusted {
319 delete mGBuf;
320 mWidth = mHeight = 0;
321 mCurX = mCurY = 0;
322 mCurVis = true;
323 mDirtyCount = 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;
344 ++mDirtyCount;
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;
349 ++mDirtyCount;
351 mCurX = ax;
352 mCurY = ay;
353 mCurVis = avis;
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 {
365 if (aw == mWidth) {
366 // only height
367 buf[$-1].mAttr.autoWrap = false;
368 buf.length = aw*ah;
369 buf.assumeSafeAppend;
370 if (ah > mHeight) foreach (ref Glyph g; buf[ah*mWidth..$]) g.dirty = true;
371 } else {
372 // collect lines
373 Glyph[][] lines;
374 scope(exit) { foreach (ref arr; lines) delete arr; delete lines; }
375 lines.reserve(mHeight);
376 int pos = 0;
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;
382 // new line
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];
387 lines ~= line;
388 pos = epos;
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);
394 if (lines.length) {
395 int srcline = cast(int)lines.length-1;
396 int desty = ah-2;
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); }
401 if (lc > 0) {
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);
410 ln = ln[xlen..$];
412 desty -= lc;
413 } else {
414 --desty;
418 delete buf;
419 buf = newbuf;
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];
430 delete buf;
431 buf = newbuf;
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);
438 mWidth = aw;
439 mHeight = 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;
451 g.dirty = true;
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;
474 return false;
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;
481 g.dirty = false;
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;
490 g.dirty = true;
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;
502 mDirtyCount = 0;
505 void scrollUp () nothrow {
506 bool wasDirty = (mDirtyCount != 0);
507 int mh = mHeight;
508 auto gbp = mGBuf.ptr;
509 // copy chars
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;
516 // clear last line
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
522 mDirtyCount = 0;
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;
530 Utf8DecoderFast dc;
531 foreach (immutable char ch; s) {
532 if (!dc.decodeSafe(ch)) continue;
533 wchar wc = filterDC(dc.codepoint);
534 // cr?
535 if (wc == 13) {
536 if (mCurY >= 0 && mCurY < mHeight) mGBuf.ptr[mCurY*mWidth+mWidth-1].mAttr.autoWrap = false; // no autowrap
537 mCurX = 0;
538 continue;
540 // lf?
541 if (wc == 10) {
542 if (mCurY >= 0 && mCurY < mHeight) mGBuf.ptr[mCurY*mWidth+mWidth-1].mAttr.autoWrap = false; // no autowrap
543 mCurX = 0;
544 ++mCurY;
545 if (mCurY >= mHeight) { mCurY = mHeight-1; scrollUp(); }
546 continue;
548 // bs?
549 if (wc == 8) {
550 if (mCurX > 0) --mCurX;
551 continue;
553 // beep?
554 if (wc == 8) continue;
555 // tab and other chars
556 int count = 1; // for tab
557 // tab?
558 if (wc == 9) {
559 if (mCurX < 0) {
560 count = mCurX%8;
561 if (count < 0) count = -count; else count = 8;
562 } else if (mCurX >= mWidth) {
563 count = 8;
564 } else {
565 count = 8-(mCurX%8);
567 wc = ' '; // put spaces instead of tabs
569 if (wc == 0) wc = ' '; //HACK
570 // put chars
571 foreach (immutable _; 0..count) {
572 // other chars
573 if (mCurX >= mWidth && mCurY >= 0 && mCurY < mHeight) mGBuf.ptr[mCurY*mWidth+mWidth-1].mAttr.autoWrap = true; // autowrap
574 // scroll?
575 if (mCurX >= mWidth) {
576 mCurX = 0;
577 ++mCurY;
578 if (mCurY >= mHeight) { mCurY = mHeight-1; scrollUp(); }
580 // put char
581 if (mCurX >= 0 && mCurX < mWidth && mCurY >= 0 && mCurY < mHeight) {
582 auto ng = Glyph(wc, mCurAttr);
583 ng.dirty = true;
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
590 ++mCurX;
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;
597 if (x < 0) {
598 if (x == int.min) return;
599 count += x;
600 if (count < 1) return;
601 x = 0;
603 wchar wc = filterDC(dch);
604 auto ng = Glyph(wc, a);
605 ng.dirty = true;
606 Glyph* g = mGBuf.ptr+y*mWidth+x;
607 foreach (immutable _; 0..count) {
608 if (*g != ng) {
609 if (!g.dirty) ++mDirtyCount;
610 *g = ng;
612 ++g;
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;
619 if (x < 0) {
620 if (x == int.min) return;
621 if (s.length <= -x) return;
623 Utf8DecoderFast dc;
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);
628 if (x >= 0) {
629 auto ng = Glyph(wc, a);
630 ng.dirty = true;
631 if (*g != ng) {
632 if (!g.dirty) ++mDirtyCount;
633 *g = ng;
635 ++g;
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;
643 if (x < 0) {
644 if (x == int.min) return;
645 x = -x;
646 if (s.length <= x) return;
647 s = s[x..$];
648 x = 0;
650 Glyph* g = mGBuf.ptr+y*mWidth+x;
651 foreach (immutable wchar wc; s) {
652 auto ng = Glyph(filterWC(wc), a);
653 ng.dirty = true;
654 if (*g != ng) {
655 if (!g.dirty) ++mDirtyCount;
656 *g = ng;
658 ++g;
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;
665 if (x < 0) {
666 if (x == int.min) return;
667 x = -x;
668 if (s.length <= x) return;
669 s = s[x..$];
670 x = 0;
672 Glyph* g = mGBuf.ptr+y*mWidth+x;
673 foreach (immutable dchar dc; s) {
674 auto ng = Glyph(filterDC(dc), a);
675 ng.dirty = true;
676 if (*g != ng) {
677 if (!g.dirty) ++mDirtyCount;
678 *g = ng;
680 ++g;
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;
692 if (h == 1) {
693 // horizontal line
694 foreach (immutable sx; x..x+w) writeCharsAt(sx, y, 1, FrameChars[ft][HLine], a);
695 } else if (w == 1) {
696 // vertical line
697 foreach (immutable sy; y..y+h) writeCharsAt(x, sy, 1, FrameChars[ft][VLine], a);
698 } else {
699 // horizontal lines
700 writeCharsAt(x+1, y, w-2, FrameChars[ft][HLine], a);
701 writeCharsAt(x+1, y+h-1, w-2, FrameChars[ft][HLine], a);
702 // vertical lines
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);
707 // corners
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 // ////////////////////////////////////////////////////////////////////// //
716 // for mouse reports
717 enum MouseMods {
718 Shift = 0x01,
719 Ctrl = 0x02,
720 Meta = 0x04,
721 Hyper = 0x08,
724 enum MouseEvent {
725 Motion,
727 Down,
730 // button: 1=left, 2=middle, 3=right
731 void doMouseReport (uint x, uint y, MouseEvent event, ubyte button, uint mods) {
734 // `true`: eaten
735 bool keypressEvent (dchar dch, KeySym ksym, X11ModState modstate) {
736 return false;
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 // ////////////////////////////////////////////////////////////////////// //
760 // resets dirty flag
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;
767 g.dirty = false;
768 ++g;
771 mDirtyCount = 0;