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; 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 // we need at least OpenGL3 with GLSL to use NanoVega, 47 // so let's tell simpledisplay about that 48 setOpenGLContextVersion(3, 0); 49 50 simple_window = new SimpleWindow(w, h, title, OpenGlOptions.yes, Resizability.allowResizing); 51 52 // we need to destroy NanoVega context on window close 53 // stricly speaking, it is not necessary, as nothing fatal 54 // will happen if you'll forget it, but let's be polite. 55 // note that we cannot do that *after* our window was closed, 56 // as we need alive OpenGL context to do proper cleanup. 57 simple_window.onClosing = delegate () { 58 nvg.kill; 59 }; 60 61 simple_window.visibleForTheFirstTime = () { 62 nvg = nvgCreateContext(); 63 enforce(nvg !is null, "cannot initialize NanoGui"); 64 65 screen = new ArsdScreen(simple_window.width, simple_window.height, Clock.currTime.stdTime); 66 screen.theme = new Theme(nvg); 67 68 // this callback will be called when we will need to repaint our window 69 simple_window.redrawOpenGlScene = () { 70 screen.size = Vector2i(simple_window.width, simple_window.height); 71 screen.draw(nvg); 72 }; 73 74 screen.mCursorSet[Cursor.Arrow] = GenericCursor.Default; 75 screen.mCursorSet[Cursor.IBeam] = GenericCursor.Text; 76 screen.mCursorSet[Cursor.Crosshair] = GenericCursor.Cross; 77 screen.mCursorSet[Cursor.Hand] = GenericCursor.Hand; 78 screen.mCursorSet[Cursor.HResize] = GenericCursor.SizeWe; // FIX ME 79 screen.mCursorSet[Cursor.VResize] = GenericCursor.SizeNs; // FIX ME 80 81 screen.wnd = simple_window; 82 83 onVisibleForTheFirstTime(); 84 }; 85 } 86 87 final void run() 88 { 89 simple_window.eventLoop(40, 90 () { 91 // unfortunately screen may be not initialized 92 if (screen) 93 { 94 screen.currTime = Clock.currTime.stdTime; 95 if (screen.needToDraw) 96 simple_window.redrawOpenGlSceneNow(); 97 } 98 }, 99 delegate (KeyEvent event) 100 { 101 if (event == "*-Q" || event == "Escape") { simple_window.close(); return; } // quit on Q, Ctrl+Q, and so on 102 }, 103 delegate (MouseEvent event) 104 { 105 import std.datetime : Clock; 106 import nanogui.common : MouseButton, MouseAction; 107 108 MouseButton btn; 109 MouseAction action; 110 int modifiers; 111 112 // convert event data from arsd.simpledisplay format 113 // to own format 114 switch(event.button) 115 { 116 case arsd.simpledisplay.MouseButton.left: 117 btn = MouseButton.Left; 118 break; 119 case arsd.simpledisplay.MouseButton.right: 120 btn = MouseButton.Right; 121 break; 122 case arsd.simpledisplay.MouseButton.middle: 123 btn = MouseButton.Middle; 124 break; 125 case arsd.simpledisplay.MouseButton.wheelUp: 126 btn = MouseButton.WheelUp; 127 screen.scrollCallbackEvent(0, +1, Clock.currTime.stdTime); 128 break; 129 case arsd.simpledisplay.MouseButton.wheelDown: 130 btn = MouseButton.WheelDown; 131 screen.scrollCallbackEvent(0, -1, Clock.currTime.stdTime); 132 break; 133 default: 134 btn = MouseButton.None; 135 } 136 137 final switch(event.type) 138 { 139 case arsd.simpledisplay.MouseEventType.buttonPressed: 140 action = MouseAction.Press; 141 break; 142 case arsd.simpledisplay.MouseEventType.buttonReleased: 143 action = MouseAction.Release; 144 break; 145 case arsd.simpledisplay.MouseEventType.motion: 146 action = MouseAction.Motion; 147 assert(screen); 148 screen.cursorPosCallbackEvent(event.x, event.y, Clock.currTime.stdTime); 149 return; 150 } 151 152 if (event.modifierState & ModifierState.leftButtonDown) 153 modifiers |= MouseButton.Left; 154 if (event.modifierState & ModifierState.rightButtonDown) 155 modifiers |= MouseButton.Right; 156 if (event.modifierState & ModifierState.middleButtonDown) 157 modifiers |= MouseButton.Middle; 158 159 // propagating button events 160 if (event.type == MouseEventType.buttonPressed || 161 event.type == MouseEventType.buttonReleased || 162 event.type == MouseEventType.motion) 163 { 164 screen.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime); 165 } 166 }, 167 ); 168 flushGui(); // let OS do it's cleanup 169 } 170 171 /// this is called just before our window will be shown for the first time. 172 /// we must create NanoVega context here, as it needs to initialize 173 /// internal OpenGL subsystem with valid OpenGL context. 174 abstract void onVisibleForTheFirstTime(); 175 176 protected: 177 NVGContext nvg; 178 SimpleWindow simple_window; 179 ArsdScreen screen; 180 }