1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
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 iv
.vl2
.core
/*is aliced*/;
20 public import iv
.sdl2
.sdl
;
22 private import core
.sys
.posix
.time
;
23 // idiotic phobos forgets 'nothrow' at it
24 extern(C
) private int clock_gettime (clockid_t
, timespec
*) @trusted nothrow @nogc;
28 static import gcc
.attribute
;
29 private enum gcc_inline
= gcc
.attribute
.attribute("forceinline");
30 private enum gcc_noinline
= gcc
.attribute
.attribute("noinline");
31 private enum gcc_flatten
= gcc
.attribute
.attribute("flatten");
33 // hackery for non-gcc compilers
34 private enum gcc_inline
;
35 private enum gcc_noinline
;
36 private enum gcc_flatten
;
40 // ////////////////////////////////////////////////////////////////////////// //
41 /// generic VideoLib exception
42 class VideoLibError
: Exception
{
43 this (string msg
, string file
=__FILE__
, usize line
=__LINE__
, Throwable next
=null) @safe pure nothrow @nogc {
44 super(msg
, file
, line
, next
);
49 // ////////////////////////////////////////////////////////////////////////// //
58 /// is videolib initialized?
61 Partial
, /// not fully (i.e. `vlInit()` failed in the middle)
62 Done
/// completely initialized
66 // ////////////////////////////////////////////////////////////////////////// //
67 private shared VLInitState pvInited
= VLInitState
.Not
;
69 /// is VideoLib properly initialized and videomode set?
70 @property VLInitState
vlInitState () @trusted nothrow @nogc {
71 import core
.atomic
: atomicLoad
;
72 return atomicLoad(pvInited
);
75 // ////////////////////////////////////////////////////////////////////////// //
76 shared bool vlFullscreen
= false; /// use fullscreen mode? this can be set to true before calling vlInit()
77 shared bool vlRealFullscreen
= false; /// use 'real' fullscreen? defaul: false
78 shared bool vlVSync
= false; /// wait VBL? this can be set to true before calling initVideo()
79 shared bool vlMag2x
= true; // set to true to create double-sized window; default: true; have no effect after calling vlInit()
80 shared bool vlScanlines
= true; // set to true to use 'scanline' filter in mag2x mode; default: true
81 shared VLFilter vlFilter
= VLFilter
.None
; /// output filter; default: VLFilter.None
83 __gshared SDL_Window
* sdlWindow
= null; /// current SDL window; DON'T CHANGE THIS!
86 // ////////////////////////////////////////////////////////////////////////// //
87 private __gshared SDL_Renderer
* sdlRenderer
= null; // current SDL rendering context
88 private __gshared SDL_Texture
* sdlScreen
= null; // current SDL screen
90 private shared bool sdlFrameChangedFlag
= false; // this will be set to false by vlPaintFrame()
92 package __gshared
void function () vlInitHook
= null;
93 package __gshared
void function () vlDeinitHook
= null;
96 // ////////////////////////////////////////////////////////////////////////// //
97 /// call this if you want VideoLib to call rebuild callback
98 void vlFrameChanged () @trusted nothrow @nogc {
99 import core
.atomic
: atomicStore
;
100 atomicStore(sdlFrameChangedFlag
, true);
103 @property bool vlIsFrameChanged () @trusted nothrow @nogc {
104 import core
.atomic
: atomicLoad
;
105 return atomicLoad(sdlFrameChangedFlag
);
108 /// screen dimensions, should be changed prior to calling vlInit()
109 private shared uint vsWidth
= 320;
110 private shared uint vsHeight
= 240;
112 /// get current screen width
113 @property uint vlWidth() () @trusted nothrow @nogc {
114 import core
.atomic
: atomicLoad
;
115 return atomicLoad(vsWidth
);
118 /// get current screen height
119 @property uint vlHeight() () @trusted nothrow @nogc {
120 import core
.atomic
: atomicLoad
;
121 return atomicLoad(vsHeight
);
124 /// set screen width; must be used before vlInit()
125 @property void vlWidth (int wdt
) @trusted {
126 import core
.atomic
: atomicLoad
, atomicStore
;
127 if (atomicLoad(pvInited
) != VLInitState
.Not
) throw new VideoLibError("trying to change screen width after initialization");
128 if (wdt
< 1 || wdt
> 8192) throw new VideoLibError("invalid screen width");
129 atomicStore(vsWidth
, wdt
);
132 /// set screen height; must be used before vlInit()
133 @property void vlHeight (int hgt
) @trusted {
134 import core
.atomic
: atomicLoad
, atomicStore
;
135 if (atomicLoad(pvInited
) != VLInitState
.Not
) throw new VideoLibError("trying to change screen height after initialization");
136 if (hgt
< 1 || hgt
> 8192) throw new VideoLibError("invalid screen height");
137 atomicStore(vsHeight
, hgt
);
140 private __gshared
int effectiveMag2x
; // effective vlMag2x
141 private __gshared SDL_Texture
* sdlScr2x
= null;
142 private __gshared
int prevLogSizeWas1x
= 0; // DON'T change to bool!
144 __gshared
uint* vlVScr
= null; /// current SDL 'virtual screen', ARGB format for LE
145 private __gshared
uint* vscr2x
= null; // this is used in magnifying blitters
148 // ////////////////////////////////////////////////////////////////////////// //
150 * Process CLI args from main().
153 * args = command line arguments
156 * command line with processed arguments removed
158 void vlProcessArgs (ref string
[] args
) @trusted nothrow {
160 for (uint idx
= 1; idx
< args
.length
; ++idx
) {
161 auto arg
= args
[idx
];
162 if (arg
== "--") break;
163 if (arg
.length
< 3 || arg
[0] != '-' || arg
[1] != '-') continue;
165 if (arg
.length
> 5 && arg
[2..5] == "no-") {
172 case "fs": vlFullscreen
= yes
; break;
173 case "win": vlFullscreen
= !yes
; break;
174 case "realfs": vlRealFullscreen
= yes
; break;
175 case "winfs": vlRealFullscreen
= !yes
; break;
176 case "tv": vlScanlines
= yes
; break;
177 case "bw": if (yes
) vlFilter
= VLFilter
.BlackNWhite
; break;
178 case "green": if (yes
) vlFilter
= VLFilter
.Green
; break;
179 case "1x": vlMag2x
= !yes
; break;
180 case "2x": vlMag2x
= yes
; break;
181 case "vbl": vlVSync
= yes
; break;
184 import core
.stdc
.stdlib
: exit
;
185 import std
.exception
: collectException
;
186 import std
.stdio
: stdout
;
187 collectException(stdout
.write(
188 "video options (add \"no-\" to negate):\n"~
191 " --realfs use \"real\" fullscreen\n"
192 " --winfs use \"windowed\" fullscreen\n"
193 " --tv scanlines filter\n"
194 " --bw black-and-white filter\n"
195 " --green green filter\n"
196 " --1x normal size\n"
202 default: continue main_loop
;
205 foreach (immutable c
; idx
+1..args
.length
) args
[c
-1] = args
[c
];
207 --idx
; // compensate for removed element
212 private void sdlPrintError () @trusted nothrow @nogc {
213 import core
.stdc
.stdio
: stderr
, fprintf
;
214 auto sdlError
= SDL_GetError();
215 fprintf(stderr
, "SDL ERROR: %s\n", sdlError
);
219 private void sdlError (bool raise
) @trusted {
222 throw new VideoLibError("SDL fucked");
228 * Initialize SDL, create drawing window and buffers, init other shit.
231 * windowName = window caption
237 * VideoLibError on error
239 void vlInit (string windowName
=null) @trusted {
240 import core
.atomic
: atomicLoad
, atomicStore
;
242 final switch (atomicLoad(pvInited
)) with (VLInitState
) {
244 case Partial
: throw new VideoLibError("can't continue initialization");
248 import core
.exception
: onOutOfMemoryError
;
249 import core
.stdc
.stdlib
: malloc
, free
;
250 import std
.string
: toStringz
;
253 if (windowName
is null) windowName
= "SDL Application";
255 effectiveMag2x
= (vlMag2x ?
2 : 1);
256 prevLogSizeWas1x
= 1;
258 if (vlVScr
!is null) free(vlVScr
);
259 vlVScr
= cast(uint*)malloc(vsWidth
*vsHeight
*vlVScr
[0].sizeof
);
260 if (vlVScr
is null) onOutOfMemoryError();
262 if (vscr2x
!is null) free(vscr2x
);
263 vscr2x
= cast(uint*)malloc(vsWidth
*effectiveMag2x
*vsHeight
*effectiveMag2x
*vscr2x
[0].sizeof
);
264 if (vscr2x
is null) onOutOfMemoryError();
266 SDL_Init(SDL_INIT_VIDEO|SDL_INIT_EVENTS|SDL_INIT_TIMER
);
267 atomicStore(pvInited
, VLInitState
.Partial
);
269 sdlWindow
= SDL_CreateWindow(
270 windowName
.toStringz
,
271 SDL_WINDOWPOS_UNDEFINED
, SDL_WINDOWPOS_UNDEFINED
,
272 vsWidth
*effectiveMag2x
, vsHeight
*effectiveMag2x
,
273 (vlFullscreen ?
(vlRealFullscreen ? SDL_WINDOW_FULLSCREEN
: SDL_WINDOW_FULLSCREEN_DESKTOP
) : 0));
274 sdlError(!sdlWindow
);
276 sdlRenderer
= SDL_CreateRenderer(sdlWindow
, -1, (vlVSync ? SDL_RENDERER_PRESENTVSYNC
: 0));
277 sdlError(!sdlRenderer
);
279 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY
, "nearest"); // "nearest" or "linear"
280 SDL_RenderSetLogicalSize(sdlRenderer
, vsWidth
*(2-prevLogSizeWas1x
), vsHeight
*(2-prevLogSizeWas1x
));
282 sdlScreen
= SDL_CreateTexture(sdlRenderer
, SDL_PIXELFORMAT_ARGB8888
, SDL_TEXTUREACCESS_STREAMING
, vsWidth
, vsHeight
);
283 sdlError(!sdlScreen
);
285 if (effectiveMag2x
== 2) {
286 sdlScr2x
= SDL_CreateTexture(sdlRenderer
, SDL_PIXELFORMAT_ARGB8888
, SDL_TEXTUREACCESS_STREAMING
, vsWidth
*2, vsHeight
*2);
292 if (vlInitHook
!is null) vlInitHook();
294 atomicStore(pvInited
, VLInitState
.Done
);
299 * Deinitialize SDL, free resources.
307 private void vlDeinitInternal () /*@trusted nothrow @nogc*/ {
308 import core
.atomic
: atomicLoad
, atomicStore
;
309 import core
.stdc
.stdlib
: free
;
311 if (atomicLoad(pvInited
) == VLInitState
.Not
) return;
313 if (vlDeinitHook
!is null) vlDeinitHook();
315 if (sdlScreen
!is null) { SDL_DestroyTexture(sdlScreen
); sdlScreen
= null; }
316 if (sdlScr2x
!is null) { SDL_DestroyTexture(sdlScr2x
); sdlScr2x
= null; }
317 if (sdlRenderer
!is null) { SDL_DestroyRenderer(sdlRenderer
); sdlRenderer
= null; }
318 if (sdlWindow
) { SDL_DestroyWindow(sdlWindow
); sdlWindow
= null; }
319 if (vlVScr
!is null) { free(vlVScr
); vlVScr
= null; }
320 if (vscr2x
!is null) { free(vscr2x
); vscr2x
= null; }
322 atomicStore(pvInited
, VLInitState
.Not
);
327 * Shutdown SDL, free resources. You don't need to call this explicitely.
335 void vlDeinit () /*@trusted nothrow @nogc*/ {
336 import core
.atomic
: atomicLoad
;
337 if (atomicLoad(pvInited
) != VLInitState
.Not
) {
345 * Switches between fullscreen and windowed modes.
353 void vlSwitchFullscreen () @trusted nothrow {
354 import core
.atomic
: atomicLoad
, atomicStore
;
355 bool ns
= !atomicLoad(vlFullscreen
);
356 if (SDL_SetWindowFullscreen(sdlWindow
, (ns ?
(atomicLoad(vlRealFullscreen
) ? SDL_WINDOW_FULLSCREEN
: SDL_WINDOW_FULLSCREEN_DESKTOP
) : 0)) != 0) {
361 SDL_SetWindowSize(sdlWindow
, vsWidth
*effectiveMag2x
, vsHeight
*effectiveMag2x
);
362 SDL_SetWindowPosition(sdlWindow
, SDL_WINDOWPOS_CENTERED
, SDL_WINDOWPOS_CENTERED
);
364 atomicStore(vlFullscreen
, ns
);
369 * Post SDL_QUIT message. Can be called to initiate clean exit from main loop.
377 void vlPostQuitMessage () @trusted nothrow @nogc {
378 import core
.stdc
.string
: memset
;
379 SDL_Event evt
= void;
380 memset(&evt
, 0, evt
.sizeof
);
386 // ////////////////////////////////////////////////////////////////////////// //
387 @gcc_inline ubyte clampToByte(T
) (T n
) @safe pure nothrow @nogc
388 if (__traits(isIntegral
, T
) && (T
.sizeof
== 2 || T
.sizeof
== 4))
390 static if (__VERSION__
> 2067) pragma(inline
, true);
391 n
&= -cast(int)(n
>= 0);
392 return cast(ubyte)(n|
((255-cast(int)n
)>>31));
395 @gcc_inline ubyte clampToByte(T
) (T n
) @safe pure nothrow @nogc
396 if (__traits(isIntegral
, T
) && T
.sizeof
== 1)
398 static if (__VERSION__
> 2067) pragma(inline
, true);
403 // ////////////////////////////////////////////////////////////////////////// //
406 version(LittleEndian
) {
407 /// vlRGBA struct to ease color components extraction/replacing
408 align(1) struct vlRGBA
{
412 } else version(BigEndian
) {
413 /// vlRGBA struct to ease color components extraction/replacing
414 align(1) struct vlRGBA
{
419 static assert(0, "WTF?!");
422 static assert(vlRGBA
.sizeof
== Color
.sizeof
);
426 vlAMask
= 0xff000000u
,
427 vlRMask
= 0x00ff0000u
,
428 vlGMask
= 0x0000ff00u
,
429 vlBMask
= 0x000000ffu
440 enum Color vlcTransparent
= vlAMask
; /// completely transparent pixel color
444 * Is color completely transparent?
452 @gcc_inline bool vlcIsTransparent(T
: Color
) (T col
) @safe pure nothrow @nogc {
453 static if (__VERSION__
> 2067) pragma(inline
, true);
454 return ((col
&vlAMask
) == vlAMask
);
458 * Is color completely non-transparent?
466 @gcc_inline bool isOpaque(T
: Color
) (T col
) @safe pure nothrow @nogc {
467 static if (__VERSION__
> 2067) pragma(inline
, true);
468 return ((col
&vlAMask
) == 0);
472 * Build opaque non-transparent rgb color from components.
475 * r = red component [0..255]
476 * g = green component [0..255]
477 * b = blue component [0..255]
478 * a = transparency [0..255] (0: completely opaque; 255: completely transparent)
483 @gcc_inline Color
rgb2col(TR
, TG
, TB
, TA
=ubyte) (TR r
, TG g
, TB b
, TA a
=0) @safe pure nothrow @nogc
484 if (__traits(isIntegral
, TR
) && __traits(isIntegral
, TG
) && __traits(isIntegral
, TB
) && __traits(isIntegral
, TA
)) {
485 static if (__VERSION__
> 2067) pragma(inline
, true);
487 (clampToByte(a
)<<vlAShift
)|
488 (clampToByte(r
)<<vlRShift
)|
489 (clampToByte(g
)<<vlGShift
)|
490 (clampToByte(b
)<<vlBShift
);
494 * Build rgba color from components.
497 * r = red component [0..255]
498 * g = green component [0..255]
499 * b = blue component [0..255]
500 * a = transparency [0..255] (0: completely opaque; 255: completely transparent)
505 alias rgba2col
= rgb2col
;
508 // generate some templates
509 private enum genRGBGetSet(string cname
) =
510 "@gcc_inline ubyte rgb"~cname
~"() (Color clr) @safe pure nothrow @nogc {\n"~
511 " static if (__VERSION__ > 2067) pragma(inline, true);\n"~
512 " return ((clr>>vl"~cname
[0]~"Shift)&0xff);\n"~
514 "@gcc_inline Color rgbSet"~cname
~"(T) (Color clr, T v) @safe pure nothrow @nogc if (__traits(isIntegral, T)) {\n"~
515 " static if (__VERSION__ > 2067) pragma(inline, true);\n"~
516 " return (clr&~vl"~cname
[0]~"Mask)|(clampToByte(v)<<vl"~cname
[0]~"Shift);\n"~
519 mixin(genRGBGetSet
!"Alpha");
520 mixin(genRGBGetSet
!"Red");
521 mixin(genRGBGetSet
!"Green");
522 mixin(genRGBGetSet
!"Blue");
525 // ////////////////////////////////////////////////////////////////////////// //
526 private enum buildBlit1x(string name
, string op
, string wrt
) =
527 `private void `~name
~` () @trusted nothrow @nogc {`~
528 ` auto s = cast(const(ubyte)*)vlVScr;`~
529 ` auto d = cast(ubyte*)vscr2x;`~
530 ` foreach (immutable _; 0..vsWidth*vsHeight) {`~
531 ` ubyte i = `~op
~`;`~
539 mixin(buildBlit1x
!("blit1xBW", "(s[0]*28+s[1]*151+s[2]*77)/256", "d[0] = d[1] = d[2] = i"));
540 mixin(buildBlit1x
!("blit1xGreen", "(s[0]*28+s[1]*151+s[2]*77)/256", "d[0] = d[2] = 0; d[1] = i"));
543 private enum buildBlit2x(string name
, string op
) =
544 `private void `~name
~` () @trusted nothrow @nogc {`~
545 ` auto s = cast(const(ubyte)*)vlVScr;`~
546 ` auto d = cast(uint*)vscr2x;`~
547 ` immutable auto wdt = vsWidth;`~
548 ` immutable auto wdt2x = vsWidth*2;`~
549 ` foreach (immutable y; 0..vsHeight) {`~
550 ` foreach (immutable x; 0..wdt) {`~
552 ` immutable uint c1 = ((((c0&0x00ff00ff)*6)>>3)&0x00ff00ff)|(((c0&0x0000ff00)*6)>>3)&0x0000ff00;`~
553 ` d[0] = d[1] = c0;`~
554 ` d[wdt2x] = d[wdt2x+1] = c1;`~
558 // fix d: skip one scanline
564 mixin(buildBlit2x
!("blit2xTV", "immutable uint c0 = (cast(immutable(uint)*)s)[0];"));
565 mixin(buildBlit2x
!("blit2xTVBW", "immutable ubyte i = cast(ubyte)((s[0]*28+s[1]*151+s[2]*77)/256); immutable uint c0 = (i<<16)|(i<<8)|i;"));
566 mixin(buildBlit2x
!("blit2xTVGreen", "immutable ubyte i = cast(ubyte)((s[0]*28+s[1]*151+s[2]*77)/256); immutable uint c0 = i<<8;"));
569 // ////////////////////////////////////////////////////////////////////////// //
571 * Paint frame contents.
579 void vlPaintFrame () @trusted nothrow @nogc {
580 import core
.atomic
: atomicStore
;
581 vlPaintFrameDefault();
582 atomicStore(sdlFrameChangedFlag
, false);
587 * Paint virtual screen onto real screen or window.
595 void vlPaintFrameDefault () @trusted nothrow @nogc {
596 import core
.atomic
: atomicLoad
;
597 // fix 'logical size'
598 immutable flt
= atomicLoad(vlFilter
);
599 if (effectiveMag2x
== 2 && atomicLoad(vlScanlines
)) {
600 // mag2x and scanlines: size is 2x
601 if (prevLogSizeWas1x
) {
602 prevLogSizeWas1x
= 0;
603 SDL_RenderSetLogicalSize(sdlRenderer
, vsWidth
*2, vsHeight
*2);
606 // any other case: size is 2x
607 if (!prevLogSizeWas1x
) {
608 prevLogSizeWas1x
= 1;
609 SDL_RenderSetLogicalSize(sdlRenderer
, vsWidth
, vsHeight
);
612 // apply filters if any
613 if (effectiveMag2x
== 2 && atomicLoad(vlScanlines
)) {
614 // heavy case: scanline filter turned on
615 final switch (flt
) with (VLFilter
) {
616 case None
: blit2xTV(); break;
617 case BlackNWhite
: blit2xTVBW(); break;
618 case Green
: blit2xTVGreen(); break;
620 SDL_UpdateTexture(sdlScr2x
, null, vscr2x
, cast(uint)(vsWidth
*2*vscr2x
[0].sizeof
));
621 SDL_RenderCopy(sdlRenderer
, sdlScr2x
, null, null);
624 if (flt
== VLFilter
.None
) {
626 SDL_UpdateTexture(sdlScreen
, null, vlVScr
, cast(uint)(vsWidth
*vlVScr
[0].sizeof
));
628 import core
.stdc
.string
: memcpy
;
629 final switch (flt
) with (VLFilter
) {
630 case None
: memcpy(vscr2x
, vlVScr
, vsWidth
*vsHeight
*vlVScr
[0].sizeof
); break; // just in case
631 case BlackNWhite
: blit1xBW(); break;
632 case Green
: blit1xGreen(); break;
634 SDL_UpdateTexture(sdlScreen
, null, vscr2x
, cast(uint)(vsWidth
*vscr2x
[0].sizeof
));
636 SDL_RenderCopy(sdlRenderer
, sdlScreen
, null, null);
638 SDL_RenderPresent(sdlRenderer
);
642 // ////////////////////////////////////////////////////////////////////////// //
644 private enum CLOCK_MONOTONIC_RAW
= 4;
645 private enum CLOCK_MONOTONIC_COARSE
= 6;
647 static assert(0, "sorry, only GNU/Linux for now; please, fix `vlGetTicks()` and company for your OS!");
651 shared static this () {
656 private __gshared timespec videolib_clock_stt
;
657 private __gshared
int vlClockType
= CLOCK_MONOTONIC_COARSE
;
660 private void vlInitializeClock () @trusted nothrow @nogc {
661 timespec cres
= void;
663 if (clock_getres(vlClockType
, &cres
) == 0) {
664 if (cres
.tv_sec
== 0 && cres
.tv_nsec
<= cast(long)1000000*1 /*1 ms*/) inited
= true;
667 vlClockType
= CLOCK_MONOTONIC_RAW
;
668 if (clock_getres(vlClockType
, &cres
) == 0) {
669 if (cres
.tv_sec
== 0 && cres
.tv_nsec
<= cast(long)1000000*1 /*1 ms*/) inited
= true;
672 if (!inited
) assert(0, "FATAL: can't initialize clock subsystem!");
673 if (clock_gettime(vlClockType
, &videolib_clock_stt
) != 0) {
674 assert(0, "FATAL: can't initialize clock subsystem!");
679 /** returns monitonically increasing time; starting value is UNDEFINED (i.e. can be any number)
680 * milliseconds; (0: no timer available) */
681 @gcc_inline ulong vlGetTicks () @trusted nothrow @nogc {
682 static if (__VERSION__
> 2067) pragma(inline
, true);
684 if (clock_gettime(vlClockType
, &ts
) != 0) assert(0, "FATAL: can't get real-time clock value!\n");
685 // ah, ignore nanoseconds in videolib_clock_stt->stt here: we need only 'differential' time, and it can start with something weird
686 return (cast(ulong)(ts
.tv_sec
-videolib_clock_stt
.tv_sec
))*1000+ts
.tv_nsec
/1000000+1;
690 /** returns monitonically increasing time; starting value is UNDEFINED (i.e. can be any number)
691 * microseconds; (0: no timer available) */
692 @gcc_inline ulong vlGetTicksMicro () @trusted nothrow @nogc {
693 static if (__VERSION__
> 2067) pragma(inline
, true);
695 if (clock_gettime(vlClockType
, &ts
) != 0) assert(0, "FATAL: can't get real-time clock value!\n");
696 // ah, ignore nanoseconds in videolib_clock_stt->stt here: we need only 'differential' time, and it can start with something weird
697 return (cast(ulong)(ts
.tv_sec
-videolib_clock_stt
.tv_sec
))*1000000+ts
.tv_nsec
/1000+1;
701 // ////////////////////////////////////////////////////////////////////////// //
704 enum SDL_MOUSEDOUBLE
= SDL_USEREVENT
+1; /// same as SDL_MouseButtonEvent
705 enum SDL_FREEEVENT
= SDL_USEREVENT
+42; /// first event available for user
708 // ////////////////////////////////////////////////////////////////////////// //
710 * mechanics is like this:
711 * when it's time to show new frame, videolib will call vlOnUpdateCB().
712 * if vlFrameChanged is set (either from previous frame, or by vlOnUpdateCB(),
713 * videolib will call vlOnRebuildCB() and vlPaintFrame().
714 * note that videolib will reset vlFrameChanged after vlOnRebuildCB() called.
715 * also note that if vlOnUpdateCB() or vlOnRebuildCB() will not set vlFrameChanged,
716 * frame visuals will not be updated properly.
717 * you can either update and vlOnRebuildCB screen in vlOnUpdateCB(), or update state in
718 * vlOnUpdateCB() and rebuild virtual screen in vlOnRebuildCB().
719 * you can count on videolib never changes virtual screen contents by itself.
721 /** elapsedTicks: ms elapsed from the last call; will try to keep up with FPS; 0: first call in vlMainLoop()
722 * can call vlFrameChanged() flag so vlMainLoop() will call vlOnRebuildCB() or call vlOnRebuildCB() itself */
724 __gshared
void delegate (int elapsedTicks
) vlOnUpdateCB
= null;
725 __gshared
void delegate () vlOnRebuildCB
= null;
727 __gshared
void delegate (in ref SDL_KeyboardEvent ev
) vlOnKeyDownCB
= null;
728 __gshared
void delegate (in ref SDL_KeyboardEvent ev
) vlOnKeyUpCB
= null;
729 __gshared
void delegate (dchar ch
) vlOnTextInputCB
= null;
731 __gshared
void delegate (in ref SDL_MouseButtonEvent ev
) vlOnMouseDownCB
= null;
732 __gshared
void delegate (in ref SDL_MouseButtonEvent ev
) vlOnMouseUpCB
= null;
733 __gshared
void delegate (in ref SDL_MouseButtonEvent ev
) vlOnMouseClickCB
= null;
734 __gshared
void delegate (in ref SDL_MouseButtonEvent ev
) vlOnMouseDoubleCB
= null; /// mouse double-click
735 __gshared
void delegate (in ref SDL_MouseMotionEvent ev
) vlOnMouseMotionCB
= null;
736 __gshared
void delegate (in ref SDL_MouseWheelEvent ev
) vlOnMouseWheelCB
= null;
740 @trusted nothrow @nogc {
741 private import std
.functional
: toDelegate
;
742 @property void vlOnUpdate (void function (int elapsedTicks
) cb
) { vlOnUpdateCB
= toDelegate(cb
); }
743 @property void vlOnRebuild (void function () cb
) { vlOnRebuildCB
= toDelegate(cb
); }
744 @property void vlOnKeyDown (void function (in ref SDL_KeyboardEvent ev
) cb
) { vlOnKeyDownCB
= toDelegate(cb
); }
745 @property void vlOnKeyUp (void function (in ref SDL_KeyboardEvent ev
) cb
) { vlOnKeyUpCB
= toDelegate(cb
); }
746 @property void vlOnTextInput (void function (dchar ch
) cb
) { vlOnTextInputCB
= toDelegate(cb
); }
747 @property void vlOnMouseDown (void function (in ref SDL_MouseButtonEvent ev
) cb
) { vlOnMouseDownCB
= toDelegate(cb
); }
748 @property void vlOnMouseUp (void function (in ref SDL_MouseButtonEvent ev
) cb
) { vlOnMouseUpCB
= toDelegate(cb
); }
749 @property void vlOnMouseClick (void function (in ref SDL_MouseButtonEvent ev
) cb
) { vlOnMouseClickCB
= toDelegate(cb
); }
750 @property void vlOnMouseDouble (void function (in ref SDL_MouseButtonEvent ev
) cb
) { vlOnMouseDoubleCB
= toDelegate(cb
); }
751 @property void vlOnMouseMotion (void function (in ref SDL_MouseMotionEvent ev
) cb
) { vlOnMouseMotionCB
= toDelegate(cb
); }
752 @property void vlOnMouseWheel (void function (in ref SDL_MouseWheelEvent ev
) cb
) { vlOnMouseWheelCB
= toDelegate(cb
); }
754 @property void vlOnUpdate (void delegate (int elapsedTicks
) cb
) { vlOnUpdateCB
= cb
; }
755 @property void vlOnRebuild (void delegate () cb
) { vlOnRebuildCB
= cb
; }
756 @property void vlOnKeyDown (void delegate (in ref SDL_KeyboardEvent ev
) cb
) { vlOnKeyDownCB
= cb
; }
757 @property void vlOnKeyUp (void delegate (in ref SDL_KeyboardEvent ev
) cb
) { vlOnKeyUpCB
= cb
; }
758 @property void vlOnTextInput (void delegate (dchar ch
) cb
) { vlOnTextInputCB
= cb
; }
759 @property void vlOnMouseDown (void delegate (in ref SDL_MouseButtonEvent ev
) cb
) { vlOnMouseDownCB
= cb
; }
760 @property void vlOnMouseUp (void delegate (in ref SDL_MouseButtonEvent ev
) cb
) { vlOnMouseUpCB
= cb
; }
761 @property void vlOnMouseClick (void delegate (in ref SDL_MouseButtonEvent ev
) cb
) { vlOnMouseClickCB
= cb
; }
762 @property void vlOnMouseDouble (void delegate (in ref SDL_MouseButtonEvent ev
) cb
) { vlOnMouseDoubleCB
= cb
; }
763 @property void vlOnMouseMotion (void delegate (in ref SDL_MouseMotionEvent ev
) cb
) { vlOnMouseMotionCB
= cb
; }
764 @property void vlOnMouseWheel (void delegate (in ref SDL_MouseWheelEvent ev
) cb
) { vlOnMouseWheelCB
= cb
; }
766 @property auto vlOnUpdate () { return vlOnUpdateCB
; }
767 @property auto vlOnRebuild () { return vlOnRebuildCB
; }
768 @property auto vlOnKeyDown () { return vlOnKeyDownCB
; }
769 @property auto vlOnKeyUp () { return vlOnKeyUpCB
; }
770 @property auto vlOnTextInput () { return vlOnTextInputCB
; }
771 @property auto vlOnMouseDown () { return vlOnMouseDownCB
; }
772 @property auto vlOnMouseUp () { return vlOnMouseUpCB
; }
773 @property auto vlOnMouseClick () { return vlOnMouseClickCB
; }
774 @property auto vlOnMouseDouble () { return vlOnMouseDoubleCB
; }
775 @property auto vlOnMouseMotion () { return vlOnMouseMotionCB
; }
776 @property auto vlOnMouseWheel () { return vlOnMouseWheelCB
; }
779 /// start receiving text input events
780 void startTextInput() () { SDL_StartTextInput(); }
782 /// stop receiving text input events
783 void stopTextInput() () { SDL_StopTextInput(); }
786 // ////////////////////////////////////////////////////////////////////////// //
787 /* doubleclick processing */
788 // first press (count is 0):
789 // start it, set count to 1, send "press"
790 // first release (count is 1):
791 // checkCond pass: set count to 2, send "release", send "clicked"
792 // checkCond fail: set count to 0, send "release"
793 // second press (count is 2):
794 // checkCond pass: set count to 3, eat press
795 // checkCond fail: start it, set count to 1, send "press"
796 // second release (count is 3):
797 // checkCond pass: set count to 0, eat release, send "double"
798 // checkCond fail: set count to 0, eat release
799 private struct VLDblClickInfo
{
801 Pass
, // send normal event
802 Eat
, // don't send any event
803 Click
, // send normal event, then click
804 Double
// send double
809 ulong pressTime
; // in msecs
811 void reset () @safe nothrow @nogc { count
= 0; }
813 bool checkConds (in ref SDL_Event ev
) @trusted nothrow @nogc {
815 import std
.math
: abs
;
817 abs(ev
.button
.x
-x
) <= vlDblTreshold
&&
818 abs(ev
.button
.y
-y
) <= vlDblTreshold
&&
819 vlGetTicks()-pressTime
<= vlDblTimeout
;
825 Action
processDown (in ref SDL_Event ev
) @trusted nothrow @nogc {
826 void register () @trusted nothrow @nogc {
830 pressTime
= vlGetTicks();
840 if (checkConds(ev
)) {
850 Action
processUp (in ref SDL_Event ev
) @trusted nothrow @nogc {
853 } else if (checkConds(ev
)) {
855 //if (w.mWantDouble) count = 2; else reset();
858 } else if (count
== 3) {
860 return Action
.Double
;
865 Action res
= (count
== 1 ? Action
.Pass
: Action
.Eat
);
873 // ////////////////////////////////////////////////////////////////////////// //
874 private __gshared VLDblClickInfo
[32] vlDblClickInfo
; // button_number-1
875 public __gshared
uint vlDblTimeout
= 400; /// milliseconds; set to 0 to disable doubleclicks
876 public __gshared
uint vlDblTreshold
= 4; /// mouse cursor should not move more than `vlDblTreshold` pixels to register dblclick
879 // return `true` if event was eaten
880 private bool vlProcessDblClick (in ref SDL_Event ev
) {
881 if (vlDblTimeout
< 1) return false;
882 if (ev
.type
!= SDL_MOUSEBUTTONDOWN
&& ev
.type
!= SDL_MOUSEBUTTONUP
) return false;
883 if (ev
.button
.button
< 1 || ev
.button
.button
>= vlDblClickInfo
.length
) return false;
884 if (ev
.type
== SDL_MOUSEBUTTONDOWN
) {
886 immutable act
= vlDblClickInfo
[ev
.button
.button
-1].processDown(ev
);
888 switch (act
) with (VLDblClickInfo
.Action
) {
892 if (vlOnMouseDownCB
!is null) vlOnMouseDownCB(ev
.button
);
893 if (vlOnMouseClickCB
!is null) vlOnMouseClickCB(ev
.button
);
896 if (vlOnMouseDoubleCB
!is null) vlOnMouseDoubleCB(ev
.button
);
902 if (!doEat
&& vlOnMouseDownCB
!is null) vlOnMouseDownCB(ev
.button
);
905 immutable act
= vlDblClickInfo
[ev
.button
.button
-1].processUp(ev
);
907 switch (act
) with (VLDblClickInfo
.Action
) {
911 if (vlOnMouseUpCB
!is null) vlOnMouseUpCB(ev
.button
);
912 if (vlOnMouseClickCB
!is null) vlOnMouseClickCB(ev
.button
);
915 if (vlOnMouseDoubleCB
!is null) vlOnMouseDoubleCB(ev
.button
);
921 if (!doEat
&& vlOnMouseUpCB
!is null) vlOnMouseUpCB(ev
.button
);
927 // ////////////////////////////////////////////////////////////////////////// //
928 /** returns !0 to stop event loop; <0: from event preprocessor; >0: from SDL_QUIT */
929 int vlProcessEvent (ref SDL_Event ev
) {
931 if (ev
.type
== SDL_MOUSEBUTTONDOWN || ev
.type
== SDL_MOUSEBUTTONUP
) {
934 } else if (ev
.type
== SDL_MOUSEMOTION
) {
939 if (vlProcessDblClick(ev
)) return 0; // process mouse
940 if (ev
.type
== SDL_QUIT
) return 1;
942 case SDL_KEYDOWN
: if (vlOnKeyDownCB
!is null) vlOnKeyDownCB(ev
.key
); break;
943 case SDL_KEYUP
: if (vlOnKeyUpCB
!is null) vlOnKeyUpCB(ev
.key
); break;
944 case SDL_MOUSEBUTTONDOWN
: if (vlOnMouseDownCB
!is null) vlOnMouseDownCB(ev
.button
); break;
945 case SDL_MOUSEBUTTONUP
: if (vlOnMouseUpCB
!is null) vlOnMouseUpCB(ev
.button
); break;
946 //case SDL_MOUSEDOUBLE: if (vlOnMouseDoubleCB !is null) vlOnMouseDoubleCB(ev.button); break;
947 case SDL_MOUSEMOTION
: if (vlOnMouseMotionCB
!is null) vlOnMouseMotionCB(ev
.motion
); break;
948 case SDL_MOUSEWHEEL
: if (vlOnMouseWheelCB
!is null) vlOnMouseWheelCB(ev
.wheel
); break;
950 if (vlOnTextInputCB
!is null) {
951 //TODO: UTF-8 decode!
952 if (ev
.text
.text
[0] && ev
.text
.text
[0] < 127) {
953 vlOnTextInputCB(ev
.text
.text
[0]);
958 //TODO: forward other user events
961 case SDL_WINDOWEVENT
:
962 switch (ev
.window
.event
) {
963 case SDL_WINDOWEVENT_EXPOSED
:
964 vlFrameChanged(); // this will force repaint
975 private __gshared
int pvCurFPS
= 35; /// desired FPS (default: 35)
976 private __gshared
ulong pvLastUpdateCall
= 0;
977 private __gshared
ulong pvNextUpdateCall
= 0;
978 private __gshared
uint pvMSecsInFrame
= 1000/35;
980 @property uint vlMSecsPerFrame () @trusted nothrow @nogc { return pvMSecsInFrame
; } /// return number of milliseconds in frame
982 @property uint vlFPS () @trusted nothrow @nogc { return pvCurFPS
; } /// return current FPS
985 @property void vlFPS (uint newfps
) @trusted nothrow @nogc {
986 if (newfps
< 1) newfps
= 1;
987 if (newfps
> 500) newfps
= 500;
988 pvMSecsInFrame
= 1000/newfps
;
989 if (pvMSecsInFrame
== 0) pvMSecsInFrame
= 1;
994 /// update and rebuild frame if necessary
995 /// will not paint frame to screen
996 void vlCheckFrameUpdate () {
997 auto tm
= vlGetTicks();
998 if (tm
>= pvNextUpdateCall
) {
999 if (vlOnUpdateCB
!is null) vlOnUpdateCB(cast(int)(tm
-pvLastUpdateCall
));
1000 pvLastUpdateCall
= tm
;//vlGetTicks();
1001 while (tm
>= pvNextUpdateCall
) {
1002 // do something with skipped frames
1003 pvNextUpdateCall
+= pvMSecsInFrame
;
1006 import core
.atomic
: cas
;
1007 bool cf
= cas(&sdlFrameChangedFlag
, true, false); // cf is true if sdlFrameChangedFlag was true
1010 if (vlOnRebuildCB
!is null) vlOnRebuildCB();
1015 /** will start and stop FPS timer automatically */
1016 void vlMainLoop () {
1017 bool doQuit
= false;
1019 pvLastUpdateCall
= vlGetTicks();
1020 pvNextUpdateCall
= pvLastUpdateCall
+pvMSecsInFrame
;
1021 if (vlOnUpdateCB
!is null) vlOnUpdateCB(0);
1022 if (vlOnRebuildCB
!is null) vlOnRebuildCB();
1025 if (!SDL_PollEvent(null)) {
1026 // no events, see if we have to wait for the next frame
1027 auto now
= vlGetTicks();
1028 if (now
< pvNextUpdateCall
) {
1029 // have some time to waste
1030 int n
= cast(int)(pvNextUpdateCall
-now
);
1031 if (!SDL_WaitEventTimeout(null, n
)) goto noevent
;
1034 while (SDL_PollEvent(&ev
)) {
1035 if (vlProcessEvent(ev
)) { doQuit
= true; break; }
1039 vlCheckFrameUpdate();
1040 if (vlIsFrameChanged
) vlPaintFrame(); // this will reset sdlFrameChangedFlag again
1045 // ////////////////////////////////////////////////////////////////////////// //
1046 shared static ~this () {