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 tkminiedit
is aliced
;
21 import arsd
.simpledisplay
;
33 // ////////////////////////////////////////////////////////////////////////// //
34 public final class MiniEdit
{
35 private import iv
.utfutil
: Utf8Decoder
;
38 NVGGlyphPosition
[8192] glyphs
;
39 Utf8Decoder ec
; // encoder for clipboard gets
45 void insertChar (dchar ch
) {
47 if (ch
< ' ' && (ch
!= '\n' && ch
!= '\t')) return;
49 if (curpos
>= dtext
.length
) {
51 curpos
= cast(int)dtext
.length
;
55 foreach (immutable idx
; curpos
..dtext
.length
-1; reverse
) dtext
[idx
+1] = dtext
[idx
];
58 glconPostScreenRepaint();
62 if (dtext
.length
== 0 || curpos
== 0) return;
64 foreach (immutable idx
; curpos
+1..dtext
.length
) dtext
[idx
-1] = dtext
[idx
];
66 dtext
.assumeSafeAppend
;
67 glconPostScreenRepaint();
71 if (dtext
.length
== 0 || curpos
>= dtext
.length
) return;
72 foreach (immutable idx
; curpos
+1..dtext
.length
) dtext
[idx
-1] = dtext
[idx
];
74 dtext
.assumeSafeAppend
;
75 glconPostScreenRepaint();
79 void putChar (char ch
) {
80 //if (ch <= ' ' || ch >= 127) conprintf("new char: 0x%02x\n", cast(ubyte)ch); else conprintf("char: '%s'\n", ch);
81 dchar dc
= ec
.decode(cast(ubyte)ch
);
82 if (dc
<= dchar.max
) insertChar(dc
); // insert if decoded
88 string
text () const nothrow {
89 import iv
.utfutil
: utf8Encode
;
91 res
.reserve(dtext
.length
*4);
92 foreach (dchar dc
; dtext
) {
94 auto len
= utf8Encode(buf
[], dc
);
103 dtext
.assumeSafeAppend
;
108 void setFont (NVGContext nvg
) {
111 nvg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Top
);
114 static struct HeightInfo
{
119 void newline () nothrow @safe @nogc { ++lines
; height
+= lineh
; }
122 // return `false` from delegate to stop
123 // `line` in delegate cannot be empty
124 // if `line` ends with '\n', this is hard newline, otherwise it is a wrap
125 void byLine (NVGContext nvg
, int width
, scope bool delegate (const(dchar)[] line
) dg
) {
126 assert(nvg
!is null);
130 if (width
< 1) width
= 1; // just for fun
133 while (curpos
< dtext
.length
) {
134 // new line; always here
136 nvg
.textGlyphPositions(0, 0, dtext
[curpos
..$], delegate (NVGGlyphPosition gpos
) {
137 if (gpos
.maxx
>= width
) return false; // doesn't fit: stop
138 if (dtext
[curpos
+gpos
.strpos
] == '\n') return false; // eol hit: stop
139 epos
= cast(uint)gpos
.strpos
+1; // swallow the glyph
140 return true; // continue
142 // no glyphs fit, but the line is not empty: swallow at least one glyph
143 if (epos
== 0 && dtext
[curpos
] != '\n') ++epos
;
144 // if we hit (or about to hit) EOL, swallow it too
145 if (epos
== 0) { assert(dtext
[curpos
] == '\n'); ++epos
; }
146 else if (curpos
+epos
< dtext
.length
&& dtext
[curpos
+epos
] == '\n') ++epos
;
147 if (dg(dtext
[curpos
..curpos
+epos
])) break;
152 // for the given width
153 HeightInfo
calcHeight (NVGContext nvg
, int width
) {
157 nvg
.textMetrics(null, null, &res
.lineh
);
159 byLine(nvg
, width
, delegate (line
) {
164 if (dtext
.length
&& dtext
[$-1] == '\n') res
.newline();
166 // we always has at least one line
167 if (res
.lines
== 0) res
.newline();
172 void draw (NVGContext nvg
, float x
, float y
, int width
, int height
) {
173 if (height
< 1) return;
178 nvg
.textMetrics(null, null, &lineh
);
181 nvg
.fillColor
= NVGColor
.k8orange
;
182 //nvg.text(x, y, text[0..repos]);
186 bool cursorDrawn
= false;
188 void drawCursorAt (float cx
, float cy
) {
189 if (cursorDrawn
) return;
192 nvg
.strokeColor
= NVGColor
.yellow
;
193 nvg
.rect(cast(int)cx
, cast(int)cy
, 1, cast(int)lineh
); // ensure that cursor looks a little blurry
198 byLine(nvg
, width
, delegate (line
) {
200 bool hardEOL
= false;
202 if (line
[$-1] == '\n') {
207 nvg
.text(x
, y
, line
[0..$-1]);
210 ex
= nvg
.text(x
, y
, line
);
215 if (!cursorDrawn
&& curpos
>= lpos
&& curpos
< lpos
+line
.length
) {
217 //if (hardEOL && curpos == lpos+line.length-1
218 float cx
= x
+nvg
.textBounds(x
, y
, line
[0..curpos
-lpos
], null);
219 drawCursorAt(cx
, cy
);
222 lpos
+= cast(int)line
.length
;
226 drawCursorAt(ex
, ey
);
229 bool onKey (KeyEvent event
) {
231 if (event
.key
== Key
.Enter
) {
232 if (event
.pressed
) insertChar('\n');
237 if (event
.key
== Key
.Backspace
) {
238 if (event
== "D-Backspace") doBackspace();
243 if (event
.key
== Key
.Delete
) {
244 if (event
== "D-Delete") doDelete();
249 if (event
.key
== Key
.A
) {
250 if (event
== "D-C-A" && curpos
> 0) { curpos
= 0; glconPostScreenRepaint(); }
255 if (event
.key
== Key
.E
) {
256 if (event
== "D-C-E" && curpos
< dtext
.length
) { curpos
= cast(int)dtext
.length
; glconPostScreenRepaint(); }
261 if (event
.key
== Key
.Left || event
.key
== Key
.Pad4
) {
262 if (curpos
> 0 && (event
== "D-Left" || event
== "D-Pad4")) { --curpos
; glconPostScreenRepaint(); }
267 if (event
.key
== Key
.Right || event
.key
== Key
.Pad6
) {
268 if (curpos
< dtext
.length
&& (event
== "D-Right" || event
== "D-Pad6")) { ++curpos
; glconPostScreenRepaint(); }
273 if (event
.key
== Key
.Y
) {
274 if (event
== "D-C-Y" && dtext
.length
> 0) {
276 glconPostScreenRepaint();
281 if (event
== "D-S-Insert" || event
== "U-S-Insert") {
282 if (event
.pressed
) glconCtlWindow
.getClipboardText(delegate (str) { foreach (immutable char ch
; str) putChar(ch
); });
286 if (event
== "D-C-Insert" || event
== "U-C-Insert") {
287 if (event
.pressed
) glconCtlWindow
.setClipboardText(text
);
294 bool onChar (dchar ch
) {
296 if (/*ch == '\t' ||*/ (ch
>= ' ' && ch
!= 127)) { insertChar(ch
); return true; } // enter is processed in key handler