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 }