text layouter is rewritten again (4th time); nothing is working yet
[xreader.git] / xreaderui.d
blob4096a68463e0fda4a61f418ccc8dc61ca670d784
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.color;
21 import arsd.png;
22 import arsd.jpeg;
24 import iv.nanovg;
25 import iv.nanovg.oui.blendish;
26 import iv.strex;
27 import iv.vfs;
28 import iv.vfs.io;
30 import xreadercfg;
33 // ////////////////////////////////////////////////////////////////////////// //
34 void loadFonts (NVGContext vg) {
35 void loadFont (ref int fid, string name, string path) {
36 fid = vg.createFont(name, path);
37 if (fid < 0) throw new Exception("can't load font '"~name~"' from '"~path~"'");
40 loadFont(textFont, "text", textFontName);
41 loadFont(textiFont, "texti", textiFontName);
42 loadFont(textbFont, "textb", textbFontName);
43 loadFont(textzFont, "textz", textzFontName);
45 loadFont(monoFont, "mono", monoFontName);
46 loadFont(monoiFont, "monoi", monoiFontName);
47 loadFont(monobFont, "monob", monobFontName);
48 loadFont(monozFont, "monoz", monozFontName);
50 loadFont(uiFont, "ui", uiFontName);
52 loadFont(galmapFont, "galmap", galmapFontName);
54 bndSetFont(uiFont);
58 // ////////////////////////////////////////////////////////////////////////// //
59 // cursor (hi, Death Track!)
60 private enum curWidth = 17;
61 private enum curHeight = 23;
62 private static immutable ubyte[curWidth*curHeight] curImg = [
63 0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
64 0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,
65 1,0,3,2,2,0,0,0,0,0,0,0,0,0,0,0,0,
66 1,1,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0,
67 1,1,3,3,4,2,2,0,0,0,0,0,0,0,0,0,0,
68 1,1,3,3,4,4,2,2,0,0,0,0,0,0,0,0,0,
69 1,1,3,3,4,4,4,2,2,0,0,0,0,0,0,0,0,
70 1,1,3,3,4,4,4,4,2,2,0,0,0,0,0,0,0,
71 1,1,3,3,4,4,4,5,6,2,2,0,0,0,0,0,0,
72 1,1,3,3,4,4,5,6,7,5,2,2,0,0,0,0,0,
73 1,1,3,3,4,5,6,7,5,4,5,2,2,0,0,0,0,
74 1,1,3,3,5,6,7,5,4,5,6,7,2,2,0,0,0,
75 1,1,3,3,6,7,5,4,5,6,7,7,7,2,2,0,0,
76 1,1,3,3,7,5,4,5,6,7,7,7,7,7,2,2,0,
77 1,1,3,3,5,4,5,6,8,8,8,8,8,8,8,8,2,
78 1,1,3,3,4,5,6,3,8,8,8,8,8,8,8,8,8,
79 1,1,3,3,5,6,3,3,1,1,1,1,1,1,1,0,0,
80 1,1,3,3,6,3,3,1,1,1,1,1,1,1,1,0,0,
81 1,1,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,
82 1,1,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,
83 1,1,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,
84 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
85 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
87 struct CC { ubyte r, g, b, a; }
88 private static immutable CC[9] curPal = [
89 CC( 0, 0, 0, 0),
90 CC( 0, 0, 0,127),
91 CC( 85,255,255,255),
92 CC( 85, 85,255,255),
93 CC(255, 85, 85,255),
94 CC(170, 0,170,255),
95 CC( 85, 85, 85,255),
96 CC( 0, 0, 0,255),
97 CC( 0, 0,170,255),
101 int createCursorImage (NVGContext vg) {
102 auto img = new ubyte[](curWidth*curHeight*4);
103 scope(exit) img.destroy;
104 const(ubyte)* s = curImg.ptr;
105 auto d = img.ptr;
106 foreach (immutable _; 0..curWidth*curHeight) {
107 auto c = curPal.ptr[*s++];
108 *d++ = c.r;
109 *d++ = c.g;
110 *d++ = c.b;
111 *d++ = c.a;
113 return vg.createImageRGBA(curWidth, curHeight, NVGImageFlags.None, img[]);
117 // ////////////////////////////////////////////////////////////////////////// //
118 final class PopupMenu {
119 //enum LeftX = 10;
120 //enum TopY = 10;
121 enum Padding = 8;
123 static struct SecInfo {
124 int w, h;
125 dstring s;
128 bool hasTitle;
129 SecInfo secTitle;
130 SecInfo[] sections;
131 int curSectionNum = 0;
132 int topSectionNum = 0;
133 int hoverItem = -1;
134 NVGContext vg;
135 int maxW; // maximum text width
136 bool needRedraw;
137 int leftX, topY;
138 bool moving;
140 void textSize (dstring s, out int tw, out int th) {
141 auto w = vg.bndLabelWidth(-1, s)+4;
142 if (w > GWidth-Padding*4) w = GWidth-Padding*4;
143 auto h = vg.bndLabelHeight(-1, s, w);
144 if (h < BND_WIDGET_HEIGHT) h = BND_WIDGET_HEIGHT;
145 tw = cast(int)w;
146 th = cast(int)h;
149 this (NVGContext avg, dstring atitle, scope dstring[] delegate () buildSections) {
150 assert(avg !is null);
151 vg = avg;
152 vg.fontFaceId(uiFont);
153 vg.textAlign(NVGAlign.Left|NVGAlign.Baseline);
154 vg.fontSize(fsizeUI);
155 maxW = 0;
156 if (atitle !is null) {
157 hasTitle = true;
158 sections.length += 1;
159 sections[0].s = atitle;
160 textSize(atitle, sections[0].w, sections[0].h);
161 maxW = sections[0].w;
163 if (buildSections !is null) {
164 auto list = buildSections();
165 auto cpos = sections.length;
166 sections.length += list.length;
167 foreach (dstring s; list) {
168 sections[cpos].s = s;
169 textSize(s, sections[cpos].w, sections[cpos].h);
170 if (maxW < sections[cpos].w) maxW = sections[cpos].w;
171 ++cpos;
174 if (hasTitle) {
175 secTitle = sections[0];
176 sections = sections[1..$];
178 leftX = (GWidth-maxW-Padding*4)/2;
179 topY = Padding;
180 int ey = calcBotY;
181 //topY = (GHeight-(ey-topY))/2;
182 topY = Padding;
183 needRedraw = true;
186 int lastFullVisible () {
187 if (sections.length == 0) return 0;
188 int res = topSectionNum;
189 int y = topY+Padding+secTitle.h;
190 int idx = topSectionNum;
191 if (idx < 0) idx = 0;
192 int ey = calcBotY-Padding;
193 while (idx < sections.length) {
194 y += sections[idx].h;
195 if (y > ey) return res;
196 res = idx;
197 ++idx;
199 return cast(int)sections.length-1;
202 int calcBotY () {
203 int y = topY+Padding*2+secTitle.h;
204 foreach (const ref sc; sections) {
205 int ny = y+sc.h;
206 if (ny > GHeight-Padding*2) return y;
207 y = ny;
209 return y;
212 void normPage () {
213 if (curSectionNum < 0) curSectionNum = 0;
214 if (curSectionNum >= sections.length) curSectionNum = cast(int)sections.length-1;
215 if (sections.length) {
216 // make current item visible
217 if (curSectionNum >= 0 && curSectionNum < sections.length) {
218 if (curSectionNum < topSectionNum) {
219 topSectionNum = curSectionNum;
220 } else {
221 //FIXME: make this faster!
222 //writeln("top=", topSectionNum, "; cur=", curSectionNum, "; lv=", lastFullVisible);
223 while (lastFullVisible < curSectionNum) {
224 if (topSectionNum >= sections.length-1) break;
225 ++topSectionNum;
230 while (topSectionNum > 0) {
231 int lv = lastFullVisible;
232 --topSectionNum;
233 int lv1 = lastFullVisible;
234 if (lv1 != lv) { ++topSectionNum; break; }
238 void draw () {
239 vg.save();
240 scope(exit) vg.restore();
241 if (moving) vg.globalAlpha(0.8);
242 vg.fontFaceId(uiFont);
243 vg.textAlign(NVGAlign.Left|NVGAlign.Baseline);
244 vg.fontSize(fsizeUI);
245 normPage();
246 int ey = calcBotY;
247 vg.bndMenuBackground(leftX, topY, maxW+Padding*2, ey-topY, BND_CORNER_NONE);
248 int y = topY+Padding;
249 vg.scissor(leftX+Padding, y, maxW, ey-Padding);
250 if (hasTitle) {
251 vg.bndMenuLabel(leftX+Padding, y, maxW, secTitle.h, -1, secTitle.s);
252 y += secTitle.h;
254 int idx = topSectionNum;
255 if (idx < 0) idx = 0;
256 ey -= Padding;
257 while (idx < sections.length) {
258 vg.bndMenuItem(leftX+Padding, y, maxW, sections[idx].h,
259 (curSectionNum == idx ? BND_ACTIVE : hoverItem == idx ? BND_HOVER : BND_DEFAULT),
260 -1, sections[idx].s);
261 y += sections[idx].h;
262 if (y >= ey) break;
263 ++idx;
265 needRedraw = false;
268 enum int Close = -666;
269 enum int Eaten = -2;
270 enum int NotMine = -1;
272 int onKey (KeyEvent event) {
273 if (!event.pressed) return NotMine;
274 switch (event.key) {
275 case Key.Escape:
276 return Close;
277 case Key.Up:
278 if (curSectionNum > 0) {
279 needRedraw = true;
280 --curSectionNum;
282 return Eaten;
283 case Key.Down:
284 if (curSectionNum+1 < sections.length) {
285 needRedraw = true;
286 ++curSectionNum;
288 return Eaten;
289 case Key.PageUp:
290 if (sections.length) {
291 if (curSectionNum == topSectionNum) {
292 while (topSectionNum > 0 && lastFullVisible != curSectionNum) --topSectionNum;
294 needRedraw = true;
295 curSectionNum = topSectionNum;
297 return Eaten;
298 case Key.PageDown:
299 if (sections.length) {
300 if (curSectionNum == lastFullVisible) {
301 while (lastFullVisible < sections.length && topSectionNum != curSectionNum) ++topSectionNum;
303 needRedraw = true;
304 curSectionNum = lastFullVisible;
306 return Eaten;
307 case Key.Home:
308 curSectionNum = 0;
309 needRedraw = true;
310 return Eaten;
311 case Key.End:
312 if (sections.length) {
313 curSectionNum = cast(int)sections.length-1;
314 needRedraw = true;
316 return Eaten;
317 case Key.Enter:
318 return (sections.length ? curSectionNum : Close);
319 default:
321 return NotMine;
324 int onMouse (MouseEvent event) {
325 int atItem = -1;
326 bool inside = false;
327 if (event.x >= leftX+Padding && event.x < leftX+Padding+maxW && event.y >= topY+Padding) {
328 normPage();
329 int ey = calcBotY-Padding;
330 if (event.y < ey) {
331 inside = true;
332 int y = topY+Padding;
333 if (hasTitle) y += secTitle.h;
334 int idx = topSectionNum;
335 if (idx < 0) idx = 0;
336 while (idx < sections.length) {
337 if (event.y >= y && event.y < y+sections[idx].h) { atItem = idx; break; }
338 y += sections[idx].h;
339 if (y >= ey) break;
340 ++idx;
344 if (hoverItem != atItem) {
345 hoverItem = atItem;
346 needRedraw = true;
348 switch (event.type) {
349 case MouseEventType.motion:
350 if (moving) {
351 leftX += event.dx;
352 topY += event.dy;
353 needRedraw = true;
355 break;
356 case MouseEventType.buttonPressed:
357 if (event.button == MouseButton.right) return Close;
358 if (event.button == MouseButton.left) {
359 //if (hoverItem >= 0 && hoverItem < sections.length) return hoverItem;
360 if (hoverItem >= 0 && hoverItem < sections.length) {
361 curSectionNum = hoverItem;
362 } else {
363 if (inside) {
364 moving = true;
365 needRedraw = true;
369 if (event.button == MouseButton.wheelUp) {
370 //if (topSectionNum > 0) { --topSectionNum; needRedraw = true; }
371 if (curSectionNum > 0) { --curSectionNum; needRedraw = true; }
373 if (event.button == MouseButton.wheelDown) {
374 //if (topSectionNum < sections.length) { ++topSectionNum; needRedraw = true; }
375 if (curSectionNum < sections.length) { ++curSectionNum; needRedraw = true; }
377 break;
378 case MouseEventType.buttonReleased:
379 if (event.button == MouseButton.left) {
380 //if (curSectionNum >= 0 && curSectionNum < sections.length) return curSectionNum;
381 if (curSectionNum == hoverItem && curSectionNum >= 0 && curSectionNum < sections.length) return curSectionNum;
382 moving = false;
383 needRedraw = true;
385 break;
386 default:
388 return NotMine;