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  _ignoreGameSafeArea(false),
60  _overlayVisible(false),
61  _overlayInGUI(false),
64  _forceRedraw(false),
65  _cursorVisible(false),
66  _cursorX(0),
67  _cursorY(0),
68  _cursorNeedsRedraw(false),
70 
71  void showOverlay(bool inGUI) override {
72  _overlayInGUI = inGUI;
73 
74  if (inGUI) {
76  _activeArea.width = getOverlayWidth();
77  _activeArea.height = getOverlayHeight();
78  } else {
80  _activeArea.width = getWidth();
81  _activeArea.height = getHeight();
82  }
83 
84  if (_overlayVisible)
85  return;
86 
87  _overlayVisible = true;
88  _forceRedraw = true;
90  }
91 
92  void hideOverlay() override {
93  if (!_overlayVisible)
94  return;
95 
96  _overlayInGUI = false;
97 
99  _activeArea.width = getWidth();
100  _activeArea.height = getHeight();
101  _overlayVisible = false;
102  _forceRedraw = true;
104  }
105 
106  bool isOverlayVisible() const override { return _overlayVisible; }
107 
108  Common::Rect getSafeOverlayArea(int16 *width, int16 *height) const override {
109  Insets insets = getSafeAreaInsets();
110 
111  // Create the overlay rect cut of the insets
112  // in the window coordinate space
113  // Make sure to avoid a negative size (and an invalid rect)
114  const int safeLeft = MAX(_overlayDrawRect.left, insets.left),
115  safeTop = MAX(_overlayDrawRect.top, insets.top);
116  Common::Rect safeArea(safeLeft, safeTop,
117  MAX(safeLeft, MIN((int)_overlayDrawRect.right, _windowWidth - insets.right)),
118  MAX(safeTop, MIN((int)_overlayDrawRect.bottom, _windowHeight - insets.bottom)));
119 
120  // Convert this safe area in the overlay coordinate space
121  const int targetWidth = getOverlayWidth(),
122  targetHeight = getOverlayHeight(),
123  sourceWidth = _overlayDrawRect.width(),
124  sourceHeight = _overlayDrawRect.height();
125 
126  if (width) *width = targetWidth;
127  if (height) *height = targetHeight;
128 
129  int rotatedTargetWidth = targetWidth,
130  rotatedTargetHeight = targetHeight;
131  if (_rotationMode == Common::kRotation90 || _rotationMode == Common::kRotation270) {
132  SWAP(rotatedTargetWidth, rotatedTargetHeight);
133  }
134 
135  // First make it relative to overlay origin and scale it
136  safeArea.left = ((safeArea.left - _overlayDrawRect.left) * rotatedTargetWidth) / sourceWidth;
137  safeArea.top = ((safeArea.top - _overlayDrawRect.top) * rotatedTargetHeight) / sourceHeight;
138  safeArea.right = ((safeArea.right - _overlayDrawRect.left) * rotatedTargetWidth) / sourceWidth;
139  safeArea.bottom = ((safeArea.bottom - _overlayDrawRect.top) * rotatedTargetHeight) / sourceHeight;
140 
141  // Now rotate it
142  switch (_rotationMode) {
143  default:
144  case Common::kRotationNormal:
145  // Nothing to do
146  break;
147  case Common::kRotation90: {
148  int16 tmp = safeArea.left;
149  safeArea.left = safeArea.top;
150  safeArea.top = rotatedTargetWidth - safeArea.right;
151  //safeArea.right = targetWidth - (rotatedTargetHeight - safeArea.bottom);
152  safeArea.right = safeArea.bottom; // targetWidth == rotatedTargetHeight
153  safeArea.bottom = targetHeight - tmp;
154  break;
155  }
156  case Common::kRotation180: {
157  int16 tmp;
158  tmp = safeArea.left;
159  safeArea.left = rotatedTargetWidth - safeArea.right;
160  safeArea.right = rotatedTargetWidth - tmp;
161  tmp = safeArea.top;
162  safeArea.top = rotatedTargetHeight - safeArea.bottom;
163  safeArea.bottom = rotatedTargetHeight - tmp;
164  break;
165  }
166  case Common::kRotation270: {
167  int16 tmp = safeArea.left;
168  safeArea.left = rotatedTargetHeight - safeArea.bottom;
169  //safeArea.bottom = targetHeight - (rotatedTargetWidth - safeArea.right);
170  safeArea.bottom = safeArea.right; // targetHeight == rotatedTargetWidth
171  safeArea.right = targetWidth - safeArea.top;
172  safeArea.top = tmp;
173  break;
174  }
175  }
176 
177  return safeArea;
178  }
179 
180  void setShakePos(int shakeXOffset, int shakeYOffset) override {
181  if (_gameScreenShakeXOffset != shakeXOffset || _gameScreenShakeYOffset != shakeYOffset) {
182  _gameScreenShakeXOffset = shakeXOffset;
183  _gameScreenShakeYOffset = shakeYOffset;
185  _cursorNeedsRedraw = true;
186  }
187  }
188 
189  int getWindowWidth() const { return _windowWidth; }
190  int getWindowHeight() const { return _windowHeight; }
191 
192  void setIgnoreGameSafeArea(bool ignoreGameSafeArea) {
193  if (_ignoreGameSafeArea == ignoreGameSafeArea) {
194  return;
195  }
196 
197  _ignoreGameSafeArea = ignoreGameSafeArea;
198 
199  Insets insets = getSafeAreaInsets();
200  if (insets.left == 0 &&
201  insets.top == 0 &&
202  insets.right == 0 &&
203  insets.bottom == 0) {
204  return;
205  }
206 
208  }
209 
210 protected:
215  virtual bool gameNeedsAspectRatioCorrection() const = 0;
216 
221  virtual void handleResizeImpl(const int width, const int height) = 0;
222 
228  Common::Point convertVirtualToWindow(const int x, const int y) const {
229  const int targetX = _activeArea.drawRect.left;
230  const int targetY = _activeArea.drawRect.top;
231  const int targetWidth = _activeArea.drawRect.width();
232  const int targetHeight = _activeArea.drawRect.height();
233  const int sourceWidth = _activeArea.width;
234  const int sourceHeight = _activeArea.height;
235 
236  if (sourceWidth == 0 || sourceHeight == 0) {
237  error("convertVirtualToWindow called without a valid draw rect");
238  }
239 
240  int windowX, windowY;
241  switch (_rotationMode) {
242  default:
243  case Common::kRotationNormal:
244  windowX = targetX + (x * targetWidth + sourceWidth / 2) / sourceWidth;
245  windowY = targetY + (y * targetHeight + sourceHeight / 2) / sourceHeight;
246  break;
247  case Common::kRotation90:
248  windowX = targetX + ((y - (sourceHeight - 1)) * targetWidth + sourceHeight / 2) / sourceHeight;
249  windowY = targetY + (x * targetHeight + sourceWidth / 2) / sourceWidth;
250  break;
251  case Common::kRotation180:
252  windowX = targetX + ((x - (sourceWidth - 1)) * targetWidth + sourceWidth / 2) / sourceWidth;
253  windowY = targetY + ((y - (sourceHeight - 1)) * targetHeight + sourceHeight / 2) / sourceHeight;
254  break;
255  case Common::kRotation270:
256  windowX = targetX + (y * targetWidth + sourceHeight / 2) / sourceHeight;
257  windowY = targetY + ((x - (sourceWidth - 1)) * targetHeight + sourceWidth / 2) / sourceWidth;
258  break;
259  }
260 
261  return Common::Point(CLIP<int>(windowX, targetX, targetX + targetWidth - 1),
262  CLIP<int>(windowY, targetY, targetY + targetHeight - 1));
263  }
264 
270  Common::Point convertWindowToVirtual(int x, int y) const {
271  const int sourceX = _activeArea.drawRect.left;
272  const int sourceY = _activeArea.drawRect.top;
273  const int sourceMaxX = _activeArea.drawRect.right - 1;
274  const int sourceMaxY = _activeArea.drawRect.bottom - 1;
275  const int sourceWidth = _activeArea.drawRect.width();
276  const int sourceHeight = _activeArea.drawRect.height();
277  const int targetWidth = _activeArea.width;
278  const int targetHeight = _activeArea.height;
279 
280  if (sourceWidth == 0 || sourceHeight == 0) {
281  error("convertWindowToVirtual called without a valid draw rect");
282  }
283 
284  x = CLIP<int>(x, sourceX, sourceMaxX);
285  y = CLIP<int>(y, sourceY, sourceMaxY);
286 
287  int virtualX, virtualY;
288  switch (_rotationMode) {
289  default:
290  case Common::kRotationNormal:
291  virtualX = ((x - sourceX) * targetWidth + sourceWidth / 2) / sourceWidth;
292  virtualY = ((y - sourceY) * targetHeight + sourceHeight / 2) / sourceHeight;
293  break;
294  case Common::kRotation90:
295  virtualY = targetHeight - 1 - ((x - sourceX) * targetHeight + sourceWidth / 2) / sourceWidth;
296  virtualX = ((y - sourceY) * targetWidth + sourceHeight / 2) / sourceHeight;
297  break;
298  case Common::kRotation180:
299  virtualX = targetWidth - 1 - ((x - sourceX) * targetWidth + sourceWidth / 2) / sourceWidth;
300  virtualY = targetHeight - 1 - ((y - sourceY) * targetHeight + sourceHeight / 2) / sourceHeight;
301  break;
302  case Common::kRotation270:
303  virtualY = ((x - sourceX) * targetHeight + sourceWidth / 2) / sourceWidth;
304  virtualX = targetWidth - 1 - ((y - sourceY) * targetWidth + sourceHeight / 2) / sourceHeight;
305  break;
306  }
307 
308  return Common::Point(CLIP<int>(virtualX, 0, targetWidth - 1),
309  CLIP<int>(virtualY, 0, targetHeight - 1));
310  }
311 
316  if (getHeight() == 0 || gameNeedsAspectRatioCorrection()) {
317  return intToFrac(4) / 3;
318  }
319 
320  return intToFrac(getWidth()) / getHeight();
321  }
322 
326  virtual int getGameRenderScale() const {
327  return 1;
328  }
329 
330  struct Insets {
331  int16 left;
332  int16 top;
333  int16 right;
334  int16 bottom;
335  };
336 
343  virtual Insets getSafeAreaInsets() const {
344  return {0, 0, 0, 0};
345  }
346 
353  void handleResize(const int width, const int height) {
354  _windowWidth = width;
355  _windowHeight = height;
356  handleResizeImpl(width, height);
357  }
358 
363  virtual void recalculateDisplayAreas() {
364  if (_windowHeight == 0) {
365  return;
366  }
367 
368  // Compute a safe area rectangle out of the insets
369  Insets insets;
370  if (_ignoreGameSafeArea) {
371  insets = {0, 0, 0, 0};
372  } else {
373  insets = getSafeAreaInsets();
374  }
375  Common::Rect safeArea(insets.left, insets.top,
376  _windowWidth - insets.right,
377  _windowHeight - insets.bottom);
378 
379  // Create a game draw rect using the safe are dimensions
380  populateDisplayAreaDrawRect(getDesiredGameAspectRatio(),
381  getWidth() * getGameRenderScale(), getHeight() * getGameRenderScale(),
382  safeArea, _gameDrawRect);
383 
384  if (getOverlayHeight()) {
385  const int16 overlayWidth = getOverlayWidth(),
386  overlayHeight = getOverlayHeight();
387  const frac_t overlayAspect = intToFrac(overlayWidth) / overlayHeight;
388  populateDisplayAreaDrawRect(overlayAspect, overlayWidth, overlayHeight,
390  }
391 
392  if (_overlayInGUI) {
394  _activeArea.width = getOverlayWidth();
395  _activeArea.height = getOverlayHeight();
396  } else {
398  _activeArea.width = getWidth();
399  _activeArea.height = getHeight();
400  }
402  }
403 
411  virtual void setSystemMousePosition(const int x, const int y) = 0;
412 
416  virtual void notifyActiveAreaChanged() {}
417 
418  bool showMouse(bool visible) override {
419  if (_cursorVisible == visible) {
420  return visible;
421  }
422 
423  const bool last = _cursorVisible;
424  _cursorVisible = visible;
425  _cursorNeedsRedraw = true;
426  return last;
427  }
428 
435  void warpMouse(int x, int y) override {
436  // Check active coordinate instead of window coordinate to avoid warping
437  // the mouse if it is still within the same virtual pixel
438  const Common::Point virtualCursor = convertWindowToVirtual(_cursorX, _cursorY);
439  if (virtualCursor.x != x || virtualCursor.y != y) {
440  // Warping the mouse in SDL generates a mouse movement event, so
441  // `setMousePosition` would be called eventually through the
442  // `notifyMousePosition` callback if we *only* set the system mouse
443  // position here. However, this can cause problems with some games.
444  // For example, the cannon script in CoMI calls to warp the mouse
445  // twice each time the cannon is reloaded, and unless we update the
446  // mouse position immediately, the second call is ignored, which
447  // causes the cannon to change its aim.
448  const Common::Point windowCursor = convertVirtualToWindow(x, y);
449  setMousePosition(windowCursor.x, windowCursor.y);
450  setSystemMousePosition(windowCursor.x, windowCursor.y);
451  }
452  }
453 
460  void setMousePosition(int x, int y) {
461  if (_cursorX != x || _cursorY != y) {
462  _cursorNeedsRedraw = true;
463  }
464 
465  _cursorX = x;
466  _cursorY = y;
467  }
468 
473 
478 
484 
489 
494 
500 
505 
510 
515 
520 
526 
530  struct DisplayArea {
535 
539  int width;
540 
544  int height;
545  };
546 
553 
558 
563 
568 
574 
578  int _cursorX, _cursorY;
579 
580 private:
581  void populateDisplayAreaDrawRect(const frac_t displayAspect, int originalWidth, int originalHeight, const Common::Rect &safeArea, Common::Rect &drawRect) const {
582  int mode = getStretchMode();
583 
584  Common::Rect rotatedSafeArea(safeArea);
585  int rotatedWindowWidth = _windowWidth,
586  rotatedWindowHeight = _windowHeight;
587 
588  if (_rotationMode == Common::kRotation90 || _rotationMode == Common::kRotation270) {
589  SWAP(rotatedSafeArea.left, rotatedSafeArea.top);
590  SWAP(rotatedSafeArea.right, rotatedSafeArea.bottom);
591  SWAP(rotatedWindowWidth, rotatedWindowHeight);
592  }
593  const int rotatedSafeWidth = rotatedSafeArea.width(),
594  rotatedSafeHeight = rotatedSafeArea.height();
595 
596  // Mode Center = use original size, or divide by an integral amount if window is smaller than game surface
597  // Mode Integral = scale by an integral amount.
598  // Mode Fit = scale to fit the window while respecting the aspect ratio
599  // Mode Stretch = scale and stretch to fit the window without respecting the aspect ratio
600  // Mode Fit Force Aspect = scale to fit the window while forcing a 4:3 aspect ratio
601  int width = 0, height = 0;
602  if (mode == STRETCH_CENTER || mode == STRETCH_INTEGRAL || mode == STRETCH_INTEGRAL_AR) {
603  width = originalWidth;
604  height = intToFrac(width) / displayAspect;
605  if (width > rotatedSafeWidth || height > rotatedSafeHeight) {
606  int fac = 1 + MAX((width - 1) / rotatedSafeWidth, (height - 1) / rotatedSafeHeight);
607  width /= fac;
608  height /= fac;
609  } else if (mode == STRETCH_INTEGRAL) {
610  int fac = MIN(rotatedSafeWidth / width, rotatedSafeHeight / height);
611  width *= fac;
612  height *= fac;
613  } else if (mode == STRETCH_INTEGRAL_AR) {
614  int targetHeight = height;
615  int horizontalFac = rotatedSafeWidth / width;
616  do {
617  width = originalWidth * horizontalFac;
618  int verticalFac = (targetHeight * horizontalFac + originalHeight / 2) / originalHeight;
619  height = originalHeight * verticalFac;
620  --horizontalFac;
621  } while (horizontalFac > 0 && height > rotatedSafeHeight);
622  if (height > rotatedSafeHeight)
623  height = targetHeight;
624  }
625  } else {
626  frac_t windowAspect = intToFrac(rotatedSafeWidth) / rotatedSafeHeight;
627  width = rotatedSafeWidth;
628  height = rotatedSafeHeight;
629  if (mode == STRETCH_FIT_FORCE_ASPECT) {
630  frac_t ratio = intToFrac(4) / 3;
631  if (windowAspect < ratio)
632  height = intToFrac(width) / ratio;
633  else if (windowAspect > ratio)
634  width = fracToInt(height * ratio);
635  } else if (mode != STRETCH_STRETCH) {
636  if (windowAspect < displayAspect)
637  height = intToFrac(width) / displayAspect;
638  else if (windowAspect > displayAspect)
639  width = fracToInt(height * displayAspect);
640  }
641  }
642 
643  int16 alignX, alignY;
644  switch (_screenAlign & SCREEN_ALIGN_XMASK) {
645  default:
646  case SCREEN_ALIGN_CENTER:
647  alignX = ((rotatedWindowWidth - width) / 2);
648  break;
649  case SCREEN_ALIGN_LEFT:
650  alignX = 0;
651  break;
652  case SCREEN_ALIGN_RIGHT:
653  alignX = (rotatedSafeArea.right - width);
654  break;
655  }
656 
657  switch (_screenAlign & SCREEN_ALIGN_YMASK) {
658  default:
659  case SCREEN_ALIGN_MIDDLE:
660  alignY = ((rotatedWindowHeight - height) / 2);
661  break;
662  case SCREEN_ALIGN_TOP:
663  alignY = 0;
664  break;
665  case SCREEN_ALIGN_BOTTOM:
666  alignY = (rotatedSafeArea.bottom - height);
667  break;
668  }
669 
670  rotatedSafeArea.constrain(alignX, alignY, width, height);
671 
672  alignX += _gameScreenShakeXOffset * width / getWidth();
673  alignY += _gameScreenShakeYOffset * height / getHeight();
674 
675  if (_rotationMode == Common::kRotation90 || _rotationMode == Common::kRotation270) {
676  drawRect.top = alignX;
677  drawRect.left = alignY;
678  drawRect.setWidth(height);
679  drawRect.setHeight(width);
680  } else {
681  drawRect.left = alignX;
682  drawRect.top = alignY;
683  drawRect.setWidth(width);
684  drawRect.setHeight(height);
685  }
686  }
687 };
688 
689 #endif
int _gameScreenShakeYOffset
Definition: windowed.h:514
T left
Definition: rect.h:170
Common::Point convertVirtualToWindow(const int x, const int y) const
Definition: windowed.h:228
virtual void handleResizeImpl(const int width, const int height)=0
Common::Rect _gameDrawRect
Definition: windowed.h:519
bool _cursorNeedsRedraw
Definition: windowed.h:567
virtual bool gameNeedsAspectRatioCorrection() const =0
Definition: rect.h:524
int _windowHeight
Definition: windowed.h:477
bool _overlayVisible
Definition: windowed.h:499
int height
Definition: windowed.h:544
void setHeight(T aHeight)
Definition: rect.h:224
int _cursorX
Definition: windowed.h:578
void SWAP(T &a, T &b)
Definition: util.h:84
bool _cursorVisible
Definition: windowed.h:562
void setWidth(T aWidth)
Definition: rect.h:220
T width() const
Definition: rect.h:217
frac_t getDesiredGameAspectRatio() const
Definition: windowed.h:315
Definition: windowed.h:52
bool _overlayInGUI
Definition: windowed.h:504
bool _forceRedraw
Definition: windowed.h:557
virtual Insets getSafeAreaInsets() const
Definition: windowed.h:343
T height() const
Definition: rect.h:218
Common::Rect drawRect
Definition: windowed.h:534
Common::RotationMode _rotationMode
Definition: windowed.h:488
T right
Definition: rect.h:171
int _windowWidth
Definition: windowed.h:472
T y
Definition: rect.h:49
bool _ignoreGameSafeArea
Definition: windowed.h:493
Common::Point convertWindowToVirtual(int x, int y) const
Definition: windowed.h:270
bool _cursorLastInActiveArea
Definition: windowed.h:573
void handleResize(const int width, const int height)
Definition: windowed.h:353
T x
Definition: rect.h:48
Definition: rect.h:144
int width
Definition: windowed.h:539
virtual void recalculateDisplayAreas()
Definition: windowed.h:363
void NORETURN_PRE error(MSVC_PRINTF const char *s,...) GCC_PRINTF(1
Definition: windowed.h:530
void setMousePosition(int x, int y)
Definition: windowed.h:460
Definition: graphics.h:38
void warpMouse(int x, int y) override
Definition: windowed.h:435
T MIN(T a, T b)
Definition: util.h:61
DisplayArea _activeArea
Definition: windowed.h:552
Common::Rect _overlayDrawRect
Definition: windowed.h:525
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:326
int32 frac_t
Definition: frac.h:52
Definition: windowed.h:330
int _screenAlign
Definition: windowed.h:483
int _gameScreenShakeXOffset
Definition: windowed.h:509
virtual void notifyActiveAreaChanged()
Definition: windowed.h:416