Skip to content

Commit

Permalink
Merge pull request #2585 from rashenck/add-size-position-namedtuples-…
Browse files Browse the repository at this point in the history
…2388

Add NamedTuples for Size and Position references
  • Loading branch information
freakboy3742 authored Jun 7, 2024
2 parents 7712d51 + 7b423c6 commit ee95e5a
Show file tree
Hide file tree
Showing 29 changed files with 276 additions and 155 deletions.
9 changes: 5 additions & 4 deletions android/src/toga_android/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
)

from toga.screens import Screen as ScreenInterface
from toga.types import Position, Size

from .widgets.base import Scalable

Expand All @@ -26,11 +27,11 @@ def __new__(cls, app, native):
def get_name(self):
return self.native.getName()

def get_origin(self):
return (0, 0)
def get_origin(self) -> Position:
return Position(0, 0)

def get_size(self):
return (
def get_size(self) -> Size:
return Size(
self.scale_out(self.native.getWidth()),
self.scale_out(self.native.getHeight()),
)
Expand Down
10 changes: 6 additions & 4 deletions android/src/toga_android/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from java import dynamic_proxy
from java.io import ByteArrayOutputStream

from toga.types import Position, Size

from .container import Container
from .screens import Screen as ScreenImpl

Expand Down Expand Up @@ -96,8 +98,8 @@ def refreshed(self):
# Window size
######################################################################

def get_size(self):
return (self.width, self.height)
def get_size(self) -> Size:
return Size(self.width, self.height)

def set_size(self, size):
# Does nothing on mobile
Expand All @@ -112,8 +114,8 @@ def get_current_screen(self):
window_manager = context.getSystemService(Context.WINDOW_SERVICE)
return ScreenImpl(self.app, window_manager.getDefaultDisplay())

def get_position(self):
return 0, 0
def get_position(self) -> Position:
return Position(0, 0)

def set_position(self, position):
# Does nothing on mobile
Expand Down
1 change: 1 addition & 0 deletions changes/2388.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Size and position properties now return values as a `Size` and `Position` namedtuple, respectively. These namedtuple objects support addition and subtraction operations. Basic tuples can still be used to *set* these properties.
9 changes: 5 additions & 4 deletions cocoa/src/toga_cocoa/screens.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rubicon.objc import CGSize

from toga.screens import Screen as ScreenInterface
from toga.types import Position, Size
from toga_cocoa.libs import (
NSImage,
core_graphics,
Expand All @@ -23,13 +24,13 @@ def __new__(cls, native):
def get_name(self):
return str(self.native.localizedName)

def get_origin(self):
def get_origin(self) -> Position:
frame_native = self.native.frame
return (int(frame_native.origin.x), int(frame_native.origin.y))
return Position(int(frame_native.origin.x), int(frame_native.origin.y))

def get_size(self):
def get_size(self) -> Size:
frame_native = self.native.frame
return (int(frame_native.size.width), int(frame_native.size.height))
return Size(int(frame_native.size.width), int(frame_native.size.height))

def get_image_data(self):
# Retrieve the device description dictionary for the NSScreen
Expand Down
9 changes: 5 additions & 4 deletions cocoa/src/toga_cocoa/window.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rubicon.objc import CGSize

from toga.command import Command, Separator
from toga.types import Position, Size
from toga_cocoa.container import Container
from toga_cocoa.libs import (
SEL,
Expand Down Expand Up @@ -285,9 +286,9 @@ def set_content(self, widget):
# Window size
######################################################################

def get_size(self):
def get_size(self) -> Size:
frame = self.native.frame
return frame.size.width, frame.size.height
return Size(frame.size.width, frame.size.height)

def set_size(self, size):
frame = self.native.frame
Expand All @@ -301,14 +302,14 @@ def set_size(self, size):
def get_current_screen(self):
return ScreenImpl(self.native.screen)

def get_position(self):
def get_position(self) -> Position:
# The "primary" screen has index 0 and origin (0, 0).
primary_screen = NSScreen.screens[0].frame
window_frame = self.native.frame

# macOS origin is bottom left of screen, and the screen might be
# offset relative to other screens. Adjust for this.
return (
return Position(
window_frame.origin.x,
primary_screen.size.height
- (window_frame.origin.y + window_frame.size.height),
Expand Down
4 changes: 3 additions & 1 deletion core/src/toga/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from .keys import Key

# Types
from .types import LatLng
from .types import LatLng, Position, Size

# Widgets
from .widgets.activityindicator import ActivityIndicator
Expand Down Expand Up @@ -78,6 +78,8 @@ def warn(self, platform, feature):
"Image",
# Types
"LatLng",
"Position",
"Size",
# Widgets
"ActivityIndicator",
"Box",
Expand Down
24 changes: 14 additions & 10 deletions core/src/toga/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
from toga.paths import Paths
from toga.platform import get_platform_factory
from toga.screens import Screen
from toga.types import Position, Size
from toga.widgets.base import Widget
from toga.window import Window

if TYPE_CHECKING:
from toga.icons import IconContent
from toga.types import PositionT, SizeT

# Make sure deprecation warnings are shown by default
warnings.filterwarnings("default", category=DeprecationWarning)
Expand Down Expand Up @@ -206,8 +208,8 @@ def __init__(
self,
id: str | None = None,
title: str | None = None,
position: tuple[int, int] = (100, 100),
size: tuple[int, int] = (640, 480),
position: PositionT = Position(100, 100),
size: SizeT = Size(640, 480),
resizable: bool = True,
minimizable: bool = True,
content: Widget | None = None,
Expand All @@ -219,10 +221,10 @@ def __init__(
:param id: A unique identifier for the window. If not provided, one will be
automatically generated.
:param title: Title for the window. Defaults to the formal name of the app.
:param position: Position of the window, as a tuple of ``(x, y)`` coordinates,
in :ref:`CSS pixels <css-units>`.
:param size: Size of the window, as a tuple of ``(width, height)``, in :ref:`CSS
pixels <css-units>`.
:param position: Position of the window, as a :any:`toga.Position` or tuple of
``(x, y)`` coordinates, in :ref:`CSS pixels <css-units>`.
:param size: Size of the window, as a :any:`toga.Size` or tuple of ``(width,
height)``, in :ref:`CSS pixels <css-units>`.
:param resizable: Can the window be resized by the user?
:param minimizable: Can the window be minimized by the user?
:param content: The initial content for the window.
Expand Down Expand Up @@ -274,8 +276,8 @@ def __init__(
doc: Document,
id: str | None = None,
title: str | None = None,
position: tuple[int, int] = (100, 100),
size: tuple[int, int] = (640, 480),
position: PositionT = Position(100, 100),
size: SizeT = Size(640, 480),
resizable: bool = True,
minimizable: bool = True,
):
Expand All @@ -289,8 +291,10 @@ def __init__(
:param document: The document being managed by this window
:param id: The ID of the window.
:param title: Title for the window. Defaults to the formal name of the app.
:param position: Position of the window, as a tuple of ``(x, y)`` coordinates.
:param size: Size of the window, as a tuple of ``(width, height)``, in pixels.
:param position: Position of the window, as a :any:`toga.Position` or tuple of
``(x, y)`` coordinates.
:param size: Size of the window, as a :any:`toga.Size` or tuple of
``(width, height)``, in pixels.
:param resizable: Can the window be manually resized by the user?
:param minimizable: Can the window be minimized by the user?
"""
Expand Down
10 changes: 6 additions & 4 deletions core/src/toga/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from toga.images import Image
from toga.platform import get_platform_factory
from toga.types import Position, Size

if TYPE_CHECKING:
from toga.images import ImageT
Expand All @@ -20,13 +21,14 @@ def name(self) -> str:
return self._impl.get_name()

@property
def origin(self) -> tuple[int, int]:
"""The absolute coordinates of the screen's origin, as a ``(x, y)`` tuple."""
def origin(self) -> Position:
"""The absolute coordinates of the screen's origin, in :ref:`CSS pixels
<css-units>`."""
return self._impl.get_origin()

@property
def size(self) -> tuple[int, int]:
"""The size of the screen, as a ``(width, height)`` tuple."""
def size(self) -> Size:
"""The size of the screen, in :ref:`CSS pixels <css-units>`."""
return self._impl.get_size()

def as_image(self, format: type[ImageT] = Image) -> ImageT:
Expand Down
46 changes: 45 additions & 1 deletion core/src/toga/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
from __future__ import annotations

from typing import NamedTuple
import sys
from typing import TYPE_CHECKING, NamedTuple, Tuple

import toga

if TYPE_CHECKING:
if sys.version_info < (3, 10):
from typing_extensions import TypeAlias
else:
from typing import TypeAlias

PositionT: TypeAlias = toga.Position | Tuple[int, int]
SizeT: TypeAlias = toga.Size | Tuple[int, int]


class LatLng(NamedTuple):
Expand All @@ -14,3 +26,35 @@ class LatLng(NamedTuple):

def __str__(self):
return f"({self.lat:6f}, {self.lng:6f})"


class Position(NamedTuple):
"""A 2D window position."""

#: X coordinate, in CSS pixels.
x: int

#: Y coordinate, in CSS pixels.
y: int

def __str__(self) -> str:
return f"({self.x}, {self.y})"

def __add__(self, other):
return Position(self.x + other.x, self.y + other.y)

def __sub__(self, other):
return Position(self.x - other.x, self.y - other.y)


class Size(NamedTuple):
"""A 2D window size."""

#: Width
width: int

#: Height
height: int

def __str__(self) -> str:
return f"({self.width} x {self.height})"
14 changes: 10 additions & 4 deletions core/src/toga/widgets/scrollcontainer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from toga.handlers import wrapped_handler
from toga.types import Position

from .base import Widget

if TYPE_CHECKING:
from toga.types import PositionT


class ScrollContainer(Widget):
def __init__(
Expand Down Expand Up @@ -191,19 +197,19 @@ def vertical_position(self, vertical_position):
# the horizontal and vertical position separately would cause the horizontal and
# vertical movement to appear as two separate animations.
@property
def position(self) -> tuple[int, int]:
"""The current scroll position, in the form (horizontal, vertical).
def position(self) -> Position:
"""The current scroll position.
If the value provided for either axis is negative, or greater than the maximum
position in that axis, the value will be clipped to the valid range.
If scrolling is disabled in either axis, the value provided for that axis will
be ignored.
"""
return (self.horizontal_position, self.vertical_position)
return Position(self.horizontal_position, self.vertical_position)

@position.setter
def position(self, position):
def position(self, position: PositionT):
horizontal_position, vertical_position = map(int, position)
if self.horizontal:
if horizontal_position < 0:
Expand Down
Loading

0 comments on commit ee95e5a

Please sign in to comment.