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 }