command console is visible when activated
[bioacid.git] / miniedit.d
blobcfea120b3f75857260b7215a6f4a9d45b5efb0e0
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;
18 private:
19 import arsd.color;
20 import arsd.simpledisplay;
22 import iv.cmdcon;
23 import iv.cmdcon.gl;
24 import iv.nanovega;
25 import iv.strex;
26 import iv.sdpyutil;
27 import iv.unarray;
28 import iv.utfutil;
29 import iv.vfs.io;
32 // ////////////////////////////////////////////////////////////////////////// //
33 public final class MiniEdit {
34 private import iv.utfutil : Utf8Decoder;
35 private:
36 dchar[] dtext;
37 NVGGlyphPosition[8192] glyphs;
38 Utf8Decoder ec; // encoder for clipboard gets
39 char[8] clipbuf;
40 uint clipbufused;
41 int curpos;
43 private:
44 void insertChar (dchar ch) {
45 assert(curpos >= 0);
46 if (ch < ' ' && (ch != '\n' && ch != '\t')) return;
47 // append?
48 if (curpos >= dtext.length) {
49 dtext ~= ch;
50 curpos = cast(int)dtext.length;
51 } else {
52 // insert
53 dtext.length += 1;
54 foreach (immutable idx; curpos..dtext.length-1; reverse) dtext[idx+1] = dtext[idx];
55 dtext[curpos++] = ch;
57 glconPostScreenRepaint();
60 void doBackspace () {
61 if (dtext.length == 0 || curpos == 0) return;
62 --curpos;
63 foreach (immutable idx; curpos+1..dtext.length) dtext[idx-1] = dtext[idx];
64 dtext.length -= 1;
65 dtext.assumeSafeAppend;
66 glconPostScreenRepaint();
69 void doDelete () {
70 if (dtext.length == 0 || curpos >= dtext.length) return;
71 foreach (immutable idx; curpos+1..dtext.length) dtext[idx-1] = dtext[idx];
72 dtext.length -= 1;
73 dtext.assumeSafeAppend;
74 glconPostScreenRepaint();
77 private:
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
84 public:
85 this () {}
87 string text () const nothrow {
88 import iv.utfutil : utf8Encode;
89 string res;
90 res.reserve(dtext.length*4);
91 foreach (dchar dc; dtext) {
92 char[4] buf = void;
93 auto len = utf8Encode(buf[], dc);
94 res ~= buf[0..len];
96 return res;
99 void clear () {
100 if (dtext.length) {
101 dtext.length = 0;
102 dtext.assumeSafeAppend;
104 curpos = 0;
107 void setFont (NVGContext nvg) {
108 nvg.fontSize = 20;
109 nvg.fontFace = "ui";
110 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Top);
113 static struct HeightInfo {
114 float height = 0;
115 int lines = 0;
116 float lineh = 0;
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);
126 assert(dg !is null);
128 setFont(nvg);
129 if (width < 1) width = 1; // just for fun
131 uint curpos = 0;
132 while (curpos < dtext.length) {
133 // new line; always here
134 uint epos = 0;
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;
147 curpos += epos;
151 // for the given width
152 HeightInfo calcHeight (NVGContext nvg, int width) {
153 setFont(nvg);
155 HeightInfo res;
156 nvg.textMetrics(null, null, &res.lineh);
158 byLine(nvg, width, delegate (line) {
159 res.newline();
160 return false;
163 if (dtext.length && dtext[$-1] == '\n') res.newline();
165 // we always has at least one line
166 if (res.lines == 0) res.newline();
168 return res;
171 void draw (NVGContext nvg, float x, float y, int width, int height) {
172 if (height < 1) return;
174 setFont(nvg);
176 float lineh;
177 nvg.textMetrics(null, null, &lineh);
179 //nvg.beginPath();
180 nvg.fillColor = NVGColor.k8orange;
181 //nvg.text(x, y, text[0..repos]);
183 float ex = x;
184 float ey = y;
185 bool cursorDrawn = false;
187 void drawCursorAt (float cx, float cy) {
188 if (cursorDrawn) return;
189 cursorDrawn = true;
190 nvg.beginPath();
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
193 nvg.stroke();
196 int lpos = 0;
197 byLine(nvg, width, delegate (line) {
198 float cy = y;
199 bool hardEOL = false;
201 if (line[$-1] == '\n') {
202 // hard wrap
203 hardEOL = true;
204 ex = x;
205 ey = y+lineh;
206 nvg.text(x, y, line[0..$-1]);
207 } else {
208 // soft wrap
209 ex = nvg.text(x, y, line);
210 ey = y;
212 y += lineh;
214 if (!cursorDrawn && curpos >= lpos && curpos < lpos+line.length) {
215 // draw cursor
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;
222 return false;
225 drawCursorAt(ex, ey);
228 bool onKey (KeyEvent event) {
229 // enter
230 if (event.key == Key.Enter) {
231 if (event.pressed) insertChar('\n');
232 return true;
235 // bs
236 if (event.key == Key.Backspace) {
237 if (event == "D-Backspace") doBackspace();
238 return true;
241 // delete
242 if (event.key == Key.Delete) {
243 if (event == "D-Delete") doDelete();
244 return true;
247 // C-A
248 if (event.key == Key.A) {
249 if (event == "D-C-A" && curpos > 0) { curpos = 0; glconPostScreenRepaint(); }
250 return true;
253 // C-E
254 if (event.key == Key.E) {
255 if (event == "D-C-E" && curpos < dtext.length) { curpos = cast(int)dtext.length; glconPostScreenRepaint(); }
256 return true;
259 // left
260 if (event.key == Key.Left || event.key == Key.Pad4) {
261 if (curpos > 0 && (event == "D-Left" || event == "D-Pad4")) { --curpos; glconPostScreenRepaint(); }
262 return true;
265 // right
266 if (event.key == Key.Right || event.key == Key.Pad6) {
267 if (curpos < dtext.length && (event == "D-Right" || event == "D-Pad6")) { ++curpos; glconPostScreenRepaint(); }
268 return true;
271 // C-Y
272 if (event.key == Key.Y) {
273 if (event == "D-C-Y" && dtext.length > 0) {
274 clear();
275 glconPostScreenRepaint();
277 return true;
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); });
282 return true;
285 if (event == "D-C-Insert" || event == "U-C-Insert") {
286 if (event.pressed) glconCtlWindow.setClipboardText(text);
287 return true;
290 return false;
293 bool onChar (dchar ch) {
294 // 127 is "delete"
295 if (/*ch == '\t' ||*/ (ch >= ' ' && ch != 127)) { insertChar(ch); return true; } // enter is processed in key handler
296 return false;