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 }