1 /// 2 module nanogui.arsdbackend; 3 4 import std.datetime : Clock; 5 import std.exception : enforce; 6 7 import arsd.simpledisplay; 8 import arsd.nanovega; 9 10 import nanogui.screen : Screen; 11 import nanogui.theme : Theme; 12 import nanogui.common : Vector2i, Cursor, NanoContext; 13 14 // Unfortunately ArsdBackend cannot inherit Screen directly 15 // because full initialization of simpledisplay occurs in 16 // `onVisibleForTheFirstTime`, not in ctor 17 class ArsdScreen : Screen 18 { 19 public: 20 this(int w, int h, long timestamp) 21 { 22 super(w, h, Clock.currTime.stdTime); 23 } 24 25 MouseCursor[6] mCursorSet; 26 27 override void cursor(Cursor value) 28 { 29 mCursor = value; 30 if (wnd) 31 wnd.cursor = mCursorSet[mCursor]; 32 } 33 34 override Cursor cursor() const 35 { 36 return mCursor; 37 } 38 39 SimpleWindow wnd; 40 } 41 42 class ArsdBackend 43 { 44 this(int w, int h, string title) 45 { 46 /* Avoid locale-related number parsing issues */ 47 version(Windows) {} 48 else { 49 import core.stdc.locale; 50 setlocale(LC_NUMERIC, "C"); 51 } 52 53 // we need at least OpenGL3 with GLSL to use NanoVega, 54 // so let's tell simpledisplay about that 55 setOpenGLContextVersion(3, 0); 56 57 simple_window = new SimpleWindow(w, h, title, OpenGlOptions.yes, Resizability.allowResizing); 58 59 // we need to destroy NanoVega context on window close 60 // stricly speaking, it is not necessary, as nothing fatal 61 // will happen if you'll forget it, but let's be polite. 62 // note that we cannot do that *after* our window was closed, 63 // as we need alive OpenGL context to do proper cleanup. 64 simple_window.onClosing = delegate () { 65 ctx.kill; 66 }; 67 68 ctx = NanoContext.init; 69 70 simple_window.visibleForTheFirstTime = () { 71 ctx = NanoContext(NVGContextFlag.None); 72 enforce(ctx !is null, "cannot initialize NanoGui"); 73 74 screen = new ArsdScreen(simple_window.width, simple_window.height, Clock.currTime.stdTime); 75 screen.theme = new Theme(ctx); 76 77 // this callback will be called when we will need to repaint our window 78 simple_window.redrawOpenGlScene = () { 79 screen.size = Vector2i(simple_window.width, simple_window.height); 80 screen.draw(ctx); 81 }; 82 83 screen.mCursorSet[Cursor.Arrow] = GenericCursor.Default; 84 screen.mCursorSet[Cursor.IBeam] = GenericCursor.Text; 85 screen.mCursorSet[Cursor.Crosshair] = GenericCursor.Cross; 86 screen.mCursorSet[Cursor.Hand] = GenericCursor.Hand; 87 screen.mCursorSet[Cursor.HResize] = GenericCursor.SizeWe; // FIX ME 88 screen.mCursorSet[Cursor.VResize] = GenericCursor.SizeNs; // FIX ME 89 90 screen.wnd = simple_window; 91 92 onVisibleForTheFirstTime(); 93 }; 94 } 95 96 final void run() 97 { 98 simple_window.eventLoop(40, 99 () { 100 // unfortunately screen may be not initialized 101 if (screen) 102 { 103 screen.currTime = Clock.currTime.stdTime; 104 if (screen.needToDraw) 105 simple_window.redrawOpenGlSceneNow(); 106 } 107 }, 108 delegate (KeyEvent event) 109 { 110 if (event == "*-Q" || event == "Escape") { simple_window.close(); return; } // quit on Q, Ctrl+Q, and so on 111 }, 112 delegate (MouseEvent event) 113 { 114 import std.datetime : Clock; 115 import nanogui.common : MouseButton, MouseAction; 116 117 MouseButton btn; 118 MouseAction action; 119 int modifiers; 120 121 // convert event data from arsd.simpledisplay format 122 // to own format 123 switch(event.button) 124 { 125 case arsd.simpledisplay.MouseButton.left: 126 btn = MouseButton.Left; 127 break; 128 case arsd.simpledisplay.MouseButton.right: 129 btn = MouseButton.Right; 130 break; 131 case arsd.simpledisplay.MouseButton.middle: 132 btn = MouseButton.Middle; 133 break; 134 case arsd.simpledisplay.MouseButton.wheelUp: 135 btn = MouseButton.WheelUp; 136 screen.scrollCallbackEvent(0, +1, Clock.currTime.stdTime); 137 break; 138 case arsd.simpledisplay.MouseButton.wheelDown: 139 btn = MouseButton.WheelDown; 140 screen.scrollCallbackEvent(0, -1, Clock.currTime.stdTime); 141 break; 142 default: 143 btn = MouseButton.None; 144 } 145 146 final switch(event.type) 147 { 148 case arsd.simpledisplay.MouseEventType.buttonPressed: 149 action = MouseAction.Press; 150 break; 151 case arsd.simpledisplay.MouseEventType.buttonReleased: 152 action = MouseAction.Release; 153 break; 154 case arsd.simpledisplay.MouseEventType.motion: 155 action = MouseAction.Motion; 156 assert(screen); 157 screen.cursorPosCallbackEvent(event.x, event.y, Clock.currTime.stdTime); 158 return; 159 } 160 161 if (event.modifierState & ModifierState.leftButtonDown) 162 modifiers |= MouseButton.Left; 163 if (event.modifierState & ModifierState.rightButtonDown) 164 modifiers |= MouseButton.Right; 165 if (event.modifierState & ModifierState.middleButtonDown) 166 modifiers |= MouseButton.Middle; 167 168 // propagating button events 169 if (event.type == MouseEventType.buttonPressed || 170 event.type == MouseEventType.buttonReleased || 171 event.type == MouseEventType.motion) 172 { 173 screen.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime); 174 } 175 }, 176 ); 177 flushGui(); // let OS do it's cleanup 178 } 179 180 /// this is called just before our window will be shown for the first time. 181 /// we must create NanoVega context here, as it needs to initialize 182 /// internal OpenGL subsystem with valid OpenGL context. 183 abstract void onVisibleForTheFirstTime(); 184 185 protected: 186 NanoContext ctx; 187 SimpleWindow simple_window; 188 ArsdScreen screen; 189 }