1 /* ScummVM - Graphic Adventure Engine 2 * 3 * ScummVM is the legal property of its developers, whose names 4 * are too numerous to list here. Please refer to the COPYRIGHT 5 * file distributed with this source distribution. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 * 21 */ 22 23 #ifndef BACKENDS_GRAPHICS_WINDOWED_H 24 #define BACKENDS_GRAPHICS_WINDOWED_H 25 26 #include "backends/graphics/graphics.h" 27 #include "common/frac.h" 28 #include "common/rect.h" 29 #include "common/config-manager.h" 30 #include "common/textconsole.h" 31 #include "graphics/scaler/aspect.h" 32 33 enum { 34 STRETCH_CENTER = 0, 35 STRETCH_INTEGRAL = 1, 36 STRETCH_INTEGRAL_AR = 2, 37 STRETCH_FIT = 3, 38 STRETCH_STRETCH = 4, 39 STRETCH_FIT_FORCE_ASPECT = 5 40 }; 41 42 class WindowedGraphicsManager : virtual public GraphicsManager { 43 public: WindowedGraphicsManager()44 WindowedGraphicsManager() : 45 _windowWidth(0), 46 _windowHeight(0), 47 _overlayVisible(false), 48 _gameScreenShakeXOffset(0), 49 _gameScreenShakeYOffset(0), 50 _forceRedraw(false), 51 _cursorVisible(false), 52 _cursorX(0), 53 _cursorY(0), 54 _cursorNeedsRedraw(false), 55 _cursorLastInActiveArea(true) {} 56 showOverlay()57 virtual void showOverlay() override { 58 if (_overlayVisible) 59 return; 60 61 _activeArea.drawRect = _overlayDrawRect; 62 _activeArea.width = getOverlayWidth(); 63 _activeArea.height = getOverlayHeight(); 64 _overlayVisible = true; 65 _forceRedraw = true; 66 } 67 hideOverlay()68 virtual void hideOverlay() override { 69 if (!_overlayVisible) 70 return; 71 72 _activeArea.drawRect = _gameDrawRect; 73 _activeArea.width = getWidth(); 74 _activeArea.height = getHeight(); 75 _overlayVisible = false; 76 _forceRedraw = true; 77 } 78 isOverlayVisible()79 virtual bool isOverlayVisible() const override { return _overlayVisible; } 80 setShakePos(int shakeXOffset,int shakeYOffset)81 virtual void setShakePos(int shakeXOffset, int shakeYOffset) override { 82 if (_gameScreenShakeXOffset != shakeXOffset || _gameScreenShakeYOffset != shakeYOffset) { 83 _gameScreenShakeXOffset = shakeXOffset; 84 _gameScreenShakeYOffset = shakeYOffset; 85 recalculateDisplayAreas(); 86 _cursorNeedsRedraw = true; 87 } 88 } 89 getWindowWidth()90 int getWindowWidth() const { return _windowWidth; } getWindowHeight()91 int getWindowHeight() const { return _windowHeight; } 92 93 protected: 94 /** 95 * @returns whether or not the game screen must have aspect ratio correction 96 * applied for correct rendering. 97 */ 98 virtual bool gameNeedsAspectRatioCorrection() const = 0; 99 100 /** 101 * Backend-specific implementation for updating internal surfaces that need 102 * to reflect the new window size. 103 */ 104 virtual void handleResizeImpl(const int width, const int height) = 0; 105 106 /** 107 * Converts the given point from the active virtual screen's coordinate 108 * space to the window's coordinate space (i.e. game-to-window or 109 * overlay-to-window). 110 */ convertVirtualToWindow(const int x,const int y)111 Common::Point convertVirtualToWindow(const int x, const int y) const { 112 const int targetX = _activeArea.drawRect.left; 113 const int targetY = _activeArea.drawRect.top; 114 const int targetWidth = _activeArea.drawRect.width(); 115 const int targetHeight = _activeArea.drawRect.height(); 116 const int sourceWidth = _activeArea.width; 117 const int sourceHeight = _activeArea.height; 118 119 if (sourceWidth == 0 || sourceHeight == 0) { 120 error("convertVirtualToWindow called without a valid draw rect"); 121 } 122 123 int windowX = targetX + (x * targetWidth + sourceWidth / 2) / sourceWidth; 124 int windowY = targetY + (y * targetHeight + sourceHeight / 2) / sourceHeight; 125 126 return Common::Point(CLIP<int>(windowX, targetX, targetX + targetWidth - 1), 127 CLIP<int>(windowY, targetY, targetY + targetHeight - 1)); 128 } 129 130 /** 131 * Converts the given point from the window's coordinate space to the 132 * active virtual screen's coordinate space (i.e. window-to-game or 133 * window-to-overlay). 134 */ convertWindowToVirtual(int x,int y)135 Common::Point convertWindowToVirtual(int x, int y) const { 136 const int sourceX = _activeArea.drawRect.left; 137 const int sourceY = _activeArea.drawRect.top; 138 const int sourceMaxX = _activeArea.drawRect.right - 1; 139 const int sourceMaxY = _activeArea.drawRect.bottom - 1; 140 const int sourceWidth = _activeArea.drawRect.width(); 141 const int sourceHeight = _activeArea.drawRect.height(); 142 const int targetWidth = _activeArea.width; 143 const int targetHeight = _activeArea.height; 144 145 if (sourceWidth == 0 || sourceHeight == 0) { 146 error("convertWindowToVirtual called without a valid draw rect"); 147 } 148 149 x = CLIP<int>(x, sourceX, sourceMaxX); 150 y = CLIP<int>(y, sourceY, sourceMaxY); 151 152 int virtualX = ((x - sourceX) * targetWidth + sourceWidth / 2) / sourceWidth; 153 int virtualY = ((y - sourceY) * targetHeight + sourceHeight / 2) / sourceHeight; 154 155 return Common::Point(CLIP<int>(virtualX, 0, targetWidth - 1), 156 CLIP<int>(virtualY, 0, targetHeight - 1)); 157 } 158 159 /** 160 * @returns the desired aspect ratio of the game surface. 161 */ getDesiredGameAspectRatio()162 frac_t getDesiredGameAspectRatio() const { 163 if (getHeight() == 0 || gameNeedsAspectRatioCorrection()) { 164 return intToFrac(4) / 3; 165 } 166 167 return intToFrac(getWidth()) / getHeight(); 168 } 169 170 /** 171 * @returns the scale used between the game size and the surface on which it is rendered. 172 */ getGameRenderScale()173 virtual int getGameRenderScale() const { 174 return 1; 175 } 176 177 /** 178 * Called after the window has been updated with new dimensions. 179 * 180 * @param width The new width of the window, excluding window decoration. 181 * @param height The new height of the window, excluding window decoration. 182 */ handleResize(const int width,const int height)183 void handleResize(const int width, const int height) { 184 _windowWidth = width; 185 _windowHeight = height; 186 handleResizeImpl(width, height); 187 } 188 189 /** 190 * Recalculates the display areas for the game and overlay surfaces within 191 * the window. 192 */ recalculateDisplayAreas()193 virtual void recalculateDisplayAreas() { 194 if (_windowHeight == 0) { 195 return; 196 } 197 198 populateDisplayAreaDrawRect(getDesiredGameAspectRatio(), getWidth() * getGameRenderScale(), getHeight() * getGameRenderScale(), _gameDrawRect); 199 200 if (getOverlayHeight()) { 201 const frac_t overlayAspect = intToFrac(getOverlayWidth()) / getOverlayHeight(); 202 populateDisplayAreaDrawRect(overlayAspect, getOverlayWidth(), getOverlayHeight(), _overlayDrawRect); 203 } 204 205 if (_overlayVisible) { 206 _activeArea.drawRect = _overlayDrawRect; 207 _activeArea.width = getOverlayWidth(); 208 _activeArea.height = getOverlayHeight(); 209 } else { 210 _activeArea.drawRect = _gameDrawRect; 211 _activeArea.width = getWidth(); 212 _activeArea.height = getHeight(); 213 } 214 } 215 216 /** 217 * Sets the position of the hardware mouse cursor in the host system, 218 * relative to the window. 219 * 220 * @param x X coordinate in window coordinates. 221 * @param y Y coordinate in window coordinates. 222 */ 223 virtual void setSystemMousePosition(const int x, const int y) = 0; 224 showMouse(bool visible)225 virtual bool showMouse(bool visible) override { 226 if (_cursorVisible == visible) { 227 return visible; 228 } 229 230 const bool last = _cursorVisible; 231 _cursorVisible = visible; 232 _cursorNeedsRedraw = true; 233 return last; 234 } 235 236 /** 237 * Move ("warp") the mouse cursor to the specified position. 238 * 239 * @param x The new X position of the mouse in virtual screen coordinates. 240 * @param y The new Y position of the mouse in virtual screen coordinates. 241 */ warpMouse(int x,int y)242 void warpMouse(int x, int y) override { 243 // Check active coordinate instead of window coordinate to avoid warping 244 // the mouse if it is still within the same virtual pixel 245 const Common::Point virtualCursor = convertWindowToVirtual(_cursorX, _cursorY); 246 if (virtualCursor.x != x || virtualCursor.y != y) { 247 // Warping the mouse in SDL generates a mouse movement event, so 248 // `setMousePosition` would be called eventually through the 249 // `notifyMousePosition` callback if we *only* set the system mouse 250 // position here. However, this can cause problems with some games. 251 // For example, the cannon script in CoMI calls to warp the mouse 252 // twice each time the cannon is reloaded, and unless we update the 253 // mouse position immediately, the second call is ignored, which 254 // causes the cannon to change its aim. 255 const Common::Point windowCursor = convertVirtualToWindow(x, y); 256 setMousePosition(windowCursor.x, windowCursor.y); 257 setSystemMousePosition(windowCursor.x, windowCursor.y); 258 } 259 } 260 261 /** 262 * Sets the position of the rendered mouse cursor in the window. 263 * 264 * @param x X coordinate in window coordinates. 265 * @param y Y coordinate in window coordinates. 266 */ setMousePosition(int x,int y)267 void setMousePosition(int x, int y) { 268 if (_cursorX != x || _cursorY != y) { 269 _cursorNeedsRedraw = true; 270 } 271 272 _cursorX = x; 273 _cursorY = y; 274 } 275 276 /** 277 * The width of the window, excluding window decoration. 278 */ 279 int _windowWidth; 280 281 /** 282 * The height of the window, excluding window decoration. 283 */ 284 int _windowHeight; 285 286 /** 287 * Whether the overlay (i.e. launcher, including the out-of-game launcher) 288 * is visible or not. 289 */ 290 bool _overlayVisible; 291 292 /** 293 * The offset by which the screen is moved horizontally. 294 */ 295 int _gameScreenShakeXOffset; 296 297 /** 298 * The offset by which the screen is moved vertically. 299 */ 300 int _gameScreenShakeYOffset; 301 302 /** 303 * The scaled draw rectangle for the game surface within the window. 304 */ 305 Common::Rect _gameDrawRect; 306 307 /** 308 * The scaled draw rectangle for the overlay (launcher) surface within the 309 * window. 310 */ 311 Common::Rect _overlayDrawRect; 312 313 /** 314 * Data about the display area of a virtual screen. 315 */ 316 struct DisplayArea { 317 /** 318 * The scaled area where the virtual screen is drawn within the window. 319 */ 320 Common::Rect drawRect; 321 322 /** 323 * The width of the virtual screen's unscaled coordinate space. 324 */ 325 int width; 326 327 /** 328 * The height of the virtual screen's unscaled coordinate space. 329 */ 330 int height; 331 }; 332 333 /** 334 * Display area information about the currently active virtual screen. This 335 * will be the overlay screen when the overlay is active, and the game 336 * screen otherwise. 337 */ 338 DisplayArea _activeArea; 339 340 /** 341 * Whether the screen must be redrawn on the next frame. 342 */ 343 bool _forceRedraw; 344 345 /** 346 * Whether the cursor is actually visible. 347 */ 348 bool _cursorVisible; 349 350 /** 351 * Whether the mouse cursor needs to be redrawn on the next frame. 352 */ 353 bool _cursorNeedsRedraw; 354 355 /** 356 * Whether the last position of the system cursor was within the active area 357 * of the window. 358 */ 359 bool _cursorLastInActiveArea; 360 361 /** 362 * The position of the mouse cursor, in window coordinates. 363 */ 364 int _cursorX, _cursorY; 365 366 private: populateDisplayAreaDrawRect(const frac_t displayAspect,int originalWidth,int originalHeight,Common::Rect & drawRect)367 void populateDisplayAreaDrawRect(const frac_t displayAspect, int originalWidth, int originalHeight, Common::Rect &drawRect) const { 368 int mode = getStretchMode(); 369 // Mode Center = use original size, or divide by an integral amount if window is smaller than game surface 370 // Mode Integral = scale by an integral amount. 371 // Mode Fit = scale to fit the window while respecting the aspect ratio 372 // Mode Stretch = scale and stretch to fit the window without respecting the aspect ratio 373 // Mode Fit Force Aspect = scale to fit the window while forcing a 4:3 aspect ratio 374 375 int width = 0, height = 0; 376 if (mode == STRETCH_CENTER || mode == STRETCH_INTEGRAL || mode == STRETCH_INTEGRAL_AR) { 377 width = originalWidth; 378 height = intToFrac(width) / displayAspect; 379 if (width > _windowWidth || height > _windowHeight) { 380 int fac = 1 + MAX((width - 1) / _windowWidth, (height - 1) / _windowHeight); 381 width /= fac; 382 height /= fac; 383 } else if (mode == STRETCH_INTEGRAL) { 384 int fac = MIN(_windowWidth / width, _windowHeight / height); 385 width *= fac; 386 height *= fac; 387 } else if (mode == STRETCH_INTEGRAL_AR) { 388 int targetHeight = height; 389 int horizontalFac = _windowWidth / width; 390 do { 391 width = originalWidth * horizontalFac; 392 int verticalFac = (targetHeight * horizontalFac + originalHeight / 2) / originalHeight; 393 height = originalHeight * verticalFac; 394 --horizontalFac; 395 } while (horizontalFac > 0 && height > _windowHeight); 396 if (height > _windowHeight) 397 height = targetHeight; 398 } 399 } else { 400 frac_t windowAspect = intToFrac(_windowWidth) / _windowHeight; 401 width = _windowWidth; 402 height = _windowHeight; 403 if (mode == STRETCH_FIT_FORCE_ASPECT) { 404 frac_t ratio = intToFrac(4) / 3; 405 if (windowAspect < ratio) 406 height = intToFrac(width) / ratio; 407 else if (windowAspect > ratio) 408 width = fracToInt(height * ratio); 409 } else if (mode != STRETCH_STRETCH) { 410 if (windowAspect < displayAspect) 411 height = intToFrac(width) / displayAspect; 412 else if (windowAspect > displayAspect) 413 width = fracToInt(height * displayAspect); 414 } 415 } 416 417 drawRect.left = ((_windowWidth - width) / 2) + _gameScreenShakeXOffset * width / getWidth(); 418 drawRect.top = ((_windowHeight - height) / 2) + _gameScreenShakeYOffset * height / getHeight(); 419 drawRect.setWidth(width); 420 drawRect.setHeight(height); 421 } 422 }; 423 424 #endif 425