don't draw contact list scrollbar if it is not required
[bioacid.git] / tkminiedit.d
blob416a23969f9c5a9cda1f3be4f2426db99557f5b7
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;
18 private:
20 import arsd.color;
21 import arsd.simpledisplay;
23 import iv.cmdcon;
24 import iv.cmdcon.gl;
25 import iv.nanovega;
26 import iv.strex;
27 import iv.sdpyutil;
28 import iv.unarray;
29 import iv.utfutil;
30 import iv.vfs.io;
33 // ////////////////////////////////////////////////////////////////////////// //
34 public final class MiniEdit {
35 private import iv.utfutil : Utf8Decoder;
36 private:
37 dchar[] dtext;
38 NVGGlyphPosition[8192] glyphs;
39 Utf8Decoder ec; // encoder for clipboard gets
40 char[8] clipbuf;
41 uint clipbufused;
42 int curpos;
44 private:
45 void insertChar (dchar ch) {
46 assert(curpos >= 0);
47 if (ch < ' ' && (ch != '\n' && ch != '\t')) return;
48 // append?
49 if (curpos >= dtext.length) {
50 dtext ~= ch;
51 curpos = cast(int)dtext.length;
52 } else {
53 // insert
54 dtext.length += 1;
55 foreach (immutable idx; curpos..dtext.length-1; reverse) dtext[idx+1] = dtext[idx];
56 dtext[curpos++] = ch;
58 glconPostScreenRepaint();
61 void doBackspace () {
62 if (dtext.length == 0 || curpos == 0) return;
63 --curpos;
64 foreach (immutable idx; curpos+1..dtext.length) dtext[idx-1] = dtext[idx];
65 dtext.length -= 1;
66 dtext.assumeSafeAppend;
67 glconPostScreenRepaint();
70 void doDelete () {
71 if (dtext.length == 0 || curpos >= dtext.length) return;
72 foreach (immutable idx; curpos+1..dtext.length) dtext[idx-1] = dtext[idx];
73 dtext.length -= 1;
74 dtext.assumeSafeAppend;
75 glconPostScreenRepaint();
78 private:
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
85 public:
86 this () nothrow {}
88 string text () const nothrow {
89 import iv.utfutil : utf8Encode;
90 string res;
91 res.reserve(dtext.length*4);
92 foreach (dchar dc; dtext) {
93 char[4] buf = void;
94 auto len = utf8Encode(buf[], dc);
95 res ~= buf[0..len];
97 return res;
100 void clear () {
101 if (dtext.length) {
102 dtext.length = 0;
103 dtext.assumeSafeAppend;
105 curpos = 0;
108 void setFont (NVGContext nvg) {
109 nvg.fontSize = 20;
110 nvg.fontFace = "ui";
111 nvg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Top);
114 static struct HeightInfo {
115 float height = 0;
116 int lines = 0;
117 float lineh = 0;
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);
127 assert(dg !is null);
129 setFont(nvg);
130 if (width < 1) width = 1; // just for fun
132 uint curpos = 0;
133 while (curpos < dtext.length) {
134 // new line; always here
135 uint epos = 0;
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;
148 curpos += epos;
152 // for the given width
153 HeightInfo calcHeight (NVGContext nvg, int width) {
154 setFont(nvg);
156 HeightInfo res;
157 nvg.textMetrics(null, null, &res.lineh);
159 byLine(nvg, width, delegate (line) {
160 res.newline();
161 return false;
164 if (dtext.length && dtext[$-1] == '\n') res.newline();
166 // we always has at least one line
167 if (res.lines == 0) res.newline();
169 return res;
172 void draw (NVGContext nvg, float x, float y, int width, int height) {
173 if (height < 1) return;
175 setFont(nvg);
177 float lineh;
178 nvg.textMetrics(null, null, &lineh);
180 //nvg.beginPath();
181 nvg.fillColor = NVGColor.k8orange;
182 //nvg.text(x, y, text[0..repos]);
184 float ex = x;
185 float ey = y;
186 bool cursorDrawn = false;
188 void drawCursorAt (float cx, float cy) {
189 if (cursorDrawn) return;
190 cursorDrawn = true;
191 nvg.beginPath();
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
194 nvg.stroke();
197 int lpos = 0;
198 byLine(nvg, width, delegate (line) {
199 float cy = y;
200 bool hardEOL = false;
202 if (line[$-1] == '\n') {
203 // hard wrap
204 hardEOL = true;
205 ex = x;
206 ey = y+lineh;
207 nvg.text(x, y, line[0..$-1]);
208 } else {
209 // soft wrap
210 ex = nvg.text(x, y, line);
211 ey = y;
213 y += lineh;
215 if (!cursorDrawn && curpos >= lpos && curpos < lpos+line.length) {
216 // draw cursor
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;
223 return false;
226 drawCursorAt(ex, ey);
229 bool onKey (KeyEvent event) {
230 // enter
231 if (event.key == Key.Enter) {
232 if (event.pressed) insertChar('\n');
233 return true;
236 // bs
237 if (event.key == Key.Backspace) {
238 if (event == "D-Backspace") doBackspace();
239 return true;
242 // delete
243 if (event.key == Key.Delete) {
244 if (event == "D-Delete") doDelete();
245 return true;
248 // C-A
249 if (event.key == Key.A) {
250 if (event == "D-C-A" && curpos > 0) { curpos = 0; glconPostScreenRepaint(); }
251 return true;
254 // C-E
255 if (event.key == Key.E) {
256 if (event == "D-C-E" && curpos < dtext.length) { curpos = cast(int)dtext.length; glconPostScreenRepaint(); }
257 return true;
260 // left
261 if (event.key == Key.Left || event.key == Key.Pad4) {
262 if (curpos > 0 && (event == "D-Left" || event == "D-Pad4")) { --curpos; glconPostScreenRepaint(); }
263 return true;
266 // right
267 if (event.key == Key.Right || event.key == Key.Pad6) {
268 if (curpos < dtext.length && (event == "D-Right" || event == "D-Pad6")) { ++curpos; glconPostScreenRepaint(); }
269 return true;
272 // C-Y
273 if (event.key == Key.Y) {
274 if (event == "D-C-Y" && dtext.length > 0) {
275 clear();
276 glconPostScreenRepaint();
278 return true;
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); });
283 return true;
286 if (event == "D-C-Insert" || event == "U-C-Insert") {
287 if (event.pressed) glconCtlWindow.setClipboardText(text);
288 return true;
291 return false;
294 bool onChar (dchar ch) {
295 // 127 is "delete"
296 if (/*ch == '\t' ||*/ (ch >= ' ' && ch != 127)) { insertChar(ch); return true; } // enter is processed in key handler
297 return false;