1 /** 2 * Copyright: Enalye 3 * License: Zlib 4 * Authors: Enalye 5 */ 6 module atelier.render.window; 7 8 import std.stdio; 9 import std..string; 10 11 import bindbc.sdl, bindbc.sdl.image, bindbc.sdl.mixer, bindbc.sdl.ttf; 12 13 import atelier.core; 14 import atelier.common; 15 import atelier.render.canvas; 16 import atelier.render.quadview; 17 import atelier.render.sprite; 18 19 static { 20 package(atelier) { 21 SDL_Window* _sdlWindow; 22 SDL_Renderer* _sdlRenderer; 23 Color _windowClearColor; 24 } 25 26 private { 27 SDL_Surface* _icon; 28 Vec2i _windowDimensions; 29 Vec2f _windowSize, _windowCenter; 30 bool _hasAudio = true; 31 bool _hasCustomCursor = false; 32 bool _showCursor = true; 33 Sprite _customCursorSprite; 34 DisplayMode _displayMode = DisplayMode.windowed; 35 } 36 } 37 38 /// Width of the window in pixels. 39 int getWindowWidth() { 40 return _windowDimensions.x; 41 } 42 43 /// Height of the window in pixels. 44 int getWindowHeight() { 45 return _windowDimensions.y; 46 } 47 48 /// Dimensions of the window in pixels. 49 Vec2i getWindowDimensions() { 50 return _windowDimensions; 51 } 52 53 /// Size of the window in pixels. 54 Vec2f getWindowSize() { 55 return _windowSize; 56 } 57 58 /// Half of the size of the window in pixels. 59 Vec2f getWindowCenter() { 60 return _windowCenter; 61 } 62 63 private struct CanvasReference { 64 const(SDL_Texture)* target; 65 Vec2f position; 66 Vec2f renderSize; 67 Vec2f size; 68 Canvas canvas; 69 } 70 71 static private CanvasReference[] _canvases; 72 73 /// Window display mode. 74 enum DisplayMode { 75 fullscreen, 76 desktop, 77 windowed 78 } 79 80 import std.exception; 81 82 /// Create the application window. 83 void createWindow(const Vec2i windowSize, string title) { 84 enforce(loadSDL() >= SDLSupport.sdl2010, "SDL support <= 2.0.10"); 85 enforce(loadSDLImage() >= SDLImageSupport.sdlImage204, "SDL image support <= 2.0.4"); 86 enforce(loadSDLTTF() >= SDLTTFSupport.sdlTTF2014, "SDL ttf support <= 2.0.14"); 87 enforce(loadSDLMixer() >= SDLMixerSupport.sdlMixer204, "SDL mixer support <= 2.0.4"); 88 89 enforce(SDL_Init(SDL_INIT_EVERYTHING) == 0, 90 "SDL initialisation failure: " ~ fromStringz(SDL_GetError())); 91 92 enforce(TTF_Init() != -1, "SDL ttf initialisation failure"); 93 enforce(Mix_OpenAudio(44_100, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 94 1024) != -1, "no audio device connected"); 95 enforce(Mix_AllocateChannels(16) != -1, "audio channels allocation failure"); 96 97 SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1"); 98 99 enforce(SDL_CreateWindowAndRenderer(windowSize.x, windowSize.y, 100 SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC | SDL_WINDOW_RESIZABLE, 101 &_sdlWindow, &_sdlRenderer) != -1, "window initialisation failure"); 102 103 CanvasReference canvasRef; 104 canvasRef.target = null; 105 canvasRef.position = cast(Vec2f)(windowSize) / 2; 106 canvasRef.size = cast(Vec2f)(windowSize); 107 canvasRef.renderSize = cast(Vec2f)(windowSize); 108 _canvases ~= canvasRef; 109 110 _windowDimensions = windowSize; 111 _windowSize = cast(Vec2f)(windowSize); 112 _windowCenter = _windowSize / 2f; 113 114 setWindowTitle(title); 115 } 116 117 /// Cleanup the application window. 118 void destroyWindow() { 119 if (_sdlWindow) 120 SDL_DestroyWindow(_sdlWindow); 121 122 if (_sdlRenderer) 123 SDL_DestroyRenderer(_sdlRenderer); 124 125 if (_hasAudio) 126 Mix_CloseAudio(); 127 } 128 129 /// Enable/Disable audio (Call before creating the window). \ 130 /// Enabled by default. 131 void enableAudio(bool enable) { 132 _hasAudio = enable; 133 } 134 135 /// Change the actual window title. 136 void setWindowTitle(string title) { 137 SDL_SetWindowTitle(_sdlWindow, toStringz(title)); 138 } 139 140 /// Change the base color of the base canvas. 141 void setWindowClearColor(Color color) { 142 _windowClearColor = color; 143 } 144 145 /// Update the window size. \ 146 /// If `isLogical` is set, the actual window won't be resized, only the canvas will. 147 void setWindowSize(const Vec2i windowSize, bool isLogical = false) { 148 resizeWindow(windowSize); 149 150 if (isLogical) 151 SDL_RenderSetLogicalSize(_sdlRenderer, windowSize.x, windowSize.y); 152 else 153 SDL_SetWindowSize(_sdlWindow, windowSize.x, windowSize.y); 154 } 155 156 /// Call this to update canvas size when window's size is changed externally. 157 package(atelier) void resizeWindow(const Vec2i windowSize) { 158 _windowDimensions = windowSize; 159 _windowSize = cast(Vec2f)(windowSize); 160 _windowCenter = _windowSize / 2f; 161 162 if (_canvases.length) { 163 _canvases[0].position = cast(Vec2f)(windowSize) / 2; 164 _canvases[0].size = cast(Vec2f)(windowSize); 165 _canvases[0].renderSize = cast(Vec2f) windowSize; 166 } 167 } 168 169 /// Current window size. 170 private Vec2i fetchWindowSize() { 171 Vec2i windowSize; 172 SDL_GetWindowSize(_sdlWindow, &windowSize.x, &windowSize.y); 173 return windowSize; 174 } 175 176 /// The window cannot be resized less than this. 177 void setWindowMinSize(Vec2i size) { 178 SDL_SetWindowMinimumSize(_sdlWindow, size.x, size.y); 179 } 180 181 /// The window cannot be resized more than this. 182 void setWindowMaxSize(Vec2i size) { 183 SDL_SetWindowMaximumSize(_sdlWindow, size.x, size.y); 184 } 185 186 /// Change the icon displayed. 187 void setWindowIcon(string path) { 188 if (_icon) { 189 SDL_FreeSurface(_icon); 190 _icon = null; 191 } 192 _icon = IMG_Load(toStringz(path)); 193 194 SDL_SetWindowIcon(_sdlWindow, _icon); 195 } 196 197 /// Change the cursor to a custom one and disable the default one. 198 void setWindowCursor(Sprite cursorSprite) { 199 _customCursorSprite = cursorSprite; 200 _hasCustomCursor = true; 201 SDL_ShowCursor(false); 202 } 203 204 /// Enable/Disable the cursor. \ 205 /// Enabled by default. 206 void showWindowCursor(bool show) { 207 _showCursor = show; 208 if (!_hasCustomCursor) 209 SDL_ShowCursor(show); 210 } 211 212 /// Change the display mode between windowed, desktop fullscreen and fullscreen. 213 void setWindowDisplay(DisplayMode displayMode) { 214 import atelier.ui : handleGuiElementEvent; 215 216 _displayMode = displayMode; 217 SDL_WindowFlags mode; 218 final switch (displayMode) with (DisplayMode) { 219 case fullscreen: 220 mode = SDL_WINDOW_FULLSCREEN; 221 break; 222 case desktop: 223 mode = SDL_WINDOW_FULLSCREEN_DESKTOP; 224 break; 225 case windowed: 226 mode = cast(SDL_WindowFlags) 0; 227 break; 228 } 229 SDL_SetWindowFullscreen(_sdlWindow, mode); 230 Vec2i newSize = fetchWindowSize(); 231 resizeWindow(newSize); 232 Event event; 233 event.type = Event.Type.resize; 234 event.window.size = newSize; 235 handleGuiElementEvent(event); 236 } 237 238 /// Current display mode. 239 DisplayMode getWindowDisplay() { 240 return _displayMode; 241 } 242 243 /// Enable/Disable the borders. 244 void setWindowBordered(bool isBordered) { 245 SDL_SetWindowBordered(_sdlWindow, isBordered ? SDL_TRUE : SDL_FALSE); 246 } 247 248 /// Allow the user to resize the window 249 void setWindowResizable(bool isResizable) { 250 SDL_SetWindowResizable(_sdlWindow, isResizable ? SDL_TRUE : SDL_FALSE); 251 } 252 253 /// Show/Hide the window. \ 254 /// Shown by default obviously. 255 void showWindow(bool show) { 256 if (show) 257 SDL_ShowWindow(_sdlWindow); 258 else 259 SDL_HideWindow(_sdlWindow); 260 } 261 262 /// Render everything on screen. 263 void renderWindow() { 264 Vec2f mousePos = getMousePos(); 265 if (_hasCustomCursor && _showCursor && mousePos.isBetween(Vec2f.one, _windowSize - Vec2f.one)) { 266 _customCursorSprite.color = Color.white; 267 _customCursorSprite.draw(mousePos + _customCursorSprite.size / 2f); 268 } 269 SDL_RenderPresent(_sdlRenderer); 270 setRenderColor(_windowClearColor); 271 SDL_RenderClear(_sdlRenderer); 272 } 273 274 /// Push a render canvas on the stack. 275 /// Everything after that and before the next popCanvas will be rendered onto this. 276 /// You **must** call popCanvas after that. 277 void pushCanvas(Canvas canvas, bool clear = true) { 278 canvas._isTargetOnStack = true; 279 CanvasReference canvasRef; 280 canvasRef.target = canvas.target; 281 canvasRef.position = canvas.position; 282 canvasRef.size = canvas.size; 283 canvasRef.renderSize = cast(Vec2f) canvas.renderSize; 284 canvasRef.canvas = canvas; 285 _canvases ~= canvasRef; 286 287 SDL_SetRenderTarget(_sdlRenderer, cast(SDL_Texture*) canvasRef.target); 288 setRenderColor(canvas.clearColor, canvas.clearAlpha); 289 if (clear) 290 SDL_RenderClear(_sdlRenderer); 291 } 292 /* 293 void pushCanvas(QuadView quadView, bool clear = true) { 294 pushCanvas(quadView.getCurrent(), clear); 295 if(clear) 296 quadView.advance(); 297 }*/ 298 299 /// Called after pushCanvas to remove the render canvas from the stack. 300 /// When there is no canvas on the stack, everything is displayed directly on screen. 301 void popCanvas() { 302 if (_canvases.length <= 1) 303 throw new Exception("Attempt to pop the main canvas."); 304 305 _canvases[$ - 1].canvas._isTargetOnStack = false; 306 _canvases.length--; 307 SDL_SetRenderTarget(_sdlRenderer, cast(SDL_Texture*) _canvases[$ - 1].target); 308 setRenderColor(_windowClearColor); 309 } 310 311 /// Change coordinate system from inside to outside the canvas. 312 Vec2f transformRenderSpace(const Vec2f pos) { 313 const CanvasReference* canvasRef = &_canvases[$ - 1]; 314 return (pos - canvasRef.position) * ( 315 canvasRef.renderSize / canvasRef.size) + canvasRef.renderSize * 0.5f; 316 } 317 318 /// Change coordinate system from outside to inside the canvas. 319 Vec2f transformCanvasSpace(const Vec2f pos, const Vec2f renderPos) { 320 const CanvasReference* canvasRef = &_canvases[$ - 1]; 321 return (pos - renderPos) * (canvasRef.size / canvasRef.renderSize) + canvasRef.position; 322 } 323 324 /// Change coordinate system from outside to insside the canvas. 325 Vec2f transformCanvasSpace(const Vec2f pos) { 326 const CanvasReference* canvasRef = &_canvases[$ - 1]; 327 return pos * (canvasRef.size / canvasRef.renderSize); 328 } 329 330 /// Change the scale from outside to inside the canvas. 331 Vec2f transformScale() { 332 const CanvasReference* canvasRef = &_canvases[$ - 1]; 333 return canvasRef.renderSize / canvasRef.size; 334 } 335 336 /// Check if something is inside the actual canvas rendering area. 337 bool isVisible(const Vec2f targetPosition, const Vec2f targetSize) { 338 const CanvasReference* canvasRef = &_canvases[$ - 1]; 339 return (((canvasRef.position.x - canvasRef.size.x * .5f) < ( 340 targetPosition.x + targetSize.x * .5f)) 341 && ((canvasRef.position.x + canvasRef.size.x * .5f) > ( 342 targetPosition.x - targetSize.x * .5f)) 343 && ((canvasRef.position.y - canvasRef.size.y * .5f) < ( 344 targetPosition.y + targetSize.y * .5f)) 345 && ((canvasRef.position.y + canvasRef.size.y * .5f) > ( 346 targetPosition.y - targetSize.y * .5f))); 347 } 348 349 /// Change the draw color, used internally. Don't bother use it. 350 void setRenderColor(const Color color, float alpha = 1f) { 351 const auto sdlColor = color.toSDL(); 352 SDL_SetRenderDrawColor(_sdlRenderer, sdlColor.r, sdlColor.g, sdlColor.b, 353 cast(ubyte)(clamp(alpha, 0f, 1f) * 255f)); 354 } 355 356 /// Draw a single point. 357 void drawPoint(const Vec2f position, const Color color, float alpha = 1f) { 358 if (isVisible(position, Vec2f(.0f, .0f))) { 359 const Vec2f rpos = transformRenderSpace(position); 360 361 setRenderColor(color, alpha); 362 SDL_RenderDrawPoint(_sdlRenderer, cast(int) rpos.x, cast(int) rpos.y); 363 } 364 } 365 366 /// Draw a line between the two positions. 367 void drawLine(const Vec2f startPosition, const Vec2f endPosition, const Color color, float alpha = 1f) { 368 const Vec2f pos1 = transformRenderSpace(startPosition); 369 const Vec2f pos2 = transformRenderSpace(endPosition); 370 371 setRenderColor(color, alpha); 372 SDL_RenderDrawLine(_sdlRenderer, cast(int) pos1.x, cast(int) pos1.y, 373 cast(int) pos2.x, cast(int) pos2.y); 374 } 375 376 /// Draw an arrow with its head pointing at the end position. 377 void drawArrow(const Vec2f startPosition, const Vec2f endPosition, const Color color, 378 float alpha = 1f) { 379 const Vec2f pos1 = transformRenderSpace(startPosition); 380 const Vec2f pos2 = transformRenderSpace(endPosition); 381 const Vec2f dir = (pos2 - pos1).normalized; 382 const Vec2f arrowBase = pos2 - dir * 25f; 383 const Vec2f pos3 = arrowBase + dir.normal * 20f; 384 const Vec2f pos4 = arrowBase - dir.normal * 20f; 385 386 setRenderColor(color, alpha); 387 SDL_RenderDrawLine(_sdlRenderer, cast(int) pos1.x, cast(int) pos1.y, 388 cast(int) pos2.x, cast(int) pos2.y); 389 SDL_RenderDrawLine(_sdlRenderer, cast(int) pos2.x, cast(int) pos2.y, 390 cast(int) pos3.x, cast(int) pos3.y); 391 SDL_RenderDrawLine(_sdlRenderer, cast(int) pos2.x, cast(int) pos2.y, 392 cast(int) pos4.x, cast(int) pos4.y); 393 } 394 395 /// Draw a vertical cross (like this: +) with the indicated size. 396 void drawCross(const Vec2f center, float length, const Color color, float alpha = 1f) { 397 const float halfLength = length / 2f; 398 drawLine(center + Vec2f(-halfLength, 0f), center + Vec2f(halfLength, 0f), color, alpha); 399 drawLine(center + Vec2f(0f, -halfLength), center + Vec2f(0f, halfLength), color, alpha); 400 } 401 402 /// Draw a rectangle border. 403 void drawRect(const Vec2f origin, const Vec2f size, const Color color, float alpha = 1f) { 404 const Vec2f pos1 = transformRenderSpace(origin); 405 const Vec2f pos2 = size * transformScale(); 406 407 const SDL_Rect rect = { 408 cast(int) pos1.x, cast(int) pos1.y, cast(int) pos2.x, cast(int) pos2.y 409 }; 410 411 setRenderColor(color, alpha); 412 SDL_RenderDrawRect(_sdlRenderer, &rect); 413 } 414 415 /// Draw a fully filled rectangle. 416 void drawFilledRect(const Vec2f origin, const Vec2f size, const Color color, float alpha = 1f) { 417 const Vec2f pos1 = transformRenderSpace(origin); 418 const Vec2f pos2 = size * transformScale(); 419 420 const SDL_Rect rect = { 421 cast(int) pos1.x, cast(int) pos1.y, cast(int) pos2.x, cast(int) pos2.y 422 }; 423 424 setRenderColor(color, alpha); 425 SDL_RenderFillRect(_sdlRenderer, &rect); 426 } 427 428 /// Draw a rectangle with a size of 1. 429 void drawPixel(const Vec2f position, const Color color, float alpha = 1f) { 430 const Vec2f pos = transformRenderSpace(position); 431 432 const SDL_Rect rect = {cast(int) pos.x, cast(int) pos.y, 1, 1}; 433 434 setRenderColor(color, alpha); 435 SDL_RenderFillRect(_sdlRenderer, &rect); 436 }