Skip to content

Commit

Permalink
Store the window's position in preferences.
Browse files Browse the repository at this point in the history
And restore it when the game restarts, assuming the display
configuration remains the same.

This also trusts the stored window size when the display
configuration remains the same - on the grounds that if it worked
in the past, it'll work in the future.
  • Loading branch information
renpytom committed Nov 20, 2023
1 parent dcc1830 commit 534af32
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 22 deletions.
38 changes: 38 additions & 0 deletions renpy/display/core.py
Expand Up @@ -1804,6 +1804,39 @@ def setup_dpi_scaling(self):
renpy.display.log.exception()
return 1.0

def get_total_display_size(self):
"""
Gets the total size of all the displays in the system.
"""

minx = 0
miny = 0
maxx = 0
maxy = 0

for i in range(pygame.display.get_num_video_displays()):
r = pygame.display.get_display_bounds(i)
minx = min(minx, r[0])
miny = min(miny, r[1])
maxx = max(maxx, r[0] + r[2])
maxy = max(maxy, r[1] + r[3])

return (minx, miny, maxx - minx, maxy - miny)

def on_move(self, pos):
"""
Called when the player moves the window.
"""

if not (renpy.windows or renpy.macintosh or renpy.linux):
return

if renpy.game.preferences.fullscreen or renpy.game.preferences.maximized:
return

renpy.game.preferences.window_position = pos
renpy.game.preferences.window_position_screen_size = self.get_total_display_size()

def start(self):
"""
Starts the interface, by opening a window and setting the mode.
Expand Down Expand Up @@ -3870,6 +3903,11 @@ def take_layer_displayable(ld):

continue

# Handle window moves.
if ev.type == pygame.WINDOWMOVED:
self.on_move(ev.pos)
continue

# If we're ignoring touch events, and get a mouse up, stop
# ignoring those events.
if self.ignore_touch and \
Expand Down
73 changes: 51 additions & 22 deletions renpy/gl2/gl2draw.pyx
Expand Up @@ -76,9 +76,6 @@ vsync = True
# A list of frame end times, used for the same purpose.
frame_times = [ ]

# The default position of the window.
default_position = (pygame.WINDOWPOS_CENTERED, pygame.WINDOWPOS_CENTERED)

cdef class GL2Draw:

def __init__(self, name):
Expand Down Expand Up @@ -146,6 +143,11 @@ cdef class GL2Draw:
it.
"""

limit_physical_size = True

if physical_size and renpy.game.preferences.window_position_screen_size == renpy.game.interface.get_total_display_size():
limit_physical_size = False

# Are we maximized?
old_surface = pygame.display.get_surface()
if old_surface is not None:
Expand Down Expand Up @@ -197,12 +199,14 @@ cdef class GL2Draw:

if (not renpy.mobile) and (not maximized):

# Limit to the visible area
pwidth = min(visible_w, pwidth)
pheight = min(visible_h, pheight)
if limit_physical_size:

# Limit to the visible area
pwidth = min(visible_w, pwidth)
pheight = min(visible_h, pheight)

pwidth = min(pwidth, head_w)
pheight = min(pheight, head_h)
pwidth = min(pwidth, head_w)
pheight = min(pheight, head_h)

# Has to be a one-liner as the two values depend on each other.
pwidth, pheight = min(pheight * virtual_ar, pwidth), min(pwidth / virtual_ar, pheight)
Expand Down Expand Up @@ -282,6 +286,37 @@ cdef class GL2Draw:
if renpy.config.gl_set_attributes is not None:
renpy.config.gl_set_attributes()


def get_window_position(self, physical_size=None):
"""
Determines the position of the window, based on what's stored
in preferences.
The stored position is used only when the total display size hasn't
changed, and the window would not overlap the edge of the screen.
"""

default = (pygame.WINDOWPOS_CENTERED, pygame.WINDOWPOS_CENTERED)

if not (renpy.linux or renpy.windows or renpy.macintosh):
return default

if renpy.game.preferences.window_position_screen_size != renpy.game.interface.get_total_display_size():
return default

pos = renpy.game.preferences.window_position
rect = renpy.game.interface.get_total_display_size()

if pos[0] < rect[0] or pos[1] < rect[1]:
return default

if physical_size is not None:
pwidth, pheight = physical_size
if pos[0] + pwidth > rect[2] and pos[1] + pheight > rect[3]:
return default

return pos

def init(self, virtual_size):
"""
This changes the video mode. It also initializes OpenGL, if it
Expand Down Expand Up @@ -383,10 +418,15 @@ cdef class GL2Draw:

if renpy.game.preferences.maximized:
window_flags |= pygame.WINDOW_MAXIMIZED
else:
self.ever_set_position = True

pos = self.get_window_position((pwidth, pheight))

try:
renpy.display.log.write("Windowed mode.")
self.window = pygame.display.set_mode((pwidth, pheight), window_flags)
self.window = pygame.display.set_mode((pwidth, pheight), window_flags, pos=pos)


except pygame.error as e:
renpy.display.log.write("Could not get pygame screen: %r", e)
Expand Down Expand Up @@ -457,11 +497,12 @@ cdef class GL2Draw:
# Are we maximized?
maximized = bool(pygame.display.get_window().get_window_flags() & pygame.WINDOW_MAXIMIZED) and not fullscreen and renpy.config.gl_resize


# See if we've ever set the screen position, and if not, center the window.
if not fullscreen and not maximized:
if not self.ever_set_position:
self.ever_set_position = True
pygame.display.get_window().set_position(default_position)
pygame.display.get_window().set_position(self.get_window_position())

# Get the size of the created screen.
pwidth, pheight = renpy.display.core.get_size()
Expand Down Expand Up @@ -608,18 +649,6 @@ cdef class GL2Draw:
Called when terminating the use of the OpenGL context.
"""

global default_position

# Are we in fullscreen mode?
fullscreen = bool(pygame.display.get_window().get_window_flags() & (pygame.WINDOW_FULLSCREEN_DESKTOP | pygame.WINDOW_FULLSCREEN))

# Are we maximized?
maximized = bool(pygame.display.get_window().get_window_flags() & pygame.WINDOW_MAXIMIZED)

# See if we've ever set the screen position, and if not, center the window.
if not fullscreen and not maximized:
default_position = pygame.display.get_position()

self.kill_textures()

if self.texture_loader is not None:
Expand Down
8 changes: 8 additions & 0 deletions renpy/preferences.py
Expand Up @@ -185,6 +185,12 @@ def __init__(self, name, default, types=None):
# Should the game be maximized?
Preference("maximized", False)

# The position of the window.
Preference("window_position", None, (tuple, type(None)))

# The size of screen that window_position was set for.
Preference("window_position_screen_size", None, (tuple, type(None)))

class Preferences(renpy.object.Object):
"""
Stores preferences that will one day be persisted.
Expand Down Expand Up @@ -236,6 +242,8 @@ class Preferences(renpy.object.Object):
web_cache_preload = False
voice_after_game_menu = False
maximized = False
window_position = (0, 0)
window_position_screen_size = (0, 0, 7680, 2160)

def init(self):
"""
Expand Down
6 changes: 6 additions & 0 deletions sphinx/source/changelog.rst
Expand Up @@ -219,6 +219,12 @@ can only be called from the main thread.)
Other Changes
-------------

On PC platforms (Windows, Mac, and Linux), when the window containing
the game moves, it's position will be store. The window's position will
be restored when the game is restarted, if the total size of all virtual
desktops is the game, and the window is fully contained on the virtual
screen.

On controllers (including the Steam Deck), the function of the B button
has changed to show and hide the game menu. The previous behavior of the
B button, selecting a button's alternate function, has been moved to X.
Expand Down

0 comments on commit 534af32

Please sign in to comment.