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

Implement Gtk CSS utility with button implementation #397

Closed
wants to merge 15 commits into from

Conversation

ArchKudo
Copy link
Contributor

@ArchKudo ArchKudo commented Mar 8, 2018

May 2019 update

  • Add Gtk CSS utility (w/ documentation and tests)
  • Added Button implementation

References for reviewers:

https://developer.gnome.org/gtk3/stable/chap-css-overview.html
https://developer.gnome.org/gtk3/stable/chap-css-properties.html

About

Most styling functions for Gtk have been depreciated since Gtk+3.16(Example), and are can be now modified using Gtk CSS
This PR is thus an attempt to add styling to Gtk widgets using CSS

What this should eventually include / Updates:

  • CSS helper methods
  • Use CSS instead of Pango for changing fonts
  • Document changes
  • Widgets implementation like set_font, set_color, ...
  • Extensively unittest old and new code
  • I also took the liberty to format files and remove unnecessary imports 😇 👐

Current Roadblocks

  • A certain test to set Gtk property is failing on ci, my guess is due to different version of pygobject being installed on ci, Could try again after PR Recommend to use pip to install pygobject #374 gets merged
  • Can only change font_family and font_size through interface: See gist for details
  • According to what I understood from chatting on Gtk+ irc, background_color cannot be unittested, since it is no more a simple GDK.RGBA type but can be anything including image, video, cairo object, etc.

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

"""
res = selector + ' {\n'
for attr, val in decls.items():
res += f'\t{attr}: {val};\n'
Copy link
Contributor Author

@ArchKudo ArchKudo Mar 8, 2018

Choose a reason for hiding this comment

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

Just realized f-strings are Python3.6+ feature, I should have used .format()

class TestCSSRuleFactoryMethod(unittest.TestCase):
def setUp(self):
self.single_line_property = {'color': '#f00'} # Red
self.multi_line_property = {
Copy link
Contributor Author

@ArchKudo ArchKudo Mar 8, 2018

Choose a reason for hiding this comment

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

Just realized dicts don't have any order , causing tests to fail 😢
Edit: For (Python<3.6)

from gi.repository import Gtk, Gdk
except ImportError:
import sys
# If we're on Linux, Gtk *should* be available. If it isn't, make
Copy link
Contributor

Choose a reason for hiding this comment

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

At column 5: (F811) redefinition of unused 'sys' from line 1


# Use OrderedDict for Python < 3.6
if sys.version_info < (3,6):
d = OrderedDict()
Copy link
Contributor

Choose a reason for hiding this comment

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

At column 33: (E231) missing whitespace after ','

css_rule_factory('button', {})

@unittest.skipIf(Gtk is None, "Can't run GTK implementation tests on a non-Linux platform")
class TestGTKApplyCSSMethod(unittest.TestCase):
Copy link
Contributor

Choose a reason for hiding this comment

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

At column 1: (E302) expected 2 blank lines, found 1


import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
Copy link
Contributor

Choose a reason for hiding this comment

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

At column 1: (E402) module level import not at top of file

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from toga.constants import LEFT, RIGHT, CENTER, JUSTIFY
Copy link
Contributor

Choose a reason for hiding this comment

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

At column 1: (E402) module level import not at top of file


import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
Copy link
Contributor

Choose a reason for hiding this comment

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

At column 1: (E402) module level import not at top of file

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from toga.constants import LEFT, RIGHT, CENTER, JUSTIFY
Copy link
Contributor

Choose a reason for hiding this comment

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

At column 1: (E402) module level import not at top of file

@ArchKudo ArchKudo changed the title [WIP] Implement remaining Gtk widget methods Implement Gtk CSS utility with button implementation May 31, 2019
Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

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

I haven't done a close-detail inspection of the tests yet; I've focussed on the main API - and I have some broader architectural questions.

In particular, I'm not clear (from the documentation you linked, combined with the usage here) what the lifecycle of a GTK style provider/context is intended to be. The CSS language GTK has implemented includes widget and class specifiers, but the API (especially as used here) seems to be specifically targeted at individual widgets.

So - what's the intended lifecycle here? Are you meant to apply a stylesheet to an entire application? Or is per-widget the intended usage? And if so - is the style applied to the "button" target on each button, or on "button#id-1234"? (i.e., what is the interaction of CSS's element targetting namespace and GTK's per-widget style application API?)

Is the only way to specify styles to load-from-string? Do we really have to pay a text parsing overhead every time we want to change a style attribute on a widget? That seems... wasteful.

Are styles additive? Or does a new style declaration replace an old one?

If they're additive (as would be suggested by the add_provider() API naming - can you modify an existing style context?

I'm not denying that this code works. I just want to make sure we're actually using the GTK API the right way - because the way it's being used right now, it seems like there's going to be a lot of object creation going on as you construct CSSProviders and get StyleContexts. And, if you were to switch (for example) the color of a widget back and forth between two values, the internal style definition of the GTK widget is going to become a long list of internal overrides.

def set_background_color(self, value):
self.interface.factory.not_implemented('Button.set_background_color()')
if value:
gtk_apply_css(self.native, {"background-color": value})
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason that this is an external utility method, rather than a method on the base toga-GTK widget?

@ArchKudo
Copy link
Contributor Author

ArchKudo commented Jun 1, 2019

In particular, I'm not clear (from the documentation you linked, combined with the usage here) what the lifecycle of a GTK style provider/context is intended to be. The CSS language GTK has implemented includes widget and class specifiers, but the API (especially as used here) seems to be specifically targeted at individual widgets.
So - what's the intended lifecycle here? Are you meant to apply a stylesheet to an entire application? Or is per-widget the intended usage?

The recommended way is to have a single application level CSS as we would have while creating a website, so in that sense toga doesn't exactly need it, since, it implements its own styling.. however, styling using python api was limited with GTK3 as, some of the theming API was deprecated in favour of CSS..

And if so - is the style applied to the "button" target on each button, or on "button#id-1234"? (i.e., what is the interaction of CSS's element targetting namespace and GTK's per-widget style application API?)

It applies to the specific widget only but, it is totally possible to have it the other way around depending on the selector...

Is the only way to specify styles to load-from-string? Do we really have to pay a text parsing overhead every time we want to change a style attribute on a widget? That seems... wasteful.

Given the underlying API is in C, I can't find any other method either than to load CSS from a file or as a string.. 🤷‍♀️

Are styles additive? Or does a new style declaration replace an old one?

If they're additive (as would be suggested by the add_provider() API naming - can you modify an existing style context?

Yes they are, and follow the CSS precedence rules, also setting priority to application always applies the most current change..

I'm not denying that this code works. I just want to make sure we're actually using the GTK API the right way - because the way it's being used right now, it seems like there's going to be a lot of object creation going on as you construct CSSProviders and get StyleContexts. And, if you were to switch (for example) the color of a widget back and forth between two values, the internal style definition of the GTK widget is going to become a long list of internal overrides.

Oh! Totally agree, it probably didn't cross my mind, one solution I can think of is adding a class based UID to each CSS rule since a quick search on SO says there exists API to remove a css class though, I must admit this requires some more research on my side best not to rush it..

Do let me know if any of my responses aren't clear enough and, thanks a lot for reviewing!

@freakboy3742
Copy link
Member

The recommended way is to have a single application level CSS as we would have while creating a website, so in that sense toga doesn't exactly need it, since, it implements its own styling.. however, styling using python api was limited with GTK3 as, some of the theming API was deprecated in favour of CSS..

In which case - are there any performance considerations here? Is setting lots of individual per-widget styles any faster or slower than having a single application-level style?

And if so - is the style applied to the "button" target on each button, or on "button#id-1234"? (i.e., what is the interaction of CSS's element targetting namespace and GTK's per-widget style application API?)

It applies to the specific widget only but, it is totally possible to have it the other way around depending on the selector...

Again - what are the performance considerations? Is it faster to set:

  • { background-color: red } set as a property on a specific widget (i.e., a global style definition, but widget-scoped by virtue of being invoked on a specific widget)
  • button: { background-color: red } set as a property on a specific widget (i.e., a global widget style definition, but widget-scoped by virtue of being invoked on a specific widget)
  • #id-12345: { background-color: red } set as a property on a specific widget (strictly overspecified due to being invoked on a specific widget)
  • button#id-12345: { background-color: red } set as a property on a specific widget (doubly-overspecified due to being invoked on a specific widget)
  • #id-12345: { background-color: red } set as a global style
  • button#id-12345: { background-color: red } set as a global style (overspecified because there is only one widget in an app with a given ID)

I'd be interested in seeing a performance comparison using a test-bed application - for example, an app with hundreds of buttons in a window, repeatedly changing a style back and forth on one of the buttons. This doesn't need to be a Toga application (in fact, it's probably better if it isn't - we're looking to verify the specific performance characteristics of GTK, not the Toga overhead).

Are styles additive? Or does a new style declaration replace an old one?
If they're additive (as would be suggested by the add_provider() API naming - can you modify an existing style context?

Yes they are, and follow the CSS precedence rules, also setting priority to application always applies the most current change..

I'm intrigued by the performance characteristics here, too. Is the internal structure retaining the full style definition and recomputing the "net" style after every addition? Or is it only retaining the "current" state of the style, and when you load a new style, determining if selectors affect the "current" style. Or something in between?

The specifics of the implementation don't particularly matter - but if it's the former, I'd expect to see a performance difference between iteration 0-100 and iteration 100000 and 100100.

Oh! Totally agree, it probably didn't cross my mind, one solution I can think of is adding a class based UID to each CSS rule since a quick search on SO says there exists API to remove a css class though, I must admit this requires some more research on my side best not to rush it..

I can see how using a "unique class" as an identifier could be manipulated to our advantage - however, the use case isn't likely to be "reset style to default value", it's "overwrite existing style with new value". To that end, it's probably worth adding button.id-12345: { background-color: red } to the pool of options to check in the performance test.

@phildini
Copy link
Member

Hi there! It looks like this PR might be dead, so we're closing it for now. Feel free to re-open it if you'd like to continue. 😄

@phildini phildini closed this Apr 25, 2020
@danyeaw
Copy link
Member

danyeaw commented Apr 25, 2020

@ArchKudo This PR was a really good step towards us getting support for Button and Label font sizes, and I think you were like 90% of the way there. Is this something you are still interested in finishing? We have a sprint going on this weekend. Otherwise we could have someone create a new PR from where you left off.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants