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

background_color Transparency Fixes #2484

Open
wants to merge 66 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
1e75114
Fixed transparency related bugs
proneon267 Apr 5, 2024
1e075fa
Minor tweaks
proneon267 Apr 5, 2024
ca50f58
Fixed cocoa table widget
proneon267 Apr 6, 2024
6c1aeea
Added changelog
proneon267 Apr 6, 2024
24abf08
Fixed test
proneon267 Apr 6, 2024
a94f0de
Fixed iOS progressbar
proneon267 Apr 6, 2024
c30aa56
Fixed android canvas test
proneon267 Apr 7, 2024
6fe78dd
Removed mocking from android canvas test
proneon267 Apr 7, 2024
4a242dd
Removed test debugging modification
proneon267 Apr 7, 2024
054e07b
Fixed android divider background_color
proneon267 Apr 7, 2024
6b1bf9e
Empty commit for CI
proneon267 Apr 7, 2024
4b5ed6f
Fixed winforms divider implementation
proneon267 Apr 7, 2024
77df2e9
Fixed winforms label transparency
proneon267 Apr 7, 2024
6981a7a
Fixed winforms box transparency
proneon267 Apr 9, 2024
b2ca091
Fixed winforms box test
proneon267 Apr 9, 2024
1b3122c
Fixed ackground_color and transparency on WinForms
proneon267 Apr 13, 2024
c767a5b
Empty commit for CI
proneon267 Apr 13, 2024
9921d79
Preliminary test to check for failures on all platforms
proneon267 Apr 25, 2024
06d40e1
Minor change on probes to run the tests
proneon267 Apr 25, 2024
9254e18
Fix winforms implementation and tests
proneon267 Apr 25, 2024
e53879d
Minor change on winforms
proneon267 Apr 25, 2024
d43aba4
Empty commit for CI
proneon267 Apr 25, 2024
059578f
100% coverage on winforms
proneon267 Apr 28, 2024
298d460
100% coverage on android
proneon267 May 5, 2024
c6382c3
Merge branch 'beeware:main' into transparency_fix
proneon267 May 5, 2024
e5e3e07
Minor modification on testbed
proneon267 May 7, 2024
ca4d5d9
Merge branch 'main' of https://github.com/proneon267/toga into transp…
proneon267 May 7, 2024
8c62422
Minor fix
proneon267 May 7, 2024
b0c8613
Merge branch 'transparency_fix' of https://github.com/proneon267/toga…
proneon267 May 7, 2024
b6cf7d5
Merge branch 'beeware:main' into transparency_fix
proneon267 May 7, 2024
4b7991d
Minor change on gtk
proneon267 May 7, 2024
9e354f8
Empty commit to restart CI for macOS
proneon267 May 7, 2024
71f461b
Minor modifications for testbed
proneon267 May 8, 2024
b2f73a7
Modifications on iOS
proneon267 May 8, 2024
a0e141f
Minor modification for transparency
proneon267 May 8, 2024
350b9d0
Minor modification on testbed
proneon267 May 8, 2024
aad928c
Minor modification on android test backend
proneon267 May 8, 2024
9e11129
Modifications on cocoa
proneon267 May 9, 2024
4aaedd4
Minor modification on cocoa
proneon267 May 9, 2024
a1cf579
Code cleanups
proneon267 May 9, 2024
2bb6a39
Fix typo
proneon267 May 9, 2024
c9c714e
Merge branch 'main' into transparency_fix
freakboy3742 May 28, 2024
553ac6e
Revert name changes
proneon267 May 30, 2024
2f657db
Merge branch 'transparency_fix' of https://github.com/proneon267/toga…
proneon267 May 30, 2024
f63859c
Re: Revert name changes
proneon267 May 30, 2024
3e0f883
Merge branch 'beeware:main' into transparency_fix
proneon267 May 30, 2024
6f75e5f
Re: Revert name changes
proneon267 May 30, 2024
a88cc13
Re: Revert name changes
proneon267 May 30, 2024
221fd10
Fixed background color assertion
proneon267 Jun 8, 2024
dbe7b42
Added default background color attribute to winforms widgets
proneon267 Jun 8, 2024
3054df1
Removed unused branch
proneon267 Jun 8, 2024
135741f
Modified as per review
proneon267 Jun 11, 2024
5991cd2
Modified as per review
proneon267 Jun 11, 2024
165d4b9
Update testbed/tests/widgets/test_button.py
proneon267 Jun 12, 2024
b2d0c47
Update testbed/tests/widgets/test_button.py
proneon267 Jun 12, 2024
a077b0e
Modified as per review
proneon267 Jun 12, 2024
d2bfbfb
Merge branch 'transparency_fix' of https://github.com/proneon267/toga…
proneon267 Jun 12, 2024
544802c
Merge branch 'beeware:main' into transparency_fix
proneon267 Jun 12, 2024
db05029
Added default background color attribute to iOS widgets
proneon267 Jun 13, 2024
f7687a8
Added default background color attribute to iOS widgets
proneon267 Jun 13, 2024
4e4a82e
Update 2484.bugfix.2.rst
proneon267 Jun 13, 2024
0ff7752
Merge branch 'beeware:main' into transparency_fix
proneon267 Jun 16, 2024
7d0515e
Merge branch 'main' into transparency_fix
proneon267 Dec 1, 2024
c0d8b5f
Updated to latest codebase
proneon267 Dec 1, 2024
479449a
Disable background color csettingon Divider widget
proneon267 Dec 1, 2024
19c0d02
Typo Fix
proneon267 Dec 1, 2024
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
6 changes: 4 additions & 2 deletions android/src/toga_android/widgets/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
from decimal import ROUND_HALF_EVEN, Decimal

from android.graphics import PorterDuff, PorterDuffColorFilter, Rect
from android.graphics import Color, PorterDuff, PorterDuffColorFilter, Rect
from android.graphics.drawable import ColorDrawable, InsetDrawable
from android.view import Gravity, View
from android.widget import RelativeLayout
Expand Down Expand Up @@ -138,8 +138,10 @@ def set_background_simple(self, value):
if not hasattr(self, "_default_background"):
self._default_background = self.native.getBackground()

if value in (None, TRANSPARENT):
if value is None:
self.native.setBackground(self._default_background)
elif value is TRANSPARENT:
self.native.setBackgroundColor(Color.TRANSPARENT)
else:
background = ColorDrawable(native_color(value))
if isinstance(self._default_background, InsetDrawable):
Expand Down
9 changes: 8 additions & 1 deletion android/src/toga_android/widgets/canvas.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import itertools
import warnings
from math import degrees

from android.graphics import (
Expand Down Expand Up @@ -236,14 +237,20 @@ def _text_paint(self, font):
paint.setTextSize(self.scale_out(font.size()))
return paint

# This has been separated out, so that it can be mocked during testing.
def _native_get_background(self):
return self.native.getBackground()
Copy link
Member

Choose a reason for hiding this comment

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

This isn't an acceptable approach. Mocking is a last resort for testing. It's used in a handful of places related to permissions and hardware APIs because using those APIs requires passing control to a separate process, at which point the test suite loses the ability to execute. Background color is an entirely internal mechanism, and isn't encumbered by this restriction.

It may be difficult to interrogate - but that doesn't mean we can mock it, because we're trying to verify the actual behavior.

Copy link
Contributor Author

@proneon267 proneon267 Apr 7, 2024

Choose a reason for hiding this comment

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

During the test, coverage reported missing coverage for:

background = self.native.getBackground()
if background:
background.draw(canvas)
self.native.draw(canvas)

I/python.stdout: Name                                                                                                        Stmts   Miss Branch BrPart  Cover   Missing
I/python.stdout: -------------------------------------------------------------------------------------------------------------------------------------------------------
I/python.stdout: data/data/org.beeware.toga.testbed/files/chaquopy/AssetFinder/requirements/toga_android/widgets/canvas.py     153      0     28      1  99.4%   245->247
I/python.stdout: -------------------------------------------------------------------------------------------------------------------------------------------------------
I/python.stdout: TOTAL                                                                                                        2190      0    324      1  99.9%

So, 245->247 means it is reporting missing coverage for the missing else branch. The else branch will be triggered when self.native.getBackground() returns None. Since, it is an internal platform method, I cannot make it return None without mocking it. The other way was to ignore the missing else branch with #pragma: no branch, which you have told me to avoid. So, is there any other way to test the missing else branch or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NVM, I just realized there was a much simpler way to test this. Thanks :) So, the question remains, should I move the test to testbed or keep it in the probe, since it is implementation specific?

Copy link
Member

Choose a reason for hiding this comment

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

There might be a specific edge case that is needed to exercise that test, but if the other backends don't have an equivalent "if background" branch, then they will run the code without branching, giving double coverage of one specific code path. That's not a problem - it's OK to test something more than once on iOS (et al) if it means we get 100% coverage on Android as well; if nothing else, it's an indication that there are two different test configurations, and we need to test both, even if the "default" implementation works for both cases on iOS.

However, I'm confused because that line did have coverage prior to this change, and it has coverage on every other platform. This, to me, indicates that there's a bigger problem here. Either this branch of code was covering over an edge case that no longer exists, or there's a case that isn't being tested.


def get_image_data(self):
bitmap = Bitmap.createBitmap(
self.native.getWidth(), self.native.getHeight(), Bitmap.Config.ARGB_8888
)
canvas = A_Canvas(bitmap)
background = self.native.getBackground()
background = self._native_get_background()
if background:
background.draw(canvas)
else:
warnings.warn("Failed to get canvas background")
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't make any sense to me. Why couldn't the background be obtained?

self.native.draw(canvas)

stream = ByteArrayOutputStream()
Expand Down
3 changes: 3 additions & 0 deletions android/src/toga_android/widgets/progressbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,6 @@ def rehint(self):
self.interface.intrinsic.height = self.scale_out(
self.native.getMeasuredHeight(), ROUND_UP
)

def set_background_color(self, value):
self.set_background_simple(value)
13 changes: 13 additions & 0 deletions android/tests_backend/widgets/canvas.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from io import BytesIO
from unittest.mock import Mock

import pytest
from android.os import SystemClock
Expand All @@ -20,6 +21,18 @@ def reference_variant(self, reference):
def get_image(self):
return Image.open(BytesIO(self.impl.get_image_data()))

def test_get_image_data_internal_fail(self, monkeypatch):
original_native_get_background = self.impl._native_get_background
mock_native_get_background = Mock(return_value=None)
monkeypatch.setattr(
self.impl, "_native_get_background", mock_native_get_background
)
with pytest.warns(match="Failed to get canvas background"):
self.impl.get_image_data()
monkeypatch.setattr(
self.impl, "_native_get_background", original_native_get_background
)

def motion_event(self, action, x, y):
time = SystemClock.uptimeMillis()
super().motion_event(
Expand Down
1 change: 1 addition & 0 deletions changes/2484.bugfix.rst
Copy link
Member

Choose a reason for hiding this comment

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

This probably needs to be broken up into a couple of release notes - one for each underlying issue that we're resolving.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Bugs related to transparent background color are fixed.
1 change: 1 addition & 0 deletions cocoa/src/toga_cocoa/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def set_color(self, color):

def set_background_color(self, color):
if color is TRANSPARENT:
self.native.backgroundColor = None
self.native.drawsBackground = False
else:
self.native.backgroundColor = native_color(color)
Expand Down
7 changes: 7 additions & 0 deletions cocoa/src/toga_cocoa/widgets/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from travertino.size import at_least

import toga
from toga.colors import TRANSPARENT
from toga_cocoa.libs import (
NSBezelBorder,
NSIndexSet,
Expand Down Expand Up @@ -257,3 +258,9 @@ def remove_column(self, index):
# delete column and identifier
self.columns.remove(column)
self.native_table.sizeToFit()

def set_background_color(self, color):
if color in {None, TRANSPARENT}:
super().set_background_color(TRANSPARENT)
else:
super().set_background_color(color)
3 changes: 3 additions & 0 deletions cocoa/tests_backend/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def get_image(self):
except KeyError:
return image

def test_get_image_data_internal_fail(self, monkeypatch):
pass

async def mouse_press(self, x, y):
await self.mouse_event(
NSEventType.LeftMouseDown,
Expand Down
8 changes: 0 additions & 8 deletions cocoa/tests_backend/widgets/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from toga_cocoa.libs import NSEventType, NSScrollView, NSTableView

from .base import SimpleProbe
from .properties import toga_color

NSEventModifierFlagCommand = 1 << 20

Expand All @@ -24,13 +23,6 @@ def __init__(self, widget):
def font(self):
skip("Font changes not implemented for Tree on macOS")

@property
def background_color(self):
if self.native.drawsBackground:
return toga_color(self.native.backgroundColor)
else:
return None

@property
def row_count(self):
return int(self.native_table.numberOfRowsInTableView(self.native_table))
Expand Down
3 changes: 3 additions & 0 deletions gtk/tests_backend/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ def reference_variant(self, reference):
def get_image(self):
return Image.open(BytesIO(self.impl.get_image_data()))

def test_get_image_data_internal_fail(self, monkeypatch):
pass

async def mouse_press(self, x, y):
event = Gdk.Event.new(Gdk.EventType.BUTTON_PRESS)
event.button = 1
Expand Down
3 changes: 3 additions & 0 deletions iOS/src/toga_iOS/widgets/progressbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ def set_max(self, value):
self._max = value
self._stop_indeterminate()

def set_background_color(self, color):
self.set_background_color_simple(color)

def rehint(self):
fitting_size = self.native.systemLayoutSizeFittingSize(CGSize(0, 0))
self.interface.intrinsic.width = at_least(self.interface._MIN_WIDTH)
Expand Down
3 changes: 3 additions & 0 deletions iOS/tests_backend/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ def reference_variant(self, reference):
def get_image(self):
return Image.open(BytesIO(self.impl.get_image_data()))

def test_get_image_data_internal_fail(self, monkeypatch):
pass

async def mouse_press(self, x, y):
touch = MockTouch.alloc().init()
touches = NSSet.setWithObject(touch)
Expand Down
19 changes: 14 additions & 5 deletions testbed/tests/widgets/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,12 @@ async def test_background_color_reset(widget, probe):

# Set the background color to something different
widget.style.background_color = RED
await probe.redraw("Widget background background color should be RED")
await probe.redraw("Widget background color should be RED")
assert_color(probe.background_color, named_color(RED))

# Reset the background color, and check that it has been restored to the original
del widget.style.background_color
await probe.redraw(
message="Widget background background color should be restored to original"
)
await probe.redraw(message="Widget background color should be restored to original")
assert_color(probe.background_color, original)


Expand All @@ -409,10 +407,21 @@ async def test_background_color_transparent(widget, probe):
original = probe.background_color
supports_alpha = getattr(probe, "background_supports_alpha", True)

# Set the background color to something different
widget.style.background_color = RED
await probe.redraw("Widget background color should be RED")
assert_color(probe.background_color, named_color(RED))
Copy link
Member

Choose a reason for hiding this comment

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

What is this test for? We already have a background color test - why do we need to test a normal background color in the transparent background color test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think, this is a debugging artifact, that I had put on for visual inspection of widgets with transparent as default background color. I'll remove it. Thanks.


# Change the background color to transparent
widget.style.background_color = TRANSPARENT
await probe.redraw("Widget background background color should be TRANSPARENT")
await probe.redraw("Widget background color should be TRANSPARENT")
assert_color(probe.background_color, TRANSPARENT if supports_alpha else original)

# Restore original background color
del widget.style.background_color
await probe.redraw("Widget background color should be restored to original")
assert_color(probe.background_color, original)


async def test_alignment(widget, probe, verify_vertical_alignment):
"""Widget honors alignment settings."""
Expand Down
1 change: 1 addition & 0 deletions testbed/tests/widgets/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .properties import ( # noqa: F401
test_background_color,
test_background_color_reset,
test_background_color_transparent,
test_enable_noop,
test_flex_widget_size,
test_focus_noop,
Expand Down
6 changes: 5 additions & 1 deletion testbed/tests/widgets/test_canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ async def test_alt_drag(
on_alt_release_handler.assert_called_once_with(canvas, 70, 90)


async def test_image_data(canvas, probe):
async def test_image_data(monkeypatch, canvas, probe):
"The canvas can be saved as an image"
with canvas.Stroke(x=0, y=0, color=RED) as stroke:
stroke.line_to(x=200, y=200)
Expand All @@ -237,6 +237,10 @@ async def test_image_data(canvas, probe):
screen=canvas.window.screen,
)

# The internal method to patch for testing are platform specific
# hence the test is on the probe.
probe.test_get_image_data_internal_fail(monkeypatch)


def assert_reference(probe, reference, threshold=0.0):
"""Assert that the canvas currently matches a reference image, within an RMS threshold"""
Expand Down
3 changes: 3 additions & 0 deletions testbed/tests/widgets/test_divider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

from ..conftest import skip_on_platforms
from .properties import ( # noqa: F401
test_background_color,
test_background_color_reset,
test_background_color_transparent,
test_enable_noop,
test_focus_noop,
)
Expand Down
3 changes: 3 additions & 0 deletions testbed/tests/widgets/test_progressbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import toga

from .properties import ( # noqa: F401
test_background_color,
test_background_color_reset,
test_background_color_transparent,
test_enable_noop,
test_flex_horizontal_widget_size,
)
Expand Down
1 change: 1 addition & 0 deletions testbed/tests/widgets/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .properties import ( # noqa: F401
test_background_color,
test_background_color_reset,
test_background_color_transparent,
test_enable_noop,
test_flex_widget_size,
test_focus_noop,
Expand Down
2 changes: 2 additions & 0 deletions winforms/src/toga_winforms/widgets/progressbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@


class ProgressBar(Widget):
_background_supports_alpha = False

TOGA_SCALE = 1000

def create(self):
Expand Down
3 changes: 3 additions & 0 deletions winforms/tests_backend/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def reference_variant(self, reference):
def get_image(self):
return Image.open(BytesIO(self.impl.get_image_data()))

def test_get_image_data_internal_fail(self, monkeypatch):
pass

async def mouse_press(self, x, y, **kwargs):
self.native.OnMouseDown(self.mouse_event(x, y, **kwargs))
self.native.OnMouseUp(self.mouse_event(x, y, **kwargs))
Expand Down
1 change: 1 addition & 0 deletions winforms/tests_backend/widgets/progressbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

class ProgressBarProbe(SimpleProbe):
native_class = System.Windows.Forms.ProgressBar
background_supports_alpha = False

@property
def is_determinate(self):
Expand Down
Loading