ScummVM API documentation
windowed.h
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 modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (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, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #ifndef BACKENDS_GRAPHICS_WINDOWED_H
23 #define BACKENDS_GRAPHICS_WINDOWED_H
24 
25 #include "backends/graphics/graphics.h"
26 #include "common/frac.h"
27 #include "common/rect.h"
28 #include "common/config-manager.h"
29 #include "common/textconsole.h"
30 #include "graphics/scaler/aspect.h"
31 
32 enum {
33  STRETCH_CENTER = 0,
34  STRETCH_INTEGRAL = 1,
35  STRETCH_INTEGRAL_AR = 2,
36  STRETCH_FIT = 3,
37  STRETCH_STRETCH = 4,
38  STRETCH_FIT_FORCE_ASPECT = 5
39 };
40 
41 enum {
42  SCREEN_ALIGN_CENTER = 0,
43  SCREEN_ALIGN_LEFT = 1,
44  SCREEN_ALIGN_RIGHT = 2,
45  SCREEN_ALIGN_XMASK = 3,
46  SCREEN_ALIGN_MIDDLE = 0,
47  SCREEN_ALIGN_TOP = 4,
48  SCREEN_ALIGN_BOTTOM = 8,
49  SCREEN_ALIGN_YMASK = 12
50 };
51 
52 class WindowedGraphicsManager : virtual public GraphicsManager {
53 public:
55  _windowWidth(0),
56  _windowHeight(0),
57  _screenAlign(SCREEN_ALIGN_CENTER | SCREEN_ALIGN_MIDDLE),
58  _overlayVisible(false),
59  _overlayInGUI(false),
62  _forceRedraw(false),
63  _cursorVisible(false),
64  _cursorX(0),
65  _cursorY(0),
66  _cursorNeedsRedraw(false),
68 
69  void showOverlay(bool inGUI) override {
70  _overlayInGUI = inGUI;
71 
72  if (inGUI) {
74  _activeArea.width = getOverlayWidth();
75  _activeArea.height = getOverlayHeight();
76  } else {
78  _activeArea.width = getWidth();
79  _activeArea.height = getHeight();
80  }
81 
82  if (_overlayVisible)
83  return;
84 
85  _overlayVisible = true;
86  _forceRedraw = true;
88  }
89 
90  void hideOverlay() override {
91  if (!_overlayVisible)
92  return;
93 
94  _overlayInGUI = false;
95 
97  _activeArea.width = getWidth();
98  _activeArea.height = getHeight();
99  _overlayVisible = false;
100  _forceRedraw = true;
102  }
103 
104  bool isOverlayVisible() const override { return _overlayVisible; }
105 
106  void setShakePos(int shakeXOffset, int shakeYOffset) override {
107  if (_gameScreenShakeXOffset != shakeXOffset || _gameScreenShakeYOffset != shakeYOffset) {
108  _gameScreenShakeXOffset = shakeXOffset;
109  _gameScreenShakeYOffset = shakeYOffset;
111  _cursorNeedsRedraw = true;
112  }
113  }
114 
115  int getWindowWidth() const { return _windowWidth; }
116  int getWindowHeight() const { return _windowHeight; }
117 
118 protected:
123  virtual bool gameNeedsAspectRatioCorrection() const = 0;
124 
129  virtual void handleResizeImpl(const int width, const int height) = 0;
130 
136  Common::Point convertVirtualToWindow(const int x, const int y) const {
137  const int targetX = _activeArea.drawRect.left;
138  const int targetY = _activeArea.drawRect.top;
139  const int targetWidth = _activeArea.drawRect.width();
140  const int targetHeight = _activeArea.drawRect.height();
141  const int sourceWidth = _activeArea.width;
142  const int sourceHeight = _activeArea.height;
143 
144  if (sourceWidth == 0 || sourceHeight == 0) {
145  error("convertVirtualToWindow called without a valid draw rect");
146  }
147 
148  int windowX = targetX + (x * targetWidth + sourceWidth / 2) / sourceWidth;
149  int windowY = targetY + (y * targetHeight + sourceHeight / 2) / sourceHeight;
150 
151  return Common::Point(CLIP<int>(windowX, targetX, targetX + targetWidth - 1),
152  CLIP<int>(windowY, targetY, targetY + targetHeight - 1));
153  }
154 
160  Common::Point convertWindowToVirtual(int x, int y) const {
161  const int sourceX = _activeArea.drawRect.left;
162  const int sourceY = _activeArea.drawRect.top;
163  const int sourceMaxX = _activeArea.drawRect.right - 1;
164  const int sourceMaxY = _activeArea.drawRect.bottom - 1;
165  const int sourceWidth = _activeArea.drawRect.width();
166  const int sourceHeight = _activeArea.drawRect.height();
167  const int targetWidth = _activeArea.width;
168  const int targetHeight = _activeArea.height;
169 
170  if (sourceWidth == 0 || sourceHeight == 0) {
171  error("convertWindowToVirtual called without a valid draw rect");
172  }
173 
174  x = CLIP<int>(x, sourceX, sourceMaxX);
175  y = CLIP<int>(y, sourceY, sourceMaxY);
176 
177  int virtualX = ((x - sourceX) * targetWidth + sourceWidth / 2) / sourceWidth;
178  int virtualY = ((y - sourceY) * targetHeight + sourceHeight / 2) / sourceHeight;
179 
180  return Common::Point(CLIP<int>(virtualX, 0, targetWidth - 1),
181  CLIP<int>(virtualY, 0, targetHeight - 1));
182  }
183 
188  if (getHeight() == 0 || gameNeedsAspectRatioCorrection()) {
189  return intToFrac(4) / 3;
190  }
191 
192  return intToFrac(getWidth()) / getHeight();
193  }
194 
198  virtual int getGameRenderScale() const {
199  return 1;
200  }
201 
208  void handleResize(const int width, const int height) {
209  _windowWidth = width;
210  _windowHeight = height;
211  handleResizeImpl(width, height);
212  }
213 
218  virtual void recalculateDisplayAreas() {
219  if (_windowHeight == 0) {
220  return;
221  }
222 
223  populateDisplayAreaDrawRect(getDesiredGameAspectRatio(), getWidth() * getGameRenderScale(), getHeight() * getGameRenderScale(), _gameDrawRect);
224 
225  if (getOverlayHeight()) {
226  const frac_t overlayAspect = intToFrac(getOverlayWidth()) / getOverlayHeight();
227  populateDisplayAreaDrawRect(overlayAspect, getOverlayWidth(), getOverlayHeight(), _overlayDrawRect);
228  }
229 
230  if (_overlayInGUI) {
232  _activeArea.width = getOverlayWidth();
233  _activeArea.height = getOverlayHeight();
234  } else {
236  _activeArea.width = getWidth();
237  _activeArea.height = getHeight();
238  }
240  }
241 
249  virtual void setSystemMousePosition(const int x, const int y) = 0;
250 
254  virtual void notifyActiveAreaChanged() {}
255 
256  bool showMouse(bool visible) override {
257  if (_cursorVisible == visible) {
258  return visible;
259  }
260 
261  const bool last = _cursorVisible;
262  _cursorVisible = visible;
263  _cursorNeedsRedraw = true;
264  return last;
265  }
266 
273  void warpMouse(int x, int y) override {
274  // Check active coordinate instead of window coordinate to avoid warping
275  // the mouse if it is still within the same virtual pixel
276  const Common::Point virtualCursor = convertWindowToVirtual(_cursorX, _cursorY);
277  if (virtualCursor.x != x || virtualCursor.y != y) {
278  // Warping the mouse in SDL generates a mouse movement event, so
279  // `setMousePosition` would be called eventually through the
280  // `notifyMousePosition` callback if we *only* set the system mouse
281  // position here. However, this can cause problems with some games.
282  // For example, the cannon script in CoMI calls to warp the mouse
283  // twice each time the cannon is reloaded, and unless we update the
284  // mouse position immediately, the second call is ignored, which
285  // causes the cannon to change its aim.
286  const Common::Point windowCursor = convertVirtualToWindow(x, y);
287  setMousePosition(windowCursor.x, windowCursor.y);
288  setSystemMousePosition(windowCursor.x, windowCursor.y);
289  }
290  }
291 
298  void setMousePosition(int x, int y) {
299  if (_cursorX != x || _cursorY != y) {
300  _cursorNeedsRedraw = true;
301  }
302 
303  _cursorX = x;
304  _cursorY = y;
305  }
306 
311 
316 
322 
328 
333 
338 
343 
348 
354 
358  struct DisplayArea {
363 
367  int width;
368 
372  int height;
373  };
374 
381 
386 
391 
396 
402 
406  int _cursorX, _cursorY;
407 
408 private:
409  void populateDisplayAreaDrawRect(const frac_t displayAspect, int originalWidth, int originalHeight, Common::Rect &drawRect) const {
410  int mode = getStretchMode();
411  Common::RotationMode rotation = getRotationMode();
412  int rotatedWindowWidth;
413  int rotatedWindowHeight;
414 
415  if (rotation == Common::kRotation90 || rotation == Common::kRotation270) {
416  rotatedWindowWidth = _windowHeight;
417  rotatedWindowHeight = _windowWidth;
418  } else {
419  rotatedWindowWidth = _windowWidth;
420  rotatedWindowHeight = _windowHeight;
421  }
422  // Mode Center = use original size, or divide by an integral amount if window is smaller than game surface
423  // Mode Integral = scale by an integral amount.
424  // Mode Fit = scale to fit the window while respecting the aspect ratio
425  // Mode Stretch = scale and stretch to fit the window without respecting the aspect ratio
426  // Mode Fit Force Aspect = scale to fit the window while forcing a 4:3 aspect ratio
427 
428  int width = 0, height = 0;
429  if (mode == STRETCH_CENTER || mode == STRETCH_INTEGRAL || mode == STRETCH_INTEGRAL_AR) {
430  width = originalWidth;
431  height = intToFrac(width) / displayAspect;
432  if (width > rotatedWindowWidth || height > rotatedWindowHeight) {
433  int fac = 1 + MAX((width - 1) / rotatedWindowWidth, (height - 1) / rotatedWindowHeight);
434  width /= fac;
435  height /= fac;
436  } else if (mode == STRETCH_INTEGRAL) {
437  int fac = MIN(rotatedWindowWidth / width, rotatedWindowHeight / height);
438  width *= fac;
439  height *= fac;
440  } else if (mode == STRETCH_INTEGRAL_AR) {
441  int targetHeight = height;
442  int horizontalFac = rotatedWindowWidth / width;
443  do {
444  width = originalWidth * horizontalFac;
445  int verticalFac = (targetHeight * horizontalFac + originalHeight / 2) / originalHeight;
446  height = originalHeight * verticalFac;
447  --horizontalFac;
448  } while (horizontalFac > 0 && height > rotatedWindowHeight);
449  if (height > rotatedWindowHeight)
450  height = targetHeight;
451  }
452  } else {
453  frac_t windowAspect = intToFrac(rotatedWindowWidth) / rotatedWindowHeight;
454  width = rotatedWindowWidth;
455  height = rotatedWindowHeight;
456  if (mode == STRETCH_FIT_FORCE_ASPECT) {
457  frac_t ratio = intToFrac(4) / 3;
458  if (windowAspect < ratio)
459  height = intToFrac(width) / ratio;
460  else if (windowAspect > ratio)
461  width = fracToInt(height * ratio);
462  } else if (mode != STRETCH_STRETCH) {
463  if (windowAspect < displayAspect)
464  height = intToFrac(width) / displayAspect;
465  else if (windowAspect > displayAspect)
466  width = fracToInt(height * displayAspect);
467  }
468  }
469 
470  int alignX, alignY;
471  switch (_screenAlign & SCREEN_ALIGN_XMASK) {
472  default:
473  case SCREEN_ALIGN_CENTER:
474  alignX = ((rotatedWindowWidth - width) / 2);
475  break;
476  case SCREEN_ALIGN_LEFT:
477  alignX = 0;
478  break;
479  case SCREEN_ALIGN_RIGHT:
480  alignX = (rotatedWindowWidth - width);
481  break;
482  }
483 
484  switch (_screenAlign & SCREEN_ALIGN_YMASK) {
485  default:
486  case SCREEN_ALIGN_MIDDLE:
487  alignY = ((rotatedWindowHeight - height) / 2);
488  break;
489  case SCREEN_ALIGN_TOP:
490  alignY = 0;
491  break;
492  case SCREEN_ALIGN_BOTTOM:
493  alignY = (rotatedWindowHeight - height);
494  break;
495  }
496 
497  drawRect.left = alignX + _gameScreenShakeXOffset * width / getWidth();
498  drawRect.top = alignY + _gameScreenShakeYOffset * height / getHeight();
499  drawRect.setWidth(width);
500  drawRect.setHeight(height);
501  }
502 };
503 
504 #endif
void setHeight(int16 aHeight)
Definition: rect.h:198
int _gameScreenShakeYOffset
Definition: windowed.h:342
Common::Point convertVirtualToWindow(const int x, const int y) const
Definition: windowed.h:136
virtual void handleResizeImpl(const int width, const int height)=0
int16 right
Definition: rect.h:146
Common::Rect _gameDrawRect
Definition: windowed.h:347
bool _cursorNeedsRedraw
Definition: windowed.h:395
virtual bool gameNeedsAspectRatioCorrection() const =0
Definition: rect.h:144
int _windowHeight
Definition: windowed.h:315
bool _overlayVisible
Definition: windowed.h:327
int height
Definition: windowed.h:372
int _cursorX
Definition: windowed.h:406
bool _cursorVisible
Definition: windowed.h:390
frac_t getDesiredGameAspectRatio() const
Definition: windowed.h:187
Definition: windowed.h:52
bool _overlayInGUI
Definition: windowed.h:332
bool _forceRedraw
Definition: windowed.h:385
Common::Rect drawRect
Definition: windowed.h:362
int _windowWidth
Definition: windowed.h:310
int16 width() const
Definition: rect.h:191
Common::Point convertWindowToVirtual(int x, int y) const
Definition: windowed.h:160
bool _cursorLastInActiveArea
Definition: windowed.h:401
void setWidth(int16 aWidth)
Definition: rect.h:194
void handleResize(const int width, const int height)
Definition: windowed.h:208
Definition: rect.h:45
int width
Definition: windowed.h:367
int16 left
Definition: rect.h:145
virtual void recalculateDisplayAreas()
Definition: windowed.h:218
void NORETURN_PRE error(MSVC_PRINTF const char *s,...) GCC_PRINTF(1
Definition: windowed.h:358
void setMousePosition(int x, int y)
Definition: windowed.h:298
Definition: graphics.h:37
int16 x
Definition: rect.h:46
void warpMouse(int x, int y) override
Definition: windowed.h:273
RotationMode
Definition: rotationmode.h:44
int16 y
Definition: rect.h:47
T MIN(T a, T b)
Definition: util.h:59
DisplayArea _activeArea
Definition: windowed.h:380
Common::Rect _overlayDrawRect
Definition: windowed.h:353
T MAX(T a, T b)
Definition: util.h:62
virtual void setSystemMousePosition(const int x, const int y)=0
virtual int getGameRenderScale() const
Definition: windowed.h:198
int32 frac_t
Definition: frac.h:52
int _screenAlign
Definition: windowed.h:321
int _gameScreenShakeXOffset
Definition: windowed.h:337
int16 height() const
Definition: rect.h:192
virtual void notifyActiveAreaChanged()
Definition: windowed.h:254