1 /**
2 * Copyright: Copyright Auburn Sounds 2015 - 2017.
3 *            Copyright Richard Andrew Cattermole 2017.
4 *
5 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 * Authors:   Guillaume Piolat
7 */
8 module dplug.window.window;
9 
10 import gfm.math.box;
11 
12 import dplug.core.nogc;
13 import dplug.graphics.image;
14 import dplug.graphics.view;
15 
16 enum Key
17 {
18     space,
19     upArrow,
20     downArrow,
21     leftArrow,
22     rightArrow,
23     digit0,
24     digit1,
25     digit2,
26     digit3,
27     digit4,
28     digit5,
29     digit6,
30     digit7,
31     digit8,
32     digit9,
33     a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
34     A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
35     backspace,
36     enter,
37     escape,
38     unsupported // special value, means "other"
39 };
40 
41 public dchar getCharFromKey(Key key) nothrow @nogc
42 {
43     switch(key)
44     {
45         case Key.backspace: return '\t';
46         case Key.digit0: .. case Key.digit9: return cast(dchar)('0' + (key - Key.digit0));
47         case Key.a: .. case Key.z: return cast(dchar)('a' + (key - Key.a));
48         case Key.A: .. case Key.Z: return cast(dchar)('A' + (key - Key.A));
49         case Key.space : return ' ';
50         default: return '\0';
51     }
52 }
53 
54 enum MouseButton
55 {
56     left,
57     right,
58     middle,
59     x1,
60     x2
61 }
62 
63 struct MouseState
64 {
65     bool leftButtonDown;
66     bool rightButtonDown;
67     bool middleButtonDown;
68     bool x1ButtonDown;
69     bool x2ButtonDown;
70     bool ctrlPressed;
71     bool shiftPressed;
72     bool altPressed;
73 }
74 
75 /// Is this window intended as a plug-in window running inside a host,
76 /// or a host window itself possibly hosting a plug-in?
77 enum WindowUsage
78 {
79     /// This window is intended to be for displaying a plugin UI.
80     /// Event pumping is done by the host (except in the X11 case where it's
81     /// done by an internal thread).
82     plugin,
83 
84     /// This window is intended to be top-level, for hosting another OS window.
85     /// Event pumping will be done by the caller manually through
86     /// Important: This case is not the nominal case.
87     ///            Some calls to the `IWindowListener` will make no sense.
88     host
89 }
90 
91 /// Giving commands to a window.
92 interface IWindow
93 {
94 nothrow:
95 @nogc:
96     /// To put in your message loop.
97     /// This call should only be used if the window was
98     /// created with `WindowUsage.host`.
99     /// Else, event pumping is managed by the host or internally (X11).
100     void waitEventAndDispatch();
101 
102     /// If exit was requested.
103     /// This call should only be used if the window was
104     /// created with `WindowUsage.host`.
105     /// In the case of a plug-in, the plugin client will request
106     /// termination of the window through its destructor.
107     bool terminated();
108 
109     /// Profile-purpose: get time in milliseconds.
110     /// Use the results of this function for deltas only.
111     uint getTimeMs();
112 
113     /// Gets the window's OS handle.
114     void* systemHandle();
115 }
116 
117 enum WindowPixelFormat
118 {
119     BGRA8,
120     ARGB8,
121     RGBA8
122 }
123 
124 /// Receiving commands from a window.
125 interface IWindowListener
126 {
127 nothrow @nogc:
128     /// Called on mouse click.
129     /// Returns: true if the event was handled.
130     bool onMouseClick(int x, int y, MouseButton mb, bool isDoubleClick, MouseState mstate);
131 
132     /// Called on mouse button release
133     /// Returns: true if the event was handled.
134     bool onMouseRelease(int x, int y, MouseButton mb, MouseState mstate);
135 
136     /// Called on mouse wheel movement
137     /// Returns: true if the event was handled.
138     bool onMouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY, MouseState mstate);
139 
140     /// Called on mouse movement (might not be within the window)
141     void onMouseMove(int x, int y, int dx, int dy, MouseState mstate);
142 
143     /// Called on keyboard press.
144     /// Returns: true if the event was handled.
145     bool onKeyDown(Key key);
146 
147     /// Called on keyboard release.
148     /// Returns: true if the event was handled.
149     bool onKeyUp(Key up);
150 
151     /// An image you have to draw to, or return that nothing has changed.
152     /// The location of this image is given before-hand by onResized.
153     /// recomputeDirtyAreas() MUST have been called before.
154     /// The pixel format cannot change over the lifetime of the window.
155     void onDraw(WindowPixelFormat pf);
156 
157     /// The drawing area size has changed.
158     /// Always called at least once before onDraw.
159     /// Returns: the location of the full rendered framebuffer.
160     ImageRef!RGBA onResized(int width, int height);
161 
162     /// Recompute internally what needs be done for the next onDraw.
163     /// This function MUST have been called before calling `onDraw` and `getDirtyRectangle`.
164     /// This method exists to allow the Window to recompute these draw lists less.
165     /// And because cache invalidation was easier on user code than internally in the UI.
166     /// Important: once you've called `recomputeDirtyAreas()` you COMMIT to redraw the
167     /// corresponding area given by `getDirtyRectangle()`.
168     /// Two calls to `recomputeDirtyAreas()` will not yield the same area.
169     void recomputeDirtyAreas();
170 
171     /// Returns: Minimal rectangle that contains dirty UIELement in UI + their graphical extent.
172     ///          Empty box if nothing to update.
173     /// recomputeDirtyAreas() MUST have been called before.
174     box2i getDirtyRectangle();
175 
176     /// Called whenever mouse capture was canceled (ALT + TAB, SetForegroundWindow...)
177     void onMouseCaptureCancelled();
178 
179     /// Must be called periodically (ideally 60 times per second but this is not mandatory).
180     /// `time` must refer to the window creation time.
181     /// `dt` and `time` are expressed in seconds (not milliseconds).
182     void onAnimate(double dt, double time);
183 }
184 
185 /// Various backends for windowing.
186 enum WindowBackend
187 {
188     autodetect,
189     win32,
190     carbon,
191     cocoa,
192     x11
193 }
194 
195 
196 
197 /// Factory function to create windows.
198 ///
199 /// The window is allocated with `mallocNew` and should be destroyed with `destroyFree`.
200 ///
201 /// Returns: null if this backend isn't available on this platform.
202 ///
203 /// Params:
204 ///   usage = Intended usage of the window.
205 ///
206 ///   parentInfo = OS handle of the parent window.
207 ///                For `WindowBackend.win32` it's a HWND.
208 ///                For `WindowBackend.carbon` it's a NSWindow.
209 ///                For `WindowBackend.x11` it's _unused_.
210 ///
211 ///   controlInfo = only used in Carbon Audio Units, an additional parenting information.
212 ///                 Can be `null` otherwise.
213 ///
214 ///   listener = A `IWindowListener` which listens to events by this window. Can be `null` for the moment.
215 ///              Must outlive the created window.
216 ///
217 ///   backend = Which windowing sub-system is used. Only Mac has any choice in this.
218 ///             Should be `WindowBackend.autodetect` in almost all cases
219 ///
220 ///   width = Initial width of the window.
221 ///
222 ///   height = Initial height of the window.
223 ///
224 nothrow @nogc
225 IWindow createWindow(WindowUsage usage,
226                      void* parentInfo,
227                      void* controlInfo,
228                      IWindowListener listener,
229                      WindowBackend backend,
230                      int width,
231                      int height)
232 {
233     //MAYDO  `null` listeners not accepted anymore.
234     //assert(listener !is null);
235 
236     static WindowBackend autoDetectBackend() nothrow @nogc
237     {
238         version(Windows)
239             return WindowBackend.win32;
240         else version(OSX)
241         {
242             version(X86_64)
243             {
244                 return WindowBackend.cocoa;
245             }
246             else
247             {
248                 return WindowBackend.carbon;
249             }
250         }
251         else version(linux)
252         {
253             return WindowBackend.x11;
254         }
255         else
256             static assert(false, "Unsupported OS");
257     }
258 
259     if (backend == WindowBackend.autodetect)
260         backend = autoDetectBackend();
261 
262     version(Windows)
263     {
264         if (backend == WindowBackend.win32)
265         {
266             import core.sys.windows.windef;
267             import dplug.window.win32window;
268             HWND parent = cast(HWND)parentInfo;
269             return mallocNew!Win32Window(parent, listener, width, height);
270         }
271         else
272             return null;
273     }
274     else version(OSX)
275     {
276         if (backend == WindowBackend.cocoa)
277         {
278             import dplug.window.cocoawindow;
279             return mallocNew!CocoaWindow(usage, parentInfo, listener, width, height);
280         }
281         else if (backend == WindowBackend.carbon)
282         {
283             import dplug.window.carbonwindow;
284             return mallocNew!CarbonWindow(usage, parentInfo, controlInfo, listener, width, height);
285         }
286         else
287             return null;
288     }
289     else version(linux)
290     {
291         if (backend == WindowBackend.x11)
292         {
293             import dplug.window.x11window;
294             return mallocNew!X11Window(parentInfo, listener, width, height);
295         }
296         else
297             return null;
298     }
299     else
300     {
301         static assert(false, "Unsupported OS.");
302     }
303 }