added command console (not really used yet)
[xreader.git] / xreaderui.d
blobd88bd949b6bc3eb913f71b76f60e47e7727f59a5
1 /* Written 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 xreaderui;
19 import arsd.simpledisplay;
20 import arsd.image;
22 import iv.nanovg;
23 import iv.nanovg.oui.blendish;
24 import iv.strex;
25 import iv.vfs;
26 import iv.vfs.io;
28 import xreadercfg;
31 // ////////////////////////////////////////////////////////////////////////// //
32 void loadFonts (NVGContext vg) {
33 void loadFont (ref int fid, string name, string path) {
34 fid = vg.createFont(name, path);
35 if (fid < 0) throw new Exception("can't load font '"~name~"' from '"~path~"'");
38 loadFont(textFont, "text", textFontName);
39 loadFont(textiFont, "texti", textiFontName);
40 loadFont(textbFont, "textb", textbFontName);
41 loadFont(textzFont, "textz", textzFontName);
43 loadFont(monoFont, "mono", monoFontName);
44 loadFont(monoiFont, "monoi", monoiFontName);
45 loadFont(monobFont, "monob", monobFontName);
46 loadFont(monozFont, "monoz", monozFontName);
48 loadFont(uiFont, "ui", uiFontName);
50 loadFont(galmapFont, "galmap", galmapFontName);
52 bndSetFont(uiFont);
56 // ////////////////////////////////////////////////////////////////////////// //
57 // cursor (hi, Death Track!)
58 private enum curWidth = 17;
59 private enum curHeight = 23;
60 private static immutable ubyte[curWidth*curHeight] curImg = [
61 0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
62 0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
63 1,0,3,2,2,0,0,0,0,0,0,0,0,0,0,0,0,
64 1,1,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0,
65 1,1,3,3,4,2,2,0,0,0,0,0,0,0,0,0,0,
66 1,1,3,3,4,4,2,2,0,0,0,0,0,0,0,0,0,
67 1,1,3,3,4,4,4,2,2,0,0,0,0,0,0,0,0,
68 1,1,3,3,4,4,4,4,2,2,0,0,0,0,0,0,0,
69 1,1,3,3,4,4,4,5,6,2,2,0,0,0,0,0,0,
70 1,1,3,3,4,4,5,6,7,5,2,2,0,0,0,0,0,
71 1,1,3,3,4,5,6,7,5,4,5,2,2,0,0,0,0,
72 1,1,3,3,5,6,7,5,4,5,6,7,2,2,0,0,0,
73 1,1,3,3,6,7,5,4,5,6,7,7,7,2,2,0,0,
74 1,1,3,3,7,5,4,5,6,7,7,7,7,7,2,2,0,
75 1,1,3,3,5,4,5,6,8,8,8,8,8,8,8,8,2,
76 1,1,3,3,4,5,6,3,8,8,8,8,8,8,8,8,8,
77 1,1,3,3,5,6,3,3,1,1,1,1,1,1,1,0,0,
78 1,1,3,3,6,3,3,1,1,1,1,1,1,1,1,0,0,
79 1,1,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,
80 1,1,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,
81 1,1,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,
82 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
83 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
85 struct CC { ubyte r, g, b, a; }
86 private static immutable CC[9] curPal = [
87 CC( 0, 0, 0, 0),
88 CC( 0, 0, 0,127),
89 CC( 85,255,255,255),
90 CC( 85, 85,255,255),
91 CC(255, 85, 85,255),
92 CC(170, 0,170,255),
93 CC( 85, 85, 85,255),
94 CC( 0, 0, 0,255),
95 CC( 0, 0,170,255),
99 int createCursorImage (NVGContext vg, bool white=false) {
100 auto img = new ubyte[](curWidth*curHeight*4);
101 scope(exit) img.destroy;
102 const(ubyte)* s = curImg.ptr;
103 auto d = img.ptr;
104 foreach (immutable _; 0..curWidth*curHeight) {
105 CC c = curPal.ptr[*s++];
106 if (white) {
107 if (c.a == 255) {
108 //c = CC(255, 127, 0, 255);
109 auto hsl = nvgRGB(c.r, c.g, c.b).asHSL;
110 //hsl.l *= 1.25; if (hsl.l > 0.8) hsl.l = 0.8;
111 //hsl.l *= 1.25; if (hsl.l > 1) hsl.l = 1;
112 //hsl.h *= 2.25; if (hsl.h > 1) hsl.h = 1;
113 hsl.h *= 0.1;
114 auto cc = hsl.asColor.asUint;
115 *d++ = cc&0xff; cc >>= 8;
116 *d++ = cc&0xff; cc >>= 8;
117 *d++ = cc&0xff; cc >>= 8;
118 *d++ = cc&0xff; cc >>= 8;
119 } else {
120 *d++ = c.r;
121 *d++ = c.g;
122 *d++ = c.b;
123 *d++ = c.a;
125 } else {
126 *d++ = c.r;
127 *d++ = c.g;
128 *d++ = c.b;
129 *d++ = c.a;
132 return vg.createImageRGBA(curWidth, curHeight, img[]);
136 // ////////////////////////////////////////////////////////////////////////// //
137 final class PopupMenu {
138 //enum LeftX = 10;
139 //enum TopY = 10;
140 enum Padding = 8;
142 static struct SecInfo {
143 int w, h;
144 dstring s;
147 bool hasTitle;
148 SecInfo menuTitle;
149 SecInfo[] items;
150 int curItemIdx = 0;
151 int topItemIdx = 0;
152 int hoverItem = -1;
153 NVGContext vg;
154 int maxW; // maximum text width
155 bool needRedraw;
156 int leftX, topY;
157 bool moving;
159 void textSize (dstring s, out int tw, out int th) {
160 auto w = vg.bndLabelWidth(-1, s)+4;
161 if (w > GWidth-Padding*4) w = GWidth-Padding*4;
162 auto h = vg.bndLabelHeight(-1, s, w);
163 if (h < BND_WIDGET_HEIGHT) h = BND_WIDGET_HEIGHT;
164 tw = cast(int)w;
165 th = cast(int)h;
168 this (NVGContext avg, dstring atitle, scope dstring[] delegate () buildSections) {
169 assert(avg !is null);
170 vg = avg;
171 vg.fontFaceId(uiFont);
172 vg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Baseline);
173 vg.fontSize(fsizeUI);
174 maxW = 0;
175 if (atitle !is null) {
176 hasTitle = true;
177 items.length += 1;
178 items[0].s = atitle;
179 textSize(atitle, items[0].w, items[0].h);
180 maxW = items[0].w;
182 if (buildSections !is null) {
183 auto list = buildSections();
184 auto cpos = items.length;
185 items.length += list.length;
186 foreach (dstring s; list) {
187 items[cpos].s = s;
188 textSize(s, items[cpos].w, items[cpos].h);
189 if (maxW < items[cpos].w) maxW = items[cpos].w;
190 ++cpos;
193 if (hasTitle) {
194 menuTitle = items[0];
195 items = items[1..$];
197 leftX = (GWidth-maxW-Padding*4)/2;
198 topY = Padding;
199 int ey = calcBotY;
200 //topY = (GHeight-(ey-topY))/2;
201 topY = Padding;
202 needRedraw = true;
205 int lastFullVisible () {
206 if (items.length == 0) return 0;
207 int res = topItemIdx;
208 int y = topY+Padding+menuTitle.h;
209 int idx = topItemIdx;
210 if (idx < 0) idx = 0;
211 int ey = calcBotY-Padding;
212 while (idx < items.length) {
213 y += items[idx].h;
214 if (y > ey) return res;
215 res = idx;
216 ++idx;
218 return cast(int)items.length-1;
221 int calcBotY () {
222 int y = topY+Padding*2+menuTitle.h;
223 foreach (const ref sc; items) {
224 int ny = y+sc.h;
225 if (ny > GHeight-Padding*2) return y;
226 y = ny;
228 return y;
231 void normPage () {
232 if (curItemIdx < 0) curItemIdx = 0;
233 if (curItemIdx >= items.length) curItemIdx = cast(int)items.length-1;
234 if (items.length) {
235 // make current item visible
236 if (curItemIdx >= 0 && curItemIdx < items.length) {
237 if (curItemIdx < topItemIdx) {
238 topItemIdx = curItemIdx;
239 } else {
240 //FIXME: make this faster!
241 //writeln("top=", topItemIdx, "; cur=", curItemIdx, "; lv=", lastFullVisible);
242 while (lastFullVisible < curItemIdx) {
243 if (topItemIdx >= items.length-1) break;
244 ++topItemIdx;
249 while (topItemIdx > 0) {
250 int lv = lastFullVisible;
251 --topItemIdx;
252 int lv1 = lastFullVisible;
253 if (lv1 != lv) { ++topItemIdx; break; }
257 void draw () {
258 vg.save();
259 scope(exit) vg.restore();
260 if (moving) vg.globalAlpha(0.8);
261 vg.fontFaceId(uiFont);
262 vg.textAlign(NVGTextAlign.H.Left, NVGTextAlign.V.Baseline);
263 vg.fontSize(fsizeUI);
264 normPage();
265 int ey = calcBotY;
266 vg.bndMenuBackground(leftX, topY, maxW+Padding*2, ey-topY, BND_CORNER_NONE);
267 int y = topY+Padding;
268 vg.scissor(leftX+Padding, y, maxW, ey-Padding);
269 if (hasTitle) {
270 vg.bndMenuLabel(leftX+Padding, y, maxW, menuTitle.h, -1, menuTitle.s);
271 y += menuTitle.h;
273 int idx = topItemIdx;
274 if (idx < 0) idx = 0;
275 ey -= Padding;
276 while (idx < items.length) {
277 vg.bndMenuItem(leftX+Padding, y, maxW, items[idx].h,
278 (curItemIdx == idx ? BND_ACTIVE : hoverItem == idx ? BND_HOVER : BND_DEFAULT),
279 -1, items[idx].s);
280 y += items[idx].h;
281 if (y >= ey) break;
282 ++idx;
284 needRedraw = false;
287 enum int Close = -666;
288 enum int Eaten = -2;
289 enum int NotMine = -1;
291 int onKey (KeyEvent event) {
292 if (!event.pressed) return NotMine;
293 switch (event.key) {
294 case Key.Escape:
295 return Close;
296 case Key.Up:
297 if (curItemIdx > 0) {
298 needRedraw = true;
299 --curItemIdx;
301 return Eaten;
302 case Key.Down:
303 if (curItemIdx+1 < items.length) {
304 needRedraw = true;
305 ++curItemIdx;
307 return Eaten;
308 case Key.PageUp:
309 if (items.length) {
310 if (curItemIdx == topItemIdx) {
311 while (topItemIdx > 0 && lastFullVisible != curItemIdx) --topItemIdx;
313 needRedraw = true;
314 curItemIdx = topItemIdx;
316 return Eaten;
317 case Key.PageDown:
318 if (items.length) {
319 if (curItemIdx == lastFullVisible) {
320 while (lastFullVisible < items.length && topItemIdx != curItemIdx) ++topItemIdx;
322 needRedraw = true;
323 curItemIdx = lastFullVisible;
325 return Eaten;
326 case Key.Home:
327 curItemIdx = 0;
328 needRedraw = true;
329 return Eaten;
330 case Key.End:
331 if (items.length) {
332 curItemIdx = cast(int)items.length-1;
333 needRedraw = true;
335 return Eaten;
336 case Key.Enter:
337 return (items.length ? curItemIdx : Close);
338 default:
340 return NotMine;
343 int onMouse (MouseEvent event) {
344 int atItem = -1;
345 bool inside = false;
346 if (event.x >= leftX+Padding && event.x < leftX+Padding+maxW && event.y >= topY+Padding) {
347 normPage();
348 int ey = calcBotY-Padding;
349 if (event.y < ey) {
350 inside = true;
351 int y = topY+Padding;
352 if (hasTitle) y += menuTitle.h;
353 int idx = topItemIdx;
354 if (idx < 0) idx = 0;
355 while (idx < items.length) {
356 if (event.y >= y && event.y < y+items[idx].h) { atItem = idx; break; }
357 y += items[idx].h;
358 if (y >= ey) break;
359 ++idx;
363 if (hoverItem != atItem) {
364 hoverItem = atItem;
365 needRedraw = true;
367 switch (event.type) {
368 case MouseEventType.motion:
369 if (moving) {
370 leftX += event.dx;
371 topY += event.dy;
372 needRedraw = true;
374 break;
375 case MouseEventType.buttonPressed:
376 if (event.button == MouseButton.right) return Close;
377 if (event.button == MouseButton.left) {
378 //if (hoverItem >= 0 && hoverItem < items.length) return hoverItem;
379 if (hoverItem >= 0 && hoverItem < items.length) {
380 curItemIdx = hoverItem;
381 } else {
382 if (inside) {
383 moving = true;
384 needRedraw = true;
388 if (event.button == MouseButton.wheelUp) {
389 //if (topItemIdx > 0) { --topItemIdx; needRedraw = true; }
390 if (curItemIdx > 0) { --curItemIdx; needRedraw = true; }
392 if (event.button == MouseButton.wheelDown) {
393 //if (topItemIdx < items.length) { ++topItemIdx; needRedraw = true; }
394 if (curItemIdx < items.length) { ++curItemIdx; needRedraw = true; }
396 break;
397 case MouseEventType.buttonReleased:
398 if (event.button == MouseButton.left) {
399 //if (curItemIdx >= 0 && curItemIdx < items.length) return curItemIdx;
400 if (curItemIdx == hoverItem && curItemIdx >= 0 && curItemIdx < items.length) return curItemIdx;
401 moving = false;
402 needRedraw = true;
404 break;
405 default:
407 return NotMine;