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
;
29 import iv
.nanovg
.oui
.blendish
;
30 import iv
.nanovg
.perf
;
51 //version = debug_draw;
54 // ////////////////////////////////////////////////////////////////////////// //
55 void run (string bookFileName
) {
59 bool fpsVisible
= false;
61 string newBookFileName
;
63 if (GWidth
< MinWinWidth
) GWidth
= MinWinWidth
;
64 if (GHeight
< MinWinHeight
) GHeight
= MinWinHeight
;
70 booktext
= loadBook(bookFileName
);
72 //setOpenGLContextVersion(3, 2); // up to GLSL 150
73 setOpenGLContextVersion(2, 0); // it's enough
74 //openGLContextCompatible = false;
76 auto sdwindow
= new SimpleWindow(GWidth
, GHeight
, booktext
.title
~" \xe2\x80\x94 "~booktext
.authorFirst
~" "~booktext
.authorLast
, OpenGlOptions
.yes
, Resizablity
.allowResizing
);
77 sdwindow
.hideCursor(); // we will do our own
78 sdwindow
.setMinSize(MinWinWidth
, MinWinHeight
);
80 auto stt
= MonoTime
.currTime
;
81 auto prevt
= MonoTime
.currTime
;
83 float dt = 0, secs
= 0;
85 int textHeight
= GHeight
-8;
86 bool doSaveCheck
= false;
87 MonoTime nextSaveTime
;
89 MonoTime nextIfTime
= MonoTime
.currTime
;
91 BookInfo
[] recentFiles
;
92 BookMetadata bookmeta
;
94 int toMove
= 0; // for smooth scroller
98 int newYLine
= -1; // index
100 bool newYFade
= false;
101 MonoTime nextFadeTime
;
103 int curImg
= -1, curImgWhite
= -1;
104 int mouseX
= -666, mouseY
= -666;
105 bool mouseHigh
= false;
106 bool mouseHidden
= false;
107 auto lastMMove
= MonoTime
.currTime
;
109 bool needRedrawFlag
= true;
111 bool firstFormat
= true;
113 bool inGalaxyMap
= false;
115 void delegate (int item
) onPopupSelect
;
116 int shipModelIndex
= 0;
117 bool popupNoShipKill
= false;
119 void refresh () { needRedrawFlag
= true; }
120 void refreshed () { needRedrawFlag
= false; }
123 return (needRedrawFlag ||
(currPopup
!is null && currPopup
.needRedraw
));
126 @property bool inMenu () { return (currPopup
!is null); }
127 void closeMenu () { if (currPopup
!is null) currPopup
.destroy
; currPopup
= null; onPopupSelect
= null; }
129 auto childTid
= spawn(&reformatThreadFn
, thisTid
);
130 childTid
.setMaxMailboxSize(128, OnCrowding
.block
);
131 thisTid
.setMaxMailboxSize(128, OnCrowding
.block
);
133 void setShip (int idx
) {
134 if (eliteShipFiles
.length
) {
135 import core
.memory
: GC
;
136 if (idx
>= eliteShipFiles
.length
) idx
= cast(int)eliteShipFiles
.length
-1;
137 if (idx
< 0) idx
= 0;
138 if (shipModelIndex
== idx
&& shipModel
!is null) return;
140 shipModelIndex
= idx
;
141 if (shipModel
!is null) {
142 shipModel
.glUnload();
143 shipModel
.freeData();
150 shipModel
= new EliteModel(eliteShipFiles
[shipModelIndex
]);
151 shipModel
.glUpload();
152 shipModel
.freeImages();
153 } catch (Exception e
) {}
154 //shipModel = eliteShips[shipModelIndex];
159 void ensureShipModel () {
160 if (eliteShipFiles
.length
&& shipModel
is null) {
161 import std
.random
: uniform
;
162 setShip(uniform
!"[)"(0, eliteShipFiles
.length
));
166 void freeShipModel () {
167 if (!showShip
&& !inMenu
&& shipModel
!is null) {
168 import core
.memory
: GC
;
169 shipModel
.glUnload();
170 shipModel
.freeData();
177 void doSaveState (bool forced
=false) {
179 if (formatWorks
!= 0) return;
180 if (!doSaveCheck
) return;
181 auto ct
= MonoTime
.currTime
;
182 if (ct
< nextSaveTime
) return;
184 if (laytext
is null || laytext
.lineCount
== 0 || formatWorks
!= 0) return;
187 auto fo
= VFile(stateFileName
, "w");
188 if (laytext
!is null && laytext
.lineCount
) {
189 auto lnum
= laytext
.findLineAtY(topY
);
190 if (lnum
>= 0) fo
.writeln("wordindex=", laytext
.line(lnum
).wstart
);
192 } catch (Exception
) {}
196 void stateChanged () {
199 nextSaveTime
= MonoTime
.currTime
+10.seconds
;
203 void hardScrollBy (int delta
) {
204 if (delta
== 0 || laytext
is null || laytext
.lineCount
== 0) return;
207 if (topY
>= laytext
.textHeight
-textHeight
) topY
= cast(int)laytext
.textHeight
-textHeight
;
208 if (topY
< 0) topY
= 0;
215 void scrollBy (int delta
) {
216 if (delta
== 0 || laytext
is null || laytext
.lineCount
== 0) return;
221 // scrolling up, mark top line
222 newYLine
= laytext
.findLineAtY(topY
);
224 // scrolling down, mark bottom line
225 newYLine
= laytext
.findLineAtY(topY
+textHeight
-2);
229 writeln("scrollBy: delta=", delta
, "; newYLine=", newYLine
, "; topLine=", laytext
.findLineAtY(topY
));
231 if (newYLine
< 0) newYFade
= false;
235 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
;
256 writeln("goto: widx=", widx
, "; lidx=", lidx
, "; fwn=", laytext
.line(lidx
).wstart
, "; topY=", topY
);
257 auto lnum
= laytext
.findLineAtY(topY
);
258 writeln(" newlnum=", lnum
, "; lidx.y=", laytext
.line(lidx
).y
, "; lnum.y=", laytext
.line(lnum
).y
);
266 xiniParse(VFile(stateFileName
),
269 if (widx
>= 0) goTo(widx
);
270 } catch (Exception
) {}
273 void loadAndFormat (string filename
) {
274 assert(formatWorks
<= 0);
278 //formatWorks = -1; //FIXME
284 //sdwindow.redrawOpenGlSceneNow();
285 //booktext = loadBook(newBookFileName);
286 //newBookFileName = null;
288 //if (formatWorks < 0) formatWorks = 1; else ++formatWorks;
290 //writeln("*** loading new book: '", filename, "'");
291 childTid
.send(ReformatWork(null, filename
, GWidth
, GHeight
));
296 if (formatWorks
< 0) formatWorks
= 1; else ++formatWorks
;
297 childTid
.send(ReformatWork(cast(shared)booktext
, null, GWidth
, GHeight
));
301 void formatComplete (ref ReformatWorkComplete w
) {
302 scope(exit
) { import core
.memory
: GC
; GC
.collect(); GC
.minimize(); }
304 auto lt
= cast(LayText
)w
.laytext
;
305 scope(exit
) if (lt
) lt
.freeMemory();
308 BookMetadata meta
= cast(BookMetadata
)w
.meta
;
311 BookText
bt = cast(BookText
)w
.booktext
;
313 if (bt !is booktext
) {
317 sdwindow
.title
= booktext
.title
~" \xe2\x80\x94 "~booktext
.authorFirst
~" "~booktext
.authorLast
;
318 } else if (bookmeta
is null) {
322 if (doQuit || formatWorks
<= 0) return;
326 if (formatWorks
== 0) reformat();
330 if (formatWorks
!= 0) return;
334 if (!firstFormat
&& laytext
!is null && laytext
.lineCount
) {
335 auto lidx
= laytext
.findLineAtY(topY
);
336 if (lidx
>= 0) widx
= laytext
.line(lidx
).wstart
;
338 if (laytext
!is null) laytext
.freeMemory();
352 void pushPosition () {
353 if (laytext
is null || laytext
.lineCount
== 0) return;
354 auto lidx
= laytext
.findLineAtY(topY
);
355 if (lidx
>= 0) posstack
~= laytext
.line(lidx
).wstart
;
358 void popPosition () {
359 if (posstack
.length
== 0) return;
360 auto widx
= posstack
[$-1];
361 posstack
.length
-= 1;
362 posstack
.assumeSafeAppend
;
366 void gotoSection (int sn
) {
367 if (laytext
is null || laytext
.lineCount
== 0 || bookmeta
is null) return;
368 if (sn
< 0 || sn
>= bookmeta
.sections
.length
) return;
369 auto lidx
= laytext
.findLineWithWord(bookmeta
.sections
[sn
].wordidx
);
371 auto newY
= laytext
.line(lidx
).y
;
380 version(X11
) sdwindow
.closeQuery
= delegate () { doQuit
= true; };
382 void closeWindow () {
383 doSaveState(true); // forced state save
384 if (!sdwindow
.closed
&& vg
!is null) {
385 if (curImg
>= 0) { vg
.deleteImage(curImg
); curImg
= -1; }
386 if (curImgWhite
>= 0) { vg
.deleteImage(curImgWhite
); curImgWhite
= -1; }
394 sdwindow
.visibleForTheFirstTime
= delegate () {
395 sdwindow
.setAsCurrentOpenGlContext(); // make this window active
396 sdwindow
.vsync
= false;
397 //sdwindow.useGLFinish = false;
398 //glbindLoadFunctions();
401 uint flags
= NVG_DEBUG
;
402 if (flagNanoAA
) flags |
= NVG_ANTIALIAS
;
403 if (flagNanoSS
) flags |
= NVG_STENCIL_STROKES
;
404 vg
= createGL2NVG(flags
);
407 writeln("Could not init nanovg.");
412 curImg
= createCursorImage(vg
);
413 curImgWhite
= createCursorImage(vg
, true);
414 fps
= new PerfGraph("Frame Time", PerfGraph
.Style
.FPS
, "ui");
415 } catch (Exception e
) {
416 import std
.stdio
: stderr
;
417 stderr
.writeln("ERROR: ", e
.msg
);
425 sdwindow
.redrawOpenGlScene();
429 void relayout (bool forced
=false) {
430 if (laytext
!is null) {
432 auto lidx
= laytext
.findLineAtY(topY
);
433 if (lidx
>= 0) widx
= laytext
.line(lidx
).wstart
;
434 int maxWidth
= GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2;
435 //if (maxWidth < MinWinWidth) maxWidth = MinWinWidth;
438 auto stt
= MonoTime
.currTime
;
439 laytext
.relayout(maxWidth
, forced
);
440 auto ett
= MonoTime
.currTime
-stt
;
441 writeln("relayouted in ", ett
.total
!"msecs", " milliseconds; lines:", laytext
.lineCount
, "; words:", laytext
.nextWordIndex
);
448 sdwindow
.windowResized
= delegate (int w
, int h
) {
449 //writeln("w=", w, "; h=", h);
450 //if (w < MinWinWidth) w = MinWinWidth;
451 //if (h < MinWinHeight) h = MinWinHeight;
452 glViewport(0, 0, w
, h
);
455 textHeight
= GHeight
-8;
460 void drawShipName () {
461 if (shipModel
is null || shipModel
.name
.length
== 0) return;
462 vg
.fontFaceId(uiFont
);
463 vg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Baseline
);
464 vg
.fontSize(fsizeUI
);
465 auto w
= vg
.bndLabelWidth(-1, shipModel
.name
)+8;
466 float h
= BND_WIDGET_HEIGHT
+8;
467 float mx
= (GWidth
-w
)/2.0;
468 float my
= (GHeight
-h
)-8;
469 vg
.bndMenuBackground(mx
, my
, w
, h
, BND_CORNER_NONE
);
470 vg
.bndMenuItem(mx
+4, my
+4, w
-8, BND_WIDGET_HEIGHT
, BND_DEFAULT
, -1, shipModel
.name
);
471 if (shipModel
.dispName
&& shipModel
.dispName
!= shipModel
.name
) {
472 my
-= BND_WIDGET_HEIGHT
+16;
473 w
= vg
.bndLabelWidth(-1, shipModel
.dispName
)+8;
475 vg
.bndMenuBackground(mx
, my
, w
, h
, BND_CORNER_NONE
);
476 vg
.bndMenuItem(mx
+4, my
+4, w
-8, BND_WIDGET_HEIGHT
, BND_DEFAULT
, -1, shipModel
.dispName
);
480 void createSectionMenu () {
482 //writeln("lc=", laytext.lineCount, "; bm: ", (bookmeta !is null));
483 if (laytext
is null || laytext
.lineCount
== 0 || bookmeta
is null) { freeShipModel(); return; }
484 currPopup
= new PopupMenu(vg
, "Sections"d
, () {
486 foreach (const ref sc
; bookmeta
.sections
) items
~= sc
.name
;
489 //writeln(currPopup.items.length);
490 // find current section
491 currPopup
.curItemIdx
= 0;
492 auto lidx
= laytext
.findLineAtY(topY
);
493 if (lidx
>= 0 && bookmeta
.sections
.length
> 0) {
494 foreach (immutable sidx
, const ref sc
; bookmeta
.sections
) {
495 auto sline
= laytext
.findLineWithWord(sc
.wordidx
);
496 if (sline
>= 0 && lidx
>= sline
) currPopup
.curItemIdx
= cast(int)sidx
;
499 onPopupSelect
= (int item
) { gotoSection(item
); };
502 void createQuitMenu (bool wantYes
) {
504 currPopup
= new PopupMenu(vg
, "Quit?"d
, () {
505 return ["Yes"d
, "No"d
];
507 currPopup
.curItemIdx
= (wantYes ?
0 : 1);
508 onPopupSelect
= (int item
) { if (item
== 0) doQuit
= true; };
511 void createRecentMenu () {
513 if (recentFiles
.length
== 0) recentFiles
= loadDetailedHistory();
514 if (recentFiles
.length
== 0) { freeShipModel(); return; }
515 currPopup
= new PopupMenu(vg
, "Recent files"d
, () {
516 import std
.conv
: to
;
518 foreach (const ref BookInfo bi
; recentFiles
) {
520 if (bi
.seqname
.length
) {
522 if (bi
.seqnum
) { s
~= to
!string(bi
.seqnum
); s
~= ": "; }
523 //writeln(bi.seqname);
527 if (bi
.author
.length
) { s
~= " \xe2\x80\x94 "; s
~= bi
.author
; }
532 currPopup
.curItemIdx
= cast(int)recentFiles
.length
-1;
533 onPopupSelect
= (int item
) {
534 newBookFileName
= recentFiles
[item
].diskfile
;
535 popupNoShipKill
= true;
539 bool menuKey (KeyEvent event
) {
540 if (formatWorks
!= 0) return false;
541 if (!inMenu
) return false;
542 if (inGalaxyMap
) return false;
543 if (!event
.pressed
) return false;
544 auto res
= currPopup
.onKey(event
);
545 if (res
== PopupMenu
.Close
) {
549 } else if (res
>= 0) {
550 if (onPopupSelect
!is null) onPopupSelect(res
);
552 if (popupNoShipKill
) popupNoShipKill
= false; else freeShipModel();
558 bool menuMouse (MouseEvent event
) {
559 if (formatWorks
!= 0) return false;
560 if (!inMenu
) return false;
561 if (inGalaxyMap
) return false;
562 auto res
= currPopup
.onMouse(event
);
563 if (res
== PopupMenu
.Close
) {
567 } else if (res
>= 0) {
568 if (onPopupSelect
!is null) onPopupSelect(res
);
570 if (popupNoShipKill
) popupNoShipKill
= false; else freeShipModel();
576 bool readerKey (KeyEvent event
) {
577 if (formatWorks
!= 0) return false;
578 if (!event
.pressed
) {
580 case Key
.Up
: arrowDir
= 0; return true;
581 case Key
.Down
: arrowDir
= 0; return true;
588 if (event
.modifierState
&ModifierState
.shift
) {
589 //goto case Key.PageUp;
590 hardScrollBy(toMove
); toMove
= 0;
591 scrollBy(-textHeight
/3*2);
593 //goto case Key.PageDown;
594 hardScrollBy(toMove
); toMove
= 0;
595 scrollBy(textHeight
/3*2);
602 hardScrollBy(toMove
); toMove
= 0;
603 hardScrollBy(-(textHeight
> 32 ? textHeight
-32 : textHeight
));
606 hardScrollBy(toMove
); toMove
= 0;
607 hardScrollBy(textHeight
> 32 ? textHeight
-32 : textHeight
);
625 bool controlKey (KeyEvent event
) {
626 if (!event
.pressed
) return false;
629 if (inGalaxyMap
) { inGalaxyMap
= false; refresh(); return true; }
630 if (inMenu
) { closeMenu(); freeShipModel(); refresh(); return true; }
631 if (showShip
) { showShip
= false; freeShipModel(); refresh(); return true; }
633 createQuitMenu(true);
636 case Key
.P
: if (event
.modifierState
== ModifierState
.ctrl
) { fpsVisible
= !fpsVisible
; refresh(); return true; } break;
637 case Key
.I
: if (event
.modifierState
== ModifierState
.ctrl
) { interAllowed
= !interAllowed
; refresh(); return true; } break;
638 case Key
.N
: if (event
.modifierState
== ModifierState
.ctrl
) { sbLeft
= !sbLeft
; refresh(); return true; } break;
639 case Key
.C
: if (event
.modifierState
== ModifierState
.ctrl
) { if (addIf()) refresh(); return true; } break;
641 if (event
.modifierState
== ModifierState
.ctrl
&& eliteShipFiles
.length
> 0) {
643 showShip
= !showShip
;
644 if (showShip
) ensureShipModel(); else freeShipModel();
650 case Key
.Q
: if (event
.modifierState
== ModifierState
.ctrl
) { doQuit
= true; return true; } break;
651 case Key
.B
: if (formatWorks
== 0 && !inMenu
&& !inGalaxyMap
&& event
.modifierState
== ModifierState
.ctrl
) { pushPosition(); return true; } break;
653 if (formatWorks
== 0 && !showShip
&& !inMenu
&& !inGalaxyMap
) {
660 if (formatWorks
== 0 && event
.modifierState
== ModifierState
.alt
) {
667 if (!inMenu
&& !showShip
) {
668 inGalaxyMap
= !inGalaxyMap
;
673 if (formatWorks
== 0 && event
.modifierState
== ModifierState
.ctrl
) relayout(true);
675 case Key
.Home
: if (showShip
) { setShip(0); return true; } break;
676 case Key
.End
: if (showShip
) { setShip(cast(int)eliteShipFiles
.length
); return true; } break;
677 case Key
.Up
: case Key
.Left
: if (showShip
) { setShip(shipModelIndex
-1); return true; } break;
678 case Key
.Down
: case Key
.Right
: if (showShip
) { setShip(shipModelIndex
+1); return true; } break;
684 static int startX () { pragma(inline
, true); return (sbLeft ?
2+BND_SCROLLBAR_WIDTH
+2 : 4); }
685 static int endX () { pragma(inline
, true); return startX
+GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2-1; }
687 int startY () { pragma(inline
, true); return (GHeight
-textHeight
)/2; }
688 int endY () { pragma(inline
, true); return startY
+textHeight
-1; }
690 sdwindow
.redrawOpenGlScene
= delegate () {
694 curt
= MonoTime
.currTime
;
695 secs
= cast(double)((curt
-stt
).total
!"msecs")/1000.0;
696 dt = cast(double)((curt
-prevt
).total
!"msecs")/1000.0;
698 //glClearColor(0, 0, 0, 0);
699 //glClearColor(0.18, 0.18, 0.18, 0);
700 glClearColor(colorBack
.r
, colorBack
.g
, colorBack
.b
, 0);
701 glClear(glNVGClearFlags|EliteModel
.glClearFlags
);
704 needRedrawFlag
= (fps
!is null && fpsVisible
);
705 if (fps
!is null) fps
.update(dt);
706 if (vg
is null) return;
708 scope(exit
) vg
.releaseImages();
709 vg
.beginFrame(GWidth
, GHeight
, 1);
713 float curHeight
= (laytext
!is null ? topY
: 0);
714 float th
= (laytext
!is null ? laytext
.textHeight
-textHeight
: 0);
715 if (th
<= 0) { curHeight
= 0; th
= 1; }
716 float sz
= cast(float)(GHeight
-4)/th
;
717 if (sz
> 1) sz
= 1; else if (sz
< 0.05) sz
= 0.05;
718 int sx
= (sbLeft ?
2 : GWidth
-BND_SCROLLBAR_WIDTH
-2);
719 vg
.bndScrollBar(sx
, 2, BND_SCROLLBAR_WIDTH
, GHeight
-4, BND_DEFAULT
, curHeight
/th
, sz
);
721 if (laytext
is null) {
722 if (shipModel
is null) {
724 vg
.fillColor(colorText
);
725 int drawY
= (GHeight
-textHeight
)/2;
726 vg
.intersectScissor((sbLeft ?
2+BND_SCROLLBAR_WIDTH
+2 : 4), drawY
, GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2, textHeight
);
727 vg
.fontFaceId(textFont
);
728 vg
.fontSize(fsizeText
);
729 vg
.textAlign(NVGTextAlign
.H
.Center
, NVGTextAlign
.V
.Middle
);
730 vg
.text(GWidth
/2, GHeight
/2, "REFORMATTING");
735 if (laytext
!is null && laytext
.lineCount
) {
737 vg
.fillColor(colorText
);
739 vg
.intersectScissor((sbLeft ?
2+BND_SCROLLBAR_WIDTH
+2 : 4), drawY
, GWidth
-4-2-BND_SCROLLBAR_WIDTH
-2, textHeight
);
740 //FIXME: not GHeight!
741 int lidx
= laytext
.findLineAtY(topY
);
742 if (lidx
>= 0 && lidx
< laytext
.lineCount
) {
743 drawY
-= topY
-laytext
.line(lidx
).y
;
744 vg
.textAlign(NVGTextAlign
.H
.Left
, NVGTextAlign
.V
.Baseline
);
745 LayFontStyle lastStyle
;
747 while (lidx
< laytext
.lineCount
&& drawY
< GHeight
) {
748 auto ln
= laytext
.line(lidx
);
749 foreach (ref LayWord w
; laytext
.lineWords(lidx
)) {
750 if (lastStyle
!= w
.style
) {
751 if (w
.style
.fontface
!= lastStyle
.fontface
) vg
.fontFace(laytext
.fontFace(w
.style
.fontface
));
752 vg
.fontSize(w
.style
.fontsize
);
753 auto c
= NVGColor(w
.style
.color
);
759 if (newYFade
&& newYLine
== lidx
) vg
.fillColor(nvgLerpRGBA(colorText
, colorTextHi
, newYAlpha
));
760 auto oid
= w
.objectIdx
;
763 laytext
.objects
[oid
].draw(vg
, startx
+w
.x
, drawY
+ln
.h
+ln
.desc
);
766 vg
.text(startx
+w
.x
, drawY
+ln
.h
+ln
.desc
, laytext
.wordText(w
));
768 //TODO: draw lines over whitespace
769 if (lastStyle
.underline
) vg
.rect(startx
+w
.x
, drawY
+ln
.h
+ln
.desc
+1, w
.w
, 1);
770 if (lastStyle
.strike
) vg
.rect(startx
+w
.x
, drawY
+ln
.h
+ln
.desc
-w
.asc
/2, w
.w
, 1);
771 if (lastStyle
.overline
) vg
.rect(startx
+w
.x
, drawY
+ln
.h
+ln
.desc
-w
.asc
-1, w
.w
, 1);
772 version(debug_draw
) {
776 vg
.strokeColor(nvgRGB(0, 0, 255));
777 vg
.rect(startx
+w
.x
, drawY
, w
.w
, w
.h
);
781 if (newYFade
&& newYLine
== lidx
) vg
.fillColor(NVGColor(lastStyle
.color
));
791 if (colorDim
.a
!= 1) {
792 //vg.scissor(0, 0, GWidth, GHeight);
795 vg
.fillColor(colorDim
);
796 vg
.rect(0, 0, GWidth
, GHeight
);
800 // dim more if menu is active
801 if (inMenu || showShip || formatWorks
!= 0) {
802 //vg.scissor(0, 0, GWidth, GHeight);
805 //vg.globalAlpha(0.5);
806 vg
.fillColor(nvgRGBA(0, 0, 0, (showShip || formatWorks
!= 0 ?
127 : 64)));
807 vg
.rect(0, 0, GWidth
, GHeight
);
810 if (shipModel
!is null) {
812 float zz
= shipModel
.bbox
[1].z
-shipModel
.bbox
[0].z
;
826 drawModel(shipAngle
, shipModel
);
827 vg
.beginFrame(GWidth
, GHeight
, 1);
831 if (formatWorks
== 0) {
833 //vg.beginFrame(GWidth, GHeight, 1);
834 //vg.scissor(0, 0, GWidth, GHeight);
840 if (fps
!is null && fpsVisible
) {
841 //vg.beginFrame(GWidth, GHeight, 1);
842 //vg.scissor(0, 0, GWidth, GHeight);
844 fps
.render(vg
, GWidth
-fps
.width
-4, GHeight
-fps
.height
-4);
847 if (inGalaxyMap
) drawGalaxy(vg
);
849 if (curImg
>= 0 && !mouseHidden
) {
851 //vg.beginFrame(GWidth, GHeight, 1);
853 //vg.scissor(0, 0, GWidth, GHeight);
855 vg
.imageSize(curImg
, &w
, &h
);
857 vg
.fillPaint(vg
.imagePattern(mouseX
, mouseY
, w
, h
, 0, curImg
, 1));
859 vg
.fillPaint(vg
.imagePattern(mouseX
, mouseY
, w
, h
, 0, curImgWhite
, 1));
861 vg
.rect(mouseX
, mouseY
, w
, h
);
866 vg.fillPaint(vg.imagePattern(mouseX, mouseY, w, h, 0, curImgWhite, 0.4));
867 vg.rect(mouseX, mouseY, w, h);
876 void processThreads () {
877 ReformatWorkComplete wd
;
879 bool workDone
= false;
880 auto res
= receiveTimeout(Duration
.zero
,
884 (ReformatWorkComplete w
) {
889 if (!res
) { assert(!workDone
); break; }
890 if (workDone
) { workDone
= false; formatComplete(wd
); }
894 sdwindow
.eventLoop(1000/35,
897 if (sdwindow
.closed
) return;
898 if (doQuit
) { closeWindow(); return; }
899 auto ctt
= MonoTime
.currTime
;
901 if (formatWorks
== 0) {
905 if (sc
> Delta
) sc
= Delta
;
908 nextFadeTime
= ctt
+500.msecs
;
909 } else if (toMove
> 0) {
911 if (sc
> Delta
) sc
= Delta
;
914 nextFadeTime
= ctt
+500.msecs
;
915 } else if (arrowDir
) {
916 hardScrollBy(arrowDir
*6*2);
920 if (ctt
>= nextFadeTime
) {
921 if ((newYAlpha
-= 0.1) <= 0) {
924 nextFadeTime
= ctt
+25.msecs
;
930 // interference processing
931 if (ctt
>= nextIfTime
) {
932 import std
.random
: uniform
;
933 if (uniform
!"[]"(0, 100) >= 50) { if (addIf()) refresh(); }
934 nextIfTime
+= (uniform
!"[]"(50, 1500)).msecs
;
936 if (processIfs()) refresh();
938 if (shipModel
!is null) {
940 if (shipAngle
< 359) shipAngle
+= 360;
944 if (!mouseHidden
&& !mouseHigh
) {
945 if ((ctt
-lastMMove
).total
!"msecs" > 2500) {
950 if (needRedraw
) sdwindow
.redrawOpenGlSceneNow();
953 if (newBookFileName
.length
&& formatWorks
== 0) {
954 doSaveState(true); // forced state save
957 loadAndFormat(newBookFileName
);
958 newBookFileName
= null;
959 sdwindow
.redrawOpenGlSceneNow();
963 delegate (KeyEvent event
) {
964 if (sdwindow
.closed
) return;
965 if (event
.key
== Key
.PadEnter
) event
.key
= Key
.Enter
;
966 if ((event
.modifierState
&ModifierState
.numLock
) == 0) {
968 case Key
.Pad0
: event
.key
= Key
.Insert
; break;
969 case Key
.PadDot
: event
.key
= Key
.Delete
; break;
970 case Key
.Pad1
: event
.key
= Key
.End
; break;
971 case Key
.Pad2
: event
.key
= Key
.Down
; break;
972 case Key
.Pad3
: event
.key
= Key
.PageDown
; break;
973 case Key
.Pad4
: event
.key
= Key
.Left
; break;
974 case Key
.Pad6
: event
.key
= Key
.Right
; break;
975 case Key
.Pad7
: event
.key
= Key
.Home
; break;
976 case Key
.Pad8
: event
.key
= Key
.Up
; break;
977 case Key
.Pad9
: event
.key
= Key
.PageUp
; break;
978 //case Key.PadEnter: event.key = Key.Enter; break;
982 if (controlKey(event
)) return;
983 if (menuKey(event
)) return;
984 if (readerKey(event
)) return;
986 delegate (MouseEvent event
) {
987 int linkAt (int msx
, int msy
) {
988 if (laytext
!is null && bookmeta
!is null) {
989 if (msx
>= startX
&& msx
<= endX
&& msy
>= startY
&& msy
<= endY
) {
990 auto widx
= laytext
.wordAtXY(msx
-startX
, topY
+msy
-startY
);
992 //writeln("word at (", msx-startX, ",", msy-startY, "): ", widx);
993 auto w
= laytext
.wordByIndex(widx
);
995 //writeln("word #", widx, "; href=", w.style.href);
996 if (!w
.style
.href
) break;
997 if (auto hr
= w
.wordNum
in bookmeta
.hrefs
) {
998 dstring href
= hr
.name
;
999 if (href
.length
> 1 && href
[0] == '#') {
1001 foreach (const ref id
; bookmeta
.ids
) {
1002 if (id
.name
== href
) {
1008 //writeln("id '", hr.name, "' not found!");
1021 lastMMove
= MonoTime
.currTime
;
1023 mouseHidden
= false;
1026 if (mouseX
!= event
.x || mouseY
!= event
.y
) {
1031 if (!menuMouse(event
) && !showShip
) {
1032 if (event
.type
== MouseEventType
.buttonPressed
) {
1033 switch (event
.button
) {
1034 case MouseButton
.wheelUp
: hardScrollBy(-42); break;
1035 case MouseButton
.wheelDown
: hardScrollBy(42); break;
1036 case MouseButton
.left
:
1037 auto wid
= linkAt(event
.x
, event
.y
);
1043 case MouseButton
.right
:
1050 mouseHigh
= (linkAt(event
.x
, event
.y
) >= 0);
1052 delegate (dchar ch
) {
1053 //if (ch == 'q') { doQuit = true; return; }
1058 childTid
.send(QuitWork());
1059 while (formatWorks
>= 0) processThreads();
1063 // ////////////////////////////////////////////////////////////////////////// //
1064 void main (string
[] args
) {
1067 universe
= Galaxy(0);
1069 if (args
.length
== 1) {
1072 foreach (string line
; VFile(buildPath(RcDir
, ".lastfile")).byLineCopy
) {
1073 if (!line
.isComment
) lnn
= line
;
1075 if (lnn
.length
) args
~= lnn
;
1076 } catch (Exception
) {}
1078 if (args
.length
== 1) assert(0, "no filename");