1 /* Written by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>f
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/>.
21 import std
.concurrency
;
23 import arsd
.simpledisplay
;
27 import iv
.nanovg
.oui
.blendish
;
28 import iv
.nanovg
.perf
;
51 //version = debug_draw;
54 // ////////////////////////////////////////////////////////////////////////// //
55 __gshared
int oglConScale
= 1;
58 // ////////////////////////////////////////////////////////////////////////// //
59 __gshared string bookFileName
;
61 __gshared
int formatWorks
= -1;
62 __gshared NVGContext vg
= null;
63 __gshared PerfGraph fps
;
64 __gshared
bool fpsVisible
= false;
65 __gshared BookText bookText
;
66 __gshared string newBookFileName
;
67 __gshared
uint[] posstack
;
68 __gshared SimpleWindow sdwindow
;
69 __gshared LayText laytext
;
70 __gshared BookInfo
[] recentFiles
;
71 __gshared BookMetadata bookmeta
;
73 __gshared
int textHeight
;
75 __gshared
int toMove
= 0; // for smooth scroller
76 __gshared
int topY
= 0;
77 __gshared
int arrowDir
= 0;
78 __gshared
int arrowSpeed
= 0;
80 __gshared
int newYLine
= -1; // index
81 __gshared
float newYAlpha
;
82 __gshared
bool newYFade
= false;
83 __gshared MonoTime nextFadeTime
;
85 __gshared
int curImg
= -1, curImgWhite
= -1;
86 __gshared
int mouseX
= -666, mouseY
= -666;
87 __gshared
bool mouseHigh
= false;
88 __gshared
bool mouseHidden
= false;
89 __gshared MonoTime lastMMove
;
90 __gshared
float mouseAlpha
= 1.0f;
91 __gshared
int mouseFadingAway
= false;
93 __gshared
bool needRedrawFlag
= true;
95 __gshared
bool firstFormat
= true;
97 __gshared
bool inGalaxyMap
= false;
98 __gshared PopupMenu currPopup
;
99 __gshared
void delegate (int item
) onPopupSelect
;
100 __gshared
int shipModelIndex
= 0;
101 __gshared
bool popupNoShipKill
= false;
103 __gshared
bool doSaveCheck
= false;
104 __gshared MonoTime nextSaveTime
;
107 // ////////////////////////////////////////////////////////////////////////// //
108 void refresh () { needRedrawFlag
= true; }
109 void refreshed () { needRedrawFlag
= false; }
112 return (needRedrawFlag ||
(currPopup
!is null && currPopup
.needRedraw
));
116 @property bool inMenu () { return (currPopup
!is null); }
117 void closeMenu () { if (currPopup
!is null) currPopup
.destroy
; currPopup
= null; onPopupSelect
= null; }
120 void setShip (int idx
) {
121 if (eliteShipFiles
.length
) {
122 import core
.memory
: GC
;
123 if (idx
>= eliteShipFiles
.length
) idx
= cast(int)eliteShipFiles
.length
-1;
124 if (idx
< 0) idx
= 0;
125 if (shipModelIndex
== idx
&& shipModel
!is null) return;
127 shipModelIndex
= idx
;
128 if (shipModel
!is null) {
129 shipModel
.glUnload();
130 shipModel
.freeData();
137 shipModel
= new EliteModel(eliteShipFiles
[shipModelIndex
]);
138 shipModel
.glUpload();
139 shipModel
.freeImages();
140 } catch (Exception e
) {}
141 //shipModel = eliteShips[shipModelIndex];
146 void ensureShipModel () {
147 if (eliteShipFiles
.length
&& shipModel
is null) {
148 import std
.random
: uniform
;
149 setShip(uniform
!"[)"(0, eliteShipFiles
.length
));
153 void freeShipModel () {
154 if (!showShip
&& !inMenu
&& shipModel
!is null) {
155 import core
.memory
: GC
;
156 shipModel
.glUnload();
157 shipModel
.freeData();
168 xiniParse(VFile(stateFileName
),
171 if (widx
>= 0) goTo(widx
);
172 } catch (Exception
) {}
175 void doSaveState (bool forced
=false) {
177 if (formatWorks
!= 0) return;
178 if (!doSaveCheck
) return;
179 auto ct
= MonoTime
.currTime
;
180 if (ct
< nextSaveTime
) return;
182 if (laytext
is null || laytext
.lineCount
== 0 || formatWorks
!= 0) return;
185 auto fo
= VFile(stateFileName
, "w");
186 if (laytext
!is null && laytext
.lineCount
) {
187 auto lnum
= laytext
.findLineAtY(topY
);
188 if (lnum
>= 0) fo
.writeln("wordindex=", laytext
.line(lnum
).wstart
);
190 } catch (Exception
) {}
194 void stateChanged () {
197 nextSaveTime
= MonoTime
.currTime
+10.seconds
;
202 void hardScrollBy (int delta
) {
203 if (delta
== 0 || laytext
is null || laytext
.lineCount
== 0) return;
206 if (topY
>= laytext
.textHeight
-textHeight
) topY
= cast(int)laytext
.textHeight
-textHeight
;
207 if (topY
< 0) topY
= 0;
214 void scrollBy (int delta
) {
215 if (delta
== 0 || laytext
is null || laytext
.lineCount
== 0) return;
220 // scrolling up, mark top line
221 newYLine
= laytext
.findLineAtY(topY
);
223 // scrolling down, mark bottom line
224 newYLine
= laytext
.findLineAtY(topY
+textHeight
-2);
227 conwriteln("scrollBy: delta=", delta
, "; newYLine=", newYLine
, "; topLine=", laytext
.findLineAtY(topY
));
229 if (newYLine
< 0) newYFade
= false;
234 if (laytext
is null) return;
243 void goTo (uint widx
) {
244 if (laytext
is null) return;
245 auto lidx
= laytext
.findLineWithWord(widx
);
247 assert(lidx
< laytext
.lineCount
);
249 if (topY
!= laytext
.line(lidx
).y
) {
250 topY
= laytext
.line(lidx
).y
;
255 conwriteln("goto: widx=", widx
, "; lidx=", lidx
, "; fwn=", laytext
.line(lidx
).wstart
, "; topY=", topY
);
256 auto lnum
= laytext
.findLineAtY(topY
);
257 conwriteln(" newlnum=", lnum
, "; lidx.y=", laytext
.line(lidx
).y
, "; lnum.y=", laytext
.line(lnum
).y
);
263 void pushPosition () {
264 if (laytext
is null || laytext
.lineCount
== 0) return;
265 auto lidx
= laytext
.findLineAtY(topY
);
266 if (lidx
>= 0) posstack
~= laytext
.line(lidx
).wstart
;
269 void popPosition () {
270 if (posstack
.length
== 0) return;
271 auto widx
= posstack
[$-1];
272 posstack
.length
-= 1;
273 posstack
.assumeSafeAppend
;
278 void gotoSection (int sn
) {
279 if (laytext
is null || laytext
.lineCount
== 0 || bookmeta
is null) return;
280 if (sn
< 0 || sn
>= bookmeta
.sections
.length
) return;
281 auto lidx
= laytext
.findLineWithWord(bookmeta
.sections
[sn
].wordidx
);
283 auto newY
= laytext
.line(lidx
).y
;
293 void relayout (bool forced
=false) {
294 if (laytext
!is null) {
296 auto lidx
= laytext
.findLineAtY(topY
);
297 if (lidx
>= 0) widx
= laytext
.line(lidx
).wstart
;
298 int maxWidth
= GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2;
299 //if (maxWidth < MinWinWidth) maxWidth = MinWinWidth;
302 auto stt
= MonoTime
.currTime
;
303 laytext
.relayout(maxWidth
, forced
);
304 auto ett
= MonoTime
.currTime
-stt
;
305 conwriteln("relayouted in ", ett
.total
!"msecs", " milliseconds; lines:", laytext
.lineCount
, "; words:", laytext
.nextWordIndex
);
313 void drawShipName () {
314 if (shipModel
is null || shipModel
.name
.length
== 0) return;
315 vg
.fontFaceId(uiFont
);
316 vg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Baseline
);
317 vg
.fontSize(fsizeUI
);
318 auto w
= vg
.bndLabelWidth(-1, shipModel
.name
)+8;
319 float h
= BND_WIDGET_HEIGHT
+8;
320 float mx
= (GWidth
-w
)/2.0;
321 float my
= (GHeight
-h
)-8;
322 vg
.bndMenuBackground(mx
, my
, w
, h
, BND_CORNER_NONE
);
323 vg
.bndMenuItem(mx
+4, my
+4, w
-8, BND_WIDGET_HEIGHT
, BND_DEFAULT
, -1, shipModel
.name
);
324 if (shipModel
.dispName
&& shipModel
.dispName
!= shipModel
.name
) {
325 my
-= BND_WIDGET_HEIGHT
+16;
326 w
= vg
.bndLabelWidth(-1, shipModel
.dispName
)+8;
328 vg
.bndMenuBackground(mx
, my
, w
, h
, BND_CORNER_NONE
);
329 vg
.bndMenuItem(mx
+4, my
+4, w
-8, BND_WIDGET_HEIGHT
, BND_DEFAULT
, -1, shipModel
.dispName
);
334 void createSectionMenu () {
336 //conwriteln("lc=", laytext.lineCount, "; bm: ", (bookmeta !is null));
337 if (laytext
is null || laytext
.lineCount
== 0 || bookmeta
is null) { freeShipModel(); return; }
338 currPopup
= new PopupMenu(vg
, "Sections"d
, () {
340 foreach (const ref sc
; bookmeta
.sections
) items
~= sc
.name
;
343 //conwriteln(currPopup.items.length);
344 // find current section
345 currPopup
.curItemIdx
= 0;
346 auto lidx
= laytext
.findLineAtY(topY
);
347 if (lidx
>= 0 && bookmeta
.sections
.length
> 0) {
348 foreach (immutable sidx
, const ref sc
; bookmeta
.sections
) {
349 auto sline
= laytext
.findLineWithWord(sc
.wordidx
);
350 if (sline
>= 0 && lidx
>= sline
) currPopup
.curItemIdx
= cast(int)sidx
;
353 onPopupSelect
= (int item
) { gotoSection(item
); };
357 void createQuitMenu (bool wantYes
) {
359 currPopup
= new PopupMenu(vg
, "Quit?"d
, () {
360 return ["Yes"d
, "No"d
];
362 currPopup
.curItemIdx
= (wantYes ?
0 : 1);
363 onPopupSelect
= (int item
) { if (item
== 0) concmd("quit"); };
367 void createRecentMenu () {
369 if (recentFiles
.length
== 0) recentFiles
= loadDetailedHistory();
370 if (recentFiles
.length
== 0) { freeShipModel(); return; }
371 currPopup
= new PopupMenu(vg
, "Recent files"d
, () {
372 import std
.conv
: to
;
374 foreach (const ref BookInfo bi
; recentFiles
) {
376 if (bi
.seqname
.length
) {
378 if (bi
.seqnum
) { s
~= to
!string(bi
.seqnum
); s
~= ": "; }
379 //conwriteln(bi.seqname);
383 if (bi
.author
.length
) { s
~= " \xe2\x80\x94 "; s
~= bi
.author
; }
388 currPopup
.curItemIdx
= cast(int)recentFiles
.length
-1;
389 onPopupSelect
= (int item
) {
390 newBookFileName
= recentFiles
[item
].diskfile
;
391 popupNoShipKill
= true;
396 bool menuKey (KeyEvent event
) {
397 if (formatWorks
!= 0) return false;
398 if (!inMenu
) return false;
399 if (inGalaxyMap
) return false;
400 if (!event
.pressed
) return false;
401 auto res
= currPopup
.onKey(event
);
402 if (res
== PopupMenu
.Close
) {
406 } else if (res
>= 0) {
407 if (onPopupSelect
!is null) onPopupSelect(res
);
409 if (popupNoShipKill
) popupNoShipKill
= false; else freeShipModel();
416 bool menuMouse (MouseEvent event
) {
417 if (formatWorks
!= 0) return false;
418 if (!inMenu
) return false;
419 if (inGalaxyMap
) return false;
420 auto res
= currPopup
.onMouse(event
);
421 if (res
== PopupMenu
.Close
) {
425 } else if (res
>= 0) {
426 if (onPopupSelect
!is null) onPopupSelect(res
);
428 if (popupNoShipKill
) popupNoShipKill
= false; else freeShipModel();
435 bool readerKey (KeyEvent event
) {
436 if (formatWorks
!= 0) return false;
437 if (!event
.pressed
) {
439 case Key
.Up
: arrowDir
= 0; return true;
440 case Key
.Down
: arrowDir
= 0; return true;
447 if (event
.modifierState
&ModifierState
.shift
) {
448 //goto case Key.PageUp;
449 hardScrollBy(toMove
); toMove
= 0;
450 scrollBy(-textHeight
/3*2);
452 //goto case Key.PageDown;
453 hardScrollBy(toMove
); toMove
= 0;
454 scrollBy(textHeight
/3*2);
461 hardScrollBy(toMove
); toMove
= 0;
462 hardScrollBy(-(textHeight
> 32 ? textHeight
-32 : textHeight
));
465 hardScrollBy(toMove
); toMove
= 0;
466 hardScrollBy(textHeight
> 32 ? textHeight
-32 : textHeight
);
485 bool controlKey (KeyEvent event
) {
486 if (!event
.pressed
) return false;
489 if (inGalaxyMap
) { inGalaxyMap
= false; refresh(); return true; }
490 if (inMenu
) { closeMenu(); freeShipModel(); refresh(); return true; }
491 if (showShip
) { showShip
= false; freeShipModel(); refresh(); return true; }
493 createQuitMenu(true);
496 case Key
.P
: if (event
.modifierState
== ModifierState
.ctrl
) { concmd("r_fps toggle"); return true; } break;
497 case Key
.I
: if (event
.modifierState
== ModifierState
.ctrl
) { concmd("r_interference toggle"); return true; } break;
498 case Key
.N
: if (event
.modifierState
== ModifierState
.ctrl
) { concmd("r_sbleft toggle"); return true; } break;
499 case Key
.C
: if (event
.modifierState
== ModifierState
.ctrl
) { if (addIf()) refresh(); return true; } break;
501 if (event
.modifierState
== ModifierState
.ctrl
&& eliteShipFiles
.length
> 0) {
503 showShip
= !showShip
;
504 if (showShip
) ensureShipModel(); else freeShipModel();
510 case Key
.Q
: if (event
.modifierState
== ModifierState
.ctrl
) { concmd("quit"); return true; } break;
511 case Key
.B
: if (formatWorks
== 0 && !inMenu
&& !inGalaxyMap
&& event
.modifierState
== ModifierState
.ctrl
) { pushPosition(); return true; } break;
513 if (formatWorks
== 0 && !showShip
&& !inMenu
&& !inGalaxyMap
) {
520 if (formatWorks
== 0 && event
.modifierState
== ModifierState
.alt
) {
527 if (!inMenu
&& !showShip
) {
528 inGalaxyMap
= !inGalaxyMap
;
533 if (formatWorks
== 0 && event
.modifierState
== ModifierState
.ctrl
) relayout(true);
535 case Key
.Home
: if (showShip
) { setShip(0); return true; } break;
536 case Key
.End
: if (showShip
) { setShip(cast(int)eliteShipFiles
.length
); return true; } break;
537 case Key
.Up
: case Key
.Left
: if (showShip
) { setShip(shipModelIndex
-1); return true; } break;
538 case Key
.Down
: case Key
.Right
: if (showShip
) { setShip(shipModelIndex
+1); return true; } break;
544 int startX () { pragma(inline
, true); return (sbLeft ?
2+BND_SCROLLBAR_WIDTH
+2 : 4); }
545 int endX () { pragma(inline
, true); return startX
+GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2-1; }
547 int startY () { pragma(inline
, true); return (GHeight
-textHeight
)/2; }
548 int endY () { pragma(inline
, true); return startY
+textHeight
-1; }
551 // ////////////////////////////////////////////////////////////////////////// //
553 if (GWidth
< MinWinWidth
) GWidth
= MinWinWidth
;
554 if (GHeight
< MinWinHeight
) GHeight
= MinWinHeight
;
556 bookText
= loadBook(bookFileName
);
558 //setOpenGLContextVersion(3, 2); // up to GLSL 150
559 setOpenGLContextVersion(2, 0); // it's enough
560 //openGLContextCompatible = false;
562 sdwindow
= new SimpleWindow(GWidth
, GHeight
, bookText
.title
~" \xe2\x80\x94 "~bookText
.authorFirst
~" "~bookText
.authorLast
, OpenGlOptions
.yes
, Resizability
.allowResizing
);
563 sdwindow
.hideCursor(); // we will do our own
564 sdwindow
.setMinSize(MinWinWidth
, MinWinHeight
);
566 version(X11
) sdwindow
.closeQuery
= delegate () { concmd("quit"); };
568 auto stt
= MonoTime
.currTime
;
569 auto prevt
= MonoTime
.currTime
;
571 textHeight
= GHeight
-8;
573 MonoTime nextIfTime
= MonoTime
.currTime
;
575 lastMMove
= MonoTime
.currTime
;
577 auto childTid
= spawn(&reformatThreadFn
, thisTid
);
578 childTid
.setMaxMailboxSize(128, OnCrowding
.block
);
579 thisTid
.setMaxMailboxSize(128, OnCrowding
.block
);
581 void loadAndFormat (string filename
) {
582 assert(formatWorks
<= 0);
586 //formatWorks = -1; //FIXME
593 //sdwindow.redrawOpenGlSceneNow();
594 //bookText = loadBook(newBookFileName);
595 //newBookFileName = null;
597 //if (formatWorks < 0) formatWorks = 1; else ++formatWorks;
599 //conwriteln("*** loading new book: '", filename, "'");
600 childTid
.send(ReformatWork(null, filename
, GWidth
, GHeight
));
605 if (formatWorks
< 0) formatWorks
= 1; else ++formatWorks
;
606 childTid
.send(ReformatWork(cast(shared)bookText
, null, GWidth
, GHeight
));
610 void formatComplete (ref ReformatWorkComplete w
) {
611 scope(exit
) { import core
.memory
: GC
; GC
.collect(); GC
.minimize(); }
613 auto lt
= cast(LayText
)w
.laytext
;
614 scope(exit
) if (lt
) lt
.freeMemory();
617 BookMetadata meta
= cast(BookMetadata
)w
.meta
;
620 BookText
bt = cast(BookText
)w
.booktext
;
622 if (bt !is bookText
) {
626 sdwindow
.title
= bookText
.title
~" \xe2\x80\x94 "~bookText
.authorFirst
~" "~bookText
.authorLast
;
627 } else if (bookmeta
is null) {
631 if (isQuitRequested || formatWorks
<= 0) return;
635 if (formatWorks
== 0) reformat();
639 if (formatWorks
!= 0) return;
643 if (!firstFormat
&& laytext
!is null && laytext
.lineCount
) {
644 auto lidx
= laytext
.findLineAtY(topY
);
645 if (lidx
>= 0) widx
= laytext
.line(lidx
).wstart
;
647 if (laytext
!is null) laytext
.freeMemory();
661 void closeWindow () {
662 doSaveState(true); // forced state save
663 if (!sdwindow
.closed
&& vg
!is null) {
664 if (curImg
>= 0) { vg
.deleteImage(curImg
); curImg
= -1; }
665 if (curImgWhite
>= 0) { vg
.deleteImage(curImgWhite
); curImgWhite
= -1; }
673 sdwindow
.visibleForTheFirstTime
= delegate () {
674 sdwindow
.setAsCurrentOpenGlContext(); // make this window active
675 sdwindow
.vsync
= false;
676 //sdwindow.useGLFinish = false;
677 //glbindLoadFunctions();
680 uint flags
= NVG_DEBUG
;
681 if (flagNanoAA
) flags |
= NVG_ANTIALIAS
;
682 if (flagNanoSS
) flags |
= NVG_STENCIL_STROKES
;
683 if (!flagNanoFAA
) flags |
= !NVG_INVERT_FONT_AA
;
684 vg
= createGL2NVG(flags
);
686 conwriteln("Could not init nanovg.");
691 curImg
= createCursorImage(vg
);
692 curImgWhite
= createCursorImage(vg
, true);
693 fps
= new PerfGraph("Frame Time", PerfGraph
.Style
.FPS
, "ui");
694 } catch (Exception e
) {
695 conwriteln("ERROR: ", e
.msg
);
703 sdwindow
.redrawOpenGlScene();
707 sdwindow
.windowResized
= delegate (int w
, int h
) {
708 //conwriteln("w=", w, "; h=", h);
709 //if (w < MinWinWidth) w = MinWinWidth;
710 //if (h < MinWinHeight) h = MinWinHeight;
711 glViewport(0, 0, w
, h
);
714 textHeight
= GHeight
-8;
720 sdwindow
.redrawOpenGlScene
= delegate () {
721 if (isQuitRequested
) return;
723 glconResize(GWidth
/oglConScale
, GHeight
/oglConScale
, oglConScale
);
726 conwriteln("cnt=", cnt++);
729 //glClearColor(0, 0, 0, 0);
730 //glClearColor(0.18, 0.18, 0.18, 0);
731 glClearColor(colorBack
.r
, colorBack
.g
, colorBack
.b
, 0);
732 glClear(glNVGClearFlags|EliteModel
.glClearFlags
);
735 needRedrawFlag
= (fps
!is null && fpsVisible
);
736 if (vg
is null) return;
738 scope(exit
) vg
.releaseImages();
739 vg
.beginFrame(GWidth
, GHeight
, 1);
743 float curHeight
= (laytext
!is null ? topY
: 0);
744 float th
= (laytext
!is null ? laytext
.textHeight
-textHeight
: 0);
745 if (th
<= 0) { curHeight
= 0; th
= 1; }
746 float sz
= cast(float)(GHeight
-4)/th
;
747 if (sz
> 1) sz
= 1; else if (sz
< 0.05) sz
= 0.05;
748 int sx
= (sbLeft ?
2 : GWidth
-BND_SCROLLBAR_WIDTH
-2);
749 vg
.bndScrollSlider(sx
, 2, BND_SCROLLBAR_WIDTH
, GHeight
-4, BND_DEFAULT
, curHeight
/th
, sz
);
751 if (laytext
is null) {
752 if (shipModel
is null) {
754 vg
.fillColor(colorText
);
755 int drawY
= (GHeight
-textHeight
)/2;
756 vg
.intersectScissor((sbLeft ?
2+BND_SCROLLBAR_WIDTH
+2 : 4), drawY
, GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2, textHeight
);
757 vg
.fontFaceId(textFont
);
758 vg
.fontSize(fsizeText
);
759 vg
.textAlign(NVGTextAlign
.H
.Center
, NVGTextAlign
.V
.Middle
);
760 vg
.text(GWidth
/2, GHeight
/2, "REFORMATTING");
765 if (laytext
!is null && laytext
.lineCount
) {
767 vg
.fillColor(colorText
);
769 vg
.intersectScissor((sbLeft ?
2+BND_SCROLLBAR_WIDTH
+2 : 4), drawY
, GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2, textHeight
);
770 //FIXME: not GHeight!
771 int lidx
= laytext
.findLineAtY(topY
);
772 if (lidx
>= 0 && lidx
< laytext
.lineCount
) {
773 drawY
-= topY
-laytext
.line(lidx
).y
;
774 vg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Baseline
);
775 LayFontStyle lastStyle
;
777 while (lidx
< laytext
.lineCount
&& drawY
< GHeight
) {
778 auto ln
= laytext
.line(lidx
);
779 foreach (ref LayWord w
; laytext
.lineWords(lidx
)) {
780 if (lastStyle
!= w
.style
) {
781 if (w
.style
.fontface
!= lastStyle
.fontface
) vg
.fontFace(laytext
.fontFace(w
.style
.fontface
));
782 vg
.fontSize(w
.style
.fontsize
);
783 auto c
= NVGColor(w
.style
.color
);
789 if (newYFade
&& newYLine
== lidx
) vg
.fillColor(nvgLerpRGBA(colorText
, colorTextHi
, newYAlpha
));
790 auto oid
= w
.objectIdx
;
793 laytext
.objects
[oid
].draw(vg
, startx
+w
.x
, drawY
+ln
.h
+ln
.desc
);
796 vg
.text(startx
+w
.x
, drawY
+ln
.h
+ln
.desc
, laytext
.wordText(w
));
798 //TODO: draw lines over whitespace
799 if (lastStyle
.underline
) vg
.rect(startx
+w
.x
, drawY
+ln
.h
+ln
.desc
+1, w
.w
, 1);
800 if (lastStyle
.strike
) vg
.rect(startx
+w
.x
, drawY
+ln
.h
+ln
.desc
-w
.asc
/2, w
.w
, 1);
801 if (lastStyle
.overline
) vg
.rect(startx
+w
.x
, drawY
+ln
.h
+ln
.desc
-w
.asc
-1, w
.w
, 1);
802 version(debug_draw
) {
806 vg
.strokeColor(nvgRGB(0, 0, 255));
807 vg
.rect(startx
+w
.x
, drawY
, w
.w
, w
.h
);
811 if (newYFade
&& newYLine
== lidx
) vg
.fillColor(NVGColor(lastStyle
.color
));
821 if (colorDim
.a
!= 1) {
822 //vg.scissor(0, 0, GWidth, GHeight);
825 vg
.fillColor(colorDim
);
826 vg
.rect(0, 0, GWidth
, GHeight
);
830 // dim more if menu is active
831 if (inMenu || showShip || formatWorks
!= 0) {
832 //vg.scissor(0, 0, GWidth, GHeight);
835 //vg.globalAlpha(0.5);
836 vg
.fillColor(nvgRGBA(0, 0, 0, (showShip || formatWorks
!= 0 ?
127 : 64)));
837 vg
.rect(0, 0, GWidth
, GHeight
);
840 if (shipModel
!is null) {
842 float zz
= shipModel
.bbox
[1].z
-shipModel
.bbox
[0].z
;
856 drawModel(shipAngle
, shipModel
);
857 vg
.beginFrame(GWidth
, GHeight
, 1);
861 if (formatWorks
== 0) {
863 //vg.beginFrame(GWidth, GHeight, 1);
864 //vg.scissor(0, 0, GWidth, GHeight);
870 if (fps
!is null && fpsVisible
) {
871 //vg.beginFrame(GWidth, GHeight, 1);
872 //vg.scissor(0, 0, GWidth, GHeight);
874 fps
.render(vg
, GWidth
-fps
.width
-4, GHeight
-fps
.height
-4);
877 if (inGalaxyMap
) drawGalaxy(vg
);
879 if (curImg
>= 0 && !mouseHidden
) {
881 //vg.beginFrame(GWidth, GHeight, 1);
883 //vg.scissor(0, 0, GWidth, GHeight);
885 vg
.imageSize(curImg
, &w
, &h
);
886 if (mouseFadingAway
) {
888 if (mouseAlpha
<= 0) { mouseFadingAway
= false; mouseHidden
= true; }
892 vg
.globalAlpha(mouseAlpha
);
894 vg
.fillPaint(vg
.imagePattern(mouseX
, mouseY
, w
, h
, 0, curImg
, 1));
896 vg
.fillPaint(vg
.imagePattern(mouseX
, mouseY
, w
, h
, 0, curImgWhite
, 1));
898 vg
.rect(mouseX
, mouseY
, w
, h
);
903 vg.fillPaint(vg.imagePattern(mouseX, mouseY, w, h, 0, curImgWhite, 0.4));
904 vg.rect(mouseX, mouseY, w, h);
914 void processThreads () {
915 ReformatWorkComplete wd
;
917 bool workDone
= false;
918 auto res
= receiveTimeout(Duration
.zero
,
922 (ReformatWorkComplete w
) {
927 if (!res
) { assert(!workDone
); break; }
928 if (workDone
) { workDone
= false; formatComplete(wd
); }
932 auto lastTimerEventTime
= MonoTime
.currTime
;
933 bool somethingVisible
= true;
935 sdwindow
.visibilityChanged
= delegate (bool vis
) {
936 //import core.stdc.stdio; printf("VISCHANGED: %s\n", (vis ? "tan" : "ona").ptr);
937 somethingVisible
= vis
;
940 conRegVar
!fpsVisible("r_fps", "show fps indicator", (self
, valstr
) { refresh(); });
941 conRegVar
!inGalaxyMap("r_galaxymap", "show Elite galaxy map", (self
, valstr
) { refresh(); });
943 conRegVar
!interAllowed("r_interference", "show interference", (self
, valstr
) { refresh(); });
944 conRegVar
!sbLeft("r_sbleft", "show scrollbar at the left side", (self
, valstr
) { refresh(); });
946 conRegVar
!showShip("r_showship", "show Elite ship", (self
, valstr
) {
947 if (eliteShipFiles
.length
== 0) return false;
951 if (showShip
) ensureShipModel(); else freeShipModel();
957 if (currPopup
!is null) {
963 onPopupSelect
= null;
964 })("menu_close", "close current popup menu");
967 if (formatWorks
== 0 && !showShip
&& !inGalaxyMap
) {
974 })("menu_section", "show section menu");
977 if (formatWorks
== 0 && !showShip
&& !inGalaxyMap
) {
984 })("menu_recent", "show recent menu");
986 sdwindow
.eventLoop(1000/34,
989 if (sdwindow
.closed
) return;
991 if (isQuitRequested
) { closeWindow(); return; }
992 auto ctt
= MonoTime
.currTime
;
995 auto spass
= (ctt
-lastTimerEventTime
).total
!"msecs";
996 //if (spass >= 30) { import core.stdc.stdio; printf("WARNING: too long frame time: %u\n", cast(uint)spass); }
997 //{ import core.stdc.stdio; printf("FRAME TIME: %u\n", cast(uint)spass); }
998 lastTimerEventTime
= ctt
;
1001 //curt = MonoTime.currTime;
1003 //auto secs = cast(double)((curt-stt).total!"msecs")/1000.0;
1004 auto dt = cast(double)((curt
-prevt
).total
!"msecs")/1000.0;
1005 if (fps
!is null) fps
.update(dt);
1009 if (formatWorks
== 0) {
1012 import std
.math
: abs
;
1013 immutable int sign
= (toMove
< 0 ?
-1 : 1);
1015 if (arrowSpeed
== 0) arrowSpeed
= 16;
1016 if (abs(toMove
) <= arrowSpeed
) {
1018 if (arrowSpeed
< 4) arrowSpeed
= 4;
1021 if (arrowSpeed
> Delta
) arrowSpeed
= Delta
;
1023 // calc move distance
1024 int sc
= arrowSpeed
;
1025 if (sc
> abs(toMove
)) sc
= abs(toMove
);
1026 hardScrollBy(sc
*sign
);
1028 if (toMove
== 0) arrowSpeed
= 0;
1029 nextFadeTime
= ctt
+500.msecs
;
1031 } else if (arrowDir
) {
1032 if ((arrowDir
< 0 && arrowSpeed
> 0) ||
(arrowDir
> 0 && arrowSpeed
< 0)) arrowSpeed
+= arrowDir
*4;
1033 arrowSpeed
+= arrowDir
*2;
1034 if (arrowSpeed
< -64) arrowSpeed
= -64; else if (arrowSpeed
> 64) arrowSpeed
= 64;
1035 hardScrollBy(arrowSpeed
);
1037 } else if (arrowSpeed
!= 0) {
1038 if (arrowSpeed
< 0) {
1039 if ((arrowSpeed
+= 4) > 0) arrowSpeed
= 0;
1041 if ((arrowSpeed
-= 4) < 0) arrowSpeed
= 0;
1044 hardScrollBy(arrowSpeed
);
1050 if (ctt
>= nextFadeTime
) {
1051 if ((newYAlpha
-= 0.1) <= 0) {
1054 nextFadeTime
= ctt
+25.msecs
;
1060 // interference processing
1061 if (ctt
>= nextIfTime
) {
1062 import std
.random
: uniform
;
1063 if (uniform
!"[]"(0, 100) >= 50) { if (addIf()) refresh(); }
1064 nextIfTime
+= (uniform
!"[]"(50, 1500)).msecs
;
1066 if (processIfs()) refresh();
1068 if (shipModel
!is null) {
1070 if (shipAngle
< 359) shipAngle
+= 360;
1074 if (!mouseFadingAway
) {
1075 if (!mouseHidden
&& !mouseHigh
) {
1076 if ((ctt
-lastMMove
).total
!"msecs" > 2500) {
1077 //mouseHidden = true;
1078 mouseFadingAway
= true;
1084 if (somethingVisible
) {
1085 // sadly, to keep framerate we have to redraw each frame, or driver will throw us out of the ship
1086 if (/*needRedraw*/true) sdwindow
.redrawOpenGlSceneNow();
1092 if (newBookFileName
.length
&& formatWorks
== 0) {
1093 doSaveState(true); // forced state save
1096 loadAndFormat(newBookFileName
);
1097 newBookFileName
= null;
1098 sdwindow
.redrawOpenGlSceneNow();
1102 delegate (KeyEvent event
) {
1103 if (sdwindow
.closed
) return;
1104 if (glconKeyEvent(event
)) return;
1105 if (event
.key
== Key
.PadEnter
) event
.key
= Key
.Enter
;
1106 if ((event
.modifierState
&ModifierState
.numLock
) == 0) {
1107 switch (event
.key
) {
1108 case Key
.Pad0
: event
.key
= Key
.Insert
; break;
1109 case Key
.PadDot
: event
.key
= Key
.Delete
; break;
1110 case Key
.Pad1
: event
.key
= Key
.End
; break;
1111 case Key
.Pad2
: event
.key
= Key
.Down
; break;
1112 case Key
.Pad3
: event
.key
= Key
.PageDown
; break;
1113 case Key
.Pad4
: event
.key
= Key
.Left
; break;
1114 case Key
.Pad6
: event
.key
= Key
.Right
; break;
1115 case Key
.Pad7
: event
.key
= Key
.Home
; break;
1116 case Key
.Pad8
: event
.key
= Key
.Up
; break;
1117 case Key
.Pad9
: event
.key
= Key
.PageUp
; break;
1118 //case Key.PadEnter: event.key = Key.Enter; break;
1122 if (controlKey(event
)) return;
1123 if (menuKey(event
)) return;
1124 if (readerKey(event
)) return;
1126 delegate (MouseEvent event
) {
1127 if (sdwindow
.closed
) return;
1129 int linkAt (int msx
, int msy
) {
1130 if (laytext
!is null && bookmeta
!is null) {
1131 if (msx
>= startX
&& msx
<= endX
&& msy
>= startY
&& msy
<= endY
) {
1132 auto widx
= laytext
.wordAtXY(msx
-startX
, topY
+msy
-startY
);
1134 //conwriteln("word at (", msx-startX, ",", msy-startY, "): ", widx);
1135 auto w
= laytext
.wordByIndex(widx
);
1137 //conwriteln("word #", widx, "; href=", w.style.href);
1138 if (!w
.style
.href
) break;
1139 if (auto hr
= w
.wordNum
in bookmeta
.hrefs
) {
1140 dstring href
= hr
.name
;
1141 if (href
.length
> 1 && href
[0] == '#') {
1143 foreach (const ref id
; bookmeta
.ids
) {
1144 if (id
.name
== href
) {
1150 //conwriteln("id '", hr.name, "' not found!");
1163 lastMMove
= MonoTime
.currTime
;
1164 if (mouseHidden || mouseFadingAway
) {
1165 mouseHidden
= false;
1166 mouseFadingAway
= false;
1170 if (mouseX
!= event
.x || mouseY
!= event
.y
) {
1175 if (!menuMouse(event
) && !showShip
) {
1176 if (event
.type
== MouseEventType
.buttonPressed
) {
1177 switch (event
.button
) {
1178 case MouseButton
.wheelUp
: hardScrollBy(-42); break;
1179 case MouseButton
.wheelDown
: hardScrollBy(42); break;
1180 case MouseButton
.left
:
1181 auto wid
= linkAt(event
.x
, event
.y
);
1187 case MouseButton
.right
:
1194 mouseHigh
= (linkAt(event
.x
, event
.y
) >= 0);
1196 delegate (dchar ch
) {
1197 if (sdwindow
.closed
) return;
1198 if (glconCharEvent(ch
)) return;
1199 //if (ch == '`') { concmd("r_console tan"); return; }
1204 childTid
.send(QuitWork());
1205 while (formatWorks
>= 0) processThreads();
1209 // ////////////////////////////////////////////////////////////////////////// //
1210 void main (string
[] args
) {
1213 conRegVar
!oglConScale(1, 4, "r_conscale", "console scale");
1215 universe
= Galaxy(0);
1217 if (args
.length
== 1) {
1220 foreach (string line
; VFile(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
1221 if (!line
.isComment
) lnn
= line
;
1223 if (lnn
.length
) args
~= lnn
;
1224 } catch (Exception
) {}
1226 if (args
.length
== 1) assert(0, "no filename");
1230 bookFileName
= args
[1];