Skip to content

Commit

Permalink
GTK Button color tests passing.
Browse files Browse the repository at this point in the history
  • Loading branch information
freakboy3742 committed Feb 7, 2023
1 parent cf2ed86 commit 065bca3
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 112 deletions.
74 changes: 27 additions & 47 deletions gtk/src/toga_gtk/libs/styles.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from toga.colors import TRANSPARENT

from . import Gtk
from toga.fonts import SYSTEM_DEFAULT_FONT_SIZE

TOGA_DEFAULT_STYLES = b"""
.toga-detailed-list-floating-buttons {
Expand All @@ -15,58 +14,39 @@
"""


def apply_gtk_style(style_context, style, name):
# creating StyleProvider (i.e CssProvider)
style_provider = Gtk.CssProvider()
style_provider.load_from_data(style.encode())

# setting the StyleProvider to StyleContext
style_context.add_provider(
style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER,
)
style_context.add_class(name)


def get_color_css(value):
# fmt: off
return (
".toga-color {"
f"color: rgba({value.r}, {value.g}, {value.b}, {value.a});"
"}"
)
# fmt: on
if value is None:
return None
else:
return {
"color": f"rgba({value.r}, {value.g}, {value.b}, {value.a})",
}


def get_bg_color_css(value):
def get_background_color_css(value):
if value == TRANSPARENT:
return (
".toga-bg-color {"
"background-color: rgba(0, 0, 0, 0);"
"background-image: none;"
"}"
)
return {
"background-color": "rgba(0, 0, 0, 0)",
"background-image": "none",
}
elif value is None:
return None
else:
return (
".toga-bg-color {"
f"background-color: rgba({value.r}, {value.g}, {value.b}, {value.a});"
"background-image: none;"
"}"
)
return {
"background-color": f"rgba({value.r}, {value.g}, {value.b}, {value.a})",
"background-image": "none",
}


def get_font_css(value):
style = [
".toga-font { "
f"font-style: {value.style}; "
f"font-variant: {value.variant}; "
f"font-weight: {value.weight}; "
f"font-family: {value.family}; "
]

if value.size != -1:
style.append(f"font-size: {value.size}px; ")
style = {
"font-style": f"{value.style}",
"font-variant": f"{value.variant}",
"font-weight": f"{value.weight}",
"font-family": f"{value.family}",
}

style.append("}")
if value.size != SYSTEM_DEFAULT_FONT_SIZE:
style["font-size"] = f"{value.size}pt"

return " ".join(style)
return style
66 changes: 53 additions & 13 deletions gtk/src/toga_gtk/widgets/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from travertino.size import at_least

from ..libs import apply_gtk_style, get_bg_color_css, get_color_css, get_font_css
from ..libs import Gtk, get_background_color_css, get_color_css, get_font_css


class Widget:
Expand All @@ -10,6 +10,7 @@ def __init__(self, interface):
self._container = None
self.viewport = None
self.native = None
self.style_providers = {}
self.create()
self.interface.style.reapply()

Expand Down Expand Up @@ -63,6 +64,54 @@ def get_tab_index(self):
def set_tab_index(self, tab_index):
self.interface.factory.not_implementated("Widget.set_tab_index()")

######################################################################
# CSS tools
######################################################################

def apply_css(self, property, css):
"""Apply a CSS style controlling a specific property type.
GTK controls appearance with CSS; each GTK widget can have
an independent style sheet, composed out of multiple providers.
Toga uses a separate provider for each property that
needs to be controlled (e.g., color, font, ...). When that
property is modified, the old provider for that property is
removed; if new CSS has been provided, a new provider is
constructed and added to the widget.
It is assumed that every Toga widget will have the class
``toga``.
:param name: The style property to modify
:param css: A dictionary of string key-value pairs, describing
the new CSS for the given property. If ``None``, the Toga
style for that property will be reset
"""
style_context = self.native.get_style_context()
style_provider = self.style_providers.pop(property, None)

# If there was a previous style provider for the given property, remove
# it from the GTK widget
if style_provider:
style_context.remove_provider(style_provider)

# If there's new CSS to apply, construct a new provider, and install it.
if css is not None:
# Create a new CSS StyleProvider
style_provider = Gtk.CssProvider()
styles = " ".join(f"{key}: {value};" for key, value in css.items())
# print(f"SET {self} {property}={styles}")
style_provider.load_from_data((".toga {" + styles + "}").encode())

# Add the provider to the widget
style_context.add_provider(
style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
# Store the provider so it can be removed later
self.style_providers[property] = style_provider

######################################################################
# APPLICATOR
######################################################################
Expand All @@ -80,22 +129,13 @@ def set_hidden(self, hidden):
return not self.native.set_visible(not hidden)

def set_color(self, color):
if color:
style_context = self.native.get_style_context()
css = get_color_css(color)
apply_gtk_style(style_context, css, "toga-color")
self.apply_css("color", get_color_css(color))

def set_background_color(self, color):
if color:
style_context = self.native.get_style_context()
css = get_bg_color_css(color)
apply_gtk_style(style_context, css, "toga-bg-color")
self.apply_css("background_color", get_background_color_css(color))

def set_font(self, font):
if font:
style_context = self.native.get_style_context()
css = get_font_css(font)
apply_gtk_style(style_context, css, "toga-font")
self.apply_css("font", get_font_css(font))

######################################################################
# INTERFACE
Expand Down
10 changes: 10 additions & 0 deletions gtk/src/toga_gtk/widgets/button.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
from travertino.size import at_least

from toga.colors import TRANSPARENT

from ..libs import Gtk
from .base import Widget


class Button(Widget):
def create(self):
self.native = Gtk.Button()
self.native.set_name(f"toga-{self.interface.id}")
self.native.get_style_context().add_class("toga")
self.native.interface = self.interface

self.native.connect("show", lambda event: self.rehint())
Expand All @@ -26,6 +30,12 @@ def set_on_press(self, handler):
# No special handling required
pass

def set_background_color(self, color):
# Buttons interpret TRANSPARENT backgrounds as a reset
if color == TRANSPARENT:
color = None
super().set_background_color(color)

def rehint(self):
# print("REHINT", self, self.native.get_preferred_width(), self.native.get_preferred_height())
width = self.native.get_preferred_width()
Expand Down
3 changes: 3 additions & 0 deletions gtk/src/toga_gtk/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
class Label(Widget):
def create(self):
self.native = Gtk.Label()
self.native.set_name(f"toga-{self.interface.id}")
self.native.get_style_context().add_class("toga")

self.native.set_line_wrap(False)

self.native.interface = self.interface
Expand Down
17 changes: 17 additions & 0 deletions gtk/tests_backend/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from toga_gtk.libs import Gtk

from .properties import toga_color, toga_font


class SimpleProbe:
def __init__(self, widget):
Expand Down Expand Up @@ -53,3 +55,18 @@ def width(self):
@property
def height(self):
return self.native.get_allocation().height

@property
def color(self):
sc = self.native.get_style_context()
return toga_color(sc.get_property("color", sc.get_state()))

@property
def background_color(self):
sc = self.native.get_style_context()
return toga_color(sc.get_property("background-color", sc.get_state()))

@property
def font(self):
sc = self.native.get_style_context()
return toga_font(sc.get_property("font", sc.get_state()))
16 changes: 5 additions & 11 deletions gtk/tests_backend/widgets/button.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from pytest import skip

from toga_gtk.libs import Gtk

from .base import SimpleProbe
Expand All @@ -12,17 +10,13 @@ class ButtonProbe(SimpleProbe):
def text(self):
return self.native.get_label()

@property
def color(self):
skip("color probe not implemented")

@property
def font(self):
skip("font probe not implemented")

@property
def background_color(self):
skip("background color probe not implemented")
color = super().background_color
# Background color of
if color.r == 0 and color.g == 0 and color.b == 0 and color.a == 0.0:
return None
return color

def press(self):
self.native.clicked()
12 changes: 0 additions & 12 deletions gtk/tests_backend/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,6 @@ class LabelProbe(SimpleProbe):
def text(self):
return self.native.get_label()

@property
def color(self):
skip("color probe not implemented")

@property
def background_color(self):
skip("background color probe not implemented")

@property
def font(self):
skip("font probe not implemented")

@property
def alignment(self):
skip("alignment probe not implemented")
25 changes: 12 additions & 13 deletions gtk/tests_backend/widgets/properties.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
from dataclasses import dataclass
from travertino.fonts import Font

from toga.fonts import NORMAL
from toga.colors import rgba
from toga.style.pack import CENTER, JUSTIFY, LEFT, RIGHT
from toga_gtk.libs import Gtk, Pango


def toga_color(color):
return color


@dataclass
class Font:
family: str
size: int
style: str = NORMAL
variant: str = NORMAL
weight: str = NORMAL
if color:
return rgba(
int(color.red * 255),
int(color.green * 255),
int(color.blue * 255),
color.alpha,
)
else:
return None


def toga_font(font):
return Font(
family=font.get_family(),
size=font.get_size() / Pango.SCALE,
size=int(font.get_size() / Pango.SCALE),
)


Expand Down
2 changes: 2 additions & 0 deletions testbed/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ test_sources = [
]
requires = [
"../gtk",
# PyGObject #119 breaks the test suite; we need to use a PR version.
"git+https://gitlab.gnome.org/monnerat/pygobject.git@issue119",
]

[tool.briefcase.app.testbed.linux.appimage]
Expand Down
26 changes: 14 additions & 12 deletions testbed/tests/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@


COLORS = [
# Avoid using (0, 0, 0, 0.0) as a test color,
# as that is indistinguishable from the TRANSPARENT color
rgba(r, g, b, a)
for r, g, b in [
for r, g, b, a in [
# Black, gray, white,
(0, 0, 0),
(1, 1, 1),
(10, 10, 10),
(128, 128, 128),
(245, 245, 245),
(254, 254, 254),
(255, 255, 255),
(0, 0, 0, 1.0),
(128, 128, 128, 1.0),
(255, 255, 255, 1.0),
# Primaries
(255, 0, 0),
(0, 255, 0),
(0, 0, 255),
(255, 0, 0, 1.0),
(0, 255, 0, 1.0),
(0, 0, 255, 1.0),
# Color with different channel values, including transparency
(50, 128, 200, 0.0),
(50, 128, 200, 0.5),
(50, 128, 200, 0.9),
(50, 128, 200, 1.0),
]
for a in [0.0, 0.01, 0.1, 0.5, 0.9, 0.99, 1.0]
]
Loading

0 comments on commit 065bca3

Please sign in to comment.