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, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module miniedit
is aliced
;
20 import arsd
.simpledisplay
;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public final class MiniEdit
{
34 private import iv
.utfutil
: Utf8Decoder
;
37 NVGGlyphPosition
[8192] glyphs
;
38 Utf8Decoder ec
; // encoder for clipboard gets
44 void insertChar (dchar ch
) {
46 if (ch
< ' ' && (ch
!= '\n' && ch
!= '\t')) return;
48 if (curpos
>= dtext
.length
) {
50 curpos
= cast(int)dtext
.length
;
54 foreach (immutable idx
; curpos
..dtext
.length
-1; reverse
) dtext
[idx
+1] = dtext
[idx
];
57 glconPostScreenRepaint();
61 if (dtext
.length
== 0 || curpos
== 0) return;
63 foreach (immutable idx
; curpos
+1..dtext
.length
) dtext
[idx
-1] = dtext
[idx
];
65 dtext
.assumeSafeAppend
;
66 glconPostScreenRepaint();
70 if (dtext
.length
== 0 || curpos
>= dtext
.length
) return;
71 foreach (immutable idx
; curpos
+1..dtext
.length
) dtext
[idx
-1] = dtext
[idx
];
73 dtext
.assumeSafeAppend
;
74 glconPostScreenRepaint();
78 void putChar (char ch
) {
79 //if (ch <= ' ' || ch >= 127) conprintf("new char: 0x%02x\n", cast(ubyte)ch); else conprintf("char: '%s'\n", ch);
80 dchar dc
= ec
.decode(cast(ubyte)ch
);
81 if (dc
<= dchar.max
) insertChar(dc
); // insert if decoded
87 string
text () const nothrow {
88 import iv
.utfutil
: utf8Encode
;
90 res
.reserve(dtext
.length
*4);
91 foreach (dchar dc
; dtext
) {
93 auto len
= utf8Encode(buf
[], dc
);
102 dtext
.assumeSafeAppend
;
107 void setFont (NVGContext nvg
) {
110 nvg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Top
);
113 static struct HeightInfo
{
118 void newline () nothrow @safe @nogc { ++lines
; height
+= lineh
; }
121 // return `false` from delegate to stop
122 // `line` in delegate cannot be empty
123 // if `line` ends with '\n', this is hard newline, otherwise it is a wrap
124 void byLine (NVGContext nvg
, int width
, scope bool delegate (const(dchar)[] line
) dg
) {
125 assert(nvg
!is null);
129 if (width
< 1) width
= 1; // just for fun
132 while (curpos
< dtext
.length
) {
133 // new line; always here
135 nvg
.textGlyphPositions(0, 0, dtext
[curpos
..$], delegate (NVGGlyphPosition gpos
) {
136 if (gpos
.maxx
>= width
) return false; // doesn't fit: stop
137 if (dtext
[curpos
+gpos
.strpos
] == '\n') return false; // eol hit: stop
138 epos
= cast(uint)gpos
.strpos
+1; // swallow the glyph
139 return true; // continue
141 // no glyphs fit, but the line is not empty: swallow at least one glyph
142 if (epos
== 0 && dtext
[curpos
] != '\n') ++epos
;
143 // if we hit (or about to hit) EOL, swallow it too
144 if (epos
== 0) { assert(dtext
[curpos
] == '\n'); ++epos
; }
145 else if (curpos
+epos
< dtext
.length
&& dtext
[curpos
+epos
] == '\n') ++epos
;
146 if (dg(dtext
[curpos
..curpos
+epos
])) break;
151 // for the given width
152 HeightInfo
calcHeight (NVGContext nvg
, int width
) {
156 nvg
.textMetrics(null, null, &res
.lineh
);
158 byLine(nvg
, width
, delegate (line
) {
163 if (dtext
.length
&& dtext
[$-1] == '\n') res
.newline();
165 // we always has at least one line
166 if (res
.lines
== 0) res
.newline();
171 void draw (NVGContext nvg
, float x
, float y
, int width
, int height
) {
172 if (height
< 1) return;
177 nvg
.textMetrics(null, null, &lineh
);
180 nvg
.fillColor
= NVGColor
.k8orange
;
181 //nvg.text(x, y, text[0..repos]);
185 bool cursorDrawn
= false;
187 void drawCursorAt (float cx
, float cy
) {
188 if (cursorDrawn
) return;
191 nvg
.strokeColor
= NVGColor
.yellow
;
192 nvg
.rect(cast(int)cx
, cast(int)cy
, 1, cast(int)lineh
); // ensure that cursor looks a little blurry
197 byLine(nvg
, width
, delegate (line
) {
199 bool hardEOL
= false;
201 if (line
[$-1] == '\n') {
206 nvg
.text(x
, y
, line
[0..$-1]);
209 ex
= nvg
.text(x
, y
, line
);
214 if (!cursorDrawn
&& curpos
>= lpos
&& curpos
< lpos
+line
.length
) {
216 //if (hardEOL && curpos == lpos+line.length-1
217 float cx
= x
+nvg
.textBounds(x
, y
, line
[0..curpos
-lpos
], null);
218 drawCursorAt(cx
, cy
);
221 lpos
+= cast(int)line
.length
;
225 drawCursorAt(ex
, ey
);
228 bool onKey (KeyEvent event
) {
230 if (event
.key
== Key
.Enter
) {
231 if (event
.pressed
) insertChar('\n');
236 if (event
.key
== Key
.Backspace
) {
237 if (event
== "D-Backspace") doBackspace();
242 if (event
.key
== Key
.Delete
) {
243 if (event
== "D-Delete") doDelete();
248 if (event
.key
== Key
.A
) {
249 if (event
== "D-C-A" && curpos
> 0) { curpos
= 0; glconPostScreenRepaint(); }
254 if (event
.key
== Key
.E
) {
255 if (event
== "D-C-E" && curpos
< dtext
.length
) { curpos
= cast(int)dtext
.length
; glconPostScreenRepaint(); }
260 if (event
.key
== Key
.Left || event
.key
== Key
.Pad4
) {
261 if (curpos
> 0 && (event
== "D-Left" || event
== "D-Pad4")) { --curpos
; glconPostScreenRepaint(); }
266 if (event
.key
== Key
.Right || event
.key
== Key
.Pad6
) {
267 if (curpos
< dtext
.length
&& (event
== "D-Right" || event
== "D-Pad6")) { ++curpos
; glconPostScreenRepaint(); }
272 if (event
.key
== Key
.Y
) {
273 if (event
== "D-C-Y" && dtext
.length
> 0) {
275 glconPostScreenRepaint();
280 if (event
== "D-S-Insert" || event
== "U-S-Insert") {
281 if (event
.pressed
) glconCtlWindow
.getClipboardText(delegate (str) { foreach (immutable char ch
; str) putChar(ch
); });
285 if (event
== "D-C-Insert" || event
== "U-C-Insert") {
286 if (event
.pressed
) glconCtlWindow
.setClipboardText(text
);
293 bool onChar (dchar ch
) {
295 if (/*ch == '\t' ||*/ (ch
>= ' ' && ch
!= 127)) { insertChar(ch
); return true; } // enter is processed in key handler