Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[widget Audit] toga.ScrollContainer #1969

Merged
merged 46 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
40a7dd0
Update docs for ScrollContainer
freakboy3742 Jun 8, 2023
e256720
Update core API for ScrollContainer.
freakboy3742 Jun 8, 2023
6705fe9
Add changenote.
freakboy3742 Jun 9, 2023
e06281d
Initial fixes for Cocoa, plus cocoa test probe for scrollcontainer.
freakboy3742 Jun 10, 2023
d72cfe5
Tweaked scrollcontainer example.
freakboy3742 Jun 10, 2023
04619a9
Misc debug cleanups.
freakboy3742 Jun 10, 2023
b89f14a
Cocoa ScrollContainer at 100%, including container rework.
freakboy3742 Jun 11, 2023
73edde4
Correct CI failures by using frame, rather than contentView for the s…
freakboy3742 Jun 12, 2023
73e1d30
GTK ScrollContainer to 100%.
freakboy3742 Jun 13, 2023
bea23c1
Add a content handler to the cocoa container impl, matching GTK.
freakboy3742 Jun 13, 2023
ae3aa13
Refactor iOS to remove viewports.
freakboy3742 Jun 13, 2023
3f34595
Initial working implementation of iOS ScrollContainer.
freakboy3742 Jun 13, 2023
e317601
Normalize some implementation details between iOS and Cocoa.
freakboy3742 Jun 13, 2023
883f8fb
iOS ScrollContainer to 100% coverage.
freakboy3742 Jun 13, 2023
ceb26be
Updated core tests, cocoa and GTK to match iOS implementation.
freakboy3742 Jun 13, 2023
dac11fb
Remove unneeded dummy class.
freakboy3742 Jun 13, 2023
20ca03e
Minor docs cleanups.
freakboy3742 Jun 14, 2023
dee3253
Corrections to the handling of containers in the Dummy backend.
freakboy3742 Jun 14, 2023
78d521a
Merge branch 'main' into audit-scrollcontainer
freakboy3742 Jun 18, 2023
c967ad4
Correct some docs markup issues.
freakboy3742 Jun 19, 2023
f37a4df
Merge branch 'main' into audit-scrollcontainer
freakboy3742 Jun 20, 2023
20697de
Merge branch 'main' into audit-scrollcontainer
freakboy3742 Jun 22, 2023
17e4a87
Merge branch 'main' into audit-scrollcontainer
freakboy3742 Jun 26, 2023
17d4120
Merge branch 'main' into audit-scrollcontainer
freakboy3742 Jun 26, 2023
8b266b9
Add current / max position indicator to scrollbar example app
mhsmith Jun 28, 2023
ffefeac
Loosen error handling when horizontal/vertical scrolling is disabled.
freakboy3742 Jun 28, 2023
572b95e
Correct the GTK implementation of scrolling reset.
freakboy3742 Jun 29, 2023
e781c3c
Corrected edge case seen in CI.
freakboy3742 Jun 29, 2023
6887b26
Android WIP
mhsmith Jun 29, 2023
2abfbf0
Merge branch 'audit-scrollcontainer' of github.com:freakboy3742/toga …
mhsmith Jun 29, 2023
8f52e48
Android ScrollContainer at 100%, including container rework
mhsmith Jul 4, 2023
0dff504
Fix CI failures
mhsmith Jul 4, 2023
fcd6260
Fix DPI and rounding issues
mhsmith Jul 4, 2023
e6c2cf2
Update widget support table
mhsmith Jul 5, 2023
814a7a2
More rounding fixes
mhsmith Jul 5, 2023
1eeb439
Further Android container refactoring, fixing padding on ScrollContai…
mhsmith Jul 5, 2023
ee52a10
Refactor Winforms container/viewport mechanism
mhsmith Jul 6, 2023
b6a470b
Fix "backend" tests on Winforms
mhsmith Jul 8, 2023
07c4e37
Fix ImageView tests on Winforms with positive scale factor
mhsmith Jul 8, 2023
0d21010
Miscellaneous cleanups
mhsmith Jul 10, 2023
7bb9766
Initial Winforms implementation (not working)
mhsmith Jul 10, 2023
2004cb8
Winforms to 100%
mhsmith Jul 10, 2023
6f6516e
Add comments explaining scale_in and scale_out
mhsmith Jul 11, 2023
1bf22d1
Remove redundant checks in GTK which are now covered by the interface…
mhsmith Jul 11, 2023
096ac1f
Cleaned up 2 stray uncovered lines.
freakboy3742 Jul 12, 2023
0e1302a
Add a test for performing a layout with no content.
freakboy3742 Jul 12, 2023
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
54 changes: 54 additions & 0 deletions android/src/toga_android/container.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from .libs.android.widget import RelativeLayout, RelativeLayout__LayoutParams


class Container:
def init_container(self, native_parent):
self.width = self.height = 0

context = native_parent.getContext()
self.native_content = RelativeLayout(context)
native_parent.addView(self.native_content)

self.dpi = context.getResources().getDisplayMetrics().densityDpi
# Toga needs to know how the current DPI compares to the platform default,
# which is 160: https://developer.android.com/training/multiscreen/screendensities
self.baseline_dpi = 160
self.scale = self.dpi / self.baseline_dpi

def set_content(self, widget):
self.clear_content()
if widget:
widget.container = self

def clear_content(self):
if self.interface.content:
self.interface.content._impl.container = None

def resize_content(self, width, height):
if (self.width, self.height) != (width, height):
self.width, self.height = (width, height)
if self.interface.content:
self.interface.content.refresh()

def refreshed(self):
# We must use the correct LayoutParams class, but we don't know what that class
# is, so reuse the existing object. Calling the constructor of type(lp) is also
# an option, but would probably be less safe because a subclass might change the
# meaning of the (int, int) constructor.
lp = self.native_content.getLayoutParams()
layout = self.interface.content.layout
lp.width = max(self.width, layout.width)
lp.height = max(self.height, layout.height)
self.native_content.setLayoutParams(lp)

def add_content(self, widget):
self.native_content.addView(widget.native)

def remove_content(self, widget):
self.native_content.removeView(widget.native)

def set_content_bounds(self, widget, x, y, width, height):
lp = RelativeLayout__LayoutParams(width, height)
lp.topMargin = y
lp.leftMargin = x
widget.native.setLayoutParams(lp)
1 change: 1 addition & 0 deletions android/src/toga_android/libs/android/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
ViewGroup__LayoutParams = JavaClass("android/view/ViewGroup$LayoutParams")
View__MeasureSpec = JavaClass("android/view/View$MeasureSpec")
View__OnFocusChangeListener = JavaInterface("android/view/View$OnFocusChangeListener")
View__OnScrollChangeListener = JavaInterface("android/view/View$OnScrollChangeListener")
View__OnTouchListener = JavaInterface("android/view/View$OnTouchListener")
ViewTreeObserver__OnGlobalLayoutListener = JavaInterface(
"android/view/ViewTreeObserver$OnGlobalLayoutListener"
Expand Down
42 changes: 20 additions & 22 deletions android/src/toga_android/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def __init__(self, interface):
self.interface = interface
self.interface._impl = self
self._container = None
self.viewport = None
self.native = None
self._native_activity = _get_activity()
self.create()
Expand All @@ -65,25 +64,30 @@ def container(self):

@container.setter
def container(self, container):
if self.container:
assert container is None, "Widget already has a container"

# container is set to None, removing self from the container.native
self._container.native.removeView(self.native)
self._container.native.invalidate()
self._container = None
elif container:
self._container = container
# When initially setting the container and adding widgets to the container,
# we provide no `LayoutParams`. Those are promptly added when Toga
# calls `widget.rehint()` and `widget.set_bounds()`.
self._container.native.addView(self.native)
if self._container:
self._container.remove_content(self)

self._container = container
if container:
container.add_content(self)

for child in self.interface.children:
child._impl.container = container

self.rehint()

@property
def viewport(self):
return self._container

# Convert CSS pixels to native pixels
def scale_in(self, value):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick note for future explorers on which direction is "in" and "out" would be helpful here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, done.

return int(round(value * self.container.scale))

# Convert native pixels to CSS pixels
def scale_out(self, value):
return int(round(value / self.container.scale))

def get_enabled(self):
return self.native.isEnabled()

Expand All @@ -103,9 +107,7 @@ def set_tab_index(self, tab_index):
# APPLICATOR

def set_bounds(self, x, y, width, height):
if self.container:
# Ask the container widget to set our bounds.
self.container.set_child_bounds(self, x, y, width, height)
self.container.set_content_bounds(self, x, y, width, height)

def set_hidden(self, hidden):
if hidden:
Expand Down Expand Up @@ -160,11 +162,7 @@ def set_color(self, color):
# INTERFACE

def add_child(self, child):
if self.viewport:
# we are the top level widget
child.container = self
else:
child.container = self.container
child.container = self.container

def insert_child(self, index, child):
self.add_child(child)
Expand Down
12 changes: 1 addition & 11 deletions android/src/toga_android/widgets/box.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
from travertino.size import at_least

from ..libs.activity import MainActivity
from ..libs.android.widget import RelativeLayout, RelativeLayout__LayoutParams
from ..libs.android.widget import RelativeLayout
from .base import Widget


class Box(Widget):
def create(self):
self.native = RelativeLayout(MainActivity.singletonThis)

def set_child_bounds(self, widget, x, y, width, height):
# We assume `widget.native` has already been added to this `RelativeLayout`.
#
# We use `topMargin` and `leftMargin` to perform absolute layout. Not very
# relative, but that's how we do it.
layout_params = RelativeLayout__LayoutParams(width, height)
layout_params.topMargin = y
layout_params.leftMargin = x
self.native.updateViewLayout(widget.native, layout_params)

def set_background_color(self, value):
self.set_background_simple(value)

Expand Down
64 changes: 29 additions & 35 deletions android/src/toga_android/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,33 +64,29 @@ def closed_path(self, x, y, path, *args, **kwargs):
path.close()

def move_to(self, x, y, path, *args, **kwargs):
path.moveTo(
self.container.viewport.scale * x, self.container.viewport.scale * y
)
path.moveTo(self.container.scale * x, self.container.scale * y)

def line_to(self, x, y, path, *args, **kwargs):
path.lineTo(
self.container.viewport.scale * x, self.container.viewport.scale * y
)
path.lineTo(self.container.scale * x, self.container.scale * y)

# Basic shapes

def bezier_curve_to(self, cp1x, cp1y, cp2x, cp2y, x, y, path, *args, **kwargs):
path.cubicTo(
cp1x * self.container.viewport.scale,
cp1y * self.container.viewport.scale,
cp2x * self.container.viewport.scale,
cp2y * self.container.viewport.scale,
x * self.container.viewport.scale,
y * self.container.viewport.scale,
cp1x * self.container.scale,
cp1y * self.container.scale,
cp2x * self.container.scale,
cp2y * self.container.scale,
x * self.container.scale,
y * self.container.scale,
)

def quadratic_curve_to(self, cpx, cpy, x, y, path, *args, **kwargs):
path.quadTo(
cpx * self.container.viewport.scale,
cpy * self.container.viewport.scale,
x * self.container.viewport.scale,
y * self.container.viewport.scale,
cpx * self.container.scale,
cpy * self.container.scale,
x * self.container.scale,
y * self.container.scale,
)

def arc(
Expand All @@ -100,10 +96,10 @@ def arc(
if anticlockwise:
sweep_angle -= math.radians(360)
path.arcTo(
self.container.viewport.scale * (x - radius),
self.container.viewport.scale * (y - radius),
self.container.viewport.scale * (x + radius),
self.container.viewport.scale * (y + radius),
self.container.scale * (x - radius),
self.container.scale * (y - radius),
self.container.scale * (x + radius),
self.container.scale * (y + radius),
math.degrees(startangle),
math.degrees(sweep_angle),
False,
Expand All @@ -128,28 +124,28 @@ def ellipse(
sweep_angle -= math.radians(360)
ellipse_path = Path()
ellipse_path.addArc(
self.container.viewport.scale * (x - radiusx),
self.container.viewport.scale * (y - radiusy),
self.container.viewport.scale * (x + radiusx),
self.container.viewport.scale * (y + radiusy),
self.container.scale * (x - radiusx),
self.container.scale * (y - radiusy),
self.container.scale * (x + radiusx),
self.container.scale * (y + radiusy),
math.degrees(startangle),
math.degrees(sweep_angle),
)
rotation_matrix = Matrix()
rotation_matrix.postRotate(
math.degrees(rotation),
self.container.viewport.scale * x,
self.container.viewport.scale * y,
self.container.scale * x,
self.container.scale * y,
)
ellipse_path.transform(rotation_matrix)
path.addPath(ellipse_path)

def rect(self, x, y, width, height, path, *args, **kwargs):
path.addRect(
self.container.viewport.scale * x,
self.container.viewport.scale * y,
self.container.viewport.scale * (x + width),
self.container.viewport.scale * (y + height),
self.container.scale * x,
self.container.scale * y,
self.container.scale * (x + width),
self.container.scale * (y + height),
Path__Direction.CW,
)

Expand All @@ -171,7 +167,7 @@ def fill(self, color, fill_rule, preserve, path, canvas, *args, **kwargs):
def stroke(self, color, line_width, line_dash, path, canvas, *args, **kwargs):
draw_paint = Paint()
draw_paint.setAntiAlias(True)
draw_paint.setStrokeWidth(self.container.viewport.scale * line_width)
draw_paint.setStrokeWidth(self.container.scale * line_width)
draw_paint.setStyle(Paint__Style.STROKE)
if color is None:
a, r, g, b = 255, 0, 0, 0
Expand All @@ -180,7 +176,7 @@ def stroke(self, color, line_width, line_dash, path, canvas, *args, **kwargs):
if line_dash is not None:
draw_paint.setPathEffect(
DashPathEffect(
[(self.container.viewport.scale * float(d)) for d in line_dash], 0.0
[(self.container.scale * float(d)) for d in line_dash], 0.0
)
)
draw_paint.setARGB(a, r, g, b)
Expand All @@ -197,9 +193,7 @@ def scale(self, sx, sy, canvas, *args, **kwargs):
canvas.scale(float(sx), float(sy))

def translate(self, tx, ty, canvas, *args, **kwargs):
canvas.translate(
self.container.viewport.scale * tx, self.container.viewport.scale * ty
)
canvas.translate(self.container.scale * tx, self.container.scale * ty)

def reset_transform(self, canvas, *args, **kwargs):
canvas.restore()
Expand Down
Loading