Skip to content

Commit

Permalink
Merge pull request #2620 from freakboy3742/cascading-windows
Browse files Browse the repository at this point in the history
Cascading windows
  • Loading branch information
mhsmith authored Jun 12, 2024
2 parents cd7ca2f + c4014ba commit 3c51620
Show file tree
Hide file tree
Showing 14 changed files with 359 additions and 281 deletions.
1 change: 1 addition & 0 deletions changes/2023.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The initial position of each newly created window is now different, cascading down the screen as windows are created.
3 changes: 2 additions & 1 deletion cocoa/src/toga_cocoa/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from toga.command import Command, Separator
from toga.types import Position, Size
from toga.window import _initial_position
from toga_cocoa.container import Container
from toga_cocoa.libs import (
SEL,
Expand Down Expand Up @@ -161,7 +162,7 @@ def __init__(self, interface, title, position, size):

self.set_title(title)
self.set_size(size)
self.set_position(position)
self.set_position(position if position is not None else _initial_position())

self.native.delegate = self.native

Expand Down
2 changes: 1 addition & 1 deletion core/src/toga/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def __init__(
self,
id: str | None = None,
title: str | None = None,
position: PositionT = Position(100, 100),
position: PositionT | None = None,
size: SizeT = Size(640, 480),
resizable: bool = True,
minimizable: bool = True,
Expand Down
31 changes: 26 additions & 5 deletions core/src/toga/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@
from toga.widgets.base import Widget


_window_count = -1


def _initial_position() -> Position:
"""Compute a cascading initial position for platforms that don't have a native
implementation.
This is a stateful method; each time it is invoked, it will yield a new initial
position.
:returns: The position for the new window.
"""
# Each new window created without an explicit position is positioned
# 50px down and to the right from the previous window, with the first
# window positioned at (100, 100). Every 15 windows, move back to a
# y coordinate of 100, and start from 50 pixels further right.
global _window_count
_window_count += 1

pos = 100 + (_window_count % 15) * 50
return Position(pos + (_window_count // 15 * 50), pos)


class FilteredWidgetRegistry:
# A class that exposes a mapping lookup interface, filtered to widgets from a single
# window. The underlying data store is on the app.
Expand Down Expand Up @@ -117,7 +140,7 @@ def __init__(
self,
id: str | None = None,
title: str | None = None,
position: PositionT = Position(100, 100),
position: PositionT | None = None,
size: SizeT = Size(640, 480),
resizable: bool = True,
closable: bool = True,
Expand Down Expand Up @@ -178,13 +201,11 @@ def __init__(
self._minimizable = minimizable

self.factory = get_platform_factory()
position = Position(*position)
size = Size(*size)
self._impl = getattr(self.factory, self._WINDOW_CLASS)(
interface=self,
title=title if title else self._default_title,
position=position,
size=size,
position=None if position is None else Position(*position),
size=Size(*size),
)

# Add the window to the app
Expand Down
3 changes: 2 additions & 1 deletion core/tests/app/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def test_create(app):
assert isinstance(window.id, str)
# Window title is the app title.
assert window.title == "Test App"
assert window.position == (100, 100)
# The app has created a main window, so this will be the second window.
assert window.position == (150, 150)
assert window.size == (640, 480)
assert window.resizable
assert window.closable
Expand Down
6 changes: 5 additions & 1 deletion core/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import pytest

import toga
from toga import window as toga_window
from toga_dummy.utils import EventLog


@pytest.fixture(autouse=True)
def reset_event_log():
def reset_global_state():
# Clear the testing event log
EventLog.reset()
# Reset the global window count
toga_window._window_count = -1


@pytest.fixture(autouse=True)
Expand Down
51 changes: 48 additions & 3 deletions core/tests/window/test_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def test_window_created(app):
# We can't know what the ID is, but it must be a string.
assert isinstance(window.id, str)
assert window.title == "Toga"
assert window.position == toga.Position(100, 100)
# The app has created a main window, so this will be the second window.
assert window.position == toga.Position(150, 150)
assert window.size == toga.Size(640, 480)
assert window.resizable
assert window.closable
Expand Down Expand Up @@ -207,6 +208,48 @@ def test_set_position(window):
assert window.position == toga.Position(123, 456)


def test_position_cascade(app):
"""The initial position of windows will cascade."""
windows = [app.main_window]

for i in range(0, 14):
win = toga.Window(title=f"Window {i}")
# The for the first 14 new windows (the app creates the first window)
# the x and y coordinates must be the same
assert win.position[0] == win.position[1]
# The position of the window should cascade down
assert win.position[0] > windows[-1].position[0]
assert win.position[1] > windows[-1].position[1]

windows.append(win)

# The 15th window will come back to the y origin, but shift along the x axis.
win = toga.Window(title=f"Window {i}")
assert win.position[0] > windows[0].position[0]
assert win.position[1] == windows[0].position[1]

windows.append(win)

# Cascade another 15 windows
for i in range(16, 30):
win = toga.Window(title=f"Window {i}")
# The position of the window should cascade down
assert win.position[0] > windows[-1].position[0]
assert win.position[1] > windows[-1].position[1]

# The y coordinate of these windows should be the same
# as 15 windows ago; the x coordinate is shifted right
assert win.position[0] > windows[i - 15].position[0]
assert win.position[1] == windows[i - 15].position[1]

windows.append(win)

# The 30 window will come back to the y origin, but shift along the x axis.
win = toga.Window(title=f"Window {i}")
assert win.position[0] > windows[15].position[0]
assert win.position[1] == windows[15].position[1]


def test_set_size(window):
"""The size of the window can be set."""
window.size = (123, 456)
Expand Down Expand Up @@ -392,9 +435,11 @@ def test_screen(window, app):
# window between the screens.
# `window.screen` will return `Secondary Screen`
assert window.screen == app.screens[1]
assert window.position == toga.Position(100, 100)
# The app has created a main window; the secondary window will be at second
# position.
assert window.position == toga.Position(150, 150)
window.screen = app.screens[0]
assert window.position == toga.Position(1466, 868)
assert window.position == toga.Position(1516, 918)


def test_screen_position(window, app):
Expand Down
3 changes: 2 additions & 1 deletion dummy/src/toga_dummy/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import toga_dummy
from toga.types import Size
from toga.window import _initial_position

from .screens import Screen as ScreenImpl
from .utils import LoggedObject
Expand Down Expand Up @@ -50,7 +51,7 @@ def __init__(self, interface, title, position, size):
self.container = Container()

self.set_title(title)
self.set_position(position)
self.set_position(position if position is not None else _initial_position())
self.set_size(size)

def create_toolbar(self):
Expand Down
3 changes: 2 additions & 1 deletion gtk/src/toga_gtk/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from toga.command import Separator
from toga.types import Position, Size
from toga.window import _initial_position

from .container import TogaContainer
from .libs import Gdk, Gtk
Expand All @@ -30,7 +31,7 @@ def __init__(self, interface, title, position, size):
self.native.set_default_size(size[0], size[1])

self.set_title(title)
self.set_position(position)
self.set_position(position if position is not None else _initial_position())

# Set the window deletable/closable.
self.native.set_deletable(self.interface.closable)
Expand Down
2 changes: 1 addition & 1 deletion testbed/tests/app/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from toga.colors import CORNFLOWERBLUE, FIREBRICK, REBECCAPURPLE
from toga.style.pack import Pack

from ..test_window import window_probe
from ..window.test_window import window_probe


@pytest.fixture
Expand Down
Empty file.
Loading

0 comments on commit 3c51620

Please sign in to comment.