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  _rotationMode(Common::kRotationNormal),
59  _overlayVisible(false),
60  _overlayInGUI(false),
63  _forceRedraw(false),
64  _cursorVisible(false),
65  _cursorX(0),
66  _cursorY(0),
67  _cursorNeedsRedraw(false),
69 
70  void showOverlay(bool inGUI) override {
71  _overlayInGUI = inGUI;
72 
73  if (inGUI) {
75  _activeArea.width = getOverlayWidth();
76  _activeArea.height = getOverlayHeight();
77  } else {
79  _activeArea.width = getWidth();
80  _activeArea.height = getHeight();
81  }
82 
83  if (_overlayVisible)
84  return;
85 
86  _overlayVisible = true;
87  _forceRedraw = true;
89  }
90 
91  void hideOverlay() override {
92  if (!_overlayVisible)
93  return;
94 
95  _overlayInGUI = false;
96 
98  _activeArea.width = getWidth();
99  _activeArea.height = getHeight();
100  _overlayVisible = false;
101  _forceRedraw = true;
103  }
104 
105  bool isOverlayVisible() const override { return _overlayVisible; }
106 
107  Common::Rect getSafeOverlayArea(int16 *width, int16 *height) const override {
108  Insets insets = getSafeAreaInsets();
109 
110  // Create the overlay rect cut of the insets
111  // in the window coordinate space
112  // Make sure to avoid a negative size (and an invalid rect)
113  const int safeLeft = MAX(_overlayDrawRect.left, insets.left),
114  safeTop = MAX(_overlayDrawRect.top, insets.top);
115  Common::Rect safeArea(safeLeft, safeTop,
116  MAX(safeLeft, MIN((int)_overlayDrawRect.right, _windowWidth - insets.right)),
117  MAX(safeTop, MIN((int)_overlayDrawRect.bottom, _windowHeight - insets.bottom)));
118 
119  // Convert this safe area in the overlay coordinate space
120  const int targetWidth = getOverlayWidth(),
121  targetHeight = getOverlayHeight(),
122  sourceWidth = _overlayDrawRect.width(),
123  sourceHeight = _overlayDrawRect.height();
124 
125  if (width) *width = targetWidth;
126  if (height) *height = targetHeight;
127 
128  int rotatedTargetWidth = targetWidth,
129  rotatedTargetHeight = targetHeight;
130  if (_rotationMode == Common::kRotation90 || _rotationMode == Common::kRotation270) {
131  SWAP(rotatedTargetWidth, rotatedTargetHeight);
132  }
133 
134  // First make it relative to overlay origin and scale it
135  safeArea.left = ((safeArea.left - _overlayDrawRect.left) * rotatedTargetWidth) / sourceWidth;
136  safeArea.top = ((safeArea.top - _overlayDrawRect.top) * rotatedTargetHeight) / sourceHeight;
137  safeArea.right = ((safeArea.right - _overlayDrawRect.left) * rotatedTargetWidth) / sourceWidth;
138  safeArea.bottom = ((safeArea.bottom - _overlayDrawRect.top) * rotatedTargetHeight) / sourceHeight;
139 
140  // Now rotate it
141  switch (_rotationMode) {
142  default:
143  case Common::kRotationNormal:
144  // Nothing to do
145  break;
146  case Common::kRotation90: {
147  int16 tmp = safeArea.left;
148  safeArea.left = safeArea.top;
149  safeArea.top = rotatedTargetWidth - safeArea.right;
150  //safeArea.right = targetWidth - (rotatedTargetHeight - safeArea.bottom);
151  safeArea.right = safeArea.bottom; // targetWidth == rotatedTargetHeight
152  safeArea.bottom = targetHeight - tmp;
153  break;
154  }
155  case Common::kRotation180: {
156  int16 tmp;
157  tmp = safeArea.left;
158  safeArea.left = rotatedTargetWidth - safeArea.right;
159  safeArea.right = rotatedTargetWidth - tmp;
160  tmp = safeArea.top;
161  safeArea.top = rotatedTargetHeight - safeArea.bottom;
162  safeArea.bottom = rotatedTargetHeight - tmp;
163  break;
164  }
165  case Common::kRotation270: {
166  int16 tmp = safeArea.left;
167  safeArea.left = rotatedTargetHeight - safeArea.bottom;
168  //safeArea.bottom = targetHeight - (rotatedTargetWidth - safeArea.right);
169  safeArea.bottom = safeArea.right; // targetHeight == rotatedTargetWidth
170  safeArea.right = targetWidth - safeArea.top;
171  safeArea.top = tmp;
172  break;
173  }
174  }
175 
176  return safeArea;
177  }
178 
179  void setShakePos(int shakeXOffset, int shakeYOffset) override {
180  if (_gameScreenShakeXOffset != shakeXOffset || _gameScreenShakeYOffset != shakeYOffset) {
181  _gameScreenShakeXOffset = shakeXOffset;
182  _gameScreenShakeYOffset = shakeYOffset;
184  _cursorNeedsRedraw = true;
185  }
186  }
187 
188  int getWindowWidth() const { return _windowWidth; }
189  int getWindowHeight() const { return _windowHeight; }
190 
191 protected:
196  virtual bool gameNeedsAspectRatioCorrection() const = 0;
197 
202  virtual void handleResizeImpl(const int width, const int height) = 0;
203 
209  Common::Point convertVirtualToWindow(const int x, const int y) const {
210  const int targetX = _activeArea.drawRect.left;
211  const int targetY = _activeArea.drawRect.top;
212  const int targetWidth = _activeArea.drawRect.width();
213  const int targetHeight = _activeArea.drawRect.height();
214  const int sourceWidth = _activeArea.width;
215  const int sourceHeight = _activeArea.height;
216 
217  if (sourceWidth == 0 || sourceHeight == 0) {
218  error("convertVirtualToWindow called without a valid draw rect");
219  }
220 
221  int windowX, windowY;
222  switch (_rotationMode) {
223  default:
224  case Common::kRotationNormal:
225  windowX = targetX + (x * targetWidth + sourceWidth / 2) / sourceWidth;
226  windowY = targetY + (y * targetHeight + sourceHeight / 2) / sourceHeight;
227  break;
228  case Common::kRotation90:
229  windowX = targetX + ((y - (sourceHeight - 1)) * targetWidth + sourceHeight / 2) / sourceHeight;
230  windowY = targetY + (x * targetHeight + sourceWidth / 2) / sourceWidth;
231  break;
232  case Common::kRotation180:
233  windowX = targetX + ((x - (sourceWidth - 1)) * targetWidth + sourceWidth / 2) / sourceWidth;
234  windowY = targetY + ((y - (sourceHeight - 1)) * targetHeight + sourceHeight / 2) / sourceHeight;
235  break;
236  case Common::kRotation270:
237  windowX = targetX + (y * targetWidth + sourceHeight / 2) / sourceHeight;
238  windowY = targetY + ((x - (sourceWidth - 1)) * targetHeight + sourceWidth / 2) / sourceWidth;
239  break;
240  }
241 
242  return Common::Point(CLIP<int>(windowX, targetX, targetX + targetWidth - 1),
243  CLIP<int>(windowY, targetY, targetY + targetHeight - 1));
244  }
245 
251  Common::Point convertWindowToVirtual(int x, int y) const {
252  const int sourceX = _activeArea.drawRect.left;
253  const int sourceY = _activeArea.drawRect.top;
254  const int sourceMaxX = _activeArea.drawRect.right - 1;
255  const int sourceMaxY = _activeArea.drawRect.bottom - 1;
256  const int sourceWidth = _activeArea.drawRect.width();
257  const int sourceHeight = _activeArea.drawRect.height();
258  const int targetWidth = _activeArea.width;
259  const int targetHeight = _activeArea.height;
260 
261  if (sourceWidth == 0 || sourceHeight == 0) {
262  error("convertWindowToVirtual called without a valid draw rect");
263  }
264 
265  x = CLIP<int>(x, sourceX, sourceMaxX);
266  y = CLIP<int>(y, sourceY, sourceMaxY);
267 
268  int virtualX, virtualY;
269  switch (_rotationMode) {
270  default:
271  case Common::kRotationNormal:
272  virtualX = ((x - sourceX) * targetWidth + sourceWidth / 2) / sourceWidth;
273  virtualY = ((y - sourceY) * targetHeight + sourceHeight / 2) / sourceHeight;
274  break;
275  case Common::kRotation90:
276  virtualY = targetHeight - 1 - ((x - sourceX) * targetHeight + sourceWidth / 2) / sourceWidth;
277  virtualX = ((y - sourceY) * targetWidth + sourceHeight / 2) / sourceHeight;
278  break;
279  case Common::kRotation180:
280  virtualX = targetWidth - 1 - ((x - sourceX) * targetWidth + sourceWidth / 2) / sourceWidth;
281  virtualY = targetHeight - 1 - ((y - sourceY) * targetHeight + sourceHeight / 2) / sourceHeight;
282  break;
283  case Common::kRotation270:
284  virtualY = ((x - sourceX) * targetHeight + sourceWidth / 2) / sourceWidth;
285  virtualX = targetWidth - 1 - ((y - sourceY) * targetWidth + sourceHeight / 2) / sourceHeight;
286  break;
287  }
288 
289  return Common::Point(CLIP<int>(virtualX, 0, targetWidth - 1),
290  CLIP<int>(virtualY, 0, targetHeight - 1));
291  }
292 
297  if (getHeight() == 0 || gameNeedsAspectRatioCorrection()) {
298  return intToFrac(4) / 3;
299  }
300 
301  return intToFrac(getWidth()) / getHeight();
302  }
303 
307  virtual int getGameRenderScale() const {
308  return 1;
309  }
310 
311  struct Insets {
312  int16 left;
313  int16 top;
314  int16 right;
315  int16 bottom;
316  };
317 
324  virtual Insets getSafeAreaInsets() const {
325  return {0, 0, 0, 0};
326  }
327 
334  void handleResize(const int width, const int height) {
335  _windowWidth = width;
336  _windowHeight = height;
337  handleResizeImpl(width, height);
338  }
339 
344  virtual void recalculateDisplayAreas() {
345  if (_windowHeight == 0) {
346  return;
347  }
348 
349  // Compute a safe area rectangle out of the insets
350  Insets insets = getSafeAreaInsets();
351  Common::Rect safeArea(insets.left, insets.top,
352  _windowWidth - insets.right,
353  _windowHeight - insets.bottom);
354 
355  // Create a game draw rect using the safe are dimensions
356  populateDisplayAreaDrawRect(getDesiredGameAspectRatio(),
357  getWidth() * getGameRenderScale(), getHeight() * getGameRenderScale(),
358  safeArea, _gameDrawRect);
359 
360  // Move the game draw rect in the safe area
361  _gameDrawRect.constrain(safeArea);
362 
363  if (getOverlayHeight()) {
364  const int16 overlayWidth = getOverlayWidth(),
365  overlayHeight = getOverlayHeight();
366  const frac_t overlayAspect = intToFrac(overlayWidth) / overlayHeight;
367  populateDisplayAreaDrawRect(overlayAspect, overlayWidth, overlayHeight,
369  }
370 
371  if (_overlayInGUI) {
373  _activeArea.width = getOverlayWidth();
374  _activeArea.height = getOverlayHeight();
375  } else {
377  _activeArea.width = getWidth();
378  _activeArea.height = getHeight();
379  }
381  }
382 
390  virtual void setSystemMousePosition(const int x, const int y) = 0;
391 
395  virtual void notifyActiveAreaChanged() {}
396 
397  bool showMouse(bool visible) override {
398  if (_cursorVisible == visible) {
399  return visible;
400  }
401 
402  const bool last = _cursorVisible;
403  _cursorVisible = visible;
404  _cursorNeedsRedraw = true;
405  return last;
406  }
407 
414  void warpMouse(int x, int y) override {
415  // Check active coordinate instead of window coordinate to avoid warping
416  // the mouse if it is still within the same virtual pixel
417  const Common::Point virtualCursor = convertWindowToVirtual(_cursorX, _cursorY);
418  if (virtualCursor.x != x || virtualCursor.y != y) {
419  // Warping the mouse in SDL generates a mouse movement event, so
420  // `setMousePosition` would be called eventually through the
421  // `notifyMousePosition` callback if we *only* set the system mouse
422  // position here. However, this can cause problems with some games.
423  // For example, the cannon script in CoMI calls to warp the mouse
424  // twice each time the cannon is reloaded, and unless we update the
425  // mouse position immediately, the second call is ignored, which
426  // causes the cannon to change its aim.
427  const Common::Point windowCursor = convertVirtualToWindow(x, y);
428  setMousePosition(windowCursor.x, windowCursor.y);
429  setSystemMousePosition(windowCursor.x, windowCursor.y);
430  }
431  }
432 
439  void setMousePosition(int x, int y) {
440  if (_cursorX != x || _cursorY != y) {
441  _cursorNeedsRedraw = true;
442  }
443 
444  _cursorX = x;
445  _cursorY = y;
446  }
447 
452 
457 
463 
468 
474 
479 
484 
489 
494 
500 
504  struct DisplayArea {
509 
513  int width;
514 
518  int height;
519  };
520 
527 
532 
537 
542 
548 
552  int _cursorX, _cursorY;
553 
554 private:
555  void populateDisplayAreaDrawRect(const frac_t displayAspect, int originalWidth, int originalHeight, const Common::Rect &safeArea, Common::Rect &drawRect) const {
556  int mode = getStretchMode();
557 
558  Common::Rect rotatedSafeArea(safeArea);
559  int rotatedWindowWidth = _windowWidth,
560  rotatedWindowHeight = _windowHeight;
561 
562  if (_rotationMode == Common::kRotation90 || _rotationMode == Common::kRotation270) {
563  SWAP(rotatedSafeArea.left, rotatedSafeArea.top);
564  SWAP(rotatedSafeArea.right, rotatedSafeArea.bottom);
565  SWAP(rotatedWindowWidth, rotatedWindowHeight);
566  }
567  const int rotatedSafeWidth = rotatedSafeArea.width(),
568  rotatedSafeHeight = rotatedSafeArea.height();
569 
570  // Mode Center = use original size, or divide by an integral amount if window is smaller than game surface
571  // Mode Integral = scale by an integral amount.
572  // Mode Fit = scale to fit the window while respecting the aspect ratio
573  // Mode Stretch = scale and stretch to fit the window without respecting the aspect ratio
574  // Mode Fit Force Aspect = scale to fit the window while forcing a 4:3 aspect ratio
575  int width = 0, height = 0;
576  if (mode == STRETCH_CENTER || mode == STRETCH_INTEGRAL || mode == STRETCH_INTEGRAL_AR) {
577  width = originalWidth;
578  height = intToFrac(width) / displayAspect;
579  if (width > rotatedSafeWidth || height > rotatedSafeHeight) {
580  int fac = 1 + MAX((width - 1) / rotatedSafeWidth, (height - 1) / rotatedSafeHeight);
581  width /= fac;
582  height /= fac;
583  } else if (mode == STRETCH_INTEGRAL) {
584  int fac = MIN(rotatedSafeWidth / width, rotatedSafeHeight / height);
585  width *= fac;
586  height *= fac;
587  } else if (mode == STRETCH_INTEGRAL_AR) {
588  int targetHeight = height;
589  int horizontalFac = rotatedSafeWidth / width;
590  do {
591  width = originalWidth * horizontalFac;
592  int verticalFac = (targetHeight * horizontalFac + originalHeight / 2) / originalHeight;
593  height = originalHeight * verticalFac;
594  --horizontalFac;
595  } while (horizontalFac > 0 && height > rotatedSafeHeight);
596  if (height > rotatedSafeHeight)
597  height = targetHeight;
598  }
599  } else {
600  frac_t windowAspect = intToFrac(rotatedSafeWidth) / rotatedSafeHeight;
601  width = rotatedSafeWidth;
602  height = rotatedSafeHeight;
603  if (mode == STRETCH_FIT_FORCE_ASPECT) {
604  frac_t ratio = intToFrac(4) / 3;
605  if (windowAspect < ratio)
606  height = intToFrac(width) / ratio;
607  else if (windowAspect > ratio)
608  width = fracToInt(height * ratio);
609  } else if (mode != STRETCH_STRETCH) {
610  if (windowAspect < displayAspect)
611  height = intToFrac(width) / displayAspect;
612  else if (windowAspect > displayAspect)
613  width = fracToInt(height * displayAspect);
614  }
615  }
616 
617  int16 alignX, alignY;
618  switch (_screenAlign & SCREEN_ALIGN_XMASK) {
619  default:
620  case SCREEN_ALIGN_CENTER:
621  alignX = ((rotatedWindowWidth - width) / 2);
622  break;
623  case SCREEN_ALIGN_LEFT:
624  alignX = 0;
625  break;
626  case SCREEN_ALIGN_RIGHT:
627  alignX = (rotatedSafeArea.right - width);
628  break;
629  }
630 
631  switch (_screenAlign & SCREEN_ALIGN_YMASK) {
632  default:
633  case SCREEN_ALIGN_MIDDLE:
634  alignY = ((rotatedWindowHeight - height) / 2);
635  break;
636  case SCREEN_ALIGN_TOP:
637  alignY = 0;
638  break;
639  case SCREEN_ALIGN_BOTTOM:
640  alignY = (rotatedSafeArea.bottom - height);
641  break;
642  }
643 
644  rotatedSafeArea.constrain(alignX, alignY, width, height);
645 
646  alignX += _gameScreenShakeXOffset * width / getWidth();
647  alignY += _gameScreenShakeYOffset * height / getHeight();
648 
649  if (_rotationMode == Common::kRotation90 || _rotationMode == Common::kRotation270) {
650  drawRect.top = alignX;
651  drawRect.left = alignY;
652  drawRect.setWidth(height);
653  drawRect.setHeight(width);
654  } else {
655  drawRect.left = alignX;
656  drawRect.top = alignY;
657  drawRect.setWidth(width);
658  drawRect.setHeight(height);
659  }
660  }
661 };
662 
663 #endif
int _gameScreenShakeYOffset
Definition: windowed.h:488
T left
Definition: rect.h:170
Common::Point convertVirtualToWindow(const int x, const int y) const
Definition: windowed.h:209
virtual void handleResizeImpl(const int width, const int height)=0
Common::Rect _gameDrawRect
Definition: windowed.h:493
bool _cursorNeedsRedraw
Definition: windowed.h:541
virtual bool gameNeedsAspectRatioCorrection() const =0
Definition: rect.h:524
int _windowHeight
Definition: windowed.h:456
bool _overlayVisible
Definition: windowed.h:473
int height
Definition: windowed.h:518
void setHeight(T aHeight)
Definition: rect.h:224
int _cursorX
Definition: windowed.h:552
void SWAP(T &a, T &b)
Definition: util.h:84
bool _cursorVisible
Definition: windowed.h:536
void setWidth(T aWidth)
Definition: rect.h:220
T width() const
Definition: rect.h:217
frac_t getDesiredGameAspectRatio() const
Definition: windowed.h:296
Definition: windowed.h:52
bool _overlayInGUI
Definition: windowed.h:478
bool _forceRedraw
Definition: windowed.h:531
virtual Insets getSafeAreaInsets() const
Definition: windowed.h:324
T height() const
Definition: rect.h:218
Common::Rect drawRect
Definition: windowed.h:508
Common::RotationMode _rotationMode
Definition: windowed.h:467
T right
Definition: rect.h:171
int _windowWidth
Definition: windowed.h:451
T y
Definition: rect.h:49
Common::Point convertWindowToVirtual(int x, int y) const
Definition: windowed.h:251
bool _cursorLastInActiveArea
Definition: windowed.h:547
void handleResize(const int width, const int height)
Definition: windowed.h:334
T x
Definition: rect.h:48
Definition: rect.h:144
int width
Definition: windowed.h:513
virtual void recalculateDisplayAreas()
Definition: windowed.h:344
void NORETURN_PRE error(MSVC_PRINTF const char *s,...) GCC_PRINTF(1
Definition: windowed.h:504
void setMousePosition(int x, int y)
Definition: windowed.h:439
Definition: graphics.h:38
void warpMouse(int x, int y) override
Definition: windowed.h:414
T MIN(T a, T b)
Definition: util.h:61
DisplayArea _activeArea
Definition: windowed.h:526
Common::Rect _overlayDrawRect
Definition: windowed.h:499
T MAX(T a, T b)
Definition: util.h:64
RotationMode
Definition: rotationmode.h:44
bool constrain(const ConcreteRect &o)
Definition: rect.h:414
virtual void setSystemMousePosition(const int x, const int y)=0
virtual int getGameRenderScale() const
Definition: windowed.h:307
int32 frac_t
Definition: frac.h:52
Definition: windowed.h:311
int _screenAlign
Definition: windowed.h:462
int _gameScreenShakeXOffset
Definition: windowed.h:483
virtual void notifyActiveAreaChanged()
Definition: windowed.h:395