diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2bf14014..d2846862 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: matrix: # Latest stable version, update at will os: [ ubuntu-18.04, windows-2019 ] - dc: [ dmd-latest, ldc-master, ldc-latest ] + dc: [ dmd-latest, ldc-latest ] runs-on: ${{ matrix.os }} steps: diff --git a/dub.sdl b/dub.sdl index fdd17475..4c5d6409 100644 --- a/dub.sdl +++ b/dub.sdl @@ -3,7 +3,7 @@ description "Port of nanogui to dlang." license "BSD-like" authors "drug007" copyright "Copyright © 2018-2022, drug007" -dependency "arsd-official:nanovega" version="~>10.8.2" +dependency "arsd-official:nanovega" version="~>10.9.2" dependency "gfm:math" version="~>8.0.6" dependency "gfm7:opengl" version="~>1.1.2" dependency "gfm7:sdl2" version="~>1.1.2" diff --git a/examples/renderer/dub.sdl b/examples/renderer/dub.sdl index d75e7b30..9f8860e5 100644 --- a/examples/renderer/dub.sdl +++ b/examples/renderer/dub.sdl @@ -5,6 +5,6 @@ copyright "Copyright © 2019-2022, drug007" license "BSL" dependency "nanogui" path="../.." -dependency "arsd-official:nanovega" version="10.8.2" +dependency "arsd-official:nanovega" version="10.9.2" dependency "taggedalgebraic" version="~>0.11.22" dependency "printed" version="~>0.0.12" diff --git a/examples/renderer/dub.selections.json b/examples/renderer/dub.selections.json index 2c86c77d..fc8b89ec 100644 --- a/examples/renderer/dub.selections.json +++ b/examples/renderer/dub.selections.json @@ -1,7 +1,7 @@ { "fileVersion": 1, "versions": { - "arsd-official": "10.8.2", + "arsd-official": "10.9.2", "automem": "0.6.7", "auxil": {"path":"../../auxil"}, "bindbc-loader": "0.3.2", diff --git a/examples/sdl/dub.selections.json b/examples/sdl/dub.selections.json index 94a3c7c7..61f37c60 100644 --- a/examples/sdl/dub.selections.json +++ b/examples/sdl/dub.selections.json @@ -1,7 +1,7 @@ { "fileVersion": 1, "versions": { - "arsd-official": "10.8.2", + "arsd-official": "10.9.2", "automem": "0.6.7", "auxil": {"path":"../../auxil"}, "bindbc-loader": "0.3.2", diff --git a/examples/sdl/resources/fonts/Amiri-Regular.ttf b/examples/sdl/resources/fonts/Amiri-Regular.ttf new file mode 100644 index 00000000..61979eb2 Binary files /dev/null and b/examples/sdl/resources/fonts/Amiri-Regular.ttf differ diff --git a/examples/sdl/resources/fonts/ElMessiri-VariableFont_wght.ttf b/examples/sdl/resources/fonts/ElMessiri-VariableFont_wght.ttf new file mode 100644 index 00000000..71971343 Binary files /dev/null and b/examples/sdl/resources/fonts/ElMessiri-VariableFont_wght.ttf differ diff --git a/examples/sdl/source/app.d b/examples/sdl/source/app.d index 07da2e94..d7a0d221 100644 --- a/examples/sdl/source/app.d +++ b/examples/sdl/source/app.d @@ -389,6 +389,44 @@ class MyGui : SdlBackend tb = new TextBox(window, "中国"); tb.theme = asian_theme; tb.editable = true; + + version(none) + { + + // Added two arabic themes after https://forum.dlang.org/thread/sbymyafmdrsfgemlgsld@forum.dlang.org + // currently don't work as expected + + auto arabic_theme1 = new Theme(ctx); + { + // sorta hack because loading font in nvg results in + // conflicting font id + auto nvg2 = nvgCreateContext(NVGContextFlag.Debug); + scope(exit) nvg2.kill; + nvg2.createFont("arabic1", "./resources/fonts/Amiri-Regular.ttf"); + ctx.nvg.addFontsFrom(nvg2); + arabic_theme1.mFontNormal = ctx.nvg.findFont("arabic1"); + } + + auto arabic_theme2 = new Theme(ctx); + { + // sorta hack because loading font in nvg results in + // conflicting font id + auto nvg2 = nvgCreateContext(NVGContextFlag.Debug); + scope(exit) nvg2.kill; + nvg2.createFont("arabic2", "./resources/fonts/ElMessiri-VariableFont_wght.ttf"); + ctx.nvg.addFontsFrom(nvg2); + arabic_theme2.mFontNormal = ctx.nvg.findFont("arabic2"); + } + + tb = new TextBox(window, "حالكم"); + tb.theme = arabic_theme1; + tb.editable = true; + + tb = new TextBox(window, "حالكم"); + tb.theme = arabic_theme2; + tb.editable = true; + + } // version(none) } { diff --git a/source/nanogui/screen.d b/source/nanogui/screen.d index 94b3451c..0c457111 100644 --- a/source/nanogui/screen.d +++ b/source/nanogui/screen.d @@ -22,6 +22,7 @@ class Screen : Widget mLastInteraction = mTimestamp = timestamp; mCursor = Cursor.Arrow; mPixelRatio = 1.0; + mClearEnabled = true; } auto currTime() const { return mTimestamp; } @@ -69,9 +70,12 @@ class Screen : Widget } // draw the rest - glViewport(0, 0, size.x, size.y); - glClearColor(0., 0., 0., 0); - glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call + if (mClearEnabled) + { + glViewport(0, 0, size.x, size.y); + glClearColor(0., 0., 0., 0); + glClear(glNVGClearFlags); // use NanoVega API to get flags for OpenGL call + } ctx.beginFrame(size.x, size.y); // begin rendering scope(exit) { @@ -395,9 +399,9 @@ class Screen : Widget /// Return the ratio between pixel and device coordinates (e.g. >= 2 on Mac Retina displays) float pixelRatio() const { return mPixelRatio; } - package bool needToDraw() const pure @safe nothrow { return mNeedToDraw; } - // TODO add package qualifier at least but better to remove it completely - void needToDraw(bool value) pure @safe nothrow { if (value) mNeedToDraw = value; } + bool clearEnabled() @safe const { return mClearEnabled; } + void clearEnabled(bool value) @safe { mClearEnabled = value; } + package bool needToPerfomLayout() const pure @safe nothrow { return mNeedToPerfomLayout; } package void needToPerfomLayout(bool value) pure @safe nothrow { if (value) mNeedToPerfomLayout = value; } package bool blinkingCursorIsVisibleNow() const pure @safe nothrow { return mBlinkingCursorVisible; } @@ -445,4 +449,5 @@ protected: float mPixelRatio; void delegate(Vector2i) mResizeCallback; Array!GLCanvas mGLCanvases; + bool mClearEnabled; } diff --git a/source/nanogui/sdlapp.d b/source/nanogui/sdlapp.d new file mode 100644 index 00000000..b9ffd7ba --- /dev/null +++ b/source/nanogui/sdlapp.d @@ -0,0 +1,488 @@ +module nanogui.sdlapp; + +import std.exception: enforce; + +import std.experimental.logger: Logger, FileLogger, globalLogLevel, LogLevel; + +import gfm.opengl: OpenGL; +import gfm.sdl2: SDL2, SDL2Window, SDL_Event, SDL_Cursor, SDL_SetCursor, + SDL_FreeCursor, SDL_Delay; + +class SdlApp +{ + alias Event = SDL_Event; + + this(int w, int h, string title) + { + /* Avoid locale-related number parsing issues */ + version(Windows) {} + else { + import core.stdc.locale; + setlocale(LC_NUMERIC, "C"); + } + + import gfm.opengl : GLSupport, loadOpenGL; + import bindbc.sdl : SDLSupport, sdlSupport, loadSDL, SDL_INIT_VIDEO, SDL_INIT_EVENTS, + SDL_GL_SetAttribute, SDL_WINDOWPOS_UNDEFINED, SDL_GL_CONTEXT_MAJOR_VERSION, + SDL_GL_CONTEXT_MINOR_VERSION,SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE, + SDL_GL_STENCIL_SIZE, SDL_WINDOW_OPENGL, SDL_WINDOW_RESIZABLE, SDL_WINDOW_HIDDEN; + + this.width = w; + this.height = h; + _dirty = true; + + // create a logger + import std.stdio : stdout; + _log = new FileLogger(stdout); + + // load dynamic libraries + SDLSupport ret = loadSDL(); + if(ret != sdlSupport) { + if(ret == SDLSupport.noLibrary) { + /* + The system failed to load the library. Usually this means that either the library or one of its dependencies could not be found. + */ + } + else if(SDLSupport.badLibrary) { + /* + This indicates that the system was able to find and successfully load the library, but one or more symbols the binding expected to find was missing. This usually indicates that the loaded library is of a lower API version than the binding was configured to load, e.g., an SDL 2.0.2 library loaded by an SDL 2.0.10 configuration. + + For many C libraries, including SDL, this is perfectly fine and the application can continue as long as none of the missing functions are called. + */ + } + } + _sdl2 = new SDL2(_log); + globalLogLevel = LogLevel.error; + + // You have to initialize each SDL subsystem you want by hand + _sdl2.subSystemInit(SDL_INIT_VIDEO); + _sdl2.subSystemInit(SDL_INIT_EVENTS); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + + // create an OpenGL-enabled SDL window + window = new SDL2Window(_sdl2, + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + width, height, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN ); + + window.setTitle(title); + + GLSupport retVal = loadOpenGL(); + if(retVal >= GLSupport.gl33) + { + // configure renderer for OpenGL 3.3 + import std.stdio; + writefln("Available version of opengl: %s", retVal); + } + else + { + import std.stdio; + if (retVal == GLSupport.noLibrary) + writeln("opengl is not available"); + else + writefln("Unsupported version of opengl %s", retVal); + import std.exception; + enforce(0); + } + + _gl = new OpenGL(_log); + + // redirect OpenGL output to our Logger + _gl.redirectDebugOutput(); + } + + ~this() + { + _gl.destroy(); + window.destroy(); + _sdl2.destroy(); + } + + Logger logger() { return _log; } + + private void delegate () _onBeforeLoopStart; + void onBeforeLoopStart(void delegate () dg) + { + _onBeforeLoopStart = dg; + } + auto onBeforeLoopStart() { return _onBeforeLoopStart; } + + alias OnDraw = void delegate(); + private OnDraw _onDraw; + void onDraw(OnDraw handler) + { + _onDraw = handler; + } + auto onDraw() { return _onDraw; } + + alias OnResize = void delegate(int w, int h); + private OnResize _onResize; + void onResize(OnResize handler) + { + _onResize = handler; + } + auto onResize() { return _onResize; } + + alias OnKeyboardChar = void delegate(dchar ch); + private OnKeyboardChar _onKeyboardChar; + void onKeyboardChar(OnKeyboardChar handler) + { + _onKeyboardChar = handler; + } + auto onKeyboardChar() { return _onKeyboardChar; } + + alias OnSdlEvent = bool delegate(ref const(SDL_Event) event); + private OnSdlEvent _onKeyDown; + void onKeyDown(OnSdlEvent handler) + { + _onKeyDown = handler; + } + auto onKeyDown() { return _onKeyDown; } + + private OnSdlEvent _onKeyUp; + void onKeyUp(OnSdlEvent handler) + { + _onKeyUp = handler; + } + auto onKeyUp() { return _onKeyUp; } + + private OnSdlEvent _onMouseWheel; + void onMouseWheel(OnSdlEvent handler) + { + _onMouseWheel = handler; + } + auto onMouseWheel() { return _onMouseWheel; } + + private OnSdlEvent _onMouseMotion; + void onMouseMotion(OnSdlEvent handler) + { + _onMouseMotion = handler; + } + auto onMouseMotion() { return _onMouseMotion; } + + private OnSdlEvent _onMouseUp; + void onMouseUp(OnSdlEvent handler) + { + _onMouseUp = handler; + } + auto onMouseUp() { return _onMouseUp; } + + private OnSdlEvent _onMouseDown; + void onMouseDown(OnSdlEvent handler) + { + _onMouseDown = handler; + } + auto onMouseDown() { return _onMouseDown; } + + alias OnClose = bool delegate(); + private OnClose _onClose; + void onClose(OnClose handler) + { + _onClose = handler; + } + auto onClose() { return _onClose; } + + private bool _running = true; + void close() { _running = false; } + + void addHandler(alias currentHandler)(OnSdlEvent newHandler) + { + auto oldHandler = currentHandler; + if (oldHandler) + { + currentHandler = (ref const(SDL_Event) event) + { + if (oldHandler(event)) + return true; + return newHandler(event); + }; + } + else + { + currentHandler = newHandler; + } + } + + void invalidate() + { + _dirty = true; + } + + void run() + { + import gfm.sdl2; + + window.hide; + SDL_FlushEvents(SDL_WINDOWEVENT, SDL_SYSWMEVENT); + + window.show; + + SDL_Event event; + + while (_running) + { + if (_onBeforeLoopStart) + _onBeforeLoopStart(); + + SDL_PumpEvents(); + + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT)) + { + switch (event.type) + { + case SDL_WINDOWEVENT: + { + switch (event.window.event) + { + case SDL_WINDOWEVENT_MOVED: + // window has been moved to other position + break; + + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + { + // window size has been resized + with(event.window) + { + width = data1; + height = data2; + if (_onResize) + _onResize(width, height); + } + break; + } + + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_MAXIMIZED: + // window has been activated + break; + + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_FOCUS_LOST: + case SDL_WINDOWEVENT_MINIMIZED: + // window has been deactivated + break; + + case SDL_WINDOWEVENT_ENTER: + // mouse cursor has entered window + // for example default cursor can be disable + // using SDL_ShowCursor(SDL_FALSE); + break; + + case SDL_WINDOWEVENT_LEAVE: + // mouse cursor has left window + // for example default cursor can be disable + // using SDL_ShowCursor(SDL_TRUE); + break; + + case SDL_WINDOWEVENT_CLOSE: + _running = (_onClose) ? !_onClose() : false; + // if we continue running then update the screen + // to improve responce time + if (_running) + invalidate; + break; + default: + } + break; + } + default: + } + } + + // mouse update + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL)) + { + switch (event.type) + { + case SDL_MOUSEBUTTONDOWN: + if (_onMouseDown) + _onMouseDown(event); + break; + case SDL_MOUSEBUTTONUP: + if (_onMouseUp) + _onMouseUp(event); + break; + case SDL_MOUSEMOTION: + if (_onMouseMotion) + _onMouseMotion(event); + break; + case SDL_MOUSEWHEEL: + if (_onMouseWheel) + _onMouseWheel(event); + break; + default: + } + } + + // keyboard update + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP)) + { + switch (event.type) + { + case SDL_KEYDOWN: + if (_onKeyDown) + _onKeyDown(event); + break; + case SDL_KEYUP: + if (_onKeyUp) + _onKeyUp(event); + break; + default: + } + } + + // text update + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT)) + { + switch (event.type) + { + case SDL_TEXTINPUT: + if (_onKeyboardChar is null) + break; + import core.stdc.string : strlen; + auto len = strlen(&event.text.text[0]); + if (!len) + break; + assert(len < event.text.text.sizeof); + auto txt = event.text.text[0..len]; + import std.utf : byDchar; + foreach(ch; txt.byDchar) + _onKeyboardChar(ch); + break; + default: + break; + } + } + + // user event, we use it as timer notification + while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_USEREVENT, SDL_USEREVENT)) + { + switch (event.type) + { + case SDL_USEREVENT: + invalidate(); + break; + default: + break; + } + } + + // perform drawing if needed + if (_dirty) + { + pauseTimeMs = 0; + + if (_onDraw) + _onDraw(); + + window.swapBuffers(); + _dirty = false; + } + else + { + pauseTimeMs = pauseTimeMs * 2 + 1; // exponential pause + if (pauseTimeMs > 100) + pauseTimeMs = 100; // max 100ms of pause + SDL_Delay(pauseTimeMs); + } + } + } + +protected: + SDL2Window window; + int width; + int height; + bool _dirty; + int pauseTimeMs; + + Logger _log; + OpenGL _gl; + SDL2 _sdl2; +} + +private auto convertSdlKeyToNanoguiKey(int sdlkey) +{ + import gfm.sdl2; + import nanogui.common : KeyAction, Key; + + int nanogui_key; + switch(sdlkey) + { + case SDLK_LEFT: + nanogui_key = Key.Left; + break; + case SDLK_RIGHT: + nanogui_key = Key.Right; + break; + case SDLK_UP: + nanogui_key = Key.Up; + break; + case SDLK_DOWN: + nanogui_key = Key.Down; + break; + case SDLK_BACKSPACE: + nanogui_key = Key.Backspace; + break; + case SDLK_DELETE: + nanogui_key = Key.Delete; + break; + case SDLK_HOME: + nanogui_key = Key.Home; + break; + case SDLK_END: + nanogui_key = Key.End; + break; + case SDLK_RETURN: + nanogui_key = Key.Enter; + break; + case SDLK_a: + nanogui_key = Key.A; + break; + case SDLK_x: + nanogui_key = Key.X; + break; + case SDLK_c: + nanogui_key = Key.C; + break; + case SDLK_v: + nanogui_key = Key.V; + break; + case SDLK_ESCAPE: + nanogui_key = Key.Esc; + break; + default: + nanogui_key = sdlkey; + } + + return nanogui_key; +} + +private auto convertSdlModifierToNanoguiModifier(int mod) +{ + import gfm.sdl2; + import nanogui.common : KeyMod; + + int nanogui_mod; + + if (mod & KMOD_LCTRL) + nanogui_mod |= KeyMod.Ctrl; + if (mod & KMOD_LSHIFT) + nanogui_mod |= KeyMod.Shift; + if (mod & KMOD_LALT) + nanogui_mod |= KeyMod.Alt; + if (mod & KMOD_RCTRL) + nanogui_mod |= KeyMod.Ctrl; + if (mod & KMOD_RSHIFT) + nanogui_mod |= KeyMod.Shift; + if (mod & KMOD_RALT) + nanogui_mod |= KeyMod.Alt; + + return nanogui_mod; +} diff --git a/source/nanogui/sdlbackend.d b/source/nanogui/sdlbackend.d index 31498d00..bee5c63e 100644 --- a/source/nanogui/sdlbackend.d +++ b/source/nanogui/sdlbackend.d @@ -1,108 +1,161 @@ module nanogui.sdlbackend; -import std.algorithm: map; -import std.array: array; -import std.exception: enforce; -import std.file: thisExePath; -import std.path: dirName, buildPath; -import std.range: iota; import std.datetime : Clock; +import std.exception: enforce; -import std.experimental.logger: Logger, NullLogger, FileLogger, globalLogLevel, LogLevel; +import std.experimental.logger: Logger; -import gfm.math: mat4f, vec3f, vec4f; -import gfm.opengl: OpenGL; -import gfm.sdl2: SDL2, SDL2Window, SDL_Event, SDL_Cursor, SDL_SetCursor, - SDL_FreeCursor, SDL_Delay; +import gfm.sdl2: SDL_Event, SDL_Cursor, SDL_SetCursor, SDL_FreeCursor; + +import arsd.nanovega : kill, NVGContextFlag; -import arsd.nanovega : nvgCreateContext, kill, NVGContextFlag; import nanogui.screen : Screen; import nanogui.theme : Theme; import nanogui.common : NanoContext, Vector2i, MouseButton, MouseAction, Cursor; +import nanogui.sdlapp : SdlApp; class SdlBackend : Screen { this(int w, int h, string title) { - /* Avoid locale-related number parsing issues */ - version(Windows) {} - else { - import core.stdc.locale; - setlocale(LC_NUMERIC, "C"); - } - - import gfm.sdl2, gfm.opengl; - import bindbc.sdl; - - this.width = w; - this.height = h; - - // create a logger - import std.stdio : stdout; - _log = new FileLogger(stdout); - - // load dynamic libraries - SDLSupport ret = loadSDL(); - if(ret != sdlSupport) { - if(ret == SDLSupport.noLibrary) { - /* - The system failed to load the library. Usually this means that either the library or one of its dependencies could not be found. - */ + _sdlApp = new SdlApp(w, h, title); + + _sdlApp.onBeforeLoopStart = () + { + import std.datetime : dur; + + currTime = Clock.currTime.stdTime; + if (currTime - mBlinkingCursorTimestamp > dur!"msecs"(500).total!"hnsecs") + { + mBlinkingCursorVisible = !mBlinkingCursorVisible; + _sdlApp.invalidate(); + mBlinkingCursorTimestamp = currTime; } - else if(SDLSupport.badLibrary) { - /* - This indicates that the system was able to find and successfully load the library, but one or more symbols the binding expected to find was missing. This usually indicates that the loaded library is of a lower API version than the binding was configured to load, e.g., an SDL 2.0.2 library loaded by an SDL 2.0.10 configuration. - For many C libraries, including SDL, this is perfectly fine and the application can continue as long as none of the missing functions are called. - */ + if (_onBeforeLoopStart) + _onBeforeLoopStart(); + }; + + _sdlApp.onDraw = () + { + if (mNeedToDraw) + _sdlApp.invalidate; + size = Vector2i(width, height); + super.draw(ctx); + }; + + _sdlApp.onKeyDown = (ref const(SDL_Event) event) + { + import nanogui.common : KeyAction; + + _sdlApp.invalidate; + + auto key = event.key.keysym.sym.convertSdlKeyToNanoguiKey; + int modifiers = event.key.keysym.mod.convertSdlModifierToNanoguiModifier; + return super.keyboardEvent(key, event.key.keysym.scancode, KeyAction.Press, modifiers); + }; + + _sdlApp.onMouseWheel = (ref const(SDL_Event) event) + { + _sdlApp.invalidate; + if (event.wheel.y > 0) + { + btn = MouseButton.WheelUp; + return super.scrollCallbackEvent(0, +1, Clock.currTime.stdTime); + } + else if (event.wheel.y < 0) + { + btn = MouseButton.WheelDown; + return super.scrollCallbackEvent(0, -1, Clock.currTime.stdTime); } - } - _sdl2 = new SDL2(_log); - globalLogLevel = LogLevel.error; + return false; + }; + + _sdlApp.onMouseMotion = (ref const(SDL_Event) event) + { + import gfm.sdl2 : SDL_BUTTON_LMASK, SDL_BUTTON_RMASK, SDL_BUTTON_MMASK; + + _sdlApp.invalidate; - // You have to initialize each SDL subsystem you want by hand - _sdl2.subSystemInit(SDL_INIT_VIDEO); - _sdl2.subSystemInit(SDL_INIT_EVENTS); + ctx.mouse.x = event.motion.x; + ctx.mouse.y = event.motion.y; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + if (event.motion.state & SDL_BUTTON_LMASK) + btn = MouseButton.Left; + else if (event.motion.state & SDL_BUTTON_RMASK) + btn = MouseButton.Right; + else if (event.motion.state & SDL_BUTTON_MMASK) + btn = MouseButton.Middle; - // create an OpenGL-enabled SDL window - window = new SDL2Window(_sdl2, - SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - width, height, - SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN ); + if (event.motion.state & SDL_BUTTON_LMASK) + modifiers |= MouseButton.Left; + if (event.motion.state & SDL_BUTTON_RMASK) + modifiers |= MouseButton.Right; + if (event.motion.state & SDL_BUTTON_MMASK) + modifiers |= MouseButton.Middle; - window.setTitle(title); + action = MouseAction.Motion; + return super.cursorPosCallbackEvent(ctx.mouse.x, ctx.mouse.y, Clock.currTime.stdTime); + }; - GLSupport retVal = loadOpenGL(); - if(retVal >= GLSupport.gl33) + _sdlApp.onMouseUp = (ref const(SDL_Event) event) { - // configure renderer for OpenGL 3.3 - import std.stdio; - writefln("Available version of opengl: %s", retVal); - } - else + import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE; + + _sdlApp.invalidate; + + switch(event.button.button) + { + case SDL_BUTTON_LEFT: + btn = MouseButton.Left; + break; + case SDL_BUTTON_RIGHT: + btn = MouseButton.Right; + break; + case SDL_BUTTON_MIDDLE: + btn = MouseButton.Middle; + break; + default: + } + action = MouseAction.Release; + return super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime); + }; + + _sdlApp.onMouseDown = (ref const(SDL_Event) event) { - import std.stdio; - if (retVal == GLSupport.noLibrary) - writeln("opengl is not available"); - else - writefln("Unsupported version of opengl %s", retVal); - import std.exception; - enforce(0); - } + import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE; - _gl = new OpenGL(_log); + _sdlApp.invalidate; - // redirect OpenGL output to our Logger - _gl.redirectDebugOutput(); + switch(event.button.button) + { + case SDL_BUTTON_LEFT: + btn = MouseButton.Left; + break; + case SDL_BUTTON_RIGHT: + btn = MouseButton.Right; + break; + case SDL_BUTTON_MIDDLE: + btn = MouseButton.Middle; + break; + default: + } + action = MouseAction.Press; + return super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime); + }; + + _sdlApp.onClose = () + { + if (_onClose) + return _onClose(); + + return true; + }; ctx = NanoContext(NVGContextFlag.Debug); enforce(ctx !is null, "cannot initialize NanoGui"); + import gfm.sdl2; mCursorSet[Cursor.Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); mCursorSet[Cursor.IBeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); mCursorSet[Cursor.Crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); @@ -110,7 +163,7 @@ class SdlBackend : Screen mCursorSet[Cursor.HResize] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); mCursorSet[Cursor.VResize] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); - super(width, height, Clock.currTime.stdTime); + super(w, h, Clock.currTime.stdTime); theme = new Theme(ctx); } @@ -124,9 +177,7 @@ class SdlBackend : Screen SDL_FreeCursor(mCursorSet[Cursor.VResize]); ctx.kill(); - _gl.destroy(); - window.destroy(); - _sdl2.destroy(); + destroy(_sdlApp); } private void delegate () _onBeforeLoopStart; @@ -134,330 +185,42 @@ class SdlBackend : Screen { _onBeforeLoopStart = dg; } + + private bool delegate() _onClose; + void onClose(bool delegate() dg) @safe + { + _onClose = dg; + } void run() { - import gfm.sdl2; - - window.hide; - SDL_FlushEvents(SDL_WINDOWEVENT, SDL_SYSWMEVENT); - onVisibleForTheFirstTime(); - window.show; - - SDL_Event event; - - bool running = true; - while (running) - { - if (_onBeforeLoopStart) - _onBeforeLoopStart(); - SDL_PumpEvents(); - - while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT)) - { - switch (event.type) - { - case SDL_WINDOWEVENT: - { - switch (event.window.event) - { - case SDL_WINDOWEVENT_MOVED: - // window has been moved to other position - break; - - case SDL_WINDOWEVENT_RESIZED: - case SDL_WINDOWEVENT_SIZE_CHANGED: - { - // window size has been resized - with(event.window) - { - width = data1; - height = data2; - resizeEvent(size); - } - break; - } - - case SDL_WINDOWEVENT_SHOWN: - case SDL_WINDOWEVENT_FOCUS_GAINED: - case SDL_WINDOWEVENT_RESTORED: - case SDL_WINDOWEVENT_MAXIMIZED: - // window has been activated - break; - - case SDL_WINDOWEVENT_HIDDEN: - case SDL_WINDOWEVENT_FOCUS_LOST: - case SDL_WINDOWEVENT_MINIMIZED: - // window has been deactivated - break; - - case SDL_WINDOWEVENT_ENTER: - // mouse cursor has entered window - // for example default cursor can be disable - // using SDL_ShowCursor(SDL_FALSE); - break; - - case SDL_WINDOWEVENT_LEAVE: - // mouse cursor has left window - // for example default cursor can be disable - // using SDL_ShowCursor(SDL_TRUE); - break; - - case SDL_WINDOWEVENT_CLOSE: - running = false; - break; - default: - } - break; - } - default: - } - } - - // mouse update - { - while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL)) - { - switch (event.type) - { - case SDL_MOUSEBUTTONDOWN: - onMouseDown(event); - // force redrawing - mNeedToDraw = true; - break; - case SDL_MOUSEBUTTONUP: - onMouseUp(event); - // force redrawing - mNeedToDraw = true; - break; - case SDL_MOUSEMOTION: - onMouseMotion(event); - // force redrawing - mNeedToDraw = true; - break; - case SDL_MOUSEWHEEL: - onMouseWheel(event); - // force redrawing - mNeedToDraw = true; - break; - default: - } - } - } - - // keyboard update - { - while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP)) - { - switch (event.type) - { - case SDL_KEYDOWN: - onKeyDown(event); - // force redrawing - mNeedToDraw = true; - break; - case SDL_KEYUP: - onKeyUp(event); - // force redrawing - mNeedToDraw = true; - break; - default: - } - } - } - - // text update - { - while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT)) - { - switch (event.type) - { - case SDL_TEXTINPUT: - import core.stdc.string : strlen; - auto len = strlen(&event.text.text[0]); - if (!len) - break; - assert(len < event.text.text.sizeof); - auto txt = event.text.text[0..len]; - import std.utf : byDchar; - foreach(ch; txt.byDchar) - super.keyboardCharacterEvent(ch); - - // force redrawing - mNeedToDraw = true; - break; - default: - break; - } - } - } - - // user event, we use it as timer notification - { - while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_USEREVENT, SDL_USEREVENT)) - { - switch (event.type) - { - case SDL_USEREVENT: - // force redrawing - mNeedToDraw = true; - break; - default: - break; - } - } - } + _sdlApp.run(); + } - // perform drawing if needed - { - import std.datetime : dur; - - static auto pauseTimeMs = 0; - currTime = Clock.currTime.stdTime; - if (currTime - mBlinkingCursorTimestamp > dur!"msecs"(500).total!"hnsecs") - { - mBlinkingCursorVisible = !mBlinkingCursorVisible; - mNeedToDraw = true; - mBlinkingCursorTimestamp = currTime; - } - - if (needToDraw) - { - pauseTimeMs = 0; - size = Vector2i(width, height); - super.draw(ctx); - - window.swapBuffers(); - } - else - { - pauseTimeMs = pauseTimeMs * 2 + 1; // exponential pause - if (pauseTimeMs > 100) - pauseTimeMs = 100; // max 100ms of pause - SDL_Delay(pauseTimeMs); - } - } - } + void close() + { + _sdlApp.close(); } + auto invalidate() { _sdlApp.invalidate; } + abstract void onVisibleForTheFirstTime(); - override Logger logger() { return _log; } + override Logger logger() { return _sdlApp.logger; } protected: - SDL2Window window; - int width; - int height; + SdlApp _sdlApp; MouseButton btn; MouseAction action; int modifiers; - Logger _log; - OpenGL _gl; - SDL2 _sdl2; - NanoContext ctx; SDL_Cursor*[6] mCursorSet; - public void onKeyDown(ref const(SDL_Event) event) - { - import nanogui.common : KeyAction; - - auto key = event.key.keysym.sym.convertSdlKeyToNanoguiKey; - int modifiers = event.key.keysym.mod.convertSdlModifierToNanoguiModifier; - super.keyboardEvent(key, event.key.keysym.scancode, KeyAction.Press, modifiers); - } - - public void onKeyUp(ref const(SDL_Event) event) - { - - } - - public void onMouseWheel(ref const(SDL_Event) event) - { - if (event.wheel.y > 0) - { - btn = MouseButton.WheelUp; - super.scrollCallbackEvent(0, +1, Clock.currTime.stdTime); - } - else if (event.wheel.y < 0) - { - btn = MouseButton.WheelDown; - super.scrollCallbackEvent(0, -1, Clock.currTime.stdTime); - } - } - - public void onMouseMotion(ref const(SDL_Event) event) - { - import gfm.sdl2 : SDL_BUTTON_LMASK, SDL_BUTTON_RMASK, SDL_BUTTON_MMASK; - - ctx.mouse.x = event.motion.x; - ctx.mouse.y = event.motion.y; - - if (event.motion.state & SDL_BUTTON_LMASK) - btn = MouseButton.Left; - else if (event.motion.state & SDL_BUTTON_RMASK) - btn = MouseButton.Right; - else if (event.motion.state & SDL_BUTTON_MMASK) - btn = MouseButton.Middle; - - if (event.motion.state & SDL_BUTTON_LMASK) - modifiers |= MouseButton.Left; - if (event.motion.state & SDL_BUTTON_RMASK) - modifiers |= MouseButton.Right; - if (event.motion.state & SDL_BUTTON_MMASK) - modifiers |= MouseButton.Middle; - - action = MouseAction.Motion; - super.cursorPosCallbackEvent(ctx.mouse.x, ctx.mouse.y, Clock.currTime.stdTime); - } - - public void onMouseUp(ref const(SDL_Event) event) - { - import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE; - - switch(event.button.button) - { - case SDL_BUTTON_LEFT: - btn = MouseButton.Left; - break; - case SDL_BUTTON_RIGHT: - btn = MouseButton.Right; - break; - case SDL_BUTTON_MIDDLE: - btn = MouseButton.Middle; - break; - default: - } - action = MouseAction.Release; - super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime); - } - - public void onMouseDown(ref const(SDL_Event) event) - { - import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE; - - switch(event.button.button) - { - case SDL_BUTTON_LEFT: - btn = MouseButton.Left; - break; - case SDL_BUTTON_RIGHT: - btn = MouseButton.Right; - break; - case SDL_BUTTON_MIDDLE: - btn = MouseButton.Middle; - break; - default: - } - action = MouseAction.Press; - super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime); - } - override void cursor(Cursor value) { mCursor = value;