Skip to content

Commit

Permalink
GTK SplitContainer tests to 100%.
Browse files Browse the repository at this point in the history
  • Loading branch information
freakboy3742 committed Jun 18, 2023
1 parent 134a3ff commit fd6d44b
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 49 deletions.
2 changes: 1 addition & 1 deletion gtk/src/toga_gtk/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def container(self, container):
for child in self.interface.children:
child._impl.container = container

self.rehint()
self.refresh()

def get_enabled(self):
return self.native.get_sensitive()
Expand Down
130 changes: 82 additions & 48 deletions gtk/src/toga_gtk/widgets/splitcontainer.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,101 @@
from travertino.size import at_least

from ..container import TogaContainer
from ..libs import Gtk
from .base import Widget


class TogaSplitContainer(Gtk.Paned):
def __init__(self, impl):
Gtk.Paned.__init__(self)
self._impl = impl
self.interface = self._impl.interface
class SplitContainer(Widget):
def create(self):
self.native = Gtk.Paned()
self.native.set_wide_handle(True)

def do_size_allocate(self, allocation):
Gtk.Paned.do_size_allocate(self, allocation)
self.sub_containers = [TogaContainer(), TogaContainer()]
self.native.pack1(self.sub_containers[0], True, False)
self.native.pack2(self.sub_containers[1], True, False)

# Turn all the weights into a fraction of 1.0
total = sum(self.interface._weight)
self.interface._weight = [weight / total for weight in self.interface._weight]
self._split_proportion = 0.5

# Set the position of splitter depending on the weight of splits.
self.set_position(
int(
self.interface._weight[0] * self.get_allocated_width()
if self.interface.direction == self.interface.VERTICAL
else self.get_allocated_height()
)
)
def set_bounds(self, x, y, width, height):
super().set_bounds(x, y, width, height)

# If we've got a pending split to apply, set the split position.
# However, only do this if the layout is more than the min size;
# there are initial 0-sized layouts for which the split is meaningless.
if (
self._split_proportion
and width >= self.interface._MIN_WIDTH
and height > self.interface._MIN_HEIGHT
):
if self.interface.direction == self.interface.VERTICAL:
position = int(self._split_proportion * width)
else:
position = int(self._split_proportion * height)

class SplitContainer(Widget):
def create(self):
self.native = TogaSplitContainer(self)
self.native.set_wide_handle(True)
self.native.set_position(position)
self._split_proportion = None

def set_content(self, content, flex):
# Clear any existing content
for container in self.sub_containers:
container.content = None

# Add all children to the content widget.
for position, widget in enumerate(content):
self.sub_containers[position].content = widget

# Use Paned widget rather than VPaned and HPaned deprecated widgets
# Note that orientation in toga behave unlike Gtk
if self.interface.direction == self.interface.VERTICAL:
# We now know the initial positions of the split. However, we can't *set* the
# because GTK requires a pixel position, and the widget isn't visible yet. So -
# store the split; and when we do our first layout, apply that proportion.
self._split_proportion = flex[0] / sum(flex)

def get_direction(self):
if self.native.get_orientation() == Gtk.Orientation.HORIZONTAL:
return self.interface.VERTICAL
else:
return self.interface.HORIZONTAL

def set_direction(self, value):
if value == self.interface.VERTICAL:
self.native.set_orientation(Gtk.Orientation.HORIZONTAL)
elif self.interface.direction == self.interface.HORIZONTAL:
self.native.set_orientation(Gtk.Orientation.VERTICAL)
else:
raise ValueError("Allowed orientation is VERTICAL or HORIZONTAL")
self.native.set_orientation(Gtk.Orientation.VERTICAL)

def add_content(self, position, widget, flex):
# Add all children to the content widget.
sub_container = TogaContainer()
sub_container.content = widget
def rehint(self):
# This is a SWAG (scientific wild-ass guess). There doesn't appear to be
# an actual API to get the true size of the splitter. 10px seems enough.
SPLITTER_WIDTH = 10
if self.interface.direction == self.interface.HORIZONTAL:
# When the splitter is horizontal, the splitcontainer must be
# at least as wide as it's widest sub-container, and at least
# as tall as the minimum height of all subcontainers, plus the
# height of the splitter itself. Enforce a minimum size in both
# axies
min_width = self.interface._MIN_WIDTH
min_height = 0
for sub_container in self.sub_containers:
# Make sure the subcontainer's size is up to date
sub_container.recompute()

if position >= 2:
raise ValueError("SplitContainer content must be a 2-tuple")
min_width = max(min_width, sub_container.min_width)
min_height += sub_container.min_height

if position == 0:
self.native.pack1(sub_container, flex, False)
elif position == 1:
self.native.pack2(sub_container, flex, False)
min_height = max(min_height, self.interface._MIN_HEIGHT) + SPLITTER_WIDTH
else:
# When the splitter is vertical, the splitcontainer must be
# at least as tall as it's tallest sub-container, and at least
# as wide as the minimum width of all subcontainers, plus the
# width of the splitter itself.
min_width = 0
min_height = self.interface._MIN_HEIGHT
for sub_container in self.sub_containers:
# Make sure the subcontainer's size is up to date
sub_container.recompute()

def set_app(self, app):
if self.interface.content:
self.interface.content[0].app = self.interface.app
self.interface.content[1].app = self.interface.app
min_width += sub_container.min_width
min_height = max(min_height, sub_container.min_height)

def set_window(self, window):
if self.interface.content:
self.interface.content[0].window = self.interface.window
self.interface.content[1].window = self.interface.window
min_width = max(min_width, self.interface._MIN_WIDTH) + SPLITTER_WIDTH

def set_direction(self, value):
self.interface.factory.not_implemented("SplitContainer.set_direction()")
self.interface.intrinsic.width = at_least(min_width)
self.interface.intrinsic.height = at_least(min_height)
28 changes: 28 additions & 0 deletions gtk/tests_backend/widgets/splitcontainer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import asyncio

from toga_gtk.libs import Gtk

from .base import SimpleProbe


class SplitContainerProbe(SimpleProbe):
native_class = Gtk.Paned

def move_split(self, position):
self.native.set_position(position)

def repaint_needed(self):
return (
self.impl.sub_containers[0].needs_redraw
or self.impl.sub_containers[1].needs_redraw
or super().repaint_needed()
)

async def wait_for_split(self):
sub1 = self.impl.sub_containers[0]
position = sub1.get_allocated_height(), sub1.get_allocated_width()
current = None
while position != current:
position = current
await asyncio.sleep(0.05)
current = sub1.get_allocated_height(), sub1.get_allocated_width()

0 comments on commit fd6d44b

Please sign in to comment.