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 62 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
62 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
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
3 changes: 3 additions & 0 deletions 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 @@ -244,6 +245,8 @@ def get_image_data(self):
background = self.native.getBackground()
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
9 changes: 5 additions & 4 deletions android/src/toga_android/widgets/divider.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ class Divider(Widget):
def create(self):
self.native = View(self._native_activity)

# Background color needs to be set or else divider will not be visible.
self.native.setBackgroundColor(Color.LTGRAY)

self._direction = self.interface.HORIZONTAL

def set_background_color(self, value):
self.set_background_simple(value)
if value is None:
# Background color needs to be set or else divider will not be visible.
self.native.setBackgroundColor(Color.LTGRAY)
else:
self.set_background_simple(value)

def get_direction(self):
return self._direction
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)
7 changes: 7 additions & 0 deletions android/tests_backend/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ 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_background = self.native.getBackground()
self.native.setBackground(None)
with pytest.warns(match="Failed to get canvas background"):
self.impl.get_image_data()
self.native.setBackground(original_background)
Copy link
Member

Choose a reason for hiding this comment

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

As with the comment on the implementation - why is this occurring? Why is the test suite passing with 100% branch coverage prior to this change?


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: 10 additions & 9 deletions testbed/tests/widgets/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,6 @@ async def test_background_color(widget, probe):
for color in COLORS:
widget.style.background_color = color
await probe.redraw("Widget background color should be %s" % color)
if not getattr(probe, "background_supports_alpha", True):
color.a = 1
assert_color(probe.background_color, color)


Expand All @@ -393,25 +391,28 @@ 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)


async def test_background_color_transparent(widget, probe):
"Background transparency is supported"
original = probe.background_color
supports_alpha = getattr(probe, "background_supports_alpha", True)

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

# 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):
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 methods to test are platform and
# implementation 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
58 changes: 53 additions & 5 deletions winforms/src/toga_winforms/colors.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,64 @@
from System.Drawing import Color
from travertino.colors import TRANSPARENT
from travertino.colors import TRANSPARENT, rgb, rgba

CACHE = {TRANSPARENT: Color.Transparent}


def native_color(c):
def native_color(toga_color):
try:
color = CACHE[c]
color = CACHE[toga_color]
except KeyError:
color = Color.FromArgb(
int(c.rgba.a * 255), int(c.rgba.r), int(c.rgba.g), int(c.rgba.b)
int(toga_color.rgba.a * 255),
int(toga_color.rgba.r),
int(toga_color.rgba.g),
int(toga_color.rgba.b),
)
CACHE[c] = color
CACHE[toga_color] = color
Copy link
Member

Choose a reason for hiding this comment

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

toga_color is also a method, so there's an inherent ambiguity here.


return color


def toga_color(native_color):
return rgba(native_color.R, native_color.G, native_color.B, native_color.A / 255)


def alpha_blending_over_operation(child_color, parent_color):
Copy link
Member

Choose a reason for hiding this comment

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

The use of child and parent nomenclature here presupposes the usage. Alpha blending is a generic task; back and front (or similar) would be better terminology.

I'd also be inclined to add this as a utility in core (or possibly even in Travertino). Although we're not actually using this anywhere other than Winforms, the generic ability to do alpha blending isn't a bad thing to have, and it would allow us to explicitly test the blending API with core tests, rather than implicitly testing it through usage in Winforms.

# The blending operation I have implemented here is the "over" operation and
Copy link
Member

Choose a reason for hiding this comment

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

Code comments generally shouldn't refer to the author. We're describing the current state of the code, which isn't dependent on any one author.

# replicates CSS's rgba mechanism. For the formula used here, see:
# https://en.wikipedia.org/wiki/Alpha_compositing#Description

blended_alpha = child_color.a + ((1 - child_color.a) * parent_color.a)

# Check if the blended alpha is zero, indicating no blending
if blended_alpha == 0:
# If both child and parent alphas are 0, no blending occurs, so return child color.
return child_color

blended_color = rgb(
# Red Component
(
(
(child_color.r * child_color.a)
+ (parent_color.r * parent_color.a * (1 - child_color.a))
)
/ blended_alpha
),
# Green Component
(
(
(child_color.g * child_color.a)
+ (parent_color.g * parent_color.a * (1 - child_color.a))
)
/ blended_alpha
),
# Blue Component
(
(
(child_color.b * child_color.a)
+ (parent_color.b * parent_color.a * (1 - child_color.a))
)
/ blended_alpha
),
)
return blended_color
Loading
Loading