Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/2942.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The initialization process for widgets has been internally restructured to avoid unnecessary style reapplications.
2 changes: 0 additions & 2 deletions cocoa/src/toga_cocoa/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ class Widget:
def __init__(self, interface):
super().__init__()
self.interface = interface
self.interface._impl = self
self._container = None
self.constraints = None
self.native = None
self.create()
self.interface.style.reapply()

@abstractmethod
def create(self): ...
Expand Down
50 changes: 27 additions & 23 deletions core/src/toga/style/applicator.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,42 @@
from __future__ import annotations
import warnings

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from toga.widgets.base import Widget
# Make sure deprecation warnings are shown by default
warnings.filterwarnings("default", category=DeprecationWarning)


class TogaApplicator:
"""Apply styles to a Toga widget."""

def __init__(self, widget: Widget):
self.widget = widget
def __init__(self, widget: None = None):
if widget is not None:
warnings.warn(
"Widget parameter is deprecated. Applicator will be given a reference "
"to its widget when it is assigned as that widget's applicator.",
DeprecationWarning,
stacklevel=2,
)

def refresh(self) -> None:
# print("RE-EVALUATE LAYOUT", self.widget)
self.widget.refresh()
# print("RE-EVALUATE LAYOUT", self.node)
self.node.refresh()

def set_bounds(self) -> None:
# print(" APPLY LAYOUT", self.widget, self.widget.layout)
self.widget._impl.set_bounds(
self.widget.layout.absolute_content_left,
self.widget.layout.absolute_content_top,
self.widget.layout.content_width,
self.widget.layout.content_height,
# print(" APPLY LAYOUT", self.node, self.node.layout)
self.node._impl.set_bounds(
self.node.layout.absolute_content_left,
self.node.layout.absolute_content_top,
self.node.layout.content_width,
self.node.layout.content_height,
)
for child in self.widget.children:
for child in self.node.children:
child.applicator.set_bounds()

def set_text_alignment(self, alignment: str) -> None:
self.widget._impl.set_alignment(alignment)
self.node._impl.set_alignment(alignment)

def set_hidden(self, hidden: bool) -> None:
self.widget._impl.set_hidden(hidden)
for child in self.widget.children:
self.node._impl.set_hidden(hidden)
for child in self.node.children:
# If the parent is hidden, then so are all children. However, if the
# parent is visible, then the child's explicit visibility style is
# taken into account. This visibility cascades into any
Expand All @@ -49,11 +53,11 @@ def set_hidden(self, hidden: bool) -> None:
def set_font(self, font: object) -> None:
# Changing the font of a widget can make the widget change size,
# which in turn means we need to do a re-layout
self.widget._impl.set_font(font)
self.widget.refresh()
self.node._impl.set_font(font)
self.node.refresh()

def set_color(self, color: object) -> None:
self.widget._impl.set_color(color)
self.node._impl.set_color(color)

def set_background_color(self, color: object) -> None:
self.widget._impl.set_background_color(color)
self.node._impl.set_background_color(color)
4 changes: 2 additions & 2 deletions core/src/toga/widgets/activityindicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@


class ActivityIndicator(Widget):
_IMPL_NAME = "ActivityIndicator"

def __init__(
self,
id: str | None = None,
Expand All @@ -22,8 +24,6 @@ def __init__(
"""
super().__init__(id=id, style=style)

self._impl = self.factory.ActivityIndicator(interface=self)

if running:
self.start()

Expand Down
27 changes: 23 additions & 4 deletions core/src/toga/widgets/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from builtins import id as identifier
from typing import TYPE_CHECKING, Any, TypeVar
from typing import TYPE_CHECKING, TypeVar

from travertino.declaration import BaseStyle
from travertino.node import Node
Expand All @@ -17,6 +17,8 @@


class Widget(Node):
_IMPL_NAME = "Widget"

_MIN_WIDTH = 100
_MIN_HEIGHT = 100

Expand All @@ -34,16 +36,33 @@ def __init__(
will be applied to the widget.
"""
super().__init__(
style=style if style else Pack(),
applicator=TogaApplicator(self),
style=style if style is not None else Pack(),
Comment thread
HalfWhitt marked this conversation as resolved.
Outdated
applicator=None,
Comment thread
HalfWhitt marked this conversation as resolved.
Outdated
)

self._id = str(id if id else identifier(self))
self._window: Window | None = None
self._app: App | None = None
self._impl: Any = None

self.factory = get_platform_factory()
self._impl = getattr(self.factory, self._IMPL_NAME)(interface=self)

self.applicator = TogaApplicator()

##############################################
# Backwards compatibility for Travertino 0.3.0
##############################################

if not hasattr(self.applicator, "node"):
Comment thread
HalfWhitt marked this conversation as resolved.
Outdated
self.applicator.node = self
self.style._applicator = self.applicator
self.style.reapply()
else: # pragma: no cover
pass

#############################
# End backwards compatibility
#############################

def __repr__(self) -> str:
return f"<{self.__class__.__name__}:0x{identifier(self):x}>"
Expand Down
5 changes: 2 additions & 3 deletions core/src/toga/widgets/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@


class Box(Widget):
_IMPL_NAME = "Box"

_MIN_WIDTH = 0
_MIN_HEIGHT = 0

Expand All @@ -24,9 +26,6 @@ def __init__(
"""
super().__init__(id=id, style=style)

# Create a platform specific implementation of a Box
self._impl = self.factory.Box(interface=self)

# Children need to be added *after* the impl has been created.
self._children: list[Widget] = []
if children is not None:
Expand Down
5 changes: 2 additions & 3 deletions core/src/toga/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def __call__(self, widget: Button, /, **kwargs: Any) -> object:


class Button(Widget):
_IMPL_NAME = "Button"

def __init__(
self,
text: str | None = None,
Expand All @@ -44,9 +46,6 @@ def __init__(
"""
super().__init__(id=id, style=style)

# Create a platform specific implementation of a Button
self._impl = self.factory.Button(interface=self)

# Set a dummy handler before installing the actual on_press, because we do not want
# on_press triggered by the initial value being set
self.on_press = None
Expand Down
5 changes: 2 additions & 3 deletions core/src/toga/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,8 @@ def __call__(


class Canvas(Widget):
_IMPL_NAME = "Canvas"

_MIN_WIDTH = 0
_MIN_HEIGHT = 0

Expand Down Expand Up @@ -1241,9 +1243,6 @@ def __init__(

self._context = Context(canvas=self)

# Create a platform specific implementation of Canvas
self._impl = self.factory.Canvas(interface=self)

# Set all the properties
self.on_resize = on_resize
self.on_press = on_press
Expand Down
5 changes: 2 additions & 3 deletions core/src/toga/widgets/dateinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def __call__(self, widget: DateInput, /, **kwargs: Any) -> object:


class DateInput(Widget):
_IMPL_NAME = "DateInput"

_MIN_WIDTH = 200

def __init__(
Expand All @@ -51,9 +53,6 @@ def __init__(
"""
super().__init__(id=id, style=style)

# Create a platform specific implementation of a DateInput
self._impl = self.factory.DateInput(interface=self)

self.on_change = None
self.min = min
self.max = max
Expand Down
22 changes: 11 additions & 11 deletions core/src/toga/widgets/detailedlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def __call__(self, widget: DetailedList, /, **kwargs: Any) -> object:


class DetailedList(Widget):
_IMPL_NAME = "DetailedList"

def __init__(
self,
id: str | None = None,
Expand Down Expand Up @@ -85,6 +87,15 @@ def __init__(
:param on_refresh: Initial :any:`on_refresh` handler.
:param on_delete: **DEPRECATED**; use ``on_primary_action``.
"""
# Prime the attributes and handlers that need to exist when the widget is created.
self._accessors = accessors
self._missing_value = missing_value
self._primary_action = primary_action
self._secondary_action = secondary_action
self.on_select = None

self._data: SourceT | ListSource = None

super().__init__(id=id, style=style)

######################################################################
Expand All @@ -103,17 +114,6 @@ def __init__(
# End backwards compatibility.
######################################################################

# Prime the attributes and handlers that need to exist when the widget is created.
self._accessors = accessors
self._missing_value = missing_value
self._primary_action = primary_action
self._secondary_action = secondary_action
self.on_select = None

self._data: SourceT | ListSource = None

self._impl = self.factory.DetailedList(interface=self)

self.data = data
self.on_primary_action = on_primary_action
self.on_secondary_action = on_secondary_action
Expand Down
4 changes: 2 additions & 2 deletions core/src/toga/widgets/divider.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@


class Divider(Widget):
_IMPL_NAME = "Divider"

HORIZONTAL = Direction.HORIZONTAL
VERTICAL = Direction.VERTICAL

Expand All @@ -29,8 +31,6 @@ def __init__(
"""
super().__init__(id=id, style=style)

# Create a platform specific implementation of a Divider
self._impl = self.factory.Divider(interface=self)
self.direction = direction

@property
Expand Down
7 changes: 5 additions & 2 deletions core/src/toga/widgets/imageview.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def rehint_imageview(


class ImageView(Widget):
_IMPL_NAME = "ImageView"

def __init__(
self,
image: ImageContentT | None = None,
Expand All @@ -83,10 +85,11 @@ def __init__(
:param style: A style object. If no style is provided, a default style will be
applied to the widget.
"""
super().__init__(id=id, style=style)
# Prime the image attribute
self._image = None
self._impl = self.factory.ImageView(interface=self)

super().__init__(id=id, style=style)

self.image = image

@property
Expand Down
5 changes: 2 additions & 3 deletions core/src/toga/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@


class Label(Widget):
_IMPL_NAME = "Label"

def __init__(
self,
text: str,
Expand All @@ -19,9 +21,6 @@ def __init__(
"""
super().__init__(id=id, style=style)

# Create a platform specific implementation of a Label
self._impl = self.factory.Label(interface=self)

self.text = text

def focus(self) -> None:
Expand Down
6 changes: 4 additions & 2 deletions core/src/toga/widgets/mapview.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@


class MapPin:
_IMPL_NAME = "MapPin"

def __init__(
self,
location: toga.LatLng | tuple[float, float],
Expand Down Expand Up @@ -131,6 +133,8 @@ def __call__(self, widget: MapView, /, *, pin: MapPin, **kwargs: Any) -> object:


class MapView(Widget):
_IMPL_NAME = "MapView"

def __init__(
self,
id: str | None = None,
Expand All @@ -155,8 +159,6 @@ def __init__(
"""
super().__init__(id=id, style=style)

self._impl: Any = self.factory.MapView(interface=self)

self._pins = MapPinSet(self, pins)

if location:
Expand Down
5 changes: 2 additions & 3 deletions core/src/toga/widgets/multilinetextinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ def __call__(self, widget: MultilineTextInput, /, **kwargs: Any) -> object:


class MultilineTextInput(Widget):
_IMPL_NAME = "MultilineTextInput"

def __init__(
self,
id: str | None = None,
Expand All @@ -42,9 +44,6 @@ def __init__(

super().__init__(id=id, style=style)

# Create a platform specific implementation of a MultilineTextInput
self._impl = self.factory.MultilineTextInput(interface=self)

# Set a dummy handler before installing the actual on_change, because we do not want
# on_change triggered by the initial value being set
self.on_change = None
Expand Down
Loading