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

Pythonic theme creation #68

Open
formateli opened this issue Apr 3, 2020 · 7 comments
Open

Pythonic theme creation #68

formateli opened this issue Apr 3, 2020 · 7 comments
Assignees
Labels

Comments

@formateli
Copy link

Hi everybody.

I see themes are created using tcl code but project is thought as python package.

Is there any plan to create themes in a pythonic way? I mean, using style functions that already exists in python Tk library. I was playing with this and found windows render much more fast than actual implementation.

Thank you.

@RedFantom RedFantom self-assigned this Apr 7, 2020
@RedFantom
Copy link
Member

Hi @formateli ! The themes are indeed created using Tcl-code. There are three reasons for this:

  • The original themes are written in Tcl, and translating all of them to Python code would take quite a bit of effort.
  • I think the Tcl code is more concise in creating the themes.
  • I expect that the Tcl-code is faster, as it is interpreted by the Tcl-interpreter directly, rather than passing through the Python translation layer first, but I have never tested it.

If you find that the performance would be better using Python code rather than Tcl code, please let me know. I'd love to see some numbers on it. If your application is rendering slowly, make sure to set gif_override=True when creating ThemedTk. This way, you use a GIF theme, which is generally much faster, but looks a little worse. It should only matter much if your application has lots of background tasks or a load of widgets though (as in, a hundred or so).

@israel-dryer
Copy link

@formateli , @RedFantom, yes, you can develop themes using python... though, consulting the ttkthemes tcl code will be crucial to understanding and learning what you need to do in python, as it's not documented anywhere well. Rebuilding the arc theme in python, and consulting the tcl/tk man pages is how I learned to do this.

Here's a simple example of creating a new theme with a single custom widget - a modern looking radiobutton.

image

from PIL import ImageDraw, Image, ImageTk
from tkinter import ttk

style = ttk.Style()

# radio off image
radio_off = Image.new('RGBA', (134, 134))
draw = ImageDraw.Draw(radio_off)
draw.ellipse([2, 2, 132, 132], outline="#adb5bd", width=3, fill="#f8f9fa")
radio_off_img = ImageTk.PhotoImage(radio_off.resize((14, 14), Image.LANCZOS))

# radio on image
radio_on = Image.new('RGBA', (134, 134))
draw = ImageDraw.Draw(radio_on)
draw.ellipse([2, 2, 132, 132], outline="#4582ec", width=12, fill="#4582ec")
draw.ellipse([40, 40, 94, 94], fill="#ffffff")
radio_on_img = ImageTk.PhotoImage(radio_on.resize((14, 14), Image.LANCZOS))

# radio disabled image
radio_disabled = Image.new('RGBA', (134, 134))
draw = ImageDraw.Draw(radio_disabled)
draw.ellipse([2, 2, 132, 132], outline="#c5c7c7", width=3, fill="#f8f9fa")
draw.ellipse([40, 40, 94, 94], fill="#f8f9fa")
radio_disabled_img = ImageTk.PhotoImage(radio_disabled.resize((14, 14), Image.LANCZOS))

# create theme settings
theme_settings = {
    'Radiobutton.indicator': {
        'element create': ('image', radio_on_img,
                           ('disabled', radio_disabled_img),
                           ('!selected', radio_off_img),
                           {'width': 28, 'border': 4, 'sticky': 'w'})},
    'TRadiobutton': {
        'layout': [
            ('Radiobutton.padding', {'children': [
                ('primary.Radiobutton.indicator', {'side': 'left', 'sticky': ''}),
                ('Radiobutton.focus', {'children': [
                    ('Radiobutton.label', {'sticky': 'nswe'})], 'side': 'left', 'sticky': ''})],
                'sticky': 'nswe'})],
        'configure': {
            'background': "#ffffff",
            'foreground': "#343a40",
            'focuscolor': ''},
        'map': {
            'background': [
                ('active', '#ffffff')],
            'foreground': [
                ('disabled', "#c5c7c7"),
                ('active !disabled', "#4582ec")]}}}

# create and apply custom theme
style.theme_create('myawesometheme', 'clam', theme_settings)
style.theme_use('myawesometheme')

# set the background to white (too lazy to create a frame)
style.master.configure(background="#ffffff")

# add the new widgets that I created
ttk.Radiobutton(text='option 1 - selected', value=1).pack(fill='x', padx=20, pady=(20, 2))
ttk.Radiobutton(text='option 2', value=2).pack(fill='x', padx=20, pady=2)
ttk.Radiobutton(text='option 3', value=3).pack(fill='x', padx=20, pady=2)
ttk.Radiobutton(text='option 4 - disabled', value=4, state='disabled').pack(fill='x', padx=20, pady=(2, 20))

style.master.mainloop()

In the example above I used a dictionary of settings. However, you can just as well do it with the Style.map, Style.layout, and Style.configure methods. But, if you're going to build an entire theme, then using a settings dictionary is more scalable, and could technically be stored in an offline json file as well.

If you're interested in seeing what can be done, I'm developing a python package called ttkbootstrap that essentially creates a template engine that iterates over color schemes to create over a dozen nice looking bootstrap inspired themes. I've also added a theme creator so that you can add your own color themes.

@rdbende
Copy link
Member

rdbende commented Apr 16, 2021

It's crazy, but I like it 😄.

I've started to make ttk themes in python several times, but I always give up because the syntax of tkinter.ttk.Style is so cluttered.
It blows my mind that you were able to create ttk-arc-clone in Python.

@RedFantom
Copy link
Member

@israel-dryer Thank you for posting this message! I probably wouldn't have come across your project on my own. While I was already aware of how themes and styles can be created through Python, your project really takes that one step further and the theme looks really good!

As creating themes by drawing pixmaps and such is not really my thing, I have also looked at other ways of loading themes from different sources into ttk, such as from Gtk and Qt. I've also spent some time on trying to parse Gtk-2.0 themes (which provide the base for quite some ttk themes) into a ttk theme, but the hurdles were too significant.

This does make me curious though: What kind of scope are you looking to achieve with your project? Because I've been working on getting tkextrafont and tksvg on PyPI for the purpose of, hopefully, building a package that separates the themes from the code that loads them and can support DPI scaling. The idea is to also build a loader for CSS (or something else simple, like JSON or YAML) theme definitions. @rdbende and I have been discussing it somewhat and there's a basic outline, but for me at least time is limited.

If you're looking to do something similar, I might focus my efforts elsewhere. Regardless, I'll be watching your project with interest!

@israel-dryer
Copy link

It's a crazy thing, but I like it 😄.

I've started creating an entire pythonic theme many times (not by drawing every single picture, of course, but simply by translating the usual tcl loader code and use it), but I'm always bored because of the tkinter style syntax is so tangled.

I = {}

def LoadImages(imgdir):
    global I
    for file in os.listdir(imgdir):
        img = os.path.basename(file).replace(".png", "")
        I[img] = tk.PhotoImage(file="{}/{}".format(imgdir, file))

LoadImages('azure')

and here comes the translation of the tcl theme into tkinter.

Yeah, the syntax is a mess...

I try to use as few images as possible on widgets that are expandable because this seems to cause the greatest amount of lag. I made the mistake of using a 1x1 pixel image for the ttk.Separator... yikes. Using a larger image on expandable widgets helps significantly because tkinter doesn't have to continually replicate the image to fill in the extra real estate.

Another interesting strategy is building a hybrid widget layout by using elements from various themes. When creating a combobox (and spinbox), I wanted to create a border effect around the entire widget while removing the border from the combobox button. In order to do this, I used the arrow from the default theme so that I could set relief='flat' on the combobox. This has no effect on clam theme highlight borders, but does remove the border from the default theme down button. So, what you end up with is a nice combobox where the arrow and the listbox are blended into a single color, while still retaining the border, hover, and focus border effects around the entire widget.

image

settings = {
    'Combobox.downarrow': {'element create': ('from', 'default')},
    'Combobox.padding': {'element create': ('from', 'clam')},
    'Combobox.textarea': {'element create': ('from', 'clam')}
}

@israel-dryer
Copy link

@israel-dryer Thank you for posting this message! I probably wouldn't have come across your project on my own. While I was already aware of how themes and styles can be created through Python, your project really takes that one step further and the theme looks really good!

As creating themes by drawing pixmaps and such is not really my thing, I have also looked at other ways of loading themes from different sources into ttk, such as from Gtk and Qt. I've also spent some time on trying to parse Gtk-2.0 themes (which provide the base for quite some ttk themes) into a ttk theme, but the hurdles were too significant.

This does make me curious though: What kind of scope are you looking to achieve with your project? Because I've been working on getting tkextrafont and tksvg on PyPI for the purpose of, hopefully, building a package that separates the themes from the code that loads them and can support DPI scaling. The idea is to also build a loader for CSS (or something else simple, like JSON or YAML) theme definitions. @rdbende and I have been discussing it somewhat and there's a basic outline, but for me at least time is limited.

If you're looking to do something similar, I might focus my efforts elsewhere. Regardless, I'll be watching your project with interest!

oh, I would love to be able to use svg natively in Tkinter! I've heard that it's added in the 8.7 edition of tcl/tk, but that version is not yet shipped in python as far as I know.

As far as scope, my intent is to create a package that has a general thematic style which can be applied to any combination of colors. So, anything I build would need to be as generic as possible with regards to extra features. The theme creation functionality is more about allowing users to select colors for a predefined style structure than anything else. I probably won't add anything else beyond that at this point. Though, depending on how the svg stuff turns out... it could definitely be a replacement for the pillow images I'm currently drawing at runtime.

@rdbende
Copy link
Member

rdbende commented Apr 16, 2021

If you want to use SVG, use this: https://github.com/TkinterEP/python-tksvg

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

No branches or pull requests

4 participants