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
32enum {
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
41enum {
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
53public:
55 _windowWidth(0),
57 _screenAlign(SCREEN_ALIGN_CENTER | SCREEN_ALIGN_MIDDLE),
58 _rotationMode(Common::kRotationNormal),
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
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
210protected:
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
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;
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
545 };
546
553
558
563
568
574
578 int _cursorX, _cursorY;
579
580private:
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
Definition: graphics.h:38
Definition: windowed.h:52
Common::RotationMode _rotationMode
Definition: windowed.h:488
bool _cursorLastInActiveArea
Definition: windowed.h:573
virtual void setSystemMousePosition(const int x, const int y)=0
Common::Rect _overlayDrawRect
Definition: windowed.h:525
int _screenAlign
Definition: windowed.h:483
int _windowHeight
Definition: windowed.h:477
void warpMouse(int x, int y) override
Definition: windowed.h:435
DisplayArea _activeArea
Definition: windowed.h:552
virtual void notifyActiveAreaChanged()
Definition: windowed.h:416
virtual void handleResizeImpl(const int width, const int height)=0
bool _forceRedraw
Definition: windowed.h:557
Common::Point convertVirtualToWindow(const int x, const int y) const
Definition: windowed.h:228
Common::Point convertWindowToVirtual(int x, int y) const
Definition: windowed.h:270
bool _overlayVisible
Definition: windowed.h:499
virtual void recalculateDisplayAreas()
Definition: windowed.h:363
bool _cursorVisible
Definition: windowed.h:562
int _cursorX
Definition: windowed.h:578
Common::Rect _gameDrawRect
Definition: windowed.h:519
bool _ignoreGameSafeArea
Definition: windowed.h:493
void setMousePosition(int x, int y)
Definition: windowed.h:460
virtual int getGameRenderScale() const
Definition: windowed.h:326
void handleResize(const int width, const int height)
Definition: windowed.h:353
virtual bool gameNeedsAspectRatioCorrection() const =0
bool _overlayInGUI
Definition: windowed.h:504
virtual Insets getSafeAreaInsets() const
Definition: windowed.h:343
frac_t getDesiredGameAspectRatio() const
Definition: windowed.h:315
bool _cursorNeedsRedraw
Definition: windowed.h:567
int _gameScreenShakeXOffset
Definition: windowed.h:509
int _windowWidth
Definition: windowed.h:472
int _gameScreenShakeYOffset
Definition: windowed.h:514
int32 frac_t
Definition: frac.h:52
RotationMode
Definition: rotationmode.h:44
void NORETURN_PRE error(MSVC_PRINTF const char *s,...) GCC_PRINTF(1
void SWAP(T &a, T &b)
Definition: util.h:467
T MIN(T a, T b)
Definition: util.h:63
T MAX(T a, T b)
Definition: util.h:66
T y
Definition: rect.h:49
T x
Definition: rect.h:48
Definition: rect.h:144
bool constrain(const ConcreteRect &o)
Definition: rect.h:426
T right
Definition: rect.h:171
T height() const
Definition: rect.h:218
T left
Definition: rect.h:170
T width() const
Definition: rect.h:217
Definition: rect.h:536
Definition: windowed.h:530
int width
Definition: windowed.h:539
int height
Definition: windowed.h:544
Common::Rect drawRect
Definition: windowed.h:534
Definition: windowed.h:330