1 module xreader
/*is aliced*/;
6 import std
.concurrency
;
11 import arsd
.simpledisplay
;
17 import iv
.nanovg
.oui
.blendish
;
18 //import iv.nanovg.svg;
19 import iv
.nanovg
.perf
;
29 // ////////////////////////////////////////////////////////////////////////// //
30 __gshared string RcDir
= "~/.xreader";
31 __gshared string stateFileName
;
33 __gshared
int rotx
, rotz
;
34 __gshared
bool showShip
= false;
37 // ////////////////////////////////////////////////////////////////////////// //
38 __gshared string textFontName
= "~/ttf/ms/arial.ttf:noaa";
39 __gshared string epiFontName
= "~/ttf/ms/ariali.ttf:noaa";
40 __gshared string uiFontName
= "~/ttf/ms/verdana.ttf:noaa";
41 __gshared
int GWidth
= 900;
42 __gshared
int GHeight
= 1024;
43 __gshared
int fSize
= 24;
44 __gshared
bool sbLeft
= false;
45 __gshared
bool interAllowed
= false;
47 __gshared
bool flagNanoAA
= false;
48 __gshared
bool flagNanoSS
= false;
50 __gshared
uint colorDim
= 0;
51 __gshared
uint colorBack
= 0x2a2a2a;
52 __gshared
uint colorText
= 0xff7f00;
53 __gshared
uint colorTextHi
= 0xffff00;
56 // ////////////////////////////////////////////////////////////////////////// //
76 // ////////////////////////////////////////////////////////////////////////// //
77 VFile
fwritef(A
...) (VFile fl
, string fmt
, /*lazy*/ A args
) {
78 import std
.string
: format
;
79 auto s
= format(fmt
, args
);
80 if (s
.length
) fl
.rawWriteExact(s
[]);
85 // ////////////////////////////////////////////////////////////////////////// //
90 fl
= VFile(buildPath(RcDir
, ".config.ui"));
93 try { import std
.file
; mkdirRecurse(RcDir
); } catch (Exception
) {}
94 fl
= VFile(buildPath(RcDir
, ".config.ui"), "w");
95 fl
.fwritef("width=%s\n", GWidth
);
96 fl
.fwritef("height=%s\n", GHeight
);
97 fl
.fwritef("font-text=%s\n", textFontName
);
98 fl
.fwritef("font-epi=%s\n", epiFontName
);
99 fl
.fwritef("font-ui=%s\n", uiFontName
);
100 fl
.fwritef("text-size=%s\n", fSize
);
101 fl
.fwritef("scrollbar-on-left=%s\n", sbLeft
);
102 fl
.fwritef("interference=%s\n", interAllowed
);
103 fl
.fwritef("color-dim=0x%08x\n", colorDim
);
104 fl
.fwritef("color-back=0x%08x\n", colorBack
);
105 fl
.fwritef("color-text=0x%08x\n", colorText
);
106 fl
.fwritef("color-text-hi=0x%08x\n", colorTextHi
);
107 fl
.fwritef("nano-aa=%s\n", flagNanoAA
);
108 fl
.fwritef("nano-ss=%s\n", flagNanoSS
);
109 } catch (Exception
) {}
113 "font-text", &textFontName
,
114 "font-ui", &uiFontName
,
118 "scrollbar-on-left", &sbLeft
,
119 "interference", &interAllowed
,
120 "color-dim", &colorDim
,
121 "color-back", &colorBack
,
122 "color-text", &colorText
,
123 "color-text-hi", &colorTextHi
,
124 "nano-aa", &flagNanoAA
,
125 "nano-ss", &flagNanoSS
,
130 // ////////////////////////////////////////////////////////////////////////// //
131 bool isComment (const(char)[] s
) {
132 while (s
.length
&& s
.ptr
[0] <= ' ') s
= s
[1..$];
133 return (s
.length
> 0 && s
.ptr
[0] == '#');
137 // ////////////////////////////////////////////////////////////////////////// //
138 shared int formatWorks
= -1;
139 //__gshared NVGContext vg = null;
140 __gshared
int textFontF
, epiFontF
, uiFontF
;
143 struct ReformatWork
{
144 shared BookText booktext
;
146 shared NVGContext fvg
;
149 struct ReformatWorkDone
{
152 shared(TextLine
[]) lines
;
159 void reformatThreadFn (Tid ownerTid
) {
162 void textWH (const(char)[] text
, TextLine
.Text type
, out int wdt
, out int hgt
) {
164 final switch (type
) {
165 case TextLine
.Text
.Normal
: vg
.fontFaceId(textFontF
); break;
166 case TextLine
.Text
.Heading
: vg
.fontFaceId(textFontF
); break;
167 case TextLine
.Text
.Epigraph
: vg
.fontFaceId(epiFontF
); break;
169 //vg.fontFaceId(textFont);
171 vg
.textBounds(0, 0, text
, bounds
[]);
172 wdt
= cast(int)(bounds
[2]-bounds
[0]);
173 hgt
= cast(int)(bounds
[3]-bounds
[1]);
188 if (!doQuit
&& cw
.w
> 0 && cw
.h
> 0 && cw
.booktext
!is null) {
189 int maxWidth
= cw
.w
-4-2-BND_SCROLLBAR_WIDTH
-2;
190 vg
= cast(NVGContext
)cw
.fvg
;
191 auto stt
= MonoTime
.currTime
;
192 auto lines
= TextLine
.format(cast()cw
.booktext
, maxWidth
, &textWH
);
193 writeln("reformatted in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds");
195 auto res
= ReformatWorkDone(cw
.w
, cw
.h
, cast(shared)lines
);
199 send(ownerTid
, QuitWork());
203 void run (BookText booktext
) {
206 static struct Interference
{
210 NVGContext vg
= null;
211 NVGContext fvg
= null;
213 bool fpsVisible
= false;
215 if (GWidth
< 32) GWidth
= 32;
216 if (GHeight
< 32) GHeight
= 32;
218 int textFont
, epiFont
, uiFont
;
227 import std
.algorithm
: endsWith
;
228 import std
.path
: expandTilde
;
231 string efn
= "textepi";
232 if (textFontName
.endsWith(":noaa")) { textFontName
= textFontName
[0..$-5]; tfn
~= ":noaa"; }
233 if (epiFontName
.endsWith(":noaa")) { epiFontName
= epiFontName
[0..$-5]; efn
~= ":noaa"; }
234 if (uiFontName
.endsWith(":noaa")) { uiFontName
= uiFontName
[0..$-5]; ufn
~= ":noaa"; }
236 textFont
= vg
.createFont(tfn
, textFontName
.expandTilde
);
237 if (textFont
< 0) throw new Exception("can't load text font: '"~textFontName
~"'");
238 epiFont
= vg
.createFont(tfn
, epiFontName
.expandTilde
);
239 if (epiFont
< 0) throw new Exception("can't load epi font: '"~epiFontName
~"'");
240 uiFont
= vg
.createFont(ufn
, uiFontName
.expandTilde
);
241 if (uiFont
< 0) throw new Exception("can't load text font: '"~uiFontName
~"'");
244 textFontF
= fvg
.createFont(tfn
, textFontName
.expandTilde
);
245 if (textFontF
< 0) throw new Exception("can't load text font: '"~textFontName
~"'");
246 epiFontF
= fvg
.createFont(tfn
, epiFontName
.expandTilde
);
247 if (epiFontF
< 0) throw new Exception("can't load epi font: '"~epiFontName
~"'");
248 uiFontF
= fvg
.createFont(ufn
, uiFontName
.expandTilde
);
249 if (uiFontF
< 0) throw new Exception("can't load text font: '"~uiFontName
~"'");
254 //setOpenGLContextVersion(3, 2); // up to GLSL 150
255 setOpenGLContextVersion(2, 0); // it's enough
256 //openGLContextCompatible = false;
258 //writeln(booktext.title~" -- "~booktext.authorFirst~" "~booktext.authorLast);
259 auto sdwindow
= new SimpleWindow(GWidth
, GHeight
, booktext
.title
~" -- "~booktext
.authorFirst
~" "~booktext
.authorLast
, OpenGlOptions
.yes
, Resizablity
.allowResizing
);
260 //sdwindow.hideCursor();
261 //scope(exit) sdwindow.close();
263 auto stt
= MonoTime
.currTime
;
264 auto prevt
= MonoTime
.currTime
;
266 float dt = 0, secs
= 0;
267 //int mxOld = -1, myOld = -1;
271 int textHeight
= GHeight
-8;
273 bool doSaveCheck
= false;
274 MonoTime nextSaveTime
;
275 bool needRedraw
= true;
278 bool newYFade
= false;
279 MonoTime nextFadeTime
;
282 bool firstFormat
= true;
284 auto childTid
= spawn(&reformatThreadFn
, thisTid
);
287 foreach (ref l
; ifs
) {
288 if (l
.speed
== 0) continue;
292 if (l
.y
< l
.ey
) l
.speed
= 0;
294 if (l
.y
> l
.ey
) l
.speed
= 0;
300 foreach (ref l
; ifs
) {
301 if (l
.speed
== 0) continue;
303 vg
.fillColor(nvgRGB(0, 40, 0));
304 vg
.rect(0, l
.y
-1, GWidth
, 3);
310 import std
.random
: uniform
;
313 while (idx
< ifs
.length
&& ifs
[idx
].speed
!= 0) ++idx
;
314 if (idx
>= ifs
.length
) {
315 if (ifs
.length
> 10) return;
316 ifs
~= Interference();
318 if (uniform
!"[]"(0, 1) == 0) {
320 ifs
[idx
].y
= GHeight
/2+uniform
!"[]"(50, GHeight
/2-100);
321 int ty
= ifs
[idx
].y
-40;
323 ifs
[idx
].ey
= uniform
!"[]"(0, ty
);
324 ifs
[idx
].speed
= -uniform
!"[]"(1, 25);
327 ifs
[idx
].y
= GHeight
/2-uniform
!"[]"(50, GHeight
/2-100);
328 int ty
= ifs
[idx
].y
+40;
329 if (ty
>= GHeight
) return;
330 ifs
[idx
].ey
= uniform
!"[]"(ty
, GHeight
);
331 ifs
[idx
].speed
= uniform
!"[]"(1, 25);
337 void doSaveState (bool forced
=false) {
339 if (atomicLoad(formatWorks
) != 0) return;
340 if (!doSaveCheck
) return;
341 auto ct
= MonoTime
.currTime
;
342 if (ct
< nextSaveTime
) return;
344 if (lines
.length
== 0) return;
347 auto fo
= File(stateFileName
, "w");
349 auto tl
= (topLine
< 0 ?
0 : (topLine
>= lines
.length ?
cast(int)lines
.length
-1 : topLine
));
350 fo
.writeln("section=", lines
[tl
].sec
);
351 fo
.writeln("paragraph=", lines
[tl
].par
);
352 fo
.writeln("word=", lines
[tl
].word
);
354 } catch (Exception
) {}
358 void stateChanged () {
361 nextSaveTime
= MonoTime
.currTime
+10.seconds
;
365 void hardScrollBy (int delta
) {
366 if (delta
== 0 || lines
.length
== 0) return;
368 if (topLine
>= lines
.length
) topLine
= cast(int)lines
.length
-1;
369 if (topLine
< 0) topLine
= 0;
370 //curHeight = lines[topLine].y;
375 if (topLine
< 0) { topYOfs
= 0; topLine
= 0; return; }
378 if (delta
<= topYOfs
) { topYOfs
-= delta
; return; }
382 while (delta
> 0 && topLine
> 0) {
383 int sc
= lines
.ptr
[topLine
].height
;
394 if (topLine
>= lines
.length
) { topYOfs
= 0; return; }
397 while (delta
> 0 && topLine
< lines
.length
-1) {
398 int sc
= lines
.ptr
[topLine
].height
;
410 void scrollBy (int delta
) {
411 if (delta
== 0 || lines
.length
== 0) return;
417 int newY = lines[topLine].y+lines[topLine].height/2;
420 while (n > 0 && ln < lines.length) {
421 if ((n -= lines[ln].height) <= 0) break;
422 newY += lines[ln].height;
426 //if (ln < lines.length) assert(newY >= lines[ln].y && newY < lines[ln].y+lines[ln].height);
431 int newY
= lines
[topLine
].y
+lines
[topLine
].height
/2;
434 while (n
> 0 && ln
< lines
.length
) {
435 if ((n
-= lines
[ln
].height
) <= 0) break;
436 newY
+= lines
[ln
].height
;
440 if (ln
< lines
.length
) {
441 //assert(newY == lines[ln].y);
442 //newY += lines[ln].height/2;
448 if (topLine
== 0 && topYOfs
== 0) return;
455 void goTo (int osec
, int opar
, int oidx
) {
456 //writeln("goto: sec=", osec, "; par=", opar, "; word=", oidx);
459 while (topLine
< lines
.length
) {
460 if (lines
[topLine
].sec
== osec
&& lines
[topLine
].par
== opar
) {
463 while (topLine
< lines
.length
&& lines
[topLine
].sec
== osec
&& lines
[topLine
].par
== opar
&& lines
[topLine
].word
< oidx
) ++topLine
;
464 if (topLine
> pl
&& topLine
< lines
.length
&& lines
[topLine
].sec
== osec
&& lines
[topLine
].par
== opar
) {
465 if (lines
[topLine
].word
> oidx
) --topLine
;
482 int sec
= -1, par
= -1, word
= -1;
483 xiniParse(VFile(stateFileName
),
488 if (sec
>= 0 && par
>= 0 && word
>= 0) goTo(sec
, par
, word
);
489 } catch (Exception
) {}
492 // reformat, and fix position
494 vg
.fontFaceId(textFont
);
497 //atomicStore(reformatComplete, false);
498 if (atomicLoad(formatWorks
) < 0) {
499 atomicStore(formatWorks
, 1);
501 atomicOp
!"+="(formatWorks
, 1);
504 childTid
.send(ReformatWork(cast(shared)booktext
, GWidth
, GHeight
, cast(shared)fvg
));
507 void formatComplete (in ref ReformatWorkDone w
) {
508 if (doQuit ||
atomicLoad(formatWorks
) <= 0) return;
509 atomicOp
!"-="(formatWorks
, 1);
512 if (atomicLoad(formatWorks
) == 0) reformat();
516 if (atomicLoad(formatWorks
) != 0) return;
518 if (!firstFormat
&& topLine
>= 0 && topLine
< lines
.length
) {
519 int osec
= lines
[topLine
].sec
;
520 int opar
= lines
[topLine
].par
;
521 int oidx
= lines
[topLine
].word
;
522 lines
= cast(TextLine
[])w
.lines
;
523 goTo(osec
, opar
, oidx
);
524 } else if (firstFormat
) {
525 lines
= cast(TextLine
[])w
.lines
;
532 lines
= cast(TextLine
[])w
.lines
;
533 if (otl
&& lines
.length
) topLine
= cast(int)lines
.length
-1; else topLine
= 0;
536 if (topLine
>= lines
.length
) topLine
= cast(int)(lines
.length ? lines
.length
-1 : 0);
539 totalHeight
= lines
[$-1].y
;
540 //curHeight = lines[topLine].y;
542 if (totalHeight
< 1) totalHeight
= 1;
547 sdwindow
.closeQuery
= delegate () { doQuit
= true; };
549 void closeWindow () {
550 doSaveState(true); // forced state save
551 if (!sdwindow
.closed
&& vg
!is null) {
552 //vg.deleteImage(vgimg);
560 sdwindow
.visibleForTheFirstTime
= delegate () {
561 sdwindow
.setAsCurrentOpenGlContext(); // make this window active
562 sdwindow
.vsync
= false;
563 //sdwindow.useGLFinish = false;
564 //glbindLoadFunctions();
567 uint flags
= NVG_DEBUG
;
568 if (flagNanoAA
) flags |
= NVG_ANTIALIAS
;
569 if (flagNanoSS
) flags |
= NVG_STENCIL_STROKES
;
570 vg
= createGL2NVG(flags
);
573 writeln("Could not init nanovg.");
577 fvg
= createGL2NVG(flags
);
580 writeln("Could not init nanovg.");
585 fps
= new PerfGraph("Frame Time", PerfGraph
.Style
.FPS
, "ui");
586 } catch (Exception e
) {
587 import std
.stdio
: stderr
;
588 stderr
.writeln("ERROR: ", e
.msg
);
597 sdwindow
.redrawOpenGlScene();
601 sdwindow
.windowResized
= delegate (int w
, int h
) {
602 //writeln("w=", w, "; h=", h);
603 glViewport(0, 0, w
, h
);
606 textHeight
= GHeight
-8;
610 string
[] sectionNames
;
612 void cacheSectionNames () {
613 if (sectionNames
.length
!= booktext
.secs
.length
) {
614 sectionNames
.length
= 0;
615 sectionNames
.reserve(booktext
.secs
.length
);
616 foreach (BookText
.Section sec
; booktext
.secs
) {
618 if (sec
.title
.length
) {
619 foreach (string w
; sec
.title
[0].words
) {
620 bool addSpace
= (s
.length
> 0);
621 if (addSpace
&& s
[$-1] == '-') {
622 if (s
.length
< 2 || s
[$-2] == '-') addSpace
= false;
624 if (addSpace
) s
~= ' ';
633 bool inSectionMenu
= false;
634 int curSectionNum
= -1;
635 int topSectionNum
= 0;
636 int maxSectionsPerPage
= 0;
638 void drawSectionMenu () {
639 int mx
= 10, my
= 10;
640 vg
.fontFaceId(uiFont
);
641 vg
.textAlign(NVGAlign
.Left|NVGAlign
.Baseline
);
646 if (curSectionNum
< 0) {
647 // find current section
649 if (topLine
>= 0 && topLine
< lines
.length
) {
650 curSectionNum
= lines
[topLine
].sec
;
653 bool noMoreH
= false;
654 foreach (string s
; sectionNames
) {
655 auto bw
= vg
.bndLabelWidth(-1, s
);
658 h
+= BND_WIDGET_HEIGHT
;
660 if (h
+BND_WIDGET_HEIGHT
+16 >= GHeight
-my
) noMoreH
= true;
663 maxSectionsPerPage
= maxSects
;
664 // make current item visible
665 if (curSectionNum
>= 0 && curSectionNum
< sectionNames
.length
) {
666 if (curSectionNum
< topSectionNum
) {
667 topSectionNum
= curSectionNum
;
668 } else if (topSectionNum
+maxSects
<= curSectionNum
) {
669 topSectionNum
= curSectionNum
-maxSects
+1;
670 if (topSectionNum
< 0) topSectionNum
= 0;
673 vg
.bndMenuBackground(mx
, my
, w
+16, h
+16, BND_CORNER_NONE
);
675 foreach (immutable idx
; 0..maxSects
) {
676 int sn
= cast(int)(topSectionNum
+idx
);
678 if (sn
>= sectionNames
.length
) break;
679 vg
.bndMenuItem(mx
+8, my
+h
, w
, BND_WIDGET_HEIGHT
, (curSectionNum
== sn ? BND_ACTIVE
: BND_DEFAULT
), -1, sectionNames
.ptr
[sn
]);
681 h
+= BND_WIDGET_HEIGHT
;
685 void pushPosition () {
686 if (lines
.length
== 0 || topLine
< 0 || topLine
>= lines
.length
) return;
694 void popPosition () {
695 if (posstack
.length
== 0) return;
696 auto pos
= posstack
[$-1];
697 posstack
.length
-= 1;
698 posstack
.assumeSafeAppend
;
699 goTo(pos
.sec
, pos
.par
, pos
.word
);
702 void gotoSection (int sn
) {
703 if (sn
< 0 || sn
>= booktext
.secs
.length
) return;
704 foreach (immutable idx
, TextLine line
; lines
) {
705 if (line
.sec
== sn
) {
707 topLine
= cast(int)idx
;
715 bool sectionMenuKey (KeyEvent event
) {
716 if (atomicLoad(formatWorks
) != 0) return false;
717 if (!inSectionMenu
) return false;
718 if (!event
.pressed
) return false;
721 inSectionMenu
= false;
725 if (booktext
.secs
.length
) {
727 if (curSectionNum
< 0) curSectionNum
= cast(int)booktext
.secs
.length
-1;
732 if (booktext
.secs
.length
) {
734 if (curSectionNum
>= booktext
.secs
.length
) curSectionNum
= 0;
739 if (booktext
.secs
.length
) {
740 if (maxSectionsPerPage
> 1) curSectionNum
-= maxSectionsPerPage
-1; else curSectionNum
= 0;
741 if (curSectionNum
< 0) curSectionNum
= 0;
746 if (booktext
.secs
.length
) {
747 if (maxSectionsPerPage
> 1) curSectionNum
+= maxSectionsPerPage
-1; else curSectionNum
= cast(int)booktext
.secs
.length
-1;
748 if (curSectionNum
>= booktext
.secs
.length
) curSectionNum
= cast(int)booktext
.secs
.length
-1;
757 if (booktext
.secs
.length
) {
758 curSectionNum
= cast(int)booktext
.secs
.length
-1;
763 inSectionMenu
= false;
765 gotoSection(curSectionNum
);
772 bool quitMenuYes
= true;
773 bool inQuitMenu
= false;
775 void drawQuitMenu () {
776 int mx
= 10, my
= 10;
777 vg
.fontFaceId(uiFont
);
778 vg
.textAlign(NVGAlign
.Left|NVGAlign
.Baseline
);
780 float w
= 0, h
= BND_WIDGET_HEIGHT
*3;
782 auto bw
= vg
.bndLabelWidth(-1, "Quit?");
786 auto bw
= vg
.bndLabelWidth(-1, "Yes");
790 auto bw
= vg
.bndLabelWidth(-1, "No");
793 vg
.bndMenuBackground(mx
, my
, w
+16, h
+16, BND_CORNER_NONE
);
795 vg
.bndMenuItem(mx
+8, my
+8+BND_WIDGET_HEIGHT
*0, w
, BND_WIDGET_HEIGHT
, BND_DEFAULT
, -1, "Quit?");
796 vg
.bndMenuItem(mx
+8, my
+8+BND_WIDGET_HEIGHT
*1, w
, BND_WIDGET_HEIGHT
, (quitMenuYes ? BND_ACTIVE
: BND_DEFAULT
), -1, "Yes");
797 vg
.bndMenuItem(mx
+8, my
+8+BND_WIDGET_HEIGHT
*2, w
, BND_WIDGET_HEIGHT
, (!quitMenuYes ? BND_ACTIVE
: BND_DEFAULT
), -1, "No");
800 bool quitMenuKey (KeyEvent event
) {
801 if (atomicLoad(formatWorks
) != 0) return false;
802 if (!inQuitMenu
) return false;
803 if (!event
.pressed
) return false;
811 quitMenuYes
= !quitMenuYes
;
814 case Key
.Home
: case Key
.PageUp
:
818 case Key
.End
: case Key
.PageDown
:
825 if (quitMenuYes
) doQuit
= true;
834 bool readerKey (KeyEvent event
) {
835 if (atomicLoad(formatWorks
) != 0) return false;
836 if (!event
.pressed
) {
838 case Key
.Up
: arrowDir
= 0; return true;
839 case Key
.Down
: arrowDir
= 0; return true;
846 if (event
.modifierState
&ModifierState
.shift
) {
847 //goto case Key.PageUp;
848 scrollBy(-textHeight
/3*2);
850 //goto case Key.PageDown;
851 scrollBy(textHeight
/3*2);
858 hardScrollBy(toMove
); toMove
= 0;
859 hardScrollBy(-(textHeight
> 32 ? textHeight
-32 : textHeight
));
862 hardScrollBy(toMove
); toMove
= 0;
863 hardScrollBy(textHeight
> 32 ? textHeight
-32 : textHeight
);
881 bool controlKey (KeyEvent event
) {
882 if (!event
.pressed
) return false;
885 if (showShip
) { showShip
= false; needRedraw
= true; return true; }
886 if (inQuitMenu
) { inQuitMenu
= false; needRedraw
= true; return true; }
887 if (inSectionMenu
) { inSectionMenu
= false; needRedraw
= true; return true; }
892 case Key
.P
: if (event
.modifierState
== ModifierState
.ctrl
) { fpsVisible
= !fpsVisible
; needRedraw
= true; return true; } break;
893 case Key
.I
: if (event
.modifierState
== ModifierState
.ctrl
) { interAllowed
= !interAllowed
; needRedraw
= true; return true; } break;
894 case Key
.N
: if (event
.modifierState
== ModifierState
.ctrl
) { sbLeft
= !sbLeft
; needRedraw
= true; return true; } break;
895 case Key
.C
: if (event
.modifierState
== ModifierState
.ctrl
) { addIf(); return true; } break;
896 case Key
.E
: if (event
.modifierState
== ModifierState
.ctrl
) { showShip
= !showShip
; needRedraw
= true; return true; } break;
897 case Key
.Q
: if (event
.modifierState
== ModifierState
.ctrl
) { doQuit
= true; return true; } break;
898 case Key
.B
: if (event
.modifierState
== ModifierState
.ctrl
) { pushPosition(); return true; } break;
900 if (!showShip
&& !inQuitMenu
) {
901 inSectionMenu
= !inSectionMenu
;
902 if (inSectionMenu
) curSectionNum
= -1; // move to current
911 sdwindow
.redrawOpenGlScene
= delegate () {
915 curt
= MonoTime
.currTime
;
916 secs
= cast(double)((curt
-stt
).total
!"msecs")/1000.0;
917 dt = cast(double)((curt
-prevt
).total
!"msecs")/1000.0;
919 //glClearColor(0, 0, 0, 0);
920 //glClearColor(0.18, 0.18, 0.18, 0);
922 cast(float)((colorBack
>>16)&0xff)/255.0f,
923 cast(float)((colorBack
>>8)&0xff)/255.0f,
924 cast(float)(colorBack
&0xff)/255.0f,
927 glClear(glNVGClearFlags
);
930 if (fps
!is null) fps
.update(dt);
931 vg
.beginFrame(GWidth
, GHeight
, 1);
935 if (true/*atomicLoad(formatWorks) == 0*/) {
937 int curHeight
= (topLine
> 0 && topLine
< lines
.length ? lines
[topLine
].y
: 0)+topYOfs
;
938 float sz
= cast(float)(GHeight
-4)/cast(float)totalHeight
;
939 if (sz
> 1) sz
= 1; else if (sz
< 0.05) sz
= 0.05;
940 int sx
= (sbLeft ?
2 : GWidth
-BND_SCROLLBAR_WIDTH
-2);
941 vg
.bndScrollBar(sx
, 2, BND_SCROLLBAR_WIDTH
, GHeight
-4, BND_DEFAULT
, cast(float)curHeight
/cast(float)totalHeight
, sz
);
944 vg
.fontFaceId(textFont
);
945 vg
.textAlign(NVGAlign
.Left|NVGAlign
.Top
);
947 vg
.fillColor(u2c(colorText
));
948 int y
= -topYOfs
, drawY
= (GHeight
-textHeight
)/2, linenum
= topLine
;
949 vg
.intersectScissor((sbLeft ?
2+BND_SCROLLBAR_WIDTH
+2 : 4), drawY
, GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2, textHeight
);
951 while (y
< textHeight
&& linenum
< lines
.length
) {
953 //int curY = lines[linenum].y;
954 if (/*newY >= curY && newY < curY+lines[linenum].height*/newYLine
== linenum
) {
955 vg
.fillColor(nvgLerpRGBA(u2c(colorText
), u2c(colorTextHi
), newYAlpha
));
956 //vg.globalAlpha(newYAlpha);
958 vg
.fillColor(u2c(colorText
));
962 if (auto inc = lines
.ptr
[linenum
].padTop
) { y
+= inc; drawY
+= inc; }
964 int ty
= drawY
+lines
[linenum
].textHeight
/2-1;
965 if (lines
.ptr
[linenum
].right
) {
966 vg
.textAlign(NVGAlign
.Right|NVGAlign
.Middle
);
967 tx
= GWidth
-(sbLeft ?
2+BND_SCROLLBAR_WIDTH
: 0)-2;
968 vg
.fontFaceId(epiFont
);
969 } else if (lines
.ptr
[linenum
].center
) {
970 vg
.textAlign(NVGAlign
.Center|NVGAlign
.Middle
);
973 vg
.textAlign(NVGAlign
.Left|NVGAlign
.Middle
);
974 tx
= (sbLeft ?
2+BND_SCROLLBAR_WIDTH
+2 : 4);
976 vg
.text(tx
, ty
, lines
[linenum
].line
);
977 if (lines
.ptr
[linenum
].right
) vg
.fontFaceId(textFont
);
978 if (auto inc = lines
[linenum
].textHeight
+lines
.ptr
[linenum
].padBottom
) { y
+= inc; drawY
+= inc; }
986 if ((colorDim
&0xff000000) != 0) {
987 vg
.scissor(0, 0, GWidth
, GHeight
);
989 vg
.fillColor(u2ca(colorDim
));
990 vg
.rect(0, 0, GWidth
, GHeight
);
994 // dim more if menu is active
995 if (inSectionMenu || inQuitMenu || showShip ||
atomicLoad(formatWorks
) != 0) {
996 vg
.scissor(0, 0, GWidth
, GHeight
);
998 //vg.globalAlpha(0.5);
999 vg
.fillColor(nvgRGBA(0, 0, 0, (showShip ||
atomicLoad(formatWorks
) != 0 ?
127 : 64)));
1000 vg
.rect(0, 0, GWidth
, GHeight
);
1003 if (atomicLoad(formatWorks
) == 0) {
1004 if (inSectionMenu
) {
1005 vg
.scissor(0, 0, GWidth
, GHeight
);
1009 vg
.scissor(0, 0, GWidth
, GHeight
);
1014 drawElite(model
, skin
, rotx
, rotz
, 0, 0, GWidth
, GHeight
);
1017 if (fps
!is null && fpsVisible
) {
1018 vg
.scissor(0, 0, GWidth
, GHeight
);
1019 fps
.render(vg
, GWidth
-fps
.width
-4, GHeight
-fps
.height
-4);
1023 needRedraw
= (fps
!is null && fpsVisible
);
1026 MonoTime nextIfTime
= MonoTime
.currTime
;
1028 void processThreads () {
1029 ReformatWorkDone wd
;
1031 bool workDone
= false;
1032 auto res
= receiveTimeout(Duration
.zero
,
1034 atomicStore(formatWorks
, -1);
1036 (ReformatWorkDone w
) {
1041 if (!res
) { assert(!workDone
); break; }
1042 if (workDone
) { workDone
= false; formatComplete(wd
); }
1046 sdwindow
.eventLoop(1000/57,
1049 if (sdwindow
.closed
) return;
1050 if (doQuit
) { closeWindow(); return; }
1052 if (atomicLoad(formatWorks
) == 0) {
1055 if (sc
> Delta
) sc
= Delta
;
1058 nextFadeTime
= MonoTime
.currTime
+500.msecs
;
1059 } else if (toMove
> 0) {
1061 if (sc
> Delta
) sc
= Delta
;
1064 nextFadeTime
= MonoTime
.currTime
+500.msecs
;
1065 } else if (arrowDir
) {
1066 hardScrollBy(arrowDir
*6);
1070 auto ct
= MonoTime
.currTime
;
1071 if (atomicLoad(formatWorks
) == 0) {
1072 if (ct
>= nextFadeTime
) {
1073 if ((newYAlpha
-= 0.1) <= 0) {
1076 nextFadeTime
= ct
+15.msecs
;
1083 auto ct
= MonoTime
.currTime
;
1084 if (ct
>= nextIfTime
) {
1085 import std
.random
: uniform
;
1086 if (uniform
!"[]"(0, 100) >= 50) addIf();
1087 nextIfTime
+= (uniform
!"[]"(50, 1500)).msecs
;
1092 __gshared
bool skip
;
1094 if (skip
) rotx
= (rotx
+1)%120;
1095 rotz
= (rotz
+1)%120;
1098 if (needRedraw
) sdwindow
.redrawOpenGlSceneNow();
1101 delegate (KeyEvent event
) {
1102 if (sdwindow
.closed
) return;
1103 if (event
.key
== Key
.PadEnter
) event
.key
= Key
.Enter
;
1104 if ((event
.modifierState
&ModifierState
.numLock
) == 0) {
1105 switch (event
.key
) {
1106 case Key
.Pad0
: event
.key
= Key
.Insert
; break;
1107 case Key
.PadDot
: event
.key
= Key
.Delete
; break;
1108 case Key
.Pad1
: event
.key
= Key
.End
; break;
1109 case Key
.Pad2
: event
.key
= Key
.Down
; break;
1110 case Key
.Pad3
: event
.key
= Key
.PageDown
; break;
1111 case Key
.Pad4
: event
.key
= Key
.Left
; break;
1112 case Key
.Pad6
: event
.key
= Key
.Right
; break;
1113 case Key
.Pad7
: event
.key
= Key
.Home
; break;
1114 case Key
.Pad8
: event
.key
= Key
.Up
; break;
1115 case Key
.Pad9
: event
.key
= Key
.PageUp
; break;
1116 //case Key.PadEnter: event.key = Key.Enter; break;
1120 if (controlKey(event
)) return;
1121 if (quitMenuKey(event
)) return;
1122 if (sectionMenuKey(event
)) return;
1123 if (readerKey(event
)) return;
1125 delegate (MouseEvent event
) {
1127 delegate (dchar ch
) {
1128 //if (ch == 'q') { doQuit = true; return; }
1133 childTid
.send(QuitWork());
1134 while (atomicLoad(formatWorks
) >= 0) processThreads();
1138 // ////////////////////////////////////////////////////////////////////////// //
1139 void main (string
[] args
) {
1141 RcDir
= RcDir
.expandTilde
.absolutePath
;
1144 if (args
.length
== 1) {
1147 foreach (string line
; File(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
1148 if (!line
.isComment
) lnn
= line
;
1150 if (lnn
.length
) args
~= lnn
;
1151 } catch (Exception
) {}
1153 if (args
.length
== 1) assert(0, "no filename");
1155 loadShip("ferdelance");
1159 auto fname
= args
[1].expandTilde
.absolutePath
;
1160 writeln("loading '", fname
, "'...");
1161 auto stt
= MonoTime
.currTime
;
1162 auto book
= new BookText(fname
);
1163 writeln("loaded: '", fname
, "' in ", (MonoTime
.currTime
-stt
).total
!"msecs", " milliseconds");
1168 foreach (string line
; File(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
1169 if (line
!= fname
) {
1172 while (lines
.length
&& lines
[$-1].isComment
) {
1173 comments
~= lines
[$-1];
1174 lines
= lines
[0..$-1];
1178 } catch (Exception
) {}
1179 try { import std
.file
; mkdirRecurse(RcDir
); } catch (Exception
) {}
1180 auto fo
= File(buildPath(RcDir
, ".lastfile"), "w");
1181 foreach (string s
; lines
) fo
.writeln(s
);
1182 foreach_reverse (string s
; comments
) fo
.writeln(s
);
1185 stateFileName
= buildPath(RcDir
, fname
.baseName
~".rc");