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 private struct MiniEditKB
{ string evt
; }
37 // ////////////////////////////////////////////////////////////////////////// //
38 public final class MiniEdit
{
39 private import iv
.utfutil
: Utf8Decoder
;
42 Utf8Decoder ec
; // encoder for clipboard gets
46 void insertChar (dchar ch
) nothrow {
48 if (ch
< ' ' && (ch
!= '\n' && ch
!= '\t')) return;
50 if (curpos
>= dtext
.length
) {
52 curpos
= cast(int)dtext
.length
;
56 dtext
.assumeSafeAppend
;
57 foreach (immutable idx
; curpos
..dtext
.length
-1; reverse
) dtext
[idx
+1] = dtext
[idx
];
62 void putChar (char ch
) nothrow {
63 dchar dc
= ec
.decode(cast(ubyte)ch
);
64 if (dc
<= dchar.max
) insertChar(dc
); // insert if decoded
68 void doBackspace () nothrow {
69 if (dtext
.length
== 0 || curpos
== 0) return;
71 foreach (immutable idx
; curpos
+1..dtext
.length
) dtext
[idx
-1] = dtext
[idx
];
73 dtext
.assumeSafeAppend
;
76 void doDelete () nothrow {
77 if (dtext
.length
== 0 || curpos
>= dtext
.length
) return;
78 foreach (immutable idx
; curpos
+1..dtext
.length
) dtext
[idx
-1] = dtext
[idx
];
80 dtext
.assumeSafeAppend
;
83 void doLeftWord () nothrow {
84 if (curpos
== 0) return;
86 if (dtext
[curpos
] <= ' ') {
87 while (curpos
> 0 && dtext
[curpos
] <= ' ') --curpos
;
88 if (dtext
[curpos
] > ' ') ++curpos
;
90 while (curpos
> 0 && dtext
[curpos
] > ' ') --curpos
;
91 if (dtext
[curpos
] <= ' ') ++curpos
;
95 void doRightWord () nothrow {
96 if (curpos
== dtext
.length
) return;
97 if (dtext
[curpos
] <= ' ') {
98 while (curpos
< dtext
.length
&& dtext
[curpos
] <= ' ') ++curpos
;
100 while (curpos
< dtext
.length
&& dtext
[curpos
] > ' ') ++curpos
;
104 void doDeleteWord () nothrow {
105 if (curpos
== 0) return;
106 if (dtext
[curpos
-1] <= ' ') {
107 while (curpos
> 0 && dtext
[curpos
-1] <= ' ') doBackspace();
109 while (curpos
> 0 && dtext
[curpos
-1] > ' ') doBackspace();
116 string
text () const nothrow {
117 import iv
.utfutil
: utf8Encode
;
119 res
.reserve(dtext
.length
*4);
120 foreach (dchar dc
; dtext
) {
122 auto len
= utf8Encode(buf
[], dc
);
128 void text (const(char)[] s
) nothrow {
133 void addText (const(char)[] s
) nothrow {
134 foreach (char ch
; s
) putChar(ch
);
137 void clear () nothrow {
140 dtext
.assumeSafeAppend
;
146 void setFont (NVGContext nvg
) nothrow {
149 nvg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Top
);
153 static struct HeightInfo
{
158 void newline () nothrow @safe @nogc { ++lines
; height
+= lineh
; }
163 const(dchar)[] text
; // never has final EOL
164 bool wrap
; // true: this line was soft-wrapped
167 // `line` in delegate cannot be empty
168 // if `line` ends with '\n', this is hard newline, otherwise it is a wrap
169 void byLine (NVGContext nvg
, int width
, scope void delegate (scope Line line
) dg
) {
170 assert(nvg
!is null);
174 if (width
< 1) width
= 1; // just for fun
176 if (dtext
.length
== 0) return;
178 auto tbi
= TextBoundsIterator(nvg
);
182 while (pos
< dtext
.length
) {
184 if (dtext
[pos
] == '\n') {
185 dg(Line(linestart
, dtext
[linestart
..pos
], false)); // not wrapped
193 while (epos
< dtext
.length
&& dtext
[epos
] != '\n' && dtext
[epos
] <= ' ') tbi
.put(dtext
[epos
++]);
194 while (epos
< dtext
.length
&& dtext
[epos
] > ' ') tbi
.put(dtext
[epos
++]);
195 // if we have spaces, they should fit too
196 while (epos
< dtext
.length
&& dtext
[epos
] != '\n' && dtext
[epos
] <= ' ') tbi
.put(dtext
[epos
++]);
198 if (tbi
.advance
< width
) {
202 // if we have some words, generate line
203 if (pos
> linestart
) {
204 dg(Line(linestart
, dtext
[linestart
..pos
], true)); // wrapped
209 // oops, it doesn't; try it char-by-char
210 // epos will contain the char at which we should break
213 while (epos
< dtext
.length
&& dtext
[epos
] != '\n' && dtext
[epos
] <= ' ') {
214 tbi
.put(dtext
[epos
]);
215 if (epos
!= linestart
&& tbi
.advance
>= width
) break;
218 while (epos
< dtext
.length
&& dtext
[epos
] > ' ') {
219 tbi
.put(dtext
[epos
]);
220 if (epos
!= linestart
&& tbi
.advance
>= width
) break;
224 dg(Line(linestart
, dtext
[linestart
..pos
], true)); // wrapped
229 // last line (if any)
230 if (pos
> linestart
) {
231 assert(pos
== dtext
.length
);
232 dg(Line(linestart
, dtext
[linestart
..pos
], true)); // wrapped
237 // for the given width
238 HeightInfo
calcHeight (NVGContext nvg
, int width
) {
242 nvg
.textMetrics(null, null, &res
.lineh
);
244 bool lastWasHardEOL
= true; // at least one line should be here
245 byLine(nvg
, width
, delegate (scope Line line
) {
247 lastWasHardEOL
= !line
.wrap
;
250 if (lastWasHardEOL
) res
.newline();
255 void draw (NVGContext nvg
, float x
, float y
, int width
, int height
) {
256 if (height
< 1) return;
261 nvg
.textMetrics(null, null, &lineh
);
264 nvg
.fillColor
= NVGColor
.k8orange
;
265 //nvg.text(x, y, text[0..repos]);
267 bool cursorDrawn
= false;
269 void drawCursorAt (float cx
, float cy
) {
270 if (cursorDrawn
) return;
273 nvg
.strokeColor
= NVGColor
.yellow
;
274 nvg
.rect(cast(int)cx
, cast(int)cy
, 1, cast(int)lineh
); // ensure that cursor looks a little blurry
278 bool lastWasHardEOL
= true;
280 byLine(nvg
, width
, delegate (scope Line line
) {
282 ex
= nvg
.text(x
, y
, line
.text
);
283 // check if we should draw cursor
285 if (curpos
>= line
.stpos
&& curpos
<= line
.stpos
+line
.text
.length
) {
287 float cx
= x
+nvg
.textBounds(x
, y
, line
.text
[0..curpos
-line
.stpos
], null);
289 } else if (curpos
== line
.stpos
-1) {
296 lastWasHardEOL
= !line
.wrap
;
299 // this will draw cursor which is after the last line char
301 if (!lastWasHardEOL
) y
-= lineh
; else ex
= x
;
309 // find and call event handler
310 final bool processKeyEvent(ME
=typeof(this)) (KeyEvent event
) {
312 foreach (string memn
; __traits(allMembers
, ME
)) {
313 static if (is(typeof(&__traits(getMember
, ME
, memn
)))) {
314 import std
.meta
: AliasSeq
;
315 alias mx
= AliasSeq
!(__traits(getMember
, ME
, memn
))[0];
316 static if (isCallable
!mx
&& hasUDA
!(mx
, MiniEditKB
)) {
318 foreach (const MiniEditKB attr
; getUDAs
!(mx
, MiniEditKB
)) {
319 //pragma(msg, " ", attr.evt);
320 if (event
== attr
.evt
) { mx(); return true; }
321 if (!event
.pressed
) {
322 event
.pressed
= true;
323 if (event
== attr
.evt
) return true;
324 event
.pressed
= false;
333 bool onKey (KeyEvent event
) {
335 if (event
.key
== Key
.Enter
) {
336 if (event
.pressed
) insertChar('\n');
340 if (processKeyEvent(event
)) { glconPostScreenRepaint(); return true; }
345 bool onChar (dchar ch
) {
347 if (/*ch == '\t' ||*/ (ch
>= ' ' && ch
!= 127)) { insertChar(ch
); return true; } // enter is processed in key handler
353 @MiniEditKB("D-S-Insert") void oeFromClip () { glconCtlWindow
.getClipboardText(delegate (str) { foreach (immutable char ch
; str) putChar(ch
); }); }
354 @MiniEditKB("D-C-Insert") void oeToClip () { glconCtlWindow
.setClipboardText(text
); }
356 @MiniEditKB("D-Backspace") void oeBackspace () { doBackspace(); }
357 @MiniEditKB("D-Delete") void oeDelete () { doDelete(); }
358 @MiniEditKB("D-C-A") @MiniEditKB("D-Home") @MiniEditKB("D-Pad7") void oeGoHome () { curpos
= 0; }
359 @MiniEditKB("D-C-E") @MiniEditKB("D-End") @MiniEditKB("D-Pad1") void oeGoEnd () { curpos
= cast(int)dtext
.length
; }
360 @MiniEditKB("D-C-Backspace") @MiniEditKB("D-M-Backspace") void oeDelWord () { doDeleteWord(); }
362 @MiniEditKB("D-C-Y") void oeKillAll () { clear(); }
364 @MiniEditKB("D-Left") @MiniEditKB("D-Pad4") void oeGoLeft () { if (curpos
> 0) --curpos
; }
365 @MiniEditKB("D-Right") @MiniEditKB("D-Pad6") void oeGoRight () { if (curpos
< dtext
.length
) ++curpos
; }
367 @MiniEditKB("D-C-Left") @MiniEditKB("D-C-Pad4") void oeGoLeftWord () { doLeftWord(); }
368 @MiniEditKB("D-C-Right") @MiniEditKB("D-C-Pad6") void oeGoRightWord () { doRightWord(); }