1 /**
2 * Copyright: Copyright Auburn Sounds 2015 and later.
3 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 * Authors:   Guillaume Piolat
5 */
6 module dplug.window.window;
7 
8 import gfm.math.box;
9 
10 import dplug.core.nogc;
11 import dplug.graphics.image;
12 import dplug.graphics.view;
13 
14 enum Key
15 {
16     space,
17     upArrow,
18     downArrow,
19     leftArrow,
20     rightArrow,
21     digit0,
22     digit1,
23     digit2,
24     digit3,
25     digit4,
26     digit5,
27     digit6,
28     digit7,
29     digit8,
30     digit9,
31     enter,
32     escape,
33     unsupported // special value, means "other"
34 };
35 
36 enum MouseButton
37 {
38     left,
39     right,
40     middle,
41     x1,
42     x2
43 }
44 
45 struct MouseState
46 {
47     bool leftButtonDown;
48     bool rightButtonDown;
49     bool middleButtonDown;
50     bool x1ButtonDown;
51     bool x2ButtonDown;
52     bool ctrlPressed;
53     bool shiftPressed;
54     bool altPressed;
55 }
56 
57 // Giving commands to a window
58 interface IWindow
59 {
60 nothrow:
61 @nogc:
62     // To put in your message loop
63     void waitEventAndDispatch();
64 
65     // If exit was requested
66     bool terminated();
67 
68     // Profile-purpose: get time in milliseconds.
69     uint getTimeMs();
70 
71     // Get the OS window handle.
72     void* systemHandle();
73 }
74 
75 enum WindowPixelFormat
76 {
77     BGRA8,
78     ARGB8,
79     RGBA8
80 }
81 
82 // Receiving commands from a window
83 interface IWindowListener
84 {
85 nothrow @nogc:
86     /// Called on mouse click.
87     /// Returns: true if the event was handled.
88     bool onMouseClick(int x, int y, MouseButton mb, bool isDoubleClick, MouseState mstate);
89 
90     /// Called on mouse button release
91     /// Returns: true if the event was handled.
92     bool onMouseRelease(int x, int y, MouseButton mb, MouseState mstate);
93 
94     /// Called on mouse wheel movement
95     /// Returns: true if the event was handled.
96     bool onMouseWheel(int x, int y, int wheelDeltaX, int wheelDeltaY, MouseState mstate);
97 
98     /// Called on mouse movement (might not be within the window)
99     void onMouseMove(int x, int y, int dx, int dy, MouseState mstate);
100 
101     /// Called on keyboard press.
102     /// Returns: true if the event was handled.
103     bool onKeyDown(Key key);
104 
105     /// Called on keyboard release.
106     /// Returns: true if the event was handled.
107     bool onKeyUp(Key up);
108 
109     /// An image you have to draw to, or return that nothing has changed.
110     /// The location of this image is given before-hand by onResized.
111     /// recomputeDirtyAreas() MUST have been called before.
112     /// The pixel format cannot change over the lifetime of the window.
113     void onDraw(WindowPixelFormat pf);
114 
115     /// The drawing area size has changed.
116     /// Always called at least once before onDraw.
117     /// Returns: the location of the full rendered framebuffer.
118     ImageRef!RGBA onResized(int width, int height);
119 
120     /// Recompute internally what needs be done for the next onDraw.
121     /// This function MUST be called before calling `onDraw` and `getDirtyRectangle`.
122     /// This method exists to allow the Window to recompute these draw lists less.
123     /// And because cache invalidation was easier on user code than internally in the UI.
124     void recomputeDirtyAreas();
125 
126     /// Returns: Minimal rectangle that contains dirty UIELement in UI + their graphical extent.
127     ///          Empty box if nothing to update.
128     /// recomputeDirtyAreas() MUST have been called before.
129     box2i getDirtyRectangle();
130 
131     /// Called whenever mouse capture was canceled (ALT + TAB, SetForegroundWindow...)
132     void onMouseCaptureCancelled();
133 
134     /// Must be called periodically (ideally 60 times per second but this is not mandatory).
135     /// `time` must refer to the window creation time.
136     void onAnimate(double dt, double time);
137 }
138 
139 
140 enum WindowBackend
141 {
142     autodetect,
143     win32,
144     carbon,
145     cocoa
146 }
147 
148 
149 /// Factory function to create windows.
150 /// The window is allocated with `mallocEmplace` and should be destroyed with `destroyFree`.
151 /// Returns: null if this backend isn't available on this platform.
152 /// Note: controlInfo is only used by Carbon + AU, can be null otherwise
153 nothrow @nogc
154 IWindow createWindow(void* parentInfo, void* controlInfo, IWindowListener listener, WindowBackend backend, int width, int height)
155 {
156     static WindowBackend autoDetectBackend() nothrow @nogc
157     {
158         version(Windows)
159             return WindowBackend.win32;
160         else version(linux)
161             return WindowBackend.autodetect;
162         else version(OSX)
163         {
164             version(X86_64)
165             {
166                 return WindowBackend.cocoa;
167             }
168             else
169             {
170                 return WindowBackend.carbon;
171             }
172         }
173     }
174 
175     if (backend == WindowBackend.autodetect)
176         backend = autoDetectBackend();
177 
178     version(Windows)
179     {
180         if (backend == WindowBackend.win32)
181         {
182             import core.sys.windows.windef;
183             import dplug.window.win32window;
184             HWND parent = cast(HWND)parentInfo;
185             return mallocEmplace!Win32Window(parent, listener, width, height);
186         }
187         else
188             return null;
189     }
190     else version(linux)
191     {
192         return null; // see linux-windowing branch
193     }
194     else version(OSX)
195     {
196         if (backend == WindowBackend.cocoa)
197         {
198             import dplug.window.cocoawindow;
199             return mallocEmplace!CocoaWindow(parentInfo, listener, width, height);
200         }
201         else if (backend == WindowBackend.carbon)
202         {
203             import dplug.window.carbonwindow;
204             return mallocEmplace!CarbonWindow(parentInfo, controlInfo, listener, width, height);
205         }
206         else
207             return null;
208     }
209     else
210     {
211         static assert(false, "Unsupported OS.");
212     }
213 }
214