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 53 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
29 changes: 28 additions & 1 deletion android/src/toga_android/colors.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
from android import R
from android.graphics import Color
from java import jint
from org.beeware.android import MainActivity
from travertino.colors import TRANSPARENT

CACHE = {TRANSPARENT: Color.TRANSPARENT}
from toga.colors import rgba

typed_array = MainActivity.singletonThis.getTheme().obtainStyledAttributes(
[R.attr.colorBackground]
)
DEFAULT_BACKGROUND_COLOR = typed_array.getColor(0, 0)
typed_array.recycle()

CACHE = {
TRANSPARENT: Color.TRANSPARENT,
}


def native_color(c):
Expand All @@ -14,3 +27,17 @@ def native_color(c):
CACHE[c] = color

return color


def toga_color(c): # pragma: no cover
# Select the `int` overloads rather than the `long` ones.
color_int = jint(c)
if color_int == 0:
return TRANSPARENT
else:
return rgba(
Color.red(color_int),
Color.green(color_int),
Color.blue(color_int),
Color.alpha(color_int) / 255,
)
17 changes: 13 additions & 4 deletions android/src/toga_android/container.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from android.widget import RelativeLayout

from .widgets.base import Scalable
from .widgets.base import ContainedWidget, Scalable


class Container(Scalable):
Expand Down Expand Up @@ -51,13 +51,22 @@ def refreshed(self):
self.native_content.setLayoutParams(lp)

def add_content(self, widget):
self.native_content.addView(widget.native)
if isinstance(widget, ContainedWidget):
self.native_content.addView(widget.native_widget_container)
else:
self.native_content.addView(widget.native)

def remove_content(self, widget):
self.native_content.removeView(widget.native)
if isinstance(widget, ContainedWidget):
self.native_content.removeView(widget.native_widget_container)
else:
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)
if isinstance(widget, ContainedWidget):
widget.native_widget_container.setLayoutParams(lp)
else:
widget.native.setLayoutParams(lp)
124 changes: 97 additions & 27 deletions android/src/toga_android/widgets/base.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from abc import ABC, abstractmethod
from decimal import ROUND_HALF_EVEN, Decimal

from android.graphics import PorterDuff, PorterDuffColorFilter, Rect
from android.graphics.drawable import ColorDrawable, InsetDrawable
from android.graphics import Color, PorterDuff, PorterDuffColorFilter
from android.graphics.drawable import ColorDrawable
from android.view import Gravity, View
from android.widget import RelativeLayout
from org.beeware.android import MainActivity
from travertino.size import at_least

from toga.constants import CENTER, JUSTIFY, LEFT, RIGHT, TRANSPARENT

from ..colors import native_color
from toga.constants import CENTER, JUSTIFY, LEFT, RIGHT
from toga_android.colors import (
DEFAULT_BACKGROUND_COLOR,
native_color,
toga_color,
)


class Scalable:
Expand Down Expand Up @@ -134,31 +137,16 @@ def set_font(self, font):
def set_background_color(self, color):
pass

def set_background_simple(self, value):
if not hasattr(self, "_default_background"):
self._default_background = self.native.getBackground()
def set_background_simple(self, color):
self.native.setBackground(
ColorDrawable(Color.TRANSPARENT if color is None else native_color(color))
)

if value in (None, TRANSPARENT):
self.native.setBackground(self._default_background)
else:
background = ColorDrawable(native_color(value))
if isinstance(self._default_background, InsetDrawable):
outer_padding = Rect()
inner_padding = Rect()
self._default_background.getPadding(outer_padding)
self._default_background.getDrawable().getPadding(inner_padding)
insets = [
getattr(outer_padding, name) - getattr(inner_padding, name)
for name in ["left", "top", "right", "bottom"]
]
background = InsetDrawable(background, *insets)
self.native.setBackground(background)

def set_background_filter(self, value):
def set_background_filter(self, color):
self.native.getBackground().setColorFilter(
None
if value in (None, TRANSPARENT)
else PorterDuffColorFilter(native_color(value), PorterDuff.Mode.SRC_IN)
if color is None
else PorterDuffColorFilter(native_color(color), PorterDuff.Mode.SRC_IN)
)

def set_alignment(self, alignment):
Expand Down Expand Up @@ -198,3 +186,85 @@ def align(value):
CENTER: Gravity.CENTER_HORIZONTAL,
JUSTIFY: Gravity.LEFT,
}[value]


# Most of the Android Widget have different effects applied them which provide
# the native look and feel of Android. These widgets' background consists of
# Drawables like ColorDrawable, InsetDrawable and other animation Effect Drawables
# like RippleDrawable. Often when such Effect Drawables are used, they are stacked
# along with other Drawables in a LayerDrawable.
#
# LayerDrawable once created cannot be modified and attempting to modify it or
# creating a new LayerDrawable using the elements of the original LayerDrawable
# stack, will destroy the native look and feel of the widgets. The original
# LayerDrawable cannot also be properly cloned. Using `getConstantState()` on the
# Drawable will produce an erroneous version of the original Drawable.
#
# These widgets are also affected by the background color of the parent inside which
# they are present. Directly, setting background color also destroys the native look
# and feel of these widgets. Moreover, these effects also serve the purpose of
# providing visual aid for the action of these widgets.
#
# Hence, the best option to preserve the native look and feel of the these widgets is
# to contain them in a `RelativeLayout` and set the background color to the layout
# instead of the widget itself.
#
# ContainedWidget should act as a drop-in replacement against the Widget class for
# such widgets, without requiring the widgets to do anything extra on their part.
# It should be used for widgets that have an animated Effect Drawable as background
# and their native look and feel needs to be preserved.
class ContainedWidget(Widget):
def __init__(self, interface):

self._native_activity = MainActivity.singletonThis

self.interface = interface
self.interface._impl = self
self._container = None
self.native_widget_container = RelativeLayout(self._native_activity)
self.native = None
super().__init__(interface)

self.create()

self.native_widget_container.addView(self.native)

self.native.setLayoutParams(
RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT, # width # height
)
)
# Immediately re-apply styles. Some widgets may defer style application until
# they have been added to a container.
self.interface.style.reapply()

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

def set_enabled(self, value):
self.native_widget_container.setEnabled(value)
self.native.setEnabled(value)

def focus(self):
if self.focusable:
self.native_widget_container.requestFocus()
self.native.requestFocus()

def set_hidden(self, hidden):
if hidden: # pragma: no cover
self.native_widget_container.setVisibility(View.INVISIBLE)
self.native.setVisibility(View.INVISIBLE)
else:
self.native_widget_container.setVisibility(View.VISIBLE)
self.native.setVisibility(View.VISIBLE)

def set_background_simple(self, color):
self.native_widget_container.setBackground(ColorDrawable(native_color(color)))

# Widgets that need to set a different default background_color should
# override this method and set a background color for the None case.
def set_background_color(self, color):
self.set_background_simple(
toga_color(DEFAULT_BACKGROUND_COLOR) if color is None else color
)
6 changes: 4 additions & 2 deletions android/src/toga_android/widgets/box.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from android.widget import RelativeLayout

from toga.colors import TRANSPARENT

from .base import Widget


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

def set_background_color(self, value):
self.set_background_simple(value)
def set_background_color(self, color):
self.set_background_simple(TRANSPARENT if color is None else color)
Copy link
Member

Choose a reason for hiding this comment

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

This is a pattern that is repeated throughout the code (on multiple backends). It seems to me like this should be a flag on the widget so that it can be implemented once, rather than repeating the implementation everywhere.

6 changes: 4 additions & 2 deletions android/src/toga_android/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from java import dynamic_proxy
from travertino.size import at_least

from toga.colors import TRANSPARENT

from .label import TextViewWidget


Expand Down Expand Up @@ -49,8 +51,8 @@ def set_icon(self, icon):
def set_enabled(self, value):
self.native.setEnabled(value)

def set_background_color(self, value):
self.set_background_filter(value)
def set_background_color(self, color):
self.set_background_filter(None if color is TRANSPARENT else color)

def rehint(self):
if self._icon:
Expand Down
7 changes: 3 additions & 4 deletions android/src/toga_android/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,12 @@ def get_image_data(self):
)
canvas = A_Canvas(bitmap)
background = self.native.getBackground()
if background:
background.draw(canvas)
background.draw(canvas)
self.native.draw(canvas)

stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream)
return bytes(stream.toByteArray())

def set_background_color(self, value):
self.set_background_simple(value)
def set_background_color(self, color):
self.set_background_simple(color)
4 changes: 3 additions & 1 deletion android/src/toga_android/widgets/dateinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from android.app import DatePickerDialog
from java import dynamic_proxy

from toga_android.widgets.base import ContainedWidget

from .internal.pickers import PickerBase


Expand All @@ -27,7 +29,7 @@ def onDateSet(self, view, year, month_0, day):
self.impl.interface.value = date(year, month_0 + 1, day)


class DateInput(PickerBase):
class DateInput(PickerBase, ContainedWidget):
@classmethod
def _get_icon(cls):
return R.drawable.ic_menu_my_calendar
Expand Down
14 changes: 6 additions & 8 deletions android/src/toga_android/widgets/detailedlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from android import R
from android.app import AlertDialog
from android.content import DialogInterface
from android.graphics import Rect
from android.graphics import Color, Rect
from android.view import Gravity, View
from android.widget import ImageView, LinearLayout, RelativeLayout, ScrollView, TextView
from java import dynamic_proxy
Expand All @@ -15,8 +15,7 @@
# No cover due to not being able to test in CI
SwipeRefreshLayout = None


from .base import Widget
from .base import ContainedWidget


class DetailedListOnClickListener(dynamic_proxy(View.OnClickListener)):
Expand Down Expand Up @@ -96,7 +95,7 @@ def onRefresh(self):
self._interface.on_refresh()


class DetailedList(Widget):
class DetailedList(ContainedWidget):
def create(self):
if SwipeRefreshLayout is None: # pragma: no cover
raise RuntimeError(
Expand All @@ -105,10 +104,9 @@ def create(self):
"is listed in your app's dependencies."
)
# get the selection color from the current theme
attrs = [R.attr.colorBackground, R.attr.colorControlHighlight]
attrs = [R.attr.colorControlHighlight]
typed_array = self._native_activity.obtainStyledAttributes(attrs)
self.color_unselected = typed_array.getColor(0, 0)
self.color_selected = typed_array.getColor(1, 0)
self.color_selected = typed_array.getColor(0, 0)
typed_array.recycle()

self.native = self._refresh_layout = SwipeRefreshLayout(self._native_activity)
Expand Down Expand Up @@ -228,7 +226,7 @@ def clear(self):

def _clear_selection(self):
if self._selection is not None:
self._get_row(self._selection).setBackgroundColor(self.color_unselected)
self._get_row(self._selection).setBackgroundColor(Color.TRANSPARENT)
self._selection = None

def _set_selection(self, index):
Expand Down
9 changes: 4 additions & 5 deletions android/src/toga_android/widgets/divider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
from android.widget import LinearLayout
from travertino.size import at_least

from toga_android.colors import toga_color

from .base import Widget


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)
def set_background_color(self, color):
self.set_background_simple(toga_color(Color.LTGRAY) if color is None else color)

def get_direction(self):
return self._direction
Expand Down
4 changes: 2 additions & 2 deletions android/src/toga_android/widgets/imageview.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def create(self):
self.native = A_ImageView(self._native_activity)
self.native.setAdjustViewBounds(True)

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

def set_image(self, image):
if image:
Expand Down
Loading
Loading