5180 lines
168 KiB
Python
5180 lines
168 KiB
Python
|
import json
|
||
|
import re
|
||
|
import colorsys
|
||
|
import tkinter as tk
|
||
|
from tkinter import font
|
||
|
from math import ceil
|
||
|
from tkinter import TclError, ttk
|
||
|
from typing import Any, Callable
|
||
|
from PIL import ImageTk, ImageDraw, Image, ImageFont
|
||
|
from ttkbootstrap.constants import *
|
||
|
from ttkbootstrap.themes.standard import STANDARD_THEMES
|
||
|
from ttkbootstrap.publisher import Publisher, Channel
|
||
|
from ttkbootstrap import utility as util
|
||
|
from ttkbootstrap import colorutils
|
||
|
from PIL import ImageColor
|
||
|
|
||
|
|
||
|
try:
|
||
|
# prevent app from failing if user.py gets corrupted
|
||
|
from ttkbootstrap.themes.user import USER_THEMES
|
||
|
except (ImportError, ModuleNotFoundError):
|
||
|
USER_THEMES = {}
|
||
|
|
||
|
|
||
|
class Colors:
|
||
|
"""A class that defines the color scheme for a theme as well as
|
||
|
provides several static methods for manipulating colors.
|
||
|
|
||
|
A `Colors` object is attached to a `ThemeDefinition` and can also
|
||
|
be accessed through the `Style.colors` property for the
|
||
|
current theme.
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
```python
|
||
|
style = Style()
|
||
|
|
||
|
# dot-notation
|
||
|
style.colors.primary
|
||
|
|
||
|
# get method
|
||
|
style.colors.get('primary')
|
||
|
```
|
||
|
|
||
|
This class is an iterator, so you can iterate over the main
|
||
|
style color labels (primary, secondary, success, info, warning,
|
||
|
danger):
|
||
|
|
||
|
```python
|
||
|
for color_label in style.colors:
|
||
|
color = style.colors.get(color_label)
|
||
|
print(color_label, color)
|
||
|
```
|
||
|
|
||
|
If, for some reason, you need to iterate over all theme color
|
||
|
labels, then you can use the `Colors.label_iter` method. This
|
||
|
will include all theme colors.
|
||
|
|
||
|
```python
|
||
|
for color_label in style.colors.label_iter():
|
||
|
color = Colors.get(color_label)
|
||
|
print(color_label, color)
|
||
|
```
|
||
|
|
||
|
If you want to adjust the hsv values of an existing color by a
|
||
|
specific percentage (delta), you can use the `Colors.update_hsv`
|
||
|
method, which is static. In the example below, the "value delta"
|
||
|
or `vd` is increased by 15%, which will lighten the color:
|
||
|
|
||
|
```python
|
||
|
Colors.update_hsv("#9954bb", vd=0.15)
|
||
|
```
|
||
|
"""
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
primary,
|
||
|
secondary,
|
||
|
success,
|
||
|
info,
|
||
|
warning,
|
||
|
danger,
|
||
|
light,
|
||
|
dark,
|
||
|
bg,
|
||
|
fg,
|
||
|
selectbg,
|
||
|
selectfg,
|
||
|
border,
|
||
|
inputfg,
|
||
|
inputbg,
|
||
|
active,
|
||
|
):
|
||
|
"""
|
||
|
Parameters:
|
||
|
|
||
|
primary (str):
|
||
|
The primary theme color; used by default for all widgets.
|
||
|
|
||
|
secondary (str):
|
||
|
An accent color; commonly of a `grey` hue.
|
||
|
|
||
|
success (str):
|
||
|
An accent color; commonly of a `green` hue.
|
||
|
|
||
|
info (str):
|
||
|
An accent color; commonly of a `blue` hue.
|
||
|
|
||
|
warning (str):
|
||
|
An accent color; commonly of an `orange` hue.
|
||
|
|
||
|
danger (str):
|
||
|
An accent color; commonly of a `red` hue.
|
||
|
|
||
|
light (str):
|
||
|
An accent color.
|
||
|
|
||
|
dark (str):
|
||
|
An accent color.
|
||
|
|
||
|
bg (str):
|
||
|
Background color.
|
||
|
|
||
|
fg (str):
|
||
|
Default text color.
|
||
|
|
||
|
selectfg (str):
|
||
|
The color of selected text.
|
||
|
|
||
|
selectbg (str):
|
||
|
The background color of selected text.
|
||
|
|
||
|
border (str):
|
||
|
The color used for widget borders.
|
||
|
|
||
|
inputfg (str):
|
||
|
The text color for input widgets.
|
||
|
|
||
|
inputbg (str):
|
||
|
The text background color for input widgets.
|
||
|
|
||
|
active (str):
|
||
|
An accent color.
|
||
|
"""
|
||
|
self.primary = primary
|
||
|
self.secondary = secondary
|
||
|
self.success = success
|
||
|
self.info = info
|
||
|
self.warning = warning
|
||
|
self.danger = danger
|
||
|
self.light = light
|
||
|
self.dark = dark
|
||
|
self.bg = bg
|
||
|
self.fg = fg
|
||
|
self.selectbg = selectbg
|
||
|
self.selectfg = selectfg
|
||
|
self.border = border
|
||
|
self.inputfg = inputfg
|
||
|
self.inputbg = inputbg
|
||
|
self.active = active
|
||
|
|
||
|
@staticmethod
|
||
|
def make_transparent(alpha, foreground, background='#ffffff'):
|
||
|
"""Simulate color transparency.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
alpha (float):
|
||
|
The amount of transparency; a number between 0 and 1.
|
||
|
|
||
|
foreground (str):
|
||
|
The foreground color.
|
||
|
|
||
|
background (str):
|
||
|
The background color.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
A hexadecimal color representing the "transparent"
|
||
|
version of the foreground color against the background
|
||
|
color.
|
||
|
"""
|
||
|
fg = ImageColor.getrgb(foreground)
|
||
|
bg = ImageColor.getrgb(background)
|
||
|
rgb_float = [alpha * c1 + (1 - alpha) * c2 for (c1, c2) in zip(fg, bg)]
|
||
|
rgb_int = [int(x) for x in rgb_float]
|
||
|
return '#{:02x}{:02x}{:02x}'.format(*rgb_int)
|
||
|
|
||
|
@staticmethod
|
||
|
def rgb_to_hsv(r, g, b):
|
||
|
"""Convert an rgb to hsv color value.
|
||
|
|
||
|
Parameters:
|
||
|
r (float):
|
||
|
red
|
||
|
g (float):
|
||
|
green
|
||
|
b (float):
|
||
|
blue
|
||
|
|
||
|
Returns:
|
||
|
Tuple[float, float, float]: The hsv color value.
|
||
|
"""
|
||
|
return colorsys.rgb_to_hsv(r, g, b)
|
||
|
|
||
|
def get_foreground(self, color_label):
|
||
|
"""Return the appropriate foreground color for the specified
|
||
|
color_label.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
color_label (str):
|
||
|
A color label corresponding to a class property
|
||
|
"""
|
||
|
if color_label == LIGHT:
|
||
|
return self.dark
|
||
|
elif color_label == DARK:
|
||
|
return self.light
|
||
|
else:
|
||
|
return self.selectfg
|
||
|
|
||
|
def get(self, color_label: str):
|
||
|
"""Lookup a color value from the color name
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
color_label (str):
|
||
|
A color label corresponding to a class propery
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
A hexadecimal color value.
|
||
|
"""
|
||
|
return self.__dict__.get(color_label)
|
||
|
|
||
|
def set(self, color_label: str, color_value: str):
|
||
|
"""Set a color property value. This does not update any existing
|
||
|
widgets. Can also be used to create on-demand color properties
|
||
|
that can be used in your program after creation.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
color_label (str):
|
||
|
The name of the color to be set (key)
|
||
|
|
||
|
color_value (str):
|
||
|
A hexadecimal color value
|
||
|
"""
|
||
|
self.__dict__[color_label] = color_value
|
||
|
|
||
|
def __iter__(self):
|
||
|
return iter(
|
||
|
[
|
||
|
"primary",
|
||
|
"secondary",
|
||
|
"success",
|
||
|
"info",
|
||
|
"warning",
|
||
|
"danger",
|
||
|
"light",
|
||
|
"dark",
|
||
|
]
|
||
|
)
|
||
|
|
||
|
def __repr__(self):
|
||
|
out = tuple(zip(self.__dict__.keys(), self.__dict__.values()))
|
||
|
return str(out)
|
||
|
|
||
|
@staticmethod
|
||
|
def label_iter():
|
||
|
"""Iterate over all color label properties in the Color class
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
iter:
|
||
|
An iterator for color label names
|
||
|
"""
|
||
|
return iter(
|
||
|
[
|
||
|
"primary",
|
||
|
"secondary",
|
||
|
"success",
|
||
|
"info",
|
||
|
"warning",
|
||
|
"danger",
|
||
|
"light",
|
||
|
"dark",
|
||
|
"bg",
|
||
|
"fg",
|
||
|
"selectbg",
|
||
|
"selectfg",
|
||
|
"border",
|
||
|
"inputfg",
|
||
|
"inputbg",
|
||
|
"active",
|
||
|
]
|
||
|
)
|
||
|
|
||
|
@staticmethod
|
||
|
def hex_to_rgb(color: str):
|
||
|
"""Convert hexadecimal color to rgb color value
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
color (str):
|
||
|
A hexadecimal color value
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
tuple[int, int, int]:
|
||
|
An rgb color value.
|
||
|
"""
|
||
|
r, g, b = colorutils.color_to_rgb(color)
|
||
|
return r/255, g/255, b/255
|
||
|
|
||
|
@staticmethod
|
||
|
def rgb_to_hex(r: int, g: int, b: int):
|
||
|
"""Convert rgb to hexadecimal color value
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
r (int):
|
||
|
red
|
||
|
|
||
|
g (int):
|
||
|
green
|
||
|
|
||
|
b (int):
|
||
|
blue
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
A hexadecimal color value
|
||
|
"""
|
||
|
r_ = int(r * 255)
|
||
|
g_ = int(g * 255)
|
||
|
b_ = int(b * 255)
|
||
|
return colorutils.color_to_hex((r_, g_, b_))
|
||
|
|
||
|
@staticmethod
|
||
|
def update_hsv(color, hd=0, sd=0, vd=0):
|
||
|
"""Modify the hue, saturation, and/or value of a given hex
|
||
|
color value by specifying the _delta_.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
color (str):
|
||
|
A hexadecimal color value to adjust.
|
||
|
|
||
|
hd (float):
|
||
|
% change in hue, _hue delta_.
|
||
|
|
||
|
sd (float):
|
||
|
% change in saturation, _saturation delta_.
|
||
|
|
||
|
vd (float):
|
||
|
% change in value, _value delta_.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
The resulting hexadecimal color value
|
||
|
"""
|
||
|
r, g, b = Colors.hex_to_rgb(color)
|
||
|
h, s, v = colorsys.rgb_to_hsv(r, g, b)
|
||
|
|
||
|
# hue
|
||
|
if h * (1 + hd) > 1:
|
||
|
h = 1
|
||
|
elif h * (1 + hd) < 0:
|
||
|
h = 0
|
||
|
else:
|
||
|
h *= 1 + hd
|
||
|
|
||
|
# saturation
|
||
|
if s * (1 + sd) > 1:
|
||
|
s = 1
|
||
|
elif s * (1 + sd) < 0:
|
||
|
s = 0
|
||
|
else:
|
||
|
s *= 1 + sd
|
||
|
|
||
|
# value
|
||
|
if v * (1 + vd) > 1:
|
||
|
v = 0.95
|
||
|
elif v * (1 + vd) < 0.05:
|
||
|
v = 0.05
|
||
|
else:
|
||
|
v *= 1 + vd
|
||
|
|
||
|
r, g, b = colorsys.hsv_to_rgb(h, s, v)
|
||
|
return Colors.rgb_to_hex(r, g, b)
|
||
|
|
||
|
|
||
|
class ThemeDefinition:
|
||
|
"""A class to provide defined name, colors, and font settings for a
|
||
|
ttkbootstrap theme."""
|
||
|
|
||
|
def __init__(self, name, colors, themetype=LIGHT):
|
||
|
"""
|
||
|
Parameters:
|
||
|
|
||
|
name (str):
|
||
|
The name of the theme.
|
||
|
|
||
|
colors (Colors):
|
||
|
An object that defines the color scheme for a theme.
|
||
|
|
||
|
themetype (str):
|
||
|
Specifies whether the theme is **light** or **dark**.
|
||
|
"""
|
||
|
self.name = name
|
||
|
self.colors = Colors(**colors)
|
||
|
self.type = themetype
|
||
|
|
||
|
def __repr__(self):
|
||
|
|
||
|
return " ".join(
|
||
|
[
|
||
|
f"name={self.name},",
|
||
|
f"type={self.type},",
|
||
|
f"colors={self.colors}",
|
||
|
]
|
||
|
)
|
||
|
|
||
|
|
||
|
class Style(ttk.Style):
|
||
|
"""A singleton class for creating and managing the application
|
||
|
theme and widget styles.
|
||
|
|
||
|
This class is meant to be a drop-in replacement for `ttk.Style` and
|
||
|
inherits all of it's methods and properties. However, in
|
||
|
ttkbootstrap, this class is implemented as a singleton. Subclassing
|
||
|
is not recommended and may have unintended consequences.
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
```python
|
||
|
# instantiate the style with default theme
|
||
|
style = Style()
|
||
|
|
||
|
# instantiate the style with another theme
|
||
|
style = Style(theme='superhero')
|
||
|
|
||
|
# check all available themes
|
||
|
for theme in style.theme_names():
|
||
|
print(theme)
|
||
|
```
|
||
|
|
||
|
See the [Python documentation](https://docs.python.org/3/library/tkinter.ttk.html#tkinter.ttk.Style)
|
||
|
on this class for more details.
|
||
|
"""
|
||
|
|
||
|
instance = None
|
||
|
|
||
|
def __new__(cls, theme=None):
|
||
|
if Style.instance is None:
|
||
|
return object.__new__(cls)
|
||
|
else:
|
||
|
return Style.instance
|
||
|
|
||
|
def __init__(self, theme=DEFAULT_THEME):
|
||
|
"""
|
||
|
Parameters:
|
||
|
|
||
|
theme (str):
|
||
|
The name of the theme to use when styling the widget.
|
||
|
"""
|
||
|
if Style.instance is not None:
|
||
|
if theme != DEFAULT_THEME:
|
||
|
Style.instance.theme_use(theme)
|
||
|
return
|
||
|
self._theme_objects = {}
|
||
|
self._theme_definitions = {}
|
||
|
self._style_registry = set() # all styles used
|
||
|
self._theme_styles = {} # styles used in theme
|
||
|
self._theme_names = set()
|
||
|
self._load_themes()
|
||
|
super().__init__()
|
||
|
|
||
|
Style.instance = self
|
||
|
self.theme_use(theme)
|
||
|
|
||
|
# apply localization
|
||
|
from ttkbootstrap import localization
|
||
|
localization.initialize_localities()
|
||
|
|
||
|
@property
|
||
|
def colors(self):
|
||
|
"""An object that contains the colors used for the current
|
||
|
theme.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Colors:
|
||
|
The colors object for the current theme.
|
||
|
"""
|
||
|
theme = self.theme.name
|
||
|
if theme in list(self._theme_names):
|
||
|
definition = self._theme_definitions.get(theme)
|
||
|
if not definition:
|
||
|
return [] # TODO refactor this
|
||
|
else:
|
||
|
return definition.colors
|
||
|
else:
|
||
|
return [] # TODO refactor this
|
||
|
|
||
|
def configure(self, style, query_opt: Any = None, **kw):
|
||
|
if query_opt:
|
||
|
return super().configure(style, query_opt=query_opt, **kw)
|
||
|
|
||
|
if not self.style_exists_in_theme(style):
|
||
|
ttkstyle = Bootstyle.update_ttk_widget_style(None, style)
|
||
|
else:
|
||
|
ttkstyle = style
|
||
|
|
||
|
if ttkstyle == style:
|
||
|
# configure an existing ttkbootrap theme
|
||
|
return super().configure(style, query_opt=query_opt, **kw)
|
||
|
else:
|
||
|
# subclass a ttkbootstrap theme
|
||
|
result = super().configure(style, query_opt=query_opt, **kw)
|
||
|
self._register_ttkstyle(style)
|
||
|
return result
|
||
|
|
||
|
def theme_names(self):
|
||
|
"""Return a list of all ttkbootstrap themes.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
List[str, ...]:
|
||
|
A list of theme names.
|
||
|
"""
|
||
|
return list(self._theme_definitions.keys())
|
||
|
|
||
|
def register_theme(self, definition):
|
||
|
"""Register a theme definition for use by the `Style`
|
||
|
object. This makes the definition and name available at
|
||
|
run-time so that the assets and styles can be created when
|
||
|
needed.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
definition (ThemeDefinition):
|
||
|
A `ThemeDefinition` object.
|
||
|
"""
|
||
|
theme = definition.name
|
||
|
self._theme_names.add(theme)
|
||
|
self._theme_definitions[theme] = definition
|
||
|
self._theme_styles[theme] = set()
|
||
|
|
||
|
def theme_use(self, themename=None):
|
||
|
"""Changes the theme used in rendering the application widgets.
|
||
|
|
||
|
If themename is None, returns the theme in use, otherwise, set
|
||
|
the current theme to themename, refreshes all widgets and emits
|
||
|
a ``<<ThemeChanged>>`` event.
|
||
|
|
||
|
Only use this method if you are changing the theme *during*
|
||
|
runtime. Otherwise, pass the theme name into the Style
|
||
|
constructor to instantiate the style with a theme.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
themename (str):
|
||
|
The name of the theme to apply when creating new widgets
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Union[str, None]:
|
||
|
The name of the current theme if `themename` is None
|
||
|
otherwise, `None`.
|
||
|
"""
|
||
|
if not themename:
|
||
|
# return current theme
|
||
|
return super().theme_use()
|
||
|
|
||
|
# change to an existing theme
|
||
|
existing_themes = super().theme_names()
|
||
|
if themename in existing_themes:
|
||
|
self.theme = self._theme_definitions.get(themename)
|
||
|
super().theme_use(themename)
|
||
|
self._create_ttk_styles_on_theme_change()
|
||
|
Publisher.publish_message(Channel.STD)
|
||
|
# setup a new theme
|
||
|
elif themename in self._theme_names:
|
||
|
self.theme = self._theme_definitions.get(themename)
|
||
|
self._theme_objects[themename] = StyleBuilderTTK()
|
||
|
self._create_ttk_styles_on_theme_change()
|
||
|
Publisher.publish_message(Channel.STD)
|
||
|
else:
|
||
|
raise TclError(themename, "is not a valid theme.")
|
||
|
|
||
|
def style_exists_in_theme(self, ttkstyle: str):
|
||
|
"""Check if a style exists in the current theme.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
ttkstyle (str):
|
||
|
The ttk style to check.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
bool:
|
||
|
`True` if the style exists, otherwise `False`.
|
||
|
"""
|
||
|
theme_styles = self._theme_styles.get(self.theme.name)
|
||
|
exists_in_theme = ttkstyle in theme_styles
|
||
|
exists_in_registry = ttkstyle in self._style_registry
|
||
|
return exists_in_theme and exists_in_registry
|
||
|
|
||
|
@staticmethod
|
||
|
def get_instance():
|
||
|
"""Returns and instance of the style class"""
|
||
|
return Style.instance
|
||
|
|
||
|
@staticmethod
|
||
|
def _get_builder():
|
||
|
"""Get the object that builds the widget styles for the current
|
||
|
theme.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
ThemeBuilderTTK:
|
||
|
The theme builder object that builds the ttk styles for
|
||
|
the current theme.
|
||
|
"""
|
||
|
style: Style = Style.get_instance()
|
||
|
theme_name = style.theme.name
|
||
|
return style._theme_objects[theme_name]
|
||
|
|
||
|
@staticmethod
|
||
|
def _get_builder_tk():
|
||
|
"""Get the object that builds the widget styles for the current
|
||
|
theme.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
ThemeBuilderTK:
|
||
|
The theme builder object that builds the ttk styles for
|
||
|
the current theme.
|
||
|
"""
|
||
|
builder = Style._get_builder()
|
||
|
return builder.builder_tk
|
||
|
|
||
|
def _build_configure(self, style, **kw):
|
||
|
"""Calls configure of superclass; used by style builder classes."""
|
||
|
super().configure(style, **kw)
|
||
|
|
||
|
def _load_themes(self):
|
||
|
"""Load all ttkbootstrap defined themes"""
|
||
|
# create a theme definition object for each theme, this will be
|
||
|
# used to generate the theme in tkinter along with any assets
|
||
|
# at run-time
|
||
|
if USER_THEMES:
|
||
|
STANDARD_THEMES.update(USER_THEMES)
|
||
|
theme_settings = {"themes": STANDARD_THEMES}
|
||
|
for name, definition in theme_settings["themes"].items():
|
||
|
self.register_theme(
|
||
|
ThemeDefinition(
|
||
|
name=name,
|
||
|
themetype=definition["type"],
|
||
|
colors=definition["colors"],
|
||
|
)
|
||
|
)
|
||
|
|
||
|
def _register_ttkstyle(self, ttkstyle):
|
||
|
"""Register that a ttk style name. This ensures that the
|
||
|
builder will not attempt to build a style that has already
|
||
|
been created.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
ttkstyle (str):
|
||
|
The name of the ttk style to register.
|
||
|
"""
|
||
|
self._style_registry.add(ttkstyle)
|
||
|
theme = self.theme.name
|
||
|
self._theme_styles[theme].add(ttkstyle)
|
||
|
|
||
|
def _create_ttk_styles_on_theme_change(self):
|
||
|
"""Create existing styles when the theme changes"""
|
||
|
for ttkstyle in self._style_registry:
|
||
|
if not self.style_exists_in_theme(ttkstyle):
|
||
|
color = Bootstyle.ttkstyle_widget_color(ttkstyle)
|
||
|
method_name = Bootstyle.ttkstyle_method_name(string=ttkstyle)
|
||
|
builder: StyleBuilderTTK = self._get_builder()
|
||
|
method: Callable = builder.name_to_method(method_name)
|
||
|
method(builder, color)
|
||
|
|
||
|
def load_user_themes(self, file):
|
||
|
"""Load user themes saved in json format"""
|
||
|
with open(file, encoding='utf-8') as f:
|
||
|
data = json.load(f)
|
||
|
themes = data['themes']
|
||
|
for theme in themes:
|
||
|
for name, definition in theme.items():
|
||
|
self.register_theme(
|
||
|
ThemeDefinition(
|
||
|
name=name,
|
||
|
themetype=definition["type"],
|
||
|
colors=definition["colors"],
|
||
|
)
|
||
|
)
|
||
|
|
||
|
|
||
|
class StyleBuilderTK:
|
||
|
"""A class for styling legacy tkinter widgets (not ttk).
|
||
|
|
||
|
The methods in this classed are used internally to update tk widget
|
||
|
style configurations and are not intended to be called by the end
|
||
|
user.
|
||
|
|
||
|
All legacy tkinter widgets are updated with a callback whenever the
|
||
|
theme is changed. The color configuration of the widget is updated
|
||
|
to match the current theme. Legacy ttk widgets are not the primary
|
||
|
focus of this library, however, an attempt was made to make sure they
|
||
|
did not stick out amongst ttk widgets if used.
|
||
|
|
||
|
Some ttk widgets contain legacy components that must be updated
|
||
|
such as the Combobox popdown, so this ensures they are styled
|
||
|
completely to match the current theme.
|
||
|
"""
|
||
|
|
||
|
def __init__(self):
|
||
|
self.style = Style.get_instance()
|
||
|
self.master = self.style.master
|
||
|
|
||
|
@property
|
||
|
def theme(self) -> ThemeDefinition:
|
||
|
"""A reference to the `ThemeDefinition` object for the current
|
||
|
theme."""
|
||
|
return self.style.theme
|
||
|
|
||
|
@property
|
||
|
def colors(self) -> Colors:
|
||
|
"""A reference to the `Colors` object for the current theme."""
|
||
|
return self.style.colors
|
||
|
|
||
|
@property
|
||
|
def is_light_theme(self) -> bool:
|
||
|
"""Returns `True` if the theme is _light_, otherwise `False`."""
|
||
|
return self.style.theme.type == LIGHT
|
||
|
|
||
|
def update_tk_style(self, widget: tk.Tk):
|
||
|
"""Update the window style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Tk):
|
||
|
The tk object to update.
|
||
|
"""
|
||
|
widget.configure(background=self.colors.bg)
|
||
|
# add default initial font for text widget
|
||
|
widget.option_add('*Text*Font', 'TkDefaultFont')
|
||
|
|
||
|
def update_toplevel_style(self, widget: tk.Toplevel):
|
||
|
"""Update the toplevel style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Toplevel):
|
||
|
The toplevel object to update.
|
||
|
"""
|
||
|
widget.configure(background=self.colors.bg)
|
||
|
|
||
|
def update_canvas_style(self, widget: tk.Canvas):
|
||
|
"""Update the canvas style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Canvas):
|
||
|
The canvas object to update.
|
||
|
"""
|
||
|
# if self.is_light_theme:
|
||
|
# bordercolor = self.colors.border
|
||
|
# else:
|
||
|
# bordercolor = self.colors.selectbg
|
||
|
|
||
|
widget.configure(
|
||
|
background=self.colors.bg,
|
||
|
highlightthickness=0,
|
||
|
# highlightbackground=bordercolor,
|
||
|
)
|
||
|
|
||
|
def update_button_style(self, widget: tk.Button):
|
||
|
"""Update the button style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Button):
|
||
|
The button object to update.
|
||
|
"""
|
||
|
background = self.colors.primary
|
||
|
foreground = self.colors.selectfg
|
||
|
activebackground = Colors.update_hsv(self.colors.primary, vd=-0.1)
|
||
|
|
||
|
widget.configure(
|
||
|
background=background,
|
||
|
foreground=foreground,
|
||
|
relief=tk.FLAT,
|
||
|
borderwidth=0,
|
||
|
activebackground=activebackground,
|
||
|
highlightbackground=self.colors.selectfg,
|
||
|
)
|
||
|
|
||
|
def update_label_style(self, widget: tk.Label):
|
||
|
"""Update the label style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Label):
|
||
|
The label object to update.
|
||
|
"""
|
||
|
widget.configure(foreground=self.colors.fg, background=self.colors.bg)
|
||
|
|
||
|
def update_frame_style(self, widget: tk.Frame):
|
||
|
"""Update the frame style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Frame):
|
||
|
The frame object to update.
|
||
|
"""
|
||
|
widget.configure(background=self.colors.bg)
|
||
|
|
||
|
def update_checkbutton_style(self, widget: tk.Checkbutton):
|
||
|
"""Update the checkbutton style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Checkbutton):
|
||
|
The checkbutton object to update.
|
||
|
"""
|
||
|
widget.configure(
|
||
|
activebackground=self.colors.bg,
|
||
|
activeforeground=self.colors.primary,
|
||
|
background=self.colors.bg,
|
||
|
foreground=self.colors.fg,
|
||
|
selectcolor=self.colors.bg,
|
||
|
)
|
||
|
|
||
|
def update_radiobutton_style(self, widget: tk.Radiobutton):
|
||
|
"""Update the radiobutton style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Radiobutton):
|
||
|
The radiobutton object to update.
|
||
|
"""
|
||
|
widget.configure(
|
||
|
activebackground=self.colors.bg,
|
||
|
activeforeground=self.colors.primary,
|
||
|
background=self.colors.bg,
|
||
|
foreground=self.colors.fg,
|
||
|
selectcolor=self.colors.bg,
|
||
|
)
|
||
|
|
||
|
def update_entry_style(self, widget: tk.Entry):
|
||
|
"""Update the entry style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Entry):
|
||
|
The entry object to update.
|
||
|
"""
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
widget.configure(
|
||
|
relief=tk.FLAT,
|
||
|
highlightthickness=1,
|
||
|
foreground=self.colors.inputfg,
|
||
|
highlightbackground=bordercolor,
|
||
|
highlightcolor=self.colors.primary,
|
||
|
background=self.colors.inputbg,
|
||
|
insertbackground=self.colors.inputfg,
|
||
|
insertwidth=1,
|
||
|
)
|
||
|
|
||
|
def update_scale_style(self, widget: tk.Scale):
|
||
|
"""Update the scale style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.scale):
|
||
|
The scale object to update.
|
||
|
"""
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
activecolor = Colors.update_hsv(self.colors.primary, vd=-0.2)
|
||
|
widget.configure(
|
||
|
background=self.colors.primary,
|
||
|
showvalue=False,
|
||
|
sliderrelief=tk.FLAT,
|
||
|
borderwidth=0,
|
||
|
activebackground=activecolor,
|
||
|
highlightthickness=1,
|
||
|
highlightcolor=bordercolor,
|
||
|
highlightbackground=bordercolor,
|
||
|
troughcolor=self.colors.inputbg,
|
||
|
)
|
||
|
|
||
|
def update_spinbox_style(self, widget: tk.Spinbox):
|
||
|
"""Update the spinbox style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Spinbox):
|
||
|
THe spinbox object to update.
|
||
|
"""
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
widget.configure(
|
||
|
relief=tk.FLAT,
|
||
|
highlightthickness=1,
|
||
|
foreground=self.colors.inputfg,
|
||
|
highlightbackground=bordercolor,
|
||
|
highlightcolor=self.colors.primary,
|
||
|
background=self.colors.inputbg,
|
||
|
buttonbackground=self.colors.inputbg,
|
||
|
insertbackground=self.colors.inputfg,
|
||
|
insertwidth=1,
|
||
|
# these options should work, but do not have any affect
|
||
|
buttonuprelief=tk.FLAT,
|
||
|
buttondownrelief=tk.SUNKEN,
|
||
|
)
|
||
|
|
||
|
def update_listbox_style(self, widget: tk.Listbox):
|
||
|
"""Update the listbox style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Listbox):
|
||
|
The listbox object to update.
|
||
|
"""
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
widget.configure(
|
||
|
foreground=self.colors.inputfg,
|
||
|
background=self.colors.inputbg,
|
||
|
selectbackground=self.colors.selectbg,
|
||
|
selectforeground=self.colors.selectfg,
|
||
|
highlightcolor=self.colors.primary,
|
||
|
highlightbackground=bordercolor,
|
||
|
highlightthickness=1,
|
||
|
activestyle="none",
|
||
|
relief=tk.FLAT,
|
||
|
)
|
||
|
|
||
|
def update_menubutton_style(self, widget: tk.Menubutton):
|
||
|
"""Update the menubutton style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Menubutton):
|
||
|
The menubutton object to update.
|
||
|
"""
|
||
|
activebackground = Colors.update_hsv(self.colors.primary, vd=-0.2)
|
||
|
widget.configure(
|
||
|
background=self.colors.primary,
|
||
|
foreground=self.colors.selectfg,
|
||
|
activebackground=activebackground,
|
||
|
activeforeground=self.colors.selectfg,
|
||
|
borderwidth=0,
|
||
|
)
|
||
|
|
||
|
def update_menu_style(self, widget: tk.Menu):
|
||
|
"""Update the menu style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Menu):
|
||
|
The menu object to update.
|
||
|
"""
|
||
|
widget.configure(
|
||
|
tearoff=False,
|
||
|
activebackground=self.colors.selectbg,
|
||
|
activeforeground=self.colors.selectfg,
|
||
|
foreground=self.colors.fg,
|
||
|
selectcolor=self.colors.primary,
|
||
|
background=self.colors.bg,
|
||
|
relief=tk.FLAT,
|
||
|
borderwidth=0,
|
||
|
)
|
||
|
|
||
|
def update_labelframe_style(self, widget: tk.LabelFrame):
|
||
|
"""Update the labelframe style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.LabelFrame):
|
||
|
The labelframe object to update.
|
||
|
"""
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
widget.configure(
|
||
|
highlightcolor=bordercolor,
|
||
|
foreground=self.colors.fg,
|
||
|
borderwidth=1,
|
||
|
highlightthickness=0,
|
||
|
background=self.colors.bg,
|
||
|
)
|
||
|
|
||
|
def update_text_style(self, widget: tk.Text):
|
||
|
"""Update the text style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (tkinter.Text):
|
||
|
The text object to update.
|
||
|
"""
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
focuscolor = widget.cget("highlightbackground")
|
||
|
|
||
|
if focuscolor in ["SystemButtonFace", bordercolor]:
|
||
|
focuscolor = bordercolor
|
||
|
|
||
|
widget.configure(
|
||
|
background=self.colors.inputbg,
|
||
|
foreground=self.colors.inputfg,
|
||
|
highlightcolor=focuscolor,
|
||
|
highlightbackground=bordercolor,
|
||
|
insertbackground=self.colors.inputfg,
|
||
|
selectbackground=self.colors.selectbg,
|
||
|
selectforeground=self.colors.selectfg,
|
||
|
insertwidth=1,
|
||
|
highlightthickness=1,
|
||
|
relief=tk.FLAT,
|
||
|
padx=5,
|
||
|
pady=5,
|
||
|
#font="TkDefaultFont",
|
||
|
)
|
||
|
|
||
|
|
||
|
class StyleBuilderTTK:
|
||
|
"""A class containing methods for building new ttk widget styles on
|
||
|
demand.
|
||
|
|
||
|
The methods in this classed are used internally to generate ttk
|
||
|
widget styles on-demand and are not intended to be called by the end
|
||
|
user.
|
||
|
"""
|
||
|
|
||
|
def __init__(self):
|
||
|
self.style: Style = Style.get_instance()
|
||
|
self.theme_images = {}
|
||
|
self.builder_tk = StyleBuilderTK()
|
||
|
self.create_theme()
|
||
|
|
||
|
@staticmethod
|
||
|
def name_to_method(method_name):
|
||
|
"""Get a method by name.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
method_name (str):
|
||
|
The name of the style builder method.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Callable:
|
||
|
The method that is named by `method_name`
|
||
|
"""
|
||
|
func = getattr(StyleBuilderTTK, method_name)
|
||
|
return func
|
||
|
|
||
|
@property
|
||
|
def colors(self) -> Colors:
|
||
|
"""A reference to the `Colors` object of the current theme."""
|
||
|
return self.style.theme.colors
|
||
|
|
||
|
@property
|
||
|
def theme(self) -> ThemeDefinition:
|
||
|
"""A reference to the `ThemeDefinition` object for the current
|
||
|
theme."""
|
||
|
return self.style.theme
|
||
|
|
||
|
@property
|
||
|
def is_light_theme(self) -> bool:
|
||
|
"""If the current theme is _light_, returns `True`, otherwise
|
||
|
returns `False`."""
|
||
|
return self.style.theme.type == LIGHT
|
||
|
|
||
|
def scale_size(self, size):
|
||
|
"""Scale the size of images and other assets based on the
|
||
|
scaling factor of ttk to ensure that the image matches the
|
||
|
screen resolution.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
size (Union[int, List, Tuple]):
|
||
|
A single integer or an iterable of integers
|
||
|
"""
|
||
|
winsys = self.style.master.tk.call("tk", "windowingsystem")
|
||
|
if winsys == "aqua":
|
||
|
BASELINE = 1.000492368291482
|
||
|
else:
|
||
|
BASELINE = 1.33398982438864281
|
||
|
scaling = self.style.master.tk.call("tk", "scaling")
|
||
|
factor = scaling / BASELINE
|
||
|
|
||
|
if isinstance(size, int) or isinstance(size, float):
|
||
|
return ceil(size * factor)
|
||
|
elif isinstance(size, tuple) or isinstance(size, list):
|
||
|
return [ceil(x * factor) for x in size]
|
||
|
|
||
|
def create_theme(self):
|
||
|
"""Create and style a new ttk theme. A wrapper around internal
|
||
|
style methods.
|
||
|
"""
|
||
|
self.style.theme_create(self.theme.name, TTK_CLAM)
|
||
|
ttk.Style.theme_use(self.style, self.theme.name)
|
||
|
self.update_ttk_theme_settings()
|
||
|
|
||
|
def update_ttk_theme_settings(self):
|
||
|
"""This method is called internally every time the theme is
|
||
|
changed to update various components included in the body of
|
||
|
the method."""
|
||
|
self.create_default_style()
|
||
|
|
||
|
def create_default_style(self):
|
||
|
"""Setup the default widget style configuration for the root
|
||
|
ttk style "."; these defaults are applied to any widget that
|
||
|
contains the configuration options updated by this style. This
|
||
|
method should be called *first* before any other style is applied
|
||
|
during theme creation.
|
||
|
"""
|
||
|
self.style._build_configure(
|
||
|
style=".",
|
||
|
background=self.colors.bg,
|
||
|
darkcolor=self.colors.border,
|
||
|
foreground=self.colors.fg,
|
||
|
troughcolor=self.colors.bg,
|
||
|
selectbg=self.colors.selectbg,
|
||
|
selectfg=self.colors.selectfg,
|
||
|
selectforeground=self.colors.selectfg,
|
||
|
selectbackground=self.colors.selectbg,
|
||
|
fieldbg="white",
|
||
|
borderwidth=1,
|
||
|
focuscolor="",
|
||
|
)
|
||
|
# this is general style applied to the tableview
|
||
|
self.create_link_button_style()
|
||
|
self.style.configure("symbol.Link.TButton", font="-size 16")
|
||
|
|
||
|
def create_combobox_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Combobox widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label to use as the primary widget color.
|
||
|
"""
|
||
|
STYLE = "TCombobox"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
disabled_fg = self.colors.border
|
||
|
bordercolor = self.colors.border
|
||
|
readonly = self.colors.light
|
||
|
else:
|
||
|
disabled_fg = self.colors.selectbg
|
||
|
bordercolor = self.colors.selectbg
|
||
|
readonly = bordercolor
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
element = f"{ttkstyle.replace('TC','C')}"
|
||
|
focuscolor = self.colors.primary
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
element = f"{ttkstyle.replace('TC','C')}"
|
||
|
focuscolor = self.colors.get(colorname)
|
||
|
|
||
|
self.style.element_create(f"{element}.downarrow", "from", TTK_DEFAULT)
|
||
|
self.style.element_create(f"{element}.padding", "from", TTK_CLAM)
|
||
|
self.style.element_create(f"{element}.textarea", "from", TTK_CLAM)
|
||
|
|
||
|
if all([colorname, colorname != DEFAULT]):
|
||
|
bordercolor = focuscolor
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
bordercolor=bordercolor,
|
||
|
darkcolor=self.colors.inputbg,
|
||
|
lightcolor=self.colors.inputbg,
|
||
|
arrowcolor=self.colors.inputfg,
|
||
|
foreground=self.colors.inputfg,
|
||
|
fieldbackground=self.colors.inputbg,
|
||
|
background=self.colors.inputbg,
|
||
|
insertcolor=self.colors.inputfg,
|
||
|
relief=tk.FLAT,
|
||
|
padding=5,
|
||
|
arrowsize=self.scale_size(12),
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
background=[("readonly", readonly)],
|
||
|
fieldbackground=[("readonly", readonly)],
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
bordercolor=[
|
||
|
("invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("hover !disabled", focuscolor),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("focus invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("pressed !disabled", focuscolor),
|
||
|
("readonly", readonly),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("focus invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("pressed !disabled", focuscolor),
|
||
|
("readonly", readonly),
|
||
|
],
|
||
|
arrowcolor=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", focuscolor),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("hover !disabled", focuscolor),
|
||
|
],
|
||
|
)
|
||
|
self.style.layout(
|
||
|
ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"combo.Spinbox.field",
|
||
|
{
|
||
|
"side": tk.TOP,
|
||
|
"sticky": tk.EW,
|
||
|
"children": [
|
||
|
(
|
||
|
"Combobox.downarrow",
|
||
|
{"side": tk.RIGHT, "sticky": tk.NS},
|
||
|
),
|
||
|
(
|
||
|
"Combobox.padding",
|
||
|
{
|
||
|
"expand": "1",
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
"Combobox.textarea",
|
||
|
{"sticky": tk.NSEW},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_separator_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Separator widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The primary widget color.
|
||
|
"""
|
||
|
HSTYLE = "Horizontal.TSeparator"
|
||
|
VSTYLE = "Vertical.TSeparator"
|
||
|
|
||
|
hsize = [40, 1]
|
||
|
vsize = [1, 40]
|
||
|
|
||
|
# style colors
|
||
|
if self.is_light_theme:
|
||
|
default_color = self.colors.border
|
||
|
else:
|
||
|
default_color = self.colors.selectbg
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
background = default_color
|
||
|
h_ttkstyle = HSTYLE
|
||
|
v_ttkstyle = VSTYLE
|
||
|
else:
|
||
|
background = self.colors.get(colorname)
|
||
|
h_ttkstyle = f"{colorname}.{HSTYLE}"
|
||
|
v_ttkstyle = f"{colorname}.{VSTYLE}"
|
||
|
|
||
|
# horizontal separator
|
||
|
h_element = h_ttkstyle.replace(".TS", ".S")
|
||
|
h_img = ImageTk.PhotoImage(Image.new("RGB", hsize, background))
|
||
|
h_name = util.get_image_name(h_img)
|
||
|
self.theme_images[h_name] = h_img
|
||
|
|
||
|
self.style.element_create(f"{h_element}.separator", "image", h_name)
|
||
|
self.style.layout(
|
||
|
h_ttkstyle, [(f"{h_element}.separator", {"sticky": tk.EW})]
|
||
|
)
|
||
|
|
||
|
# vertical separator
|
||
|
v_element = v_ttkstyle.replace(".TS", ".S")
|
||
|
v_img = ImageTk.PhotoImage(Image.new("RGB", vsize, background))
|
||
|
v_name = util.get_image_name(v_img)
|
||
|
self.theme_images[v_name] = v_img
|
||
|
self.style.element_create(f"{v_element}.separator", "image", v_name)
|
||
|
self.style.layout(
|
||
|
v_ttkstyle, [(f"{v_element}.separator", {"sticky": tk.NS})]
|
||
|
)
|
||
|
self.style._register_ttkstyle(h_ttkstyle)
|
||
|
self.style._register_ttkstyle(v_ttkstyle)
|
||
|
|
||
|
def create_striped_progressbar_assets(self, thickness, colorname=DEFAULT):
|
||
|
"""Create the striped progressbar image and return as a
|
||
|
`PhotoImage`
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Tuple[str]:
|
||
|
A list of photoimage names.
|
||
|
"""
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
barcolor = self.colors.primary
|
||
|
else:
|
||
|
barcolor = self.colors.get(colorname)
|
||
|
|
||
|
# calculate value of the light color
|
||
|
brightness = Colors.rgb_to_hsv(*Colors.hex_to_rgb(barcolor))[2]
|
||
|
if brightness < 0.4:
|
||
|
value_delta = 0.3
|
||
|
elif brightness > 0.8:
|
||
|
value_delta = 0
|
||
|
else:
|
||
|
value_delta = 0.1
|
||
|
|
||
|
barcolor_light = Colors.update_hsv(barcolor, sd=-0.2, vd=value_delta)
|
||
|
|
||
|
# horizontal progressbar
|
||
|
img = Image.new("RGBA", (100, 100), barcolor_light)
|
||
|
draw = ImageDraw.Draw(img)
|
||
|
draw.polygon(
|
||
|
xy=[(0, 0), (48, 0), (100, 52), (100, 100)],
|
||
|
fill=barcolor,
|
||
|
)
|
||
|
draw.polygon(xy=[(0, 52), (48, 100), (0, 100)], fill=barcolor)
|
||
|
|
||
|
_resized = img.resize((thickness, thickness), Image.LANCZOS)
|
||
|
h_img = ImageTk.PhotoImage(_resized)
|
||
|
h_name = h_img._PhotoImage__photo.name
|
||
|
v_img = ImageTk.PhotoImage(_resized.rotate(90))
|
||
|
v_name = v_img._PhotoImage__photo.name
|
||
|
|
||
|
self.theme_images[h_name] = h_img
|
||
|
self.theme_images[v_name] = v_img
|
||
|
return h_name, v_name
|
||
|
|
||
|
def create_striped_progressbar_style(self, colorname=DEFAULT):
|
||
|
"""Create a striped style for the ttk.Progressbar widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The primary widget color label.
|
||
|
"""
|
||
|
HSTYLE = "Striped.Horizontal.TProgressbar"
|
||
|
VSTYLE = "Striped.Vertical.TProgressbar"
|
||
|
|
||
|
thickness = self.scale_size(12)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
h_ttkstyle = HSTYLE
|
||
|
v_ttkstyle = VSTYLE
|
||
|
else:
|
||
|
h_ttkstyle = f"{colorname}.{HSTYLE}"
|
||
|
v_ttkstyle = f"{colorname}.{VSTYLE}"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
if colorname == LIGHT:
|
||
|
troughcolor = self.colors.bg
|
||
|
bordercolor = self.colors.light
|
||
|
else:
|
||
|
troughcolor = self.colors.light
|
||
|
bordercolor = troughcolor
|
||
|
else:
|
||
|
troughcolor = Colors.update_hsv(self.colors.selectbg, vd=-0.2)
|
||
|
bordercolor = troughcolor
|
||
|
|
||
|
# ( horizontal, vertical )
|
||
|
images = self.create_striped_progressbar_assets(thickness, colorname)
|
||
|
|
||
|
# horizontal progressbar
|
||
|
h_element = h_ttkstyle.replace(".TP", ".P")
|
||
|
self.style.element_create(
|
||
|
f"{h_element}.pbar",
|
||
|
"image",
|
||
|
images[0],
|
||
|
width=thickness,
|
||
|
sticky=tk.EW,
|
||
|
)
|
||
|
self.style.layout(
|
||
|
h_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
f"{h_element}.trough",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
f"{h_element}.pbar",
|
||
|
{"side": tk.LEFT, "sticky": tk.NS},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
h_ttkstyle,
|
||
|
troughcolor=troughcolor,
|
||
|
thickness=thickness,
|
||
|
bordercolor=bordercolor,
|
||
|
borderwidth=1,
|
||
|
)
|
||
|
|
||
|
# vertical progressbar
|
||
|
v_element = v_ttkstyle.replace(".TP", ".P")
|
||
|
self.style.element_create(
|
||
|
f"{v_element}.pbar",
|
||
|
"image",
|
||
|
images[1],
|
||
|
width=thickness,
|
||
|
sticky=tk.NS,
|
||
|
)
|
||
|
self.style.layout(
|
||
|
v_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
f"{v_element}.trough",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
f"{v_element}.pbar",
|
||
|
{"side": tk.BOTTOM, "sticky": tk.EW},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
v_ttkstyle,
|
||
|
troughcolor=troughcolor,
|
||
|
bordercolor=bordercolor,
|
||
|
thickness=thickness,
|
||
|
borderwidth=1,
|
||
|
)
|
||
|
self.style._register_ttkstyle(h_ttkstyle)
|
||
|
self.style._register_ttkstyle(v_ttkstyle)
|
||
|
|
||
|
def create_progressbar_style(self, colorname=DEFAULT):
|
||
|
"""Create a solid ttk style for the ttk.Progressbar widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The primary widget color.
|
||
|
"""
|
||
|
H_STYLE = "Horizontal.TProgressbar"
|
||
|
V_STYLE = "Vertical.TProgressbar"
|
||
|
|
||
|
thickness = self.scale_size(10)
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
if colorname == LIGHT:
|
||
|
troughcolor = self.colors.bg
|
||
|
bordercolor = self.colors.light
|
||
|
else:
|
||
|
troughcolor = self.colors.light
|
||
|
bordercolor = troughcolor
|
||
|
else:
|
||
|
troughcolor = Colors.update_hsv(self.colors.selectbg, vd=-0.2)
|
||
|
bordercolor = troughcolor
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
background = self.colors.primary
|
||
|
h_ttkstyle = H_STYLE
|
||
|
v_ttkstyle = V_STYLE
|
||
|
else:
|
||
|
background = self.colors.get(colorname)
|
||
|
h_ttkstyle = f"{colorname}.{H_STYLE}"
|
||
|
v_ttkstyle = f"{colorname}.{V_STYLE}"
|
||
|
|
||
|
self.style._build_configure(
|
||
|
h_ttkstyle,
|
||
|
thickness=thickness,
|
||
|
borderwidth=1,
|
||
|
bordercolor=bordercolor,
|
||
|
lightcolor=self.colors.border,
|
||
|
pbarrelief=tk.FLAT,
|
||
|
troughcolor=troughcolor,
|
||
|
)
|
||
|
existing_elements = self.style.element_names()
|
||
|
|
||
|
self.style._build_configure(
|
||
|
v_ttkstyle,
|
||
|
thickness=thickness,
|
||
|
borderwidth=1,
|
||
|
bordercolor=bordercolor,
|
||
|
lightcolor=self.colors.border,
|
||
|
pbarrelief=tk.FLAT,
|
||
|
troughcolor=troughcolor,
|
||
|
)
|
||
|
existing_elements = self.style.element_names()
|
||
|
|
||
|
# horizontal progressbar
|
||
|
h_element = h_ttkstyle.replace(".TP", ".P")
|
||
|
trough_element = f"{h_element}.trough"
|
||
|
pbar_element = f"{h_element}.pbar"
|
||
|
if trough_element not in existing_elements:
|
||
|
self.style.element_create(trough_element, "from", TTK_CLAM)
|
||
|
self.style.element_create(pbar_element, "from", TTK_DEFAULT)
|
||
|
|
||
|
self.style.layout(
|
||
|
h_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
trough_element,
|
||
|
{
|
||
|
"sticky": "nswe",
|
||
|
"children": [
|
||
|
(pbar_element, {"side": "left", "sticky": "ns"})
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(h_ttkstyle, background=background)
|
||
|
|
||
|
# vertical progressbar
|
||
|
v_element = v_ttkstyle.replace(".TP", ".P")
|
||
|
trough_element = f"{v_element}.trough"
|
||
|
pbar_element = f"{v_element}.pbar"
|
||
|
if trough_element not in existing_elements:
|
||
|
self.style.element_create(trough_element, "from", TTK_CLAM)
|
||
|
self.style.element_create(pbar_element, "from", TTK_DEFAULT)
|
||
|
self.style._build_configure(v_ttkstyle, background=background)
|
||
|
self.style.layout(
|
||
|
v_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
trough_element,
|
||
|
{
|
||
|
"sticky": "nswe",
|
||
|
"children": [
|
||
|
(pbar_element, {"side": "bottom", "sticky": "we"})
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
|
||
|
# register ttkstyles
|
||
|
self.style._register_ttkstyle(h_ttkstyle)
|
||
|
self.style._register_ttkstyle(v_ttkstyle)
|
||
|
|
||
|
def create_scale_assets(self, colorname=DEFAULT, size=14):
|
||
|
"""Create the assets used for the ttk.Scale widget.
|
||
|
|
||
|
The slider handle is automatically adjusted to fit the
|
||
|
screen resolution.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label.
|
||
|
|
||
|
size (int):
|
||
|
The size diameter of the slider circle; default=16.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Tuple[str]:
|
||
|
A tuple of PhotoImage names to be used in the image
|
||
|
layout when building the style.
|
||
|
"""
|
||
|
size = self.scale_size(size)
|
||
|
if self.is_light_theme:
|
||
|
disabled_color = self.colors.border
|
||
|
if colorname == LIGHT:
|
||
|
track_color = self.colors.bg
|
||
|
else:
|
||
|
track_color = self.colors.light
|
||
|
else:
|
||
|
disabled_color = self.colors.selectbg
|
||
|
track_color = Colors.update_hsv(self.colors.selectbg, vd=-0.2)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
normal_color = self.colors.primary
|
||
|
else:
|
||
|
normal_color = self.colors.get(colorname)
|
||
|
|
||
|
pressed_color = Colors.update_hsv(normal_color, vd=-0.1)
|
||
|
hover_color = Colors.update_hsv(normal_color, vd=0.1)
|
||
|
|
||
|
# normal state
|
||
|
_normal = Image.new("RGBA", (100, 100))
|
||
|
draw = ImageDraw.Draw(_normal)
|
||
|
draw.ellipse((0, 0, 95, 95), fill=normal_color)
|
||
|
normal_img = ImageTk.PhotoImage(
|
||
|
_normal.resize((size, size), Image.LANCZOS)
|
||
|
)
|
||
|
normal_name = util.get_image_name(normal_img)
|
||
|
self.theme_images[normal_name] = normal_img
|
||
|
|
||
|
# pressed state
|
||
|
_pressed = Image.new("RGBA", (100, 100))
|
||
|
draw = ImageDraw.Draw(_pressed)
|
||
|
draw.ellipse((0, 0, 95, 95), fill=pressed_color)
|
||
|
pressed_img = ImageTk.PhotoImage(
|
||
|
_pressed.resize((size, size), Image.LANCZOS)
|
||
|
)
|
||
|
pressed_name = util.get_image_name(pressed_img)
|
||
|
self.theme_images[pressed_name] = pressed_img
|
||
|
|
||
|
# hover state
|
||
|
_hover = Image.new("RGBA", (100, 100))
|
||
|
draw = ImageDraw.Draw(_hover)
|
||
|
draw.ellipse((0, 0, 95, 95), fill=hover_color)
|
||
|
hover_img = ImageTk.PhotoImage(
|
||
|
_hover.resize((size, size), Image.LANCZOS)
|
||
|
)
|
||
|
hover_name = util.get_image_name(hover_img)
|
||
|
self.theme_images[hover_name] = hover_img
|
||
|
|
||
|
# disabled state
|
||
|
_disabled = Image.new("RGBA", (100, 100))
|
||
|
draw = ImageDraw.Draw(_disabled)
|
||
|
draw.ellipse((0, 0, 95, 95), fill=disabled_color)
|
||
|
disabled_img = ImageTk.PhotoImage(
|
||
|
_disabled.resize((size, size), Image.LANCZOS)
|
||
|
)
|
||
|
disabled_name = util.get_image_name(disabled_img)
|
||
|
self.theme_images[disabled_name] = disabled_img
|
||
|
|
||
|
# vertical track
|
||
|
h_track_img = ImageTk.PhotoImage(
|
||
|
Image.new("RGB", self.scale_size((40, 5)), track_color)
|
||
|
)
|
||
|
h_track_name = util.get_image_name(h_track_img)
|
||
|
self.theme_images[h_track_name] = h_track_img
|
||
|
|
||
|
# horizontal track
|
||
|
v_track_img = ImageTk.PhotoImage(
|
||
|
Image.new("RGB", self.scale_size((5, 40)), track_color)
|
||
|
)
|
||
|
v_track_name = util.get_image_name(v_track_img)
|
||
|
self.theme_images[v_track_name] = v_track_img
|
||
|
|
||
|
return (
|
||
|
normal_name,
|
||
|
pressed_name,
|
||
|
hover_name,
|
||
|
disabled_name,
|
||
|
h_track_name,
|
||
|
v_track_name,
|
||
|
)
|
||
|
|
||
|
def create_scale_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Scale widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TScale"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
h_ttkstyle = f"Horizontal.{STYLE}"
|
||
|
v_ttkstyle = f"Vertical.{STYLE}"
|
||
|
else:
|
||
|
h_ttkstyle = f"{colorname}.Horizontal.{STYLE}"
|
||
|
v_ttkstyle = f"{colorname}.Vertical.{STYLE}"
|
||
|
|
||
|
# ( normal, pressed, hover, disabled, htrack, vtrack )
|
||
|
images = self.create_scale_assets(colorname)
|
||
|
|
||
|
# horizontal scale
|
||
|
h_element = h_ttkstyle.replace(".TS", ".S")
|
||
|
self.style.element_create(
|
||
|
f"{h_element}.slider",
|
||
|
"image",
|
||
|
images[0],
|
||
|
("disabled", images[3]),
|
||
|
("pressed", images[1]),
|
||
|
("hover", images[2]),
|
||
|
)
|
||
|
self.style.element_create(f"{h_element}.track", "image", images[4])
|
||
|
self.style.layout(
|
||
|
h_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
f"{h_element}.focus",
|
||
|
{
|
||
|
"expand": "1",
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(f"{h_element}.track", {"sticky": tk.EW}),
|
||
|
(
|
||
|
f"{h_element}.slider",
|
||
|
{"side": tk.LEFT, "sticky": ""},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
# vertical scale
|
||
|
v_element = v_ttkstyle.replace(".TS", ".S")
|
||
|
self.style.element_create(
|
||
|
f"{v_element}.slider",
|
||
|
"image",
|
||
|
images[0],
|
||
|
("disabled", images[3]),
|
||
|
("pressed", images[1]),
|
||
|
("hover", images[2]),
|
||
|
)
|
||
|
self.style.element_create(f"{v_element}.track", "image", images[5])
|
||
|
self.style.layout(
|
||
|
v_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
f"{v_element}.focus",
|
||
|
{
|
||
|
"expand": "1",
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(f"{v_element}.track", {"sticky": tk.NS}),
|
||
|
(
|
||
|
f"{v_element}.slider",
|
||
|
{"side": tk.TOP, "sticky": ""},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyles
|
||
|
self.style._register_ttkstyle(h_ttkstyle)
|
||
|
self.style._register_ttkstyle(v_ttkstyle)
|
||
|
|
||
|
def create_floodgauge_style(self, colorname=DEFAULT):
|
||
|
"""Create a ttk style for the ttkbootstrap.widgets.Floodgauge
|
||
|
widget. This is a custom widget style that uses components of
|
||
|
the progressbar and label.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
HSTYLE = "Horizontal.TFloodgauge"
|
||
|
VSTYLE = "Vertical.TFloodgauge"
|
||
|
FLOOD_FONT = "-size 14"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
h_ttkstyle = HSTYLE
|
||
|
v_ttkstyle = VSTYLE
|
||
|
background = self.colors.primary
|
||
|
else:
|
||
|
h_ttkstyle = f"{colorname}.{HSTYLE}"
|
||
|
v_ttkstyle = f"{colorname}.{VSTYLE}"
|
||
|
background = self.colors.get(colorname)
|
||
|
|
||
|
if colorname == LIGHT:
|
||
|
foreground = self.colors.fg
|
||
|
troughcolor = self.colors.bg
|
||
|
else:
|
||
|
troughcolor = Colors.update_hsv(background, sd=-0.3, vd=0.8)
|
||
|
foreground = self.colors.selectfg
|
||
|
|
||
|
# horizontal floodgauge
|
||
|
h_element = h_ttkstyle.replace(".TF", ".F")
|
||
|
self.style.element_create(f"{h_element}.trough", "from", TTK_CLAM)
|
||
|
self.style.element_create(f"{h_element}.pbar", "from", TTK_DEFAULT)
|
||
|
self.style.layout(
|
||
|
h_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
f"{h_element}.trough",
|
||
|
{
|
||
|
"children": [
|
||
|
(f"{h_element}.pbar", {"sticky": tk.NS}),
|
||
|
("Floodgauge.label", {"sticky": ""}),
|
||
|
],
|
||
|
"sticky": tk.NSEW,
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
h_ttkstyle,
|
||
|
thickness=50,
|
||
|
borderwidth=1,
|
||
|
bordercolor=background,
|
||
|
lightcolor=background,
|
||
|
pbarrelief=tk.FLAT,
|
||
|
troughcolor=troughcolor,
|
||
|
background=background,
|
||
|
foreground=foreground,
|
||
|
justify=tk.CENTER,
|
||
|
anchor=tk.CENTER,
|
||
|
font=FLOOD_FONT,
|
||
|
)
|
||
|
# vertical floodgauge
|
||
|
v_element = v_ttkstyle.replace(".TF", ".F")
|
||
|
self.style.element_create(f"{v_element}.trough", "from", TTK_CLAM)
|
||
|
self.style.element_create(f"{v_element}.pbar", "from", TTK_DEFAULT)
|
||
|
self.style.layout(
|
||
|
v_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
f"{v_element}.trough",
|
||
|
{
|
||
|
"children": [
|
||
|
(f"{v_element}.pbar", {"sticky": tk.EW}),
|
||
|
("Floodgauge.label", {"sticky": ""}),
|
||
|
],
|
||
|
"sticky": tk.NSEW,
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
v_ttkstyle,
|
||
|
thickness=50,
|
||
|
borderwidth=1,
|
||
|
bordercolor=background,
|
||
|
lightcolor=background,
|
||
|
pbarrelief=tk.FLAT,
|
||
|
troughcolor=troughcolor,
|
||
|
background=background,
|
||
|
foreground=foreground,
|
||
|
justify=tk.CENTER,
|
||
|
anchor=tk.CENTER,
|
||
|
font=FLOOD_FONT,
|
||
|
)
|
||
|
# register ttkstyles
|
||
|
self.style._register_ttkstyle(h_ttkstyle)
|
||
|
self.style._register_ttkstyle(v_ttkstyle)
|
||
|
|
||
|
def create_arrow_assets(self, arrowcolor, pressed, active):
|
||
|
"""Create arrow assets used for various widget buttons.
|
||
|
|
||
|
!!! note
|
||
|
This method is currently not being utilized.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
arrowcolor (str):
|
||
|
The color value to use as the arrow fill color.
|
||
|
|
||
|
pressed (str):
|
||
|
The color value to use when the arrow is pressed.
|
||
|
|
||
|
active (str):
|
||
|
The color value to use when the arrow is active or
|
||
|
hovered.
|
||
|
"""
|
||
|
|
||
|
def draw_arrow(color: str):
|
||
|
|
||
|
img = Image.new("RGBA", (11, 11))
|
||
|
draw = ImageDraw.Draw(img)
|
||
|
size = self.scale_size([11, 11])
|
||
|
|
||
|
draw.line([2, 6, 2, 9], fill=color)
|
||
|
draw.line([3, 5, 3, 8], fill=color)
|
||
|
draw.line([4, 4, 4, 7], fill=color)
|
||
|
draw.line([5, 3, 5, 6], fill=color)
|
||
|
draw.line([6, 4, 6, 7], fill=color)
|
||
|
draw.line([7, 5, 7, 8], fill=color)
|
||
|
draw.line([8, 6, 8, 9], fill=color)
|
||
|
|
||
|
img = img.resize(size, Image.BICUBIC)
|
||
|
|
||
|
up_img = ImageTk.PhotoImage(img)
|
||
|
up_name = util.get_image_name(up_img)
|
||
|
self.theme_images[up_name] = up_img
|
||
|
|
||
|
down_img = ImageTk.PhotoImage(img.rotate(180))
|
||
|
down_name = util.get_image_name(down_img)
|
||
|
self.theme_images[down_name] = down_img
|
||
|
|
||
|
left_img = ImageTk.PhotoImage(img.rotate(90))
|
||
|
left_name = util.get_image_name(left_img)
|
||
|
self.theme_images[left_name] = left_img
|
||
|
|
||
|
right_img = ImageTk.PhotoImage(img.rotate(-90))
|
||
|
right_name = util.get_image_name(right_img)
|
||
|
self.theme_images[right_name] = right_img
|
||
|
|
||
|
return up_name, down_name, left_name, right_name
|
||
|
|
||
|
normal_names = draw_arrow(arrowcolor)
|
||
|
pressed_names = draw_arrow(pressed)
|
||
|
active_names = draw_arrow(active)
|
||
|
|
||
|
return normal_names, pressed_names, active_names
|
||
|
|
||
|
def create_round_scrollbar_assets(self, thumbcolor, pressed, active):
|
||
|
"""Create image assets to be used when building the round
|
||
|
scrollbar style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
thumbcolor (str):
|
||
|
The color value of the thumb in normal state.
|
||
|
|
||
|
pressed (str):
|
||
|
The color value to use when the thumb is pressed.
|
||
|
|
||
|
active (str):
|
||
|
The color value to use when the thumb is active or
|
||
|
hovered.
|
||
|
"""
|
||
|
vsize = self.scale_size([9, 28])
|
||
|
hsize = self.scale_size([28, 9])
|
||
|
|
||
|
def rounded_rect(size, fill):
|
||
|
x = size[0] * 10
|
||
|
y = size[1] * 10
|
||
|
img = Image.new("RGBA", (x, y))
|
||
|
draw = ImageDraw.Draw(img)
|
||
|
radius = min([x, y]) // 2
|
||
|
draw.rounded_rectangle([0, 0, x - 1, y - 1], radius, fill)
|
||
|
image = ImageTk.PhotoImage(img.resize(size, Image.BICUBIC))
|
||
|
name = util.get_image_name(image)
|
||
|
self.theme_images[name] = image
|
||
|
return name
|
||
|
|
||
|
# create images
|
||
|
h_normal_img = rounded_rect(hsize, thumbcolor)
|
||
|
h_pressed_img = rounded_rect(hsize, pressed)
|
||
|
h_active_img = rounded_rect(hsize, active)
|
||
|
|
||
|
v_normal_img = rounded_rect(vsize, thumbcolor)
|
||
|
v_pressed_img = rounded_rect(vsize, pressed)
|
||
|
v_active_img = rounded_rect(vsize, active)
|
||
|
|
||
|
return (
|
||
|
h_normal_img,
|
||
|
h_pressed_img,
|
||
|
h_active_img,
|
||
|
v_normal_img,
|
||
|
v_pressed_img,
|
||
|
v_active_img,
|
||
|
)
|
||
|
|
||
|
def create_round_scrollbar_style(self, colorname=DEFAULT):
|
||
|
"""Create a round style for the ttk.Scrollbar widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TScrollbar"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
h_ttkstyle = f"Round.Horizontal.{STYLE}"
|
||
|
v_ttkstyle = f"Round.Vertical.{STYLE}"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
background = self.colors.border
|
||
|
else:
|
||
|
background = self.colors.selectbg
|
||
|
|
||
|
else:
|
||
|
h_ttkstyle = f"{colorname}.Round.Horizontal.{STYLE}"
|
||
|
v_ttkstyle = f"{colorname}.Round.Vertical.{STYLE}"
|
||
|
background = self.colors.get(colorname)
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
if colorname == LIGHT:
|
||
|
troughcolor = self.colors.bg
|
||
|
else:
|
||
|
troughcolor = self.colors.light
|
||
|
else:
|
||
|
troughcolor = Colors.update_hsv(self.colors.selectbg, vd=-0.2)
|
||
|
|
||
|
pressed = Colors.update_hsv(background, vd=-0.05)
|
||
|
active = Colors.update_hsv(background, vd=0.05)
|
||
|
|
||
|
scroll_images = self.create_round_scrollbar_assets(
|
||
|
background, pressed, active
|
||
|
)
|
||
|
|
||
|
# horizontal scrollbar
|
||
|
self.style._build_configure(
|
||
|
h_ttkstyle,
|
||
|
troughcolor=troughcolor,
|
||
|
darkcolor=troughcolor,
|
||
|
bordercolor=troughcolor,
|
||
|
lightcolor=troughcolor,
|
||
|
arrowcolor=background,
|
||
|
arrowsize=self.scale_size(11),
|
||
|
background=troughcolor,
|
||
|
relief=tk.FLAT,
|
||
|
borderwidth=0,
|
||
|
)
|
||
|
self.style.element_create(
|
||
|
f"{h_ttkstyle}.thumb",
|
||
|
"image",
|
||
|
scroll_images[0],
|
||
|
("pressed", scroll_images[1]),
|
||
|
("active", scroll_images[2]),
|
||
|
border=self.scale_size(9),
|
||
|
padding=0,
|
||
|
sticky=tk.EW,
|
||
|
)
|
||
|
self.style.layout(
|
||
|
h_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Horizontal.Scrollbar.trough",
|
||
|
{
|
||
|
"sticky": "we",
|
||
|
"children": [
|
||
|
(
|
||
|
"Horizontal.Scrollbar.leftarrow",
|
||
|
{"side": "left", "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
"Horizontal.Scrollbar.rightarrow",
|
||
|
{"side": "right", "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
f"{h_ttkstyle}.thumb",
|
||
|
{"expand": "1", "sticky": "nswe"},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(h_ttkstyle, arrowcolor=background)
|
||
|
self.style.map(
|
||
|
h_ttkstyle, arrowcolor=[("pressed", pressed), ("active", active)]
|
||
|
)
|
||
|
|
||
|
# vertical scrollbar
|
||
|
self.style._build_configure(
|
||
|
v_ttkstyle,
|
||
|
troughcolor=troughcolor,
|
||
|
darkcolor=troughcolor,
|
||
|
bordercolor=troughcolor,
|
||
|
lightcolor=troughcolor,
|
||
|
arrowcolor=background,
|
||
|
arrowsize=self.scale_size(11),
|
||
|
background=troughcolor,
|
||
|
relief=tk.FLAT,
|
||
|
)
|
||
|
self.style.element_create(
|
||
|
f"{v_ttkstyle}.thumb",
|
||
|
"image",
|
||
|
scroll_images[3],
|
||
|
("pressed", scroll_images[4]),
|
||
|
("active", scroll_images[5]),
|
||
|
border=self.scale_size(9),
|
||
|
padding=0,
|
||
|
sticky=tk.NS,
|
||
|
)
|
||
|
self.style.layout(
|
||
|
v_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Vertical.Scrollbar.trough",
|
||
|
{
|
||
|
"sticky": "ns",
|
||
|
"children": [
|
||
|
(
|
||
|
"Vertical.Scrollbar.uparrow",
|
||
|
{"side": "top", "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
"Vertical.Scrollbar.downarrow",
|
||
|
{"side": "bottom", "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
f"{v_ttkstyle}.thumb",
|
||
|
{"expand": "1", "sticky": "nswe"},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(v_ttkstyle, arrowcolor=background)
|
||
|
self.style.map(
|
||
|
v_ttkstyle, arrowcolor=[("pressed", pressed), ("active", active)]
|
||
|
)
|
||
|
|
||
|
# register ttkstyles
|
||
|
self.style._register_ttkstyle(h_ttkstyle)
|
||
|
self.style._register_ttkstyle(v_ttkstyle)
|
||
|
|
||
|
def create_scrollbar_assets(self, thumbcolor, pressed, active):
|
||
|
"""Create the image assets used to build the standard scrollbar
|
||
|
style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
thumbcolor (str):
|
||
|
The primary color value used to color the thumb.
|
||
|
|
||
|
pressed (str):
|
||
|
The color value to use when the thumb is pressed.
|
||
|
|
||
|
active (str):
|
||
|
The color value to use when the thumb is active or
|
||
|
hovered.
|
||
|
"""
|
||
|
vsize = self.scale_size([9, 28])
|
||
|
hsize = self.scale_size([28, 9])
|
||
|
|
||
|
def draw_rect(size, fill):
|
||
|
x = size[0] * 10
|
||
|
y = size[1] * 10
|
||
|
img = Image.new("RGBA", (x, y), fill)
|
||
|
image = ImageTk.PhotoImage(img.resize(size), Image.BICUBIC)
|
||
|
name = util.get_image_name(image)
|
||
|
self.theme_images[name] = image
|
||
|
return name
|
||
|
|
||
|
# create images
|
||
|
h_normal_img = draw_rect(hsize, thumbcolor)
|
||
|
h_pressed_img = draw_rect(hsize, pressed)
|
||
|
h_active_img = draw_rect(hsize, active)
|
||
|
|
||
|
v_normal_img = draw_rect(vsize, thumbcolor)
|
||
|
v_pressed_img = draw_rect(vsize, pressed)
|
||
|
v_active_img = draw_rect(vsize, active)
|
||
|
|
||
|
return (
|
||
|
h_normal_img,
|
||
|
h_pressed_img,
|
||
|
h_active_img,
|
||
|
v_normal_img,
|
||
|
v_pressed_img,
|
||
|
v_active_img,
|
||
|
)
|
||
|
|
||
|
def create_scrollbar_style(self, colorname=DEFAULT):
|
||
|
"""Create a standard style for the ttk.Scrollbar widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TScrollbar"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
h_ttkstyle = f"Horizontal.{STYLE}"
|
||
|
v_ttkstyle = f"Vertical.{STYLE}"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
background = self.colors.border
|
||
|
else:
|
||
|
background = self.colors.selectbg
|
||
|
|
||
|
else:
|
||
|
h_ttkstyle = f"{colorname}.Horizontal.{STYLE}"
|
||
|
v_ttkstyle = f"{colorname}.Vertical.{STYLE}"
|
||
|
background = self.colors.get(colorname)
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
if colorname == LIGHT:
|
||
|
troughcolor = self.colors.bg
|
||
|
else:
|
||
|
troughcolor = self.colors.light
|
||
|
else:
|
||
|
troughcolor = Colors.update_hsv(self.colors.selectbg, vd=-0.2)
|
||
|
|
||
|
pressed = Colors.update_hsv(background, vd=-0.05)
|
||
|
active = Colors.update_hsv(background, vd=0.05)
|
||
|
|
||
|
scroll_images = self.create_scrollbar_assets(
|
||
|
background, pressed, active
|
||
|
)
|
||
|
|
||
|
# horizontal scrollbar
|
||
|
self.style._build_configure(
|
||
|
h_ttkstyle,
|
||
|
troughcolor=troughcolor,
|
||
|
darkcolor=troughcolor,
|
||
|
bordercolor=troughcolor,
|
||
|
lightcolor=troughcolor,
|
||
|
arrowcolor=background,
|
||
|
arrowsize=self.scale_size(11),
|
||
|
background=troughcolor,
|
||
|
relief=tk.FLAT,
|
||
|
borderwidth=0,
|
||
|
)
|
||
|
self.style.element_create(
|
||
|
f"{h_ttkstyle}.thumb",
|
||
|
"image",
|
||
|
scroll_images[0],
|
||
|
("pressed", scroll_images[1]),
|
||
|
("active", scroll_images[2]),
|
||
|
border=(3, 0),
|
||
|
sticky=tk.NSEW,
|
||
|
)
|
||
|
self.style.layout(
|
||
|
h_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Horizontal.Scrollbar.trough",
|
||
|
{
|
||
|
"sticky": "we",
|
||
|
"children": [
|
||
|
(
|
||
|
"Horizontal.Scrollbar.leftarrow",
|
||
|
{"side": "left", "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
"Horizontal.Scrollbar.rightarrow",
|
||
|
{"side": "right", "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
f"{h_ttkstyle}.thumb",
|
||
|
{"expand": "1", "sticky": "nswe"},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(h_ttkstyle, arrowcolor=background)
|
||
|
self.style.map(
|
||
|
h_ttkstyle, arrowcolor=[("pressed", pressed), ("active", active)]
|
||
|
)
|
||
|
|
||
|
# vertical scrollbar
|
||
|
self.style._build_configure(
|
||
|
v_ttkstyle,
|
||
|
troughcolor=troughcolor,
|
||
|
darkcolor=troughcolor,
|
||
|
bordercolor=troughcolor,
|
||
|
lightcolor=troughcolor,
|
||
|
arrowcolor=background,
|
||
|
arrowsize=self.scale_size(11),
|
||
|
background=troughcolor,
|
||
|
relief=tk.FLAT,
|
||
|
borderwidth=0,
|
||
|
)
|
||
|
self.style.element_create(
|
||
|
f"{v_ttkstyle}.thumb",
|
||
|
"image",
|
||
|
scroll_images[3],
|
||
|
("pressed", scroll_images[4]),
|
||
|
("active", scroll_images[5]),
|
||
|
border=(0, 3),
|
||
|
sticky=tk.NSEW,
|
||
|
)
|
||
|
self.style.layout(
|
||
|
v_ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Vertical.Scrollbar.trough",
|
||
|
{
|
||
|
"sticky": "ns",
|
||
|
"children": [
|
||
|
(
|
||
|
"Vertical.Scrollbar.uparrow",
|
||
|
{"side": "top", "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
"Vertical.Scrollbar.downarrow",
|
||
|
{"side": "bottom", "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
f"{v_ttkstyle}.thumb",
|
||
|
{"expand": "1", "sticky": "nswe"},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(v_ttkstyle, arrowcolor=background)
|
||
|
self.style.map(
|
||
|
v_ttkstyle, arrowcolor=[("pressed", pressed), ("active", active)]
|
||
|
)
|
||
|
|
||
|
# register ttkstyles
|
||
|
self.style._register_ttkstyle(h_ttkstyle)
|
||
|
self.style._register_ttkstyle(v_ttkstyle)
|
||
|
|
||
|
def create_spinbox_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Spinbox widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TSpinbox"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
disabled_fg = self.colors.border
|
||
|
bordercolor = self.colors.border
|
||
|
readonly = self.colors.light
|
||
|
else:
|
||
|
disabled_fg = self.colors.selectbg
|
||
|
bordercolor = self.colors.selectbg
|
||
|
readonly = bordercolor
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
focuscolor = self.colors.primary
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
focuscolor = self.colors.get(colorname)
|
||
|
|
||
|
if all([colorname, colorname != DEFAULT]):
|
||
|
bordercolor = focuscolor
|
||
|
|
||
|
if colorname == "light":
|
||
|
arrowfocus = self.colors.fg
|
||
|
else:
|
||
|
arrowfocus = focuscolor
|
||
|
|
||
|
element = ttkstyle.replace(".TS", ".S")
|
||
|
self.style.element_create(f"{element}.uparrow", "from", TTK_DEFAULT)
|
||
|
self.style.element_create(f"{element}.downarrow", "from", TTK_DEFAULT)
|
||
|
self.style.layout(
|
||
|
ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
f"{element}.field",
|
||
|
{
|
||
|
"side": tk.TOP,
|
||
|
"sticky": tk.EW,
|
||
|
"children": [
|
||
|
(
|
||
|
"null",
|
||
|
{
|
||
|
"side": tk.RIGHT,
|
||
|
"sticky": "",
|
||
|
"children": [
|
||
|
(
|
||
|
f"{element}.uparrow",
|
||
|
{"side": tk.TOP, "sticky": tk.E},
|
||
|
),
|
||
|
(
|
||
|
f"{element}.downarrow",
|
||
|
{
|
||
|
"side": tk.BOTTOM,
|
||
|
"sticky": tk.E,
|
||
|
},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
),
|
||
|
(
|
||
|
f"{element}.padding",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
f"{element}.textarea",
|
||
|
{"sticky": tk.NSEW},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
bordercolor=bordercolor,
|
||
|
darkcolor=self.colors.inputbg,
|
||
|
lightcolor=self.colors.inputbg,
|
||
|
fieldbackground=self.colors.inputbg,
|
||
|
foreground=self.colors.inputfg,
|
||
|
borderwidth=0,
|
||
|
background=self.colors.inputbg,
|
||
|
relief=tk.FLAT,
|
||
|
arrowcolor=self.colors.inputfg,
|
||
|
insertcolor=self.colors.inputfg,
|
||
|
arrowsize=self.scale_size(12),
|
||
|
padding=(10, 5),
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
fieldbackground=[("readonly", readonly)],
|
||
|
background=[("readonly", readonly)],
|
||
|
lightcolor=[
|
||
|
("focus invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("readonly", readonly),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("focus invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("readonly", readonly),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("hover !disabled", focuscolor),
|
||
|
],
|
||
|
arrowcolor=[
|
||
|
("disabled !disabled", disabled_fg),
|
||
|
("pressed !disabled", arrowfocus),
|
||
|
("hover !disabled", arrowfocus),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyles
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_table_treeview_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the Tableview widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Table.Treeview"
|
||
|
|
||
|
f = font.nametofont("TkDefaultFont")
|
||
|
rowheight = f.metrics()["linespace"]
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
disabled_fg = Colors.update_hsv(self.colors.inputbg, vd=-0.2)
|
||
|
bordercolor = self.colors.border
|
||
|
hover = Colors.update_hsv(self.colors.light, vd=-0.1)
|
||
|
else:
|
||
|
disabled_fg = Colors.update_hsv(self.colors.inputbg, vd=-0.3)
|
||
|
bordercolor = self.colors.selectbg
|
||
|
hover = Colors.update_hsv(self.colors.dark, vd=0.1)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
background = self.colors.inputbg
|
||
|
foreground = self.colors.inputfg
|
||
|
body_style = STYLE
|
||
|
header_style = f"{STYLE}.Heading"
|
||
|
elif colorname == LIGHT and self.is_light_theme:
|
||
|
background = self.colors.get(colorname)
|
||
|
foreground = self.colors.fg
|
||
|
body_style = f"{colorname}.{STYLE}"
|
||
|
header_style = f"{colorname}.{STYLE}.Heading"
|
||
|
hover = Colors.update_hsv(background, vd=-0.1)
|
||
|
else:
|
||
|
background = self.colors.get(colorname)
|
||
|
foreground = self.colors.selectfg
|
||
|
body_style = f"{colorname}.{STYLE}"
|
||
|
header_style = f"{colorname}.{STYLE}.Heading"
|
||
|
hover = Colors.update_hsv(background, vd=0.1)
|
||
|
|
||
|
|
||
|
# treeview header
|
||
|
self.style._build_configure(
|
||
|
header_style,
|
||
|
background=background,
|
||
|
foreground=foreground,
|
||
|
relief=RAISED,
|
||
|
borderwidth=1,
|
||
|
darkcolor=background,
|
||
|
bordercolor=bordercolor,
|
||
|
lightcolor=background,
|
||
|
padding=5,
|
||
|
)
|
||
|
self.style.map(
|
||
|
header_style,
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
background=[
|
||
|
("active !disabled", hover),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("active !disabled", hover),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("active !disabled", hover),
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
body_style,
|
||
|
background=self.colors.inputbg,
|
||
|
fieldbackground=self.colors.inputbg,
|
||
|
foreground=self.colors.inputfg,
|
||
|
bordercolor=bordercolor,
|
||
|
lightcolor=self.colors.inputbg,
|
||
|
darkcolor=self.colors.inputbg,
|
||
|
borderwidth=2,
|
||
|
padding=0,
|
||
|
rowheight=rowheight,
|
||
|
relief=tk.RAISED,
|
||
|
)
|
||
|
self.style.map(
|
||
|
body_style,
|
||
|
background=[("selected", self.colors.selectbg)],
|
||
|
foreground=[
|
||
|
("disabled", disabled_fg),
|
||
|
("selected", self.colors.selectfg),
|
||
|
],
|
||
|
)
|
||
|
self.style.layout(
|
||
|
body_style,
|
||
|
[
|
||
|
(
|
||
|
"Button.border",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"border": "1",
|
||
|
"children": [
|
||
|
(
|
||
|
"Treeview.padding",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
"Treeview.treearea",
|
||
|
{"sticky": tk.NSEW},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyles
|
||
|
self.style._register_ttkstyle(body_style)
|
||
|
|
||
|
def create_treeview_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Treeview widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Treeview"
|
||
|
|
||
|
f = font.nametofont("TkDefaultFont")
|
||
|
rowheight = f.metrics()["linespace"]
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
disabled_fg = Colors.update_hsv(self.colors.inputbg, vd=-0.2)
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
disabled_fg = Colors.update_hsv(self.colors.inputbg, vd=-0.3)
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
background = self.colors.inputbg
|
||
|
foreground = self.colors.inputfg
|
||
|
body_style = STYLE
|
||
|
header_style = f"{STYLE}.Heading"
|
||
|
focuscolor = self.colors.primary
|
||
|
elif colorname == LIGHT and self.is_light_theme:
|
||
|
background = self.colors.get(colorname)
|
||
|
foreground = self.colors.fg
|
||
|
body_style = f"{colorname}.{STYLE}"
|
||
|
header_style = f"{colorname}.{STYLE}.Heading"
|
||
|
focuscolor = background
|
||
|
bordercolor = focuscolor
|
||
|
else:
|
||
|
background = self.colors.get(colorname)
|
||
|
foreground = self.colors.selectfg
|
||
|
body_style = f"{colorname}.{STYLE}"
|
||
|
header_style = f"{colorname}.{STYLE}.Heading"
|
||
|
focuscolor = background
|
||
|
bordercolor = focuscolor
|
||
|
|
||
|
# treeview header
|
||
|
self.style._build_configure(
|
||
|
header_style,
|
||
|
background=background,
|
||
|
foreground=foreground,
|
||
|
relief=tk.FLAT,
|
||
|
padding=5,
|
||
|
)
|
||
|
self.style.map(
|
||
|
header_style,
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
bordercolor=[("focus !disabled", background)],
|
||
|
)
|
||
|
# treeview body
|
||
|
self.style._build_configure(
|
||
|
body_style,
|
||
|
background=self.colors.inputbg,
|
||
|
fieldbackground=self.colors.inputbg,
|
||
|
foreground=self.colors.inputfg,
|
||
|
bordercolor=bordercolor,
|
||
|
lightcolor=self.colors.inputbg,
|
||
|
darkcolor=self.colors.inputbg,
|
||
|
borderwidth=2,
|
||
|
padding=0,
|
||
|
rowheight=rowheight,
|
||
|
relief=tk.RAISED,
|
||
|
)
|
||
|
self.style.map(
|
||
|
body_style,
|
||
|
background=[("selected", self.colors.selectbg)],
|
||
|
foreground=[
|
||
|
("disabled", disabled_fg),
|
||
|
("selected", self.colors.selectfg),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("disabled", bordercolor),
|
||
|
("focus", focuscolor),
|
||
|
("pressed", focuscolor),
|
||
|
("hover", focuscolor),
|
||
|
],
|
||
|
lightcolor=[("focus", focuscolor)],
|
||
|
darkcolor=[("focus", focuscolor)],
|
||
|
)
|
||
|
self.style.layout(
|
||
|
body_style,
|
||
|
[
|
||
|
(
|
||
|
"Button.border",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"border": "1",
|
||
|
"children": [
|
||
|
(
|
||
|
"Treeview.padding",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
"Treeview.treearea",
|
||
|
{"sticky": tk.NSEW},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
|
||
|
try:
|
||
|
self.style.element_create("Treeitem.indicator", "from", TTK_ALT)
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
# register ttkstyles
|
||
|
self.style._register_ttkstyle(body_style)
|
||
|
|
||
|
def create_frame_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Frame widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TFrame"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
background = self.colors.bg
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
background = self.colors.get(colorname)
|
||
|
|
||
|
self.style._build_configure(ttkstyle, background=background)
|
||
|
|
||
|
# register style
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_button_style(self, colorname=DEFAULT):
|
||
|
"""Create a solid style for the ttk.Button widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TButton"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
foreground = self.colors.get_foreground(PRIMARY)
|
||
|
background = self.colors.primary
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
foreground = self.colors.get_foreground(colorname)
|
||
|
background = self.colors.get(colorname)
|
||
|
|
||
|
bordercolor = background
|
||
|
disabled_bg = Colors.make_transparent(0.10, self.colors.fg, self.colors.bg)
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
pressed = Colors.make_transparent(0.80, background, self.colors.bg)
|
||
|
hover = Colors.make_transparent(0.90, background, self.colors.bg)
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=foreground,
|
||
|
background=background,
|
||
|
bordercolor=bordercolor,
|
||
|
darkcolor=background,
|
||
|
lightcolor=background,
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor=foreground,
|
||
|
padding=(10, 5),
|
||
|
anchor=tk.CENTER,
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
background=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
bordercolor=[("disabled", disabled_bg)],
|
||
|
darkcolor=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_outline_button_style(self, colorname=DEFAULT):
|
||
|
"""Create an outline style for the ttk.Button widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Outline.TButton"
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
colorname = PRIMARY
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
foreground = self.colors.get(colorname)
|
||
|
background = self.colors.get_foreground(colorname)
|
||
|
foreground_pressed = background
|
||
|
bordercolor = foreground
|
||
|
pressed = foreground
|
||
|
hover = foreground
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=foreground,
|
||
|
background=self.colors.bg,
|
||
|
bordercolor=bordercolor,
|
||
|
darkcolor=self.colors.bg,
|
||
|
lightcolor=self.colors.bg,
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor=foreground,
|
||
|
padding=(10, 5),
|
||
|
anchor=tk.CENTER,
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", foreground_pressed),
|
||
|
("hover !disabled", foreground_pressed),
|
||
|
],
|
||
|
background=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
focuscolor=[
|
||
|
("pressed !disabled", foreground_pressed),
|
||
|
("hover !disabled", foreground_pressed),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_link_button_style(self, colorname=DEFAULT):
|
||
|
"""Create a link button style for the ttk.Button widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Link.TButton"
|
||
|
|
||
|
pressed = self.colors.info
|
||
|
hover = self.colors.info
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
foreground = self.colors.fg
|
||
|
ttkstyle = STYLE
|
||
|
elif colorname == LIGHT:
|
||
|
foreground = self.colors.fg
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
else:
|
||
|
foreground = self.colors.get(colorname)
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=foreground,
|
||
|
background=self.colors.bg,
|
||
|
bordercolor=self.colors.bg,
|
||
|
darkcolor=self.colors.bg,
|
||
|
lightcolor=self.colors.bg,
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor=foreground,
|
||
|
anchor=tk.CENTER,
|
||
|
padding=(10, 5),
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
shiftrelief=[("pressed !disabled", -1)],
|
||
|
foreground=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
focuscolor=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", pressed),
|
||
|
],
|
||
|
background=[
|
||
|
("disabled", self.colors.bg),
|
||
|
("pressed !disabled", self.colors.bg),
|
||
|
("hover !disabled", self.colors.bg),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("disabled", self.colors.bg),
|
||
|
("pressed !disabled", self.colors.bg),
|
||
|
("hover !disabled", self.colors.bg),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("disabled", self.colors.bg),
|
||
|
("pressed !disabled", self.colors.bg),
|
||
|
("hover !disabled", self.colors.bg),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("disabled", self.colors.bg),
|
||
|
("pressed !disabled", self.colors.bg),
|
||
|
("hover !disabled", self.colors.bg),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_square_toggle_assets(self, colorname=DEFAULT):
|
||
|
"""Create the image assets used to build a square toggle
|
||
|
style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Tuple[str]:
|
||
|
A tuple of PhotoImage names.
|
||
|
"""
|
||
|
size = self.scale_size([24, 15])
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
colorname = PRIMARY
|
||
|
|
||
|
# set default style color values
|
||
|
prime_color = self.colors.get(colorname)
|
||
|
on_border = prime_color
|
||
|
on_indicator = self.colors.selectfg
|
||
|
on_fill = prime_color
|
||
|
off_fill = self.colors.bg
|
||
|
disabled_fg = Colors.make_transparent(0.3, self.colors.fg, self.colors.bg)
|
||
|
off_border = Colors.make_transparent(0.4, self.colors.fg, self.colors.bg)
|
||
|
off_indicator = Colors.make_transparent(0.4, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
# override defaults for light and dark colors
|
||
|
if colorname == LIGHT:
|
||
|
on_border = self.colors.dark
|
||
|
on_indicator = on_border
|
||
|
elif colorname == DARK:
|
||
|
on_border = self.colors.light
|
||
|
on_indicator = on_border
|
||
|
|
||
|
# toggle off
|
||
|
_off = Image.new("RGBA", (226, 130))
|
||
|
draw = ImageDraw.Draw(_off)
|
||
|
draw.rectangle(
|
||
|
xy=[1, 1, 225, 129], outline=off_border, width=6, fill=off_fill
|
||
|
)
|
||
|
draw.rectangle([18, 18, 110, 110], fill=off_indicator)
|
||
|
|
||
|
off_img = ImageTk.PhotoImage(_off.resize(size, Image.LANCZOS))
|
||
|
off_name = util.get_image_name(off_img)
|
||
|
self.theme_images[off_name] = off_img
|
||
|
|
||
|
# toggle on
|
||
|
toggle_on = Image.new("RGBA", (226, 130))
|
||
|
draw = ImageDraw.Draw(toggle_on)
|
||
|
draw.rectangle(
|
||
|
xy=[1, 1, 225, 129], outline=on_border, width=6, fill=on_fill
|
||
|
)
|
||
|
draw.rectangle([18, 18, 110, 110], fill=on_indicator)
|
||
|
_on = toggle_on.transpose(Image.ROTATE_180)
|
||
|
on_img = ImageTk.PhotoImage(_on.resize(size, Image.LANCZOS))
|
||
|
on_name = util.get_image_name(on_img)
|
||
|
self.theme_images[on_name] = on_img
|
||
|
|
||
|
# toggle disabled
|
||
|
_disabled = Image.new("RGBA", (226, 130))
|
||
|
draw = ImageDraw.Draw(_disabled)
|
||
|
draw.rectangle([1, 1, 225, 129], outline=disabled_fg, width=6)
|
||
|
draw.rectangle([18, 18, 110, 110], fill=disabled_fg)
|
||
|
disabled_img = ImageTk.PhotoImage(
|
||
|
_disabled.resize(size, Image.LANCZOS)
|
||
|
)
|
||
|
disabled_name = util.get_image_name(disabled_img)
|
||
|
self.theme_images[disabled_name] = disabled_img
|
||
|
|
||
|
# toggle on / disabled
|
||
|
toggle_on_disabled = Image.new("RGBA", (226, 130))
|
||
|
draw = ImageDraw.Draw(toggle_on_disabled)
|
||
|
draw.rectangle(
|
||
|
xy=[1, 1, 225, 129], outline=disabled_fg, width=6, fill=off_fill
|
||
|
)
|
||
|
draw.rectangle([18, 18, 110, 110], fill=disabled_fg)
|
||
|
_on_disabled = toggle_on_disabled.transpose(Image.ROTATE_180)
|
||
|
on_dis_img = ImageTk.PhotoImage(_on_disabled.resize(size, Image.LANCZOS))
|
||
|
on_disabled_name = util.get_image_name(on_dis_img)
|
||
|
self.theme_images[on_disabled_name] = on_dis_img
|
||
|
|
||
|
|
||
|
return off_name, on_name, disabled_name, on_disabled_name
|
||
|
|
||
|
def create_toggle_style(self, colorname=DEFAULT):
|
||
|
"""Create a round toggle style for the ttk.Checkbutton widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
"""
|
||
|
self.create_round_toggle_style(colorname)
|
||
|
|
||
|
def create_round_toggle_assets(self, colorname=DEFAULT):
|
||
|
"""Create image assets for the round toggle style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label assigned to the colors property.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Tuple[str]:
|
||
|
A tuple of PhotoImage names.
|
||
|
"""
|
||
|
size = self.scale_size([24, 15])
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
colorname = PRIMARY
|
||
|
|
||
|
# set default style color values
|
||
|
prime_color = self.colors.get(colorname)
|
||
|
on_border = prime_color
|
||
|
on_indicator = self.colors.selectfg
|
||
|
on_fill = prime_color
|
||
|
off_fill = self.colors.bg
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.3, self.colors.fg, self.colors.bg)
|
||
|
off_border = Colors.make_transparent(0.4, self.colors.fg, self.colors.bg)
|
||
|
off_indicator = Colors.make_transparent(0.4, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
# override defaults for light and dark colors
|
||
|
if colorname == LIGHT:
|
||
|
on_border = self.colors.dark
|
||
|
on_indicator = on_border
|
||
|
elif colorname == DARK:
|
||
|
on_border = self.colors.light
|
||
|
on_indicator = on_border
|
||
|
|
||
|
# toggle off
|
||
|
_off = Image.new("RGBA", (226, 130))
|
||
|
draw = ImageDraw.Draw(_off)
|
||
|
draw.rounded_rectangle(
|
||
|
xy=[1, 1, 225, 129],
|
||
|
radius=(128 / 2),
|
||
|
outline=off_border,
|
||
|
width=6,
|
||
|
fill=off_fill,
|
||
|
)
|
||
|
draw.ellipse([20, 18, 112, 110], fill=off_indicator)
|
||
|
off_img = ImageTk.PhotoImage(_off.resize(size, Image.LANCZOS))
|
||
|
off_name = util.get_image_name(off_img)
|
||
|
self.theme_images[off_name] = off_img
|
||
|
|
||
|
# toggle on
|
||
|
_on = Image.new("RGBA", (226, 130))
|
||
|
draw = ImageDraw.Draw(_on)
|
||
|
draw.rounded_rectangle(
|
||
|
xy=[1, 1, 225, 129],
|
||
|
radius=(128 / 2),
|
||
|
outline=on_border,
|
||
|
width=6,
|
||
|
fill=on_fill,
|
||
|
)
|
||
|
draw.ellipse([20, 18, 112, 110], fill=on_indicator)
|
||
|
_on = _on.transpose(Image.ROTATE_180)
|
||
|
on_img = ImageTk.PhotoImage(_on.resize(size, Image.LANCZOS))
|
||
|
on_name = util.get_image_name(on_img)
|
||
|
self.theme_images[on_name] = on_img
|
||
|
|
||
|
# toggle on / disabled
|
||
|
_on_disabled = Image.new("RGBA", (226, 130))
|
||
|
draw = ImageDraw.Draw(_on_disabled)
|
||
|
draw.rounded_rectangle(
|
||
|
xy=[1, 1, 225, 129],
|
||
|
radius=(128 / 2),
|
||
|
outline=disabled_fg,
|
||
|
width=6,
|
||
|
fill=off_fill,
|
||
|
)
|
||
|
draw.ellipse([20, 18, 112, 110], fill=disabled_fg)
|
||
|
_on_disabled = _on_disabled.transpose(Image.ROTATE_180)
|
||
|
on_dis_img = ImageTk.PhotoImage(_on_disabled.resize(size, Image.LANCZOS))
|
||
|
on_disabled_name = util.get_image_name(on_dis_img)
|
||
|
self.theme_images[on_disabled_name] = on_dis_img
|
||
|
|
||
|
# toggle disabled
|
||
|
_disabled = Image.new("RGBA", (226, 130))
|
||
|
draw = ImageDraw.Draw(_disabled)
|
||
|
draw.rounded_rectangle(
|
||
|
xy=[1, 1, 225, 129], radius=(128 / 2), outline=disabled_fg, width=6
|
||
|
)
|
||
|
draw.ellipse([20, 18, 112, 110], fill=disabled_fg)
|
||
|
disabled_img = ImageTk.PhotoImage(
|
||
|
_disabled.resize(size, Image.LANCZOS)
|
||
|
)
|
||
|
disabled_name = util.get_image_name(disabled_img)
|
||
|
self.theme_images[disabled_name] = disabled_img
|
||
|
|
||
|
return off_name, on_name, disabled_name, on_disabled_name
|
||
|
|
||
|
def create_round_toggle_style(self, colorname=DEFAULT):
|
||
|
"""Create a round toggle style for the ttk.Checkbutton widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Round.Toggle"
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
colorname = PRIMARY
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
# ( off, on, disabled )
|
||
|
images = self.create_round_toggle_assets(colorname)
|
||
|
|
||
|
try:
|
||
|
width = self.scale_size(28)
|
||
|
borderpad = self.scale_size(4)
|
||
|
self.style.element_create(
|
||
|
f"{ttkstyle}.indicator",
|
||
|
"image",
|
||
|
images[1],
|
||
|
("disabled selected", images[3]),
|
||
|
("disabled", images[2]),
|
||
|
("!selected", images[0]),
|
||
|
width=width,
|
||
|
border=borderpad,
|
||
|
sticky=tk.W,
|
||
|
)
|
||
|
except:
|
||
|
"""This method is used as the default Toggle style, so it
|
||
|
is neccessary to catch Tcl Errors when it tries to create
|
||
|
and element that was already created by the Toggle or
|
||
|
Round Toggle style"""
|
||
|
pass
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
relief=tk.FLAT,
|
||
|
borderwidth=0,
|
||
|
padding=0,
|
||
|
foreground=self.colors.fg,
|
||
|
background=self.colors.bg,
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
background=[("selected", self.colors.bg)],
|
||
|
)
|
||
|
self.style.layout(
|
||
|
ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Toolbutton.border",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
"Toolbutton.padding",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
f"{ttkstyle}.indicator",
|
||
|
{"side": tk.LEFT},
|
||
|
),
|
||
|
(
|
||
|
"Toolbutton.label",
|
||
|
{"side": tk.LEFT},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_square_toggle_style(self, colorname=DEFAULT):
|
||
|
"""Create a square toggle style for the ttk.Checkbutton widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
|
||
|
STYLE = "Square.Toggle"
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
# ( off, on, disabled )
|
||
|
images = self.create_square_toggle_assets(colorname)
|
||
|
|
||
|
width = self.scale_size(28)
|
||
|
borderpad = self.scale_size(4)
|
||
|
|
||
|
self.style.element_create(
|
||
|
f"{ttkstyle}.indicator",
|
||
|
"image",
|
||
|
images[1],
|
||
|
("disabled selected", images[3]),
|
||
|
("disabled", images[2]),
|
||
|
("!selected", images[0]),
|
||
|
width=width,
|
||
|
border=borderpad,
|
||
|
sticky=tk.W,
|
||
|
)
|
||
|
self.style.layout(
|
||
|
ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Toolbutton.border",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
"Toolbutton.padding",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
f"{ttkstyle}.indicator",
|
||
|
{"side": tk.LEFT},
|
||
|
),
|
||
|
(
|
||
|
"Toolbutton.label",
|
||
|
{"side": tk.LEFT},
|
||
|
),
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
ttkstyle, relief=tk.FLAT, borderwidth=0, foreground=self.colors.fg
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
background=[
|
||
|
("selected", self.colors.bg),
|
||
|
("!selected", self.colors.bg),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_toolbutton_style(self, colorname=DEFAULT):
|
||
|
"""Create a solid toolbutton style for the ttk.Checkbutton
|
||
|
and ttk.Radiobutton widgets.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Toolbutton"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
toggle_on = self.colors.primary
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
toggle_on = self.colors.get(colorname)
|
||
|
|
||
|
foreground = self.colors.get_foreground(colorname)
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
toggle_off = self.colors.border
|
||
|
else:
|
||
|
toggle_off = self.colors.selectbg
|
||
|
|
||
|
disabled_bg = Colors.make_transparent(0.10, self.colors.fg, self.colors.bg)
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=self.colors.selectfg,
|
||
|
background=toggle_off,
|
||
|
bordercolor=toggle_off,
|
||
|
darkcolor=toggle_off,
|
||
|
lightcolor=toggle_off,
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor="",
|
||
|
padding=(10, 5),
|
||
|
anchor=tk.CENTER,
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[
|
||
|
("disabled", disabled_fg),
|
||
|
("hover", foreground),
|
||
|
("selected", foreground),
|
||
|
],
|
||
|
background=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", toggle_on),
|
||
|
("selected !disabled", toggle_on),
|
||
|
("hover !disabled", toggle_on),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", toggle_on),
|
||
|
("selected !disabled", toggle_on),
|
||
|
("hover !disabled", toggle_on),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", toggle_on),
|
||
|
("selected !disabled", toggle_on),
|
||
|
("hover !disabled", toggle_on),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", toggle_on),
|
||
|
("selected !disabled", toggle_on),
|
||
|
("hover !disabled", toggle_on),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_outline_toolbutton_style(self, colorname=DEFAULT):
|
||
|
"""Create an outline toolbutton style for the ttk.Checkbutton
|
||
|
and ttk.Radiobutton widgets.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Outline.Toolbutton"
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
colorname = PRIMARY
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
foreground = self.colors.get(colorname)
|
||
|
background = self.colors.get_foreground(colorname)
|
||
|
foreground_pressed = background
|
||
|
bordercolor = foreground
|
||
|
pressed = foreground
|
||
|
hover = foreground
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=foreground,
|
||
|
background=self.colors.bg,
|
||
|
bordercolor=bordercolor,
|
||
|
darkcolor=self.colors.bg,
|
||
|
lightcolor=self.colors.bg,
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor=foreground,
|
||
|
padding=(10, 5),
|
||
|
anchor=tk.CENTER,
|
||
|
arrowcolor=foreground,
|
||
|
arrowpadding=(0, 0, 15, 0),
|
||
|
arrowsize=3,
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", foreground_pressed),
|
||
|
("selected !disabled", foreground_pressed),
|
||
|
("hover !disabled", foreground_pressed),
|
||
|
],
|
||
|
background=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("selected !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("selected !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("disabled", self.colors.bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("selected !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("disabled", self.colors.bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("selected !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_entry_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Entry widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TEntry"
|
||
|
|
||
|
# general default colors
|
||
|
if self.is_light_theme:
|
||
|
disabled_fg = self.colors.border
|
||
|
bordercolor = self.colors.border
|
||
|
readonly = self.colors.light
|
||
|
else:
|
||
|
disabled_fg = self.colors.selectbg
|
||
|
bordercolor = self.colors.selectbg
|
||
|
readonly = bordercolor
|
||
|
|
||
|
if any([colorname == DEFAULT, not colorname]):
|
||
|
# default style
|
||
|
ttkstyle = STYLE
|
||
|
focuscolor = self.colors.primary
|
||
|
else:
|
||
|
# colored style
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
focuscolor = self.colors.get(colorname)
|
||
|
bordercolor = focuscolor
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
bordercolor=bordercolor,
|
||
|
darkcolor=self.colors.inputbg,
|
||
|
lightcolor=self.colors.inputbg,
|
||
|
fieldbackground=self.colors.inputbg,
|
||
|
foreground=self.colors.inputfg,
|
||
|
insertcolor=self.colors.inputfg,
|
||
|
padding=5,
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
fieldbackground=[("readonly", readonly)],
|
||
|
bordercolor=[
|
||
|
("invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("hover !disabled", focuscolor),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("focus invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("readonly", readonly),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("focus invalid", self.colors.danger),
|
||
|
("focus !disabled", focuscolor),
|
||
|
("readonly", readonly),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_radiobutton_assets(self, colorname=DEFAULT):
|
||
|
"""Create the image assets used to build the radiobutton style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Tuple[str]:
|
||
|
A tuple of PhotoImage names
|
||
|
"""
|
||
|
prime_color = self.colors.get(colorname)
|
||
|
on_fill = prime_color
|
||
|
off_fill = self.colors.bg
|
||
|
on_indicator = self.colors.selectfg
|
||
|
size = self.scale_size([14, 14])
|
||
|
off_border = Colors.make_transparent(0.4, self.colors.fg, self.colors.bg)
|
||
|
disabled = Colors.make_transparent(0.3, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
if colorname == LIGHT:
|
||
|
on_indicator = self.colors.dark
|
||
|
|
||
|
# radio off
|
||
|
_off = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(_off)
|
||
|
draw.ellipse(
|
||
|
xy=[1, 1, 133, 133], outline=off_border, width=6, fill=off_fill
|
||
|
)
|
||
|
off_img = ImageTk.PhotoImage(_off.resize(size, Image.LANCZOS))
|
||
|
off_name = util.get_image_name(off_img)
|
||
|
self.theme_images[off_name] = off_img
|
||
|
|
||
|
# radio on
|
||
|
_on = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(_on)
|
||
|
if colorname == LIGHT and self.is_light_theme:
|
||
|
draw.ellipse(xy=[1, 1, 133, 133], outline=off_border, width=6)
|
||
|
else:
|
||
|
draw.ellipse(xy=[1, 1, 133, 133], fill=on_fill)
|
||
|
draw.ellipse([40, 40, 94, 94], fill=on_indicator)
|
||
|
on_img = ImageTk.PhotoImage(_on.resize(size, Image.LANCZOS))
|
||
|
on_name = util.get_image_name(on_img)
|
||
|
self.theme_images[on_name] = on_img
|
||
|
|
||
|
# radio on/disabled
|
||
|
_on_dis = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(_on_dis)
|
||
|
if colorname == LIGHT and self.is_light_theme:
|
||
|
draw.ellipse(xy=[1, 1, 133, 133], outline=off_border, width=6)
|
||
|
else:
|
||
|
draw.ellipse(xy=[1, 1, 133, 133], fill=disabled)
|
||
|
draw.ellipse([40, 40, 94, 94], fill=off_fill)
|
||
|
on_dis_img = ImageTk.PhotoImage(_on_dis.resize(size, Image.LANCZOS))
|
||
|
on_disabled_name = util.get_image_name(on_dis_img)
|
||
|
self.theme_images[on_disabled_name] = on_dis_img
|
||
|
|
||
|
# radio disabled
|
||
|
_disabled = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(_disabled)
|
||
|
draw.ellipse(
|
||
|
xy=[1, 1, 133, 133], outline=disabled, width=3, fill=off_fill
|
||
|
)
|
||
|
disabled_img = ImageTk.PhotoImage(
|
||
|
_disabled.resize(size, Image.LANCZOS)
|
||
|
)
|
||
|
disabled_name = util.get_image_name(disabled_img)
|
||
|
self.theme_images[disabled_name] = disabled_img
|
||
|
|
||
|
return off_name, on_name, disabled_name, on_disabled_name
|
||
|
|
||
|
def create_radiobutton_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Radiobutton widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
|
||
|
STYLE = "TRadiobutton"
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
colorname = PRIMARY
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
# ( off, on, disabled )
|
||
|
images = self.create_radiobutton_assets(colorname)
|
||
|
width = self.scale_size(20)
|
||
|
borderpad = self.scale_size(4)
|
||
|
self.style.element_create(
|
||
|
f"{ttkstyle}.indicator",
|
||
|
"image",
|
||
|
images[1],
|
||
|
("disabled selected", images[3]),
|
||
|
("disabled", images[2]),
|
||
|
("!selected", images[0]),
|
||
|
width=width,
|
||
|
border=borderpad,
|
||
|
sticky=tk.W,
|
||
|
)
|
||
|
self.style.map(ttkstyle, foreground=[("disabled", disabled_fg)])
|
||
|
self.style._build_configure(ttkstyle)
|
||
|
self.style.layout(
|
||
|
ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Radiobutton.padding",
|
||
|
{
|
||
|
"children": [
|
||
|
(
|
||
|
f"{ttkstyle}.indicator",
|
||
|
{"side": tk.LEFT, "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
"Radiobutton.focus",
|
||
|
{
|
||
|
"children": [
|
||
|
(
|
||
|
"Radiobutton.label",
|
||
|
{"sticky": tk.NSEW},
|
||
|
)
|
||
|
],
|
||
|
"side": tk.LEFT,
|
||
|
"sticky": "",
|
||
|
},
|
||
|
),
|
||
|
],
|
||
|
"sticky": tk.NSEW,
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_date_button_assets(self, foreground):
|
||
|
"""Create the image assets used to build the date button
|
||
|
style. This button style applied to the button in the
|
||
|
ttkbootstrap.widgets.DateEntry.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
foreground (str):
|
||
|
The color value used to draw the calendar image.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
The PhotoImage name.
|
||
|
"""
|
||
|
fill = foreground
|
||
|
image = Image.new("RGBA", (210, 220))
|
||
|
draw = ImageDraw.Draw(image)
|
||
|
|
||
|
draw.rounded_rectangle(
|
||
|
[10, 30, 200, 210], radius=20, outline=fill, width=10
|
||
|
)
|
||
|
|
||
|
calendar_image_coordinates = [
|
||
|
# page spirals
|
||
|
[40, 10, 50, 50],
|
||
|
[100, 10, 110, 50],
|
||
|
[160, 10, 170, 50],
|
||
|
# row 1
|
||
|
[70, 90, 90, 110],
|
||
|
[110, 90, 130, 110],
|
||
|
[150, 90, 170, 110],
|
||
|
# row 2
|
||
|
[30, 130, 50, 150],
|
||
|
[70, 130, 90, 150],
|
||
|
[110, 130, 130, 150],
|
||
|
[150, 130, 170, 150],
|
||
|
# row 3
|
||
|
[30, 170, 50, 190],
|
||
|
[70, 170, 90, 190],
|
||
|
[110, 170, 130, 190],
|
||
|
]
|
||
|
for xy in calendar_image_coordinates:
|
||
|
draw.rectangle(xy=xy, fill=fill)
|
||
|
|
||
|
size = self.scale_size([21, 22])
|
||
|
tk_img = ImageTk.PhotoImage(image.resize(size, Image.LANCZOS))
|
||
|
tk_name = util.get_image_name(tk_img)
|
||
|
self.theme_images[tk_name] = tk_img
|
||
|
return tk_name
|
||
|
|
||
|
def create_date_button_style(self, colorname=DEFAULT):
|
||
|
"""Create a date button style for the ttk.Button widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style widget.
|
||
|
"""
|
||
|
STYLE = "Date.TButton"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
disabled_fg = self.colors.border
|
||
|
else:
|
||
|
disabled_fg = self.colors.selectbg
|
||
|
|
||
|
btn_foreground = Colors.get_foreground(self.colors, colorname)
|
||
|
|
||
|
img_normal = self.create_date_button_assets(btn_foreground)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
foreground = self.colors.get_foreground(PRIMARY)
|
||
|
background = self.colors.primary
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
foreground = self.colors.get_foreground(colorname)
|
||
|
background = self.colors.get(colorname)
|
||
|
|
||
|
pressed = Colors.update_hsv(background, vd=-0.1)
|
||
|
hover = Colors.update_hsv(background, vd=0.10)
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=foreground,
|
||
|
background=background,
|
||
|
bordercolor=background,
|
||
|
darkcolor=background,
|
||
|
lightcolor=background,
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor=foreground,
|
||
|
padding=(2, 2),
|
||
|
anchor=tk.CENTER,
|
||
|
image=img_normal,
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
background=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
bordercolor=[("disabled", disabled_fg)],
|
||
|
darkcolor=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
)
|
||
|
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_calendar_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the
|
||
|
ttkbootstrap.dialogs.DatePickerPopup widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
|
||
|
STYLE = "TCalendar"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
prime_color = self.colors.primary
|
||
|
ttkstyle = STYLE
|
||
|
chevron_style = "Chevron.TButton"
|
||
|
else:
|
||
|
prime_color = self.colors.get(colorname)
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
chevron_style = f"Chevron.{colorname}.TButton"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
disabled_fg = Colors.update_hsv(self.colors.inputbg, vd=-0.2)
|
||
|
pressed = Colors.update_hsv(prime_color, vd=-0.1)
|
||
|
else:
|
||
|
disabled_fg = Colors.update_hsv(self.colors.inputbg, vd=-0.3)
|
||
|
pressed = Colors.update_hsv(prime_color, vd=0.1)
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=self.colors.fg,
|
||
|
background=self.colors.bg,
|
||
|
bordercolor=self.colors.bg,
|
||
|
darkcolor=self.colors.bg,
|
||
|
lightcolor=self.colors.bg,
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor="",
|
||
|
borderwidth=1,
|
||
|
padding=(10, 5),
|
||
|
anchor=tk.CENTER,
|
||
|
)
|
||
|
self.style.layout(
|
||
|
ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Toolbutton.border",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
"Toolbutton.padding",
|
||
|
{
|
||
|
"sticky": tk.NSEW,
|
||
|
"children": [
|
||
|
(
|
||
|
"Toolbutton.label",
|
||
|
{"sticky": tk.NSEW},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", self.colors.selectfg),
|
||
|
("selected !disabled", self.colors.selectfg),
|
||
|
("hover !disabled", self.colors.selectfg),
|
||
|
],
|
||
|
background=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("selected !disabled", pressed),
|
||
|
("hover !disabled", pressed),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("selected !disabled", pressed),
|
||
|
("hover !disabled", pressed),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("selected !disabled", pressed),
|
||
|
("hover !disabled", pressed),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("selected !disabled", pressed),
|
||
|
("hover !disabled", pressed),
|
||
|
],
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
chevron_style, font="-size 14", focuscolor=""
|
||
|
)
|
||
|
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
self.style._register_ttkstyle(chevron_style)
|
||
|
|
||
|
def create_metersubtxt_label_style(self, colorname=DEFAULT):
|
||
|
"""Create a subtext label style for the
|
||
|
ttkbootstrap.widgets.Meter widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Metersubtxt.TLabel"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
if self.is_light_theme:
|
||
|
foreground = self.colors.secondary
|
||
|
else:
|
||
|
foreground = self.colors.light
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
foreground = self.colors.get(colorname)
|
||
|
|
||
|
background = self.colors.bg
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle, foreground=foreground, background=background
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_meter_label_style(self, colorname=DEFAULT):
|
||
|
"""Create a label style for the
|
||
|
ttkbootstrap.widgets.Meter widget. This style also stores some
|
||
|
metadata that is called by the Meter class to lookup relevant
|
||
|
colors for the trough and bar when the new image is drawn.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
|
||
|
STYLE = "Meter.TLabel"
|
||
|
|
||
|
# text color = `foreground`
|
||
|
# trough color = `space`
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
if colorname == LIGHT:
|
||
|
troughcolor = self.colors.bg
|
||
|
else:
|
||
|
troughcolor = self.colors.light
|
||
|
else:
|
||
|
troughcolor = Colors.update_hsv(self.colors.selectbg, vd=-0.2)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
background = self.colors.bg
|
||
|
textcolor = self.colors.primary
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
textcolor = self.colors.get(colorname)
|
||
|
background = self.colors.bg
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=textcolor,
|
||
|
background=background,
|
||
|
space=troughcolor,
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_label_style(self, colorname=DEFAULT):
|
||
|
"""Create a standard style for the ttk.Label widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TLabel"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
foreground = self.colors.fg
|
||
|
background = self.colors.bg
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
foreground = self.colors.get(colorname)
|
||
|
background = self.colors.bg
|
||
|
|
||
|
# standard label
|
||
|
self.style._build_configure(
|
||
|
ttkstyle, foreground=foreground, background=background
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_inverse_label_style(self, colorname=DEFAULT):
|
||
|
"""Create an inverted style for the ttk.Label.
|
||
|
|
||
|
The foreground and background are inverted versions of that
|
||
|
used in the standard label style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE_INVERSE = "Inverse.TLabel"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE_INVERSE
|
||
|
background = self.colors.fg
|
||
|
foreground = self.colors.bg
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE_INVERSE}"
|
||
|
background = self.colors.get(colorname)
|
||
|
foreground = self.colors.get_foreground(colorname)
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle, foreground=foreground, background=background
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_labelframe_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Labelframe widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TLabelframe"
|
||
|
|
||
|
background = self.colors.bg
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
foreground = self.colors.fg
|
||
|
ttkstyle = STYLE
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
else:
|
||
|
foreground = self.colors.get(colorname)
|
||
|
bordercolor = foreground
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
# create widget style
|
||
|
self.style._build_configure(
|
||
|
f"{ttkstyle}.Label",
|
||
|
foreground=foreground,
|
||
|
background=background,
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
relief=tk.RAISED,
|
||
|
borderwidth=1,
|
||
|
bordercolor=bordercolor,
|
||
|
lightcolor=background,
|
||
|
darkcolor=background,
|
||
|
background=background,
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_checkbutton_style(self, colorname=DEFAULT):
|
||
|
"""Create a standard style for the ttk.Checkbutton widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TCheckbutton"
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.3, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
colorname = PRIMARY
|
||
|
ttkstyle = STYLE
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.TCheckbutton"
|
||
|
|
||
|
# ( off, on, disabled )
|
||
|
images = self.create_checkbutton_assets(colorname)
|
||
|
|
||
|
element = ttkstyle.replace(".TC", ".C")
|
||
|
width = self.scale_size(20)
|
||
|
borderpad = self.scale_size(4)
|
||
|
self.style.element_create(
|
||
|
f"{element}.indicator",
|
||
|
"image",
|
||
|
images[1],
|
||
|
("disabled selected", images[4]),
|
||
|
("disabled alternate", images[5]),
|
||
|
("disabled", images[2]),
|
||
|
("alternate", images[3]),
|
||
|
("!selected", images[0]),
|
||
|
width=width,
|
||
|
border=borderpad,
|
||
|
sticky=tk.W,
|
||
|
)
|
||
|
self.style._build_configure(ttkstyle, foreground=self.colors.fg)
|
||
|
self.style.map(ttkstyle, foreground=[("disabled", disabled_fg)])
|
||
|
self.style.layout(
|
||
|
ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
"Checkbutton.padding",
|
||
|
{
|
||
|
"children": [
|
||
|
(
|
||
|
f"{element}.indicator",
|
||
|
{"side": tk.LEFT, "sticky": ""},
|
||
|
),
|
||
|
(
|
||
|
"Checkbutton.focus",
|
||
|
{
|
||
|
"children": [
|
||
|
(
|
||
|
"Checkbutton.label",
|
||
|
{"sticky": tk.NSEW},
|
||
|
)
|
||
|
],
|
||
|
"side": tk.LEFT,
|
||
|
"sticky": "",
|
||
|
},
|
||
|
),
|
||
|
],
|
||
|
"sticky": tk.NSEW,
|
||
|
},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_checkbutton_assets(self, colorname=DEFAULT):
|
||
|
"""Create the image assets used to build the standard
|
||
|
checkbutton style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
Tuple[str]:
|
||
|
A tuple of PhotoImage names.
|
||
|
"""
|
||
|
# set platform specific checkfont
|
||
|
winsys = self.style.tk.call("tk", "windowingsystem")
|
||
|
indicator = "✓"
|
||
|
if winsys == "win32":
|
||
|
# Windows font
|
||
|
fnt = ImageFont.truetype("seguisym.ttf", 120)
|
||
|
font_offset = -20
|
||
|
# TODO consider using ImageFont.getsize for offsets
|
||
|
elif winsys == "x11":
|
||
|
# Linux fonts
|
||
|
try:
|
||
|
# this should be available on most Linux distros
|
||
|
fnt = ImageFont.truetype("FreeSerif.ttf", 130)
|
||
|
font_offset = 10
|
||
|
except:
|
||
|
try:
|
||
|
# this should be available as a backup on Linux
|
||
|
# distros that don't have the FreeSerif.ttf file
|
||
|
fnt = ImageFont.truetype("DejaVuSans.ttf", 160)
|
||
|
font_offset = -15
|
||
|
except:
|
||
|
# If all else fails, use the default ImageFont
|
||
|
# this won't actually show anything in practice
|
||
|
# because of how I'm scaling the image, but it
|
||
|
# will prevent the program from crashing. I need
|
||
|
# a better solution for a missing font
|
||
|
fnt = ImageFont.load_default()
|
||
|
font_offset = 0
|
||
|
indicator = "x"
|
||
|
else:
|
||
|
# Mac OS font
|
||
|
fnt = ImageFont.truetype("LucidaGrande.ttc", 120)
|
||
|
font_offset = -10
|
||
|
|
||
|
prime_color = self.colors.get(colorname)
|
||
|
on_border = prime_color
|
||
|
on_fill = prime_color
|
||
|
off_fill = self.colors.bg
|
||
|
off_border = self.colors.selectbg
|
||
|
off_border = Colors.make_transparent(0.4, self.colors.fg, self.colors.bg)
|
||
|
disabled_fg = Colors.make_transparent(0.3, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if colorname == LIGHT:
|
||
|
check_color = self.colors.dark
|
||
|
on_border = check_color
|
||
|
elif colorname == DARK:
|
||
|
check_color = self.colors.light
|
||
|
on_border = check_color
|
||
|
else:
|
||
|
check_color = self.colors.selectfg
|
||
|
|
||
|
size = self.scale_size([14, 14])
|
||
|
|
||
|
# checkbutton off
|
||
|
checkbutton_off = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(checkbutton_off)
|
||
|
draw.rounded_rectangle(
|
||
|
[2, 2, 132, 132],
|
||
|
radius=16,
|
||
|
outline=off_border,
|
||
|
width=6,
|
||
|
fill=off_fill,
|
||
|
)
|
||
|
off_img = ImageTk.PhotoImage(
|
||
|
checkbutton_off.resize(size, Image.LANCZOS)
|
||
|
)
|
||
|
off_name = util.get_image_name(off_img)
|
||
|
self.theme_images[off_name] = off_img
|
||
|
|
||
|
# checkbutton on
|
||
|
checkbutton_on = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(checkbutton_on)
|
||
|
draw.rounded_rectangle(
|
||
|
[2, 2, 132, 132],
|
||
|
radius=16,
|
||
|
fill=on_fill,
|
||
|
outline=on_border,
|
||
|
width=3,
|
||
|
)
|
||
|
|
||
|
draw.text((20, font_offset), indicator, font=fnt, fill=check_color)
|
||
|
on_img = ImageTk.PhotoImage(checkbutton_on.resize(size, Image.LANCZOS))
|
||
|
on_name = util.get_image_name(on_img)
|
||
|
self.theme_images[on_name] = on_img
|
||
|
|
||
|
# checkbutton on/disabled
|
||
|
checkbutton_on_disabled = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(checkbutton_on_disabled)
|
||
|
draw.rounded_rectangle(
|
||
|
[2, 2, 132, 132],
|
||
|
radius=16,
|
||
|
fill=disabled_fg,
|
||
|
outline=disabled_fg,
|
||
|
width=3,
|
||
|
)
|
||
|
|
||
|
draw.text((20, font_offset), indicator, font=fnt, fill=off_fill)
|
||
|
on_dis_img = ImageTk.PhotoImage(checkbutton_on_disabled.resize(size, Image.LANCZOS))
|
||
|
on_dis_name = util.get_image_name(on_dis_img)
|
||
|
self.theme_images[on_dis_name] = on_dis_img
|
||
|
|
||
|
# checkbutton alt
|
||
|
checkbutton_alt = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(checkbutton_alt)
|
||
|
draw.rounded_rectangle(
|
||
|
[2, 2, 132, 132],
|
||
|
radius=16,
|
||
|
fill=on_fill,
|
||
|
outline=on_border,
|
||
|
width=3,
|
||
|
)
|
||
|
draw.line([36, 67, 100, 67], fill=check_color, width=12)
|
||
|
alt_img = ImageTk.PhotoImage(
|
||
|
checkbutton_alt.resize(size, Image.LANCZOS)
|
||
|
)
|
||
|
alt_name = util.get_image_name(alt_img)
|
||
|
self.theme_images[alt_name] = alt_img
|
||
|
|
||
|
# checkbutton alt/disabled
|
||
|
checkbutton_alt_disabled = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(checkbutton_alt_disabled)
|
||
|
draw.rounded_rectangle(
|
||
|
[2, 2, 132, 132],
|
||
|
radius=16,
|
||
|
fill=disabled_fg,
|
||
|
outline=disabled_fg,
|
||
|
width=3,
|
||
|
)
|
||
|
draw.line([36, 67, 100, 67], fill=off_fill, width=12)
|
||
|
alt_dis_img = ImageTk.PhotoImage(
|
||
|
checkbutton_alt_disabled.resize(size, Image.LANCZOS)
|
||
|
)
|
||
|
alt_dis_name = util.get_image_name(alt_dis_img)
|
||
|
self.theme_images[alt_dis_name] = alt_dis_img
|
||
|
|
||
|
# checkbutton disabled
|
||
|
checkbutton_disabled = Image.new("RGBA", (134, 134))
|
||
|
draw = ImageDraw.Draw(checkbutton_disabled)
|
||
|
draw.rounded_rectangle(
|
||
|
[2, 2, 132, 132], radius=16, outline=disabled_fg, width=3
|
||
|
)
|
||
|
disabled_img = ImageTk.PhotoImage(
|
||
|
checkbutton_disabled.resize(size, Image.LANCZOS)
|
||
|
)
|
||
|
disabled_name = util.get_image_name(disabled_img)
|
||
|
self.theme_images[disabled_name] = disabled_img
|
||
|
|
||
|
return off_name, on_name, disabled_name, alt_name, on_dis_name, alt_dis_name
|
||
|
|
||
|
def create_menubutton_style(self, colorname=DEFAULT):
|
||
|
"""Create a solid style for the ttk.Menubutton widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TMenubutton"
|
||
|
|
||
|
foreground = self.colors.get_foreground(colorname)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
background = self.colors.primary
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
background = self.colors.get(colorname)
|
||
|
|
||
|
disabled_bg = Colors.make_transparent(0.10, self.colors.fg, self.colors.bg)
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
pressed = Colors.make_transparent(0.80, background, self.colors.bg)
|
||
|
hover = Colors.make_transparent(0.90, background, self.colors.bg)
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=foreground,
|
||
|
background=background,
|
||
|
bordercolor=background,
|
||
|
darkcolor=background,
|
||
|
lightcolor=background,
|
||
|
arrowsize=self.scale_size(4),
|
||
|
arrowcolor=foreground,
|
||
|
arrowpadding=(0, 0, 15, 0),
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor=self.colors.selectfg,
|
||
|
padding=(10, 5),
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
arrowcolor=[("disabled", disabled_fg)],
|
||
|
foreground=[("disabled", disabled_fg)],
|
||
|
background=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("disabled", disabled_bg),
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_outline_menubutton_style(self, colorname=DEFAULT):
|
||
|
"""Create an outline button style for the ttk.Menubutton widget
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "Outline.TMenubutton"
|
||
|
|
||
|
disabled_fg = Colors.make_transparent(0.30, self.colors.fg, self.colors.bg)
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
colorname = PRIMARY
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
foreground = self.colors.get(colorname)
|
||
|
background = self.colors.get_foreground(colorname)
|
||
|
foreground_pressed = background
|
||
|
bordercolor = foreground
|
||
|
pressed = foreground
|
||
|
hover = foreground
|
||
|
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
foreground=foreground,
|
||
|
background=self.colors.bg,
|
||
|
bordercolor=bordercolor,
|
||
|
darkcolor=self.colors.bg,
|
||
|
lightcolor=self.colors.bg,
|
||
|
relief=tk.RAISED,
|
||
|
focusthickness=0,
|
||
|
focuscolor=foreground,
|
||
|
padding=(10, 5),
|
||
|
arrowcolor=foreground,
|
||
|
arrowpadding=(0, 0, 15, 0),
|
||
|
arrowsize=self.scale_size(4),
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle,
|
||
|
foreground=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed !disabled", foreground_pressed),
|
||
|
("hover !disabled", foreground_pressed),
|
||
|
],
|
||
|
background=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed", pressed),
|
||
|
("hover", hover),
|
||
|
],
|
||
|
darkcolor=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("pressed !disabled", pressed),
|
||
|
("hover !disabled", hover),
|
||
|
],
|
||
|
arrowcolor=[
|
||
|
("disabled", disabled_fg),
|
||
|
("pressed", foreground_pressed),
|
||
|
("hover", foreground_pressed),
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_notebook_style(self, colorname=DEFAULT):
|
||
|
"""Create a standard style for the ttk.Notebook widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TNotebook"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
foreground = self.colors.inputfg
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
foreground = self.colors.selectfg
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
background = self.colors.inputbg
|
||
|
selectfg = self.colors.fg
|
||
|
ttkstyle = STYLE
|
||
|
else:
|
||
|
selectfg = self.colors.get_foreground(colorname)
|
||
|
background = self.colors.get(colorname)
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
|
||
|
ttkstyle_tab = f"{ttkstyle}.Tab"
|
||
|
|
||
|
# create widget style
|
||
|
self.style._build_configure(
|
||
|
ttkstyle,
|
||
|
background=self.colors.bg,
|
||
|
bordercolor=bordercolor,
|
||
|
lightcolor=self.colors.bg,
|
||
|
darkcolor=self.colors.bg,
|
||
|
tabmargins=(0, 1, 1, 0),
|
||
|
)
|
||
|
self.style._build_configure(
|
||
|
ttkstyle_tab, focuscolor="", foreground=foreground, padding=(6, 5)
|
||
|
)
|
||
|
self.style.map(
|
||
|
ttkstyle_tab,
|
||
|
background=[
|
||
|
("selected", self.colors.bg),
|
||
|
("!selected", background),
|
||
|
],
|
||
|
lightcolor=[
|
||
|
("selected", self.colors.bg),
|
||
|
("!selected", background),
|
||
|
],
|
||
|
bordercolor=[
|
||
|
("selected", bordercolor),
|
||
|
("!selected", bordercolor),
|
||
|
],
|
||
|
padding=[("selected", (6, 5)), ("!selected", (6, 5))],
|
||
|
foreground=[("selected", foreground), ("!selected", selectfg)],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def create_panedwindow_style(self, colorname=DEFAULT):
|
||
|
"""Create a standard style for the ttk.Panedwindow widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
H_STYLE = "Horizontal.TPanedwindow"
|
||
|
V_STYLE = "Vertical.TPanedwindow"
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
default_color = self.colors.border
|
||
|
else:
|
||
|
default_color = self.colors.selectbg
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
sashcolor = default_color
|
||
|
h_ttkstyle = H_STYLE
|
||
|
v_ttkstyle = V_STYLE
|
||
|
else:
|
||
|
sashcolor = self.colors.get(colorname)
|
||
|
h_ttkstyle = f"{colorname}.{H_STYLE}"
|
||
|
v_ttkstyle = f"{colorname}.{V_STYLE}"
|
||
|
|
||
|
self.style._build_configure(
|
||
|
"Sash", gripcount=0, sashthickness=self.scale_size(2)
|
||
|
)
|
||
|
self.style._build_configure(h_ttkstyle, background=sashcolor)
|
||
|
self.style._build_configure(v_ttkstyle, background=sashcolor)
|
||
|
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(h_ttkstyle)
|
||
|
self.style._register_ttkstyle(v_ttkstyle)
|
||
|
|
||
|
def create_sizegrip_assets(self, color):
|
||
|
"""Create image assets used to build the sizegrip style.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
color (str):
|
||
|
The color _value_ used to draw the image.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
The PhotoImage name.
|
||
|
"""
|
||
|
from math import ceil
|
||
|
|
||
|
box = self.scale_size(1)
|
||
|
pad = box * 2
|
||
|
chunk = box + pad # 4
|
||
|
|
||
|
w = chunk * 3 + pad # 14
|
||
|
h = chunk * 3 + pad # 14
|
||
|
|
||
|
size = [w, h]
|
||
|
|
||
|
im = Image.new("RGBA", size)
|
||
|
draw = ImageDraw.Draw(im)
|
||
|
|
||
|
draw.rectangle((chunk * 2 + pad, pad, chunk * 3, chunk), fill=color)
|
||
|
draw.rectangle(
|
||
|
(chunk * 2 + pad, chunk + pad, chunk * 3, chunk * 2), fill=color
|
||
|
)
|
||
|
draw.rectangle(
|
||
|
(chunk * 2 + pad, chunk * 2 + pad, chunk * 3, chunk * 3),
|
||
|
fill=color,
|
||
|
)
|
||
|
|
||
|
draw.rectangle(
|
||
|
(chunk + pad, chunk + pad, chunk * 2, chunk * 2), fill=color
|
||
|
)
|
||
|
draw.rectangle(
|
||
|
(chunk + pad, chunk * 2 + pad, chunk * 2, chunk * 3), fill=color
|
||
|
)
|
||
|
|
||
|
draw.rectangle((pad, chunk * 2 + pad, chunk, chunk * 3), fill=color)
|
||
|
|
||
|
_img = ImageTk.PhotoImage(im)
|
||
|
_name = util.get_image_name(_img)
|
||
|
self.theme_images[_name] = _img
|
||
|
return _name
|
||
|
|
||
|
def create_sizegrip_style(self, colorname=DEFAULT):
|
||
|
"""Create a style for the ttk.Sizegrip widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
colorname (str):
|
||
|
The color label used to style the widget.
|
||
|
"""
|
||
|
STYLE = "TSizegrip"
|
||
|
|
||
|
if any([colorname == DEFAULT, colorname == ""]):
|
||
|
ttkstyle = STYLE
|
||
|
|
||
|
if self.is_light_theme:
|
||
|
grip_color = self.colors.border
|
||
|
else:
|
||
|
grip_color = self.colors.inputbg
|
||
|
else:
|
||
|
ttkstyle = f"{colorname}.{STYLE}"
|
||
|
grip_color = self.colors.get(colorname)
|
||
|
|
||
|
image = self.create_sizegrip_assets(grip_color)
|
||
|
|
||
|
self.style.element_create(
|
||
|
f"{ttkstyle}.Sizegrip.sizegrip", "image", image
|
||
|
)
|
||
|
self.style.layout(
|
||
|
ttkstyle,
|
||
|
[
|
||
|
(
|
||
|
f"{ttkstyle}.Sizegrip.sizegrip",
|
||
|
{"side": tk.BOTTOM, "sticky": tk.SE},
|
||
|
)
|
||
|
],
|
||
|
)
|
||
|
# register ttkstyle
|
||
|
self.style._register_ttkstyle(ttkstyle)
|
||
|
|
||
|
def update_combobox_popdown_style(self, widget):
|
||
|
"""Update the legacy ttk.Combobox elements. This method is
|
||
|
called every time the theme is changed in order to ensure
|
||
|
that the legacy tkinter components embedded in this ttk widget
|
||
|
are styled appropriate to the current theme.
|
||
|
|
||
|
The ttk.Combobox contains several elements that are not styled
|
||
|
using the ttk theme engine. This includes the **popdownwindow**
|
||
|
and the **scrollbar**. Both of these widgets are configured
|
||
|
manually using calls to tcl/tk.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (ttk.Combobox):
|
||
|
The combobox element to be updated.
|
||
|
"""
|
||
|
if self.is_light_theme:
|
||
|
bordercolor = self.colors.border
|
||
|
else:
|
||
|
bordercolor = self.colors.selectbg
|
||
|
|
||
|
tk_settings = []
|
||
|
tk_settings.extend(["-borderwidth", 2])
|
||
|
tk_settings.extend(["-highlightthickness", 1])
|
||
|
tk_settings.extend(["-highlightcolor", bordercolor])
|
||
|
tk_settings.extend(["-background", self.colors.inputbg])
|
||
|
tk_settings.extend(["-foreground", self.colors.inputfg])
|
||
|
tk_settings.extend(["-selectbackground", self.colors.selectbg])
|
||
|
tk_settings.extend(["-selectforeground", self.colors.selectfg])
|
||
|
|
||
|
# set popdown style
|
||
|
popdown = widget.tk.eval(f"ttk::combobox::PopdownWindow {widget}")
|
||
|
widget.tk.call(f"{popdown}.f.l", "configure", *tk_settings)
|
||
|
|
||
|
# set scrollbar style
|
||
|
sb_style = "TCombobox.Vertical.TScrollbar"
|
||
|
widget.tk.call(f"{popdown}.f.sb", "configure", "-style", sb_style)
|
||
|
|
||
|
|
||
|
class Keywords:
|
||
|
|
||
|
# TODO possibly refactor the bootstyle keyword methods into this class?
|
||
|
# Leave for now.
|
||
|
|
||
|
COLORS = [
|
||
|
"primary",
|
||
|
"secondary",
|
||
|
"success",
|
||
|
"info",
|
||
|
"warning",
|
||
|
"danger",
|
||
|
"light",
|
||
|
"dark",
|
||
|
]
|
||
|
ORIENTS = ["horizontal", "vertical"]
|
||
|
TYPES = [
|
||
|
"outline",
|
||
|
"link",
|
||
|
"inverse",
|
||
|
"round",
|
||
|
"square",
|
||
|
"striped",
|
||
|
"focus",
|
||
|
"input",
|
||
|
"date",
|
||
|
"metersubtxt",
|
||
|
"meter",
|
||
|
"table"
|
||
|
]
|
||
|
CLASSES = [
|
||
|
"button",
|
||
|
"progressbar",
|
||
|
"checkbutton",
|
||
|
"combobox",
|
||
|
"entry",
|
||
|
"labelframe",
|
||
|
"label",
|
||
|
"frame",
|
||
|
"floodgauge",
|
||
|
"sizegrip",
|
||
|
"optionmenu",
|
||
|
"menubutton",
|
||
|
"menu",
|
||
|
"notebook",
|
||
|
"panedwindow",
|
||
|
"radiobutton",
|
||
|
"separator",
|
||
|
"scrollbar",
|
||
|
"spinbox",
|
||
|
"scale",
|
||
|
"text",
|
||
|
"toolbutton",
|
||
|
"treeview",
|
||
|
"toggle",
|
||
|
"tk",
|
||
|
"calendar",
|
||
|
"listbox",
|
||
|
"canvas",
|
||
|
"toplevel",
|
||
|
]
|
||
|
COLOR_PATTERN = re.compile("|".join(COLORS))
|
||
|
ORIENT_PATTERN = re.compile("|".join(ORIENTS))
|
||
|
CLASS_PATTERN = re.compile("|".join(CLASSES))
|
||
|
TYPE_PATTERN = re.compile("|".join(TYPES))
|
||
|
|
||
|
|
||
|
class Bootstyle:
|
||
|
@staticmethod
|
||
|
def ttkstyle_widget_class(widget=None, string=""):
|
||
|
"""Find and return the widget class
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (Widget):
|
||
|
The widget object.
|
||
|
|
||
|
string (str):
|
||
|
A keyword string to parse.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
A widget class keyword.
|
||
|
"""
|
||
|
# find widget class from string pattern
|
||
|
match = re.search(Keywords.CLASS_PATTERN, string.lower())
|
||
|
if match is not None:
|
||
|
widget_class = match.group(0)
|
||
|
return widget_class
|
||
|
|
||
|
# find widget class from tkinter/tcl method
|
||
|
if widget is None:
|
||
|
return ""
|
||
|
_class = widget.winfo_class()
|
||
|
match = re.search(Keywords.CLASS_PATTERN, _class.lower())
|
||
|
if match is not None:
|
||
|
widget_class = match.group(0)
|
||
|
return widget_class
|
||
|
else:
|
||
|
return ""
|
||
|
|
||
|
@staticmethod
|
||
|
def ttkstyle_widget_type(string):
|
||
|
"""Find and return the widget type.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
string (str):
|
||
|
A keyword string to parse.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
A widget type keyword.
|
||
|
"""
|
||
|
match = re.search(Keywords.TYPE_PATTERN, string.lower())
|
||
|
if match is None:
|
||
|
return ""
|
||
|
else:
|
||
|
widget_type = match.group(0)
|
||
|
return widget_type
|
||
|
|
||
|
@staticmethod
|
||
|
def ttkstyle_widget_orient(widget=None, string="", **kwargs):
|
||
|
"""Find and return widget orient, or default orient for widget if
|
||
|
a widget with orientation.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (Widget):
|
||
|
The widget object.
|
||
|
|
||
|
string (str):
|
||
|
A keyword string to parse.
|
||
|
|
||
|
**kwargs:
|
||
|
Optional keyword arguments passed in the widget constructor.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
A widget orientation keyword.
|
||
|
"""
|
||
|
# string method (priority)
|
||
|
match = re.search(Keywords.ORIENT_PATTERN, string)
|
||
|
widget_orient = ""
|
||
|
|
||
|
if match is not None:
|
||
|
widget_orient = match.group(0)
|
||
|
return widget_orient
|
||
|
|
||
|
# orient from kwargs
|
||
|
if "orient" in kwargs:
|
||
|
_orient = kwargs.pop("orient")
|
||
|
if _orient == "h":
|
||
|
widget_orient = "horizontal"
|
||
|
elif _orient == "v":
|
||
|
widget_orient = "vertical"
|
||
|
else:
|
||
|
widget_orient = _orient
|
||
|
return widget_orient
|
||
|
|
||
|
# orient from settings
|
||
|
if widget is None:
|
||
|
return widget_orient
|
||
|
try:
|
||
|
widget_orient = str(widget.cget("orient"))
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
return widget_orient
|
||
|
|
||
|
@staticmethod
|
||
|
def ttkstyle_widget_color(string):
|
||
|
"""Find and return widget color
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
string (str):
|
||
|
A keyword string to parse.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
A color keyword.
|
||
|
"""
|
||
|
_color = re.search(Keywords.COLOR_PATTERN, string.lower())
|
||
|
if _color is None:
|
||
|
return ""
|
||
|
else:
|
||
|
widget_color = _color.group(0)
|
||
|
return widget_color
|
||
|
|
||
|
@staticmethod
|
||
|
def ttkstyle_name(widget=None, string="", **kwargs):
|
||
|
"""Parse a string to build and return a ttkstyle name.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (Widget):
|
||
|
The widget object.
|
||
|
|
||
|
string (str):
|
||
|
A keyword string to parse.
|
||
|
|
||
|
**kwargs:
|
||
|
Other keyword arguments to parse widget orientation.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
A ttk style name
|
||
|
"""
|
||
|
style_string = "".join(string).lower()
|
||
|
widget_color = Bootstyle.ttkstyle_widget_color(style_string)
|
||
|
widget_type = Bootstyle.ttkstyle_widget_type(style_string)
|
||
|
widget_orient = Bootstyle.ttkstyle_widget_orient(
|
||
|
widget, style_string, **kwargs
|
||
|
)
|
||
|
widget_class = Bootstyle.ttkstyle_widget_class(widget, style_string)
|
||
|
|
||
|
if widget_color:
|
||
|
widget_color = f"{widget_color}."
|
||
|
|
||
|
if widget_type:
|
||
|
widget_type = f"{widget_type.title()}."
|
||
|
|
||
|
if widget_orient:
|
||
|
widget_orient = f"{widget_orient.title()}."
|
||
|
|
||
|
if widget_class.startswith("t"):
|
||
|
widget_class = widget_class.title()
|
||
|
else:
|
||
|
widget_class = f"T{widget_class.title()}"
|
||
|
|
||
|
ttkstyle = f"{widget_color}{widget_type}{widget_orient}{widget_class}"
|
||
|
return ttkstyle
|
||
|
|
||
|
@staticmethod
|
||
|
def ttkstyle_method_name(widget=None, string=""):
|
||
|
"""Parse a string to build and return the name of the
|
||
|
`StyleBuilderTTK` method that creates the ttk style for the
|
||
|
target widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (Widget):
|
||
|
The widget object to lookup.
|
||
|
|
||
|
string (str):
|
||
|
The keyword string to parse.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
The name of the update method used to update the widget.
|
||
|
"""
|
||
|
style_string = "".join(string).lower()
|
||
|
widget_type = Bootstyle.ttkstyle_widget_type(style_string)
|
||
|
widget_class = Bootstyle.ttkstyle_widget_class(widget, style_string)
|
||
|
|
||
|
if widget_type:
|
||
|
widget_type = f"_{widget_type}"
|
||
|
|
||
|
if widget_class:
|
||
|
widget_class = f"_{widget_class}"
|
||
|
|
||
|
if not widget_type and not widget_class:
|
||
|
return ""
|
||
|
else:
|
||
|
method_name = f"create{widget_type}{widget_class}_style"
|
||
|
return method_name
|
||
|
|
||
|
@staticmethod
|
||
|
def tkupdate_method_name(widget):
|
||
|
"""Lookup the tkinter style update method from the widget class
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (Widget):
|
||
|
The widget object to lookup.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
The name of the method used to update the widget object.
|
||
|
"""
|
||
|
widget_class = Bootstyle.ttkstyle_widget_class(widget)
|
||
|
|
||
|
if widget_class:
|
||
|
widget_class = f"_{widget_class}"
|
||
|
|
||
|
method_name = f"update{widget_class}_style"
|
||
|
return method_name
|
||
|
|
||
|
@staticmethod
|
||
|
def override_ttk_widget_constructor(func):
|
||
|
"""Override widget constructors with bootstyle api options.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
func (Callable):
|
||
|
The widget class `__init__` method
|
||
|
"""
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
|
||
|
# capture bootstyle and style arguments
|
||
|
if "bootstyle" in kwargs:
|
||
|
bootstyle = kwargs.pop("bootstyle")
|
||
|
else:
|
||
|
bootstyle = ""
|
||
|
|
||
|
if "style" in kwargs:
|
||
|
style = kwargs.pop("style") or ""
|
||
|
else:
|
||
|
style = ""
|
||
|
|
||
|
# instantiate the widget
|
||
|
func(self, *args, **kwargs)
|
||
|
|
||
|
# must be called AFTER instantiation in order to use winfo_class
|
||
|
# in the `get_ttkstyle_name` method
|
||
|
|
||
|
if style:
|
||
|
if Style.get_instance().style_exists_in_theme(style):
|
||
|
self.configure(style=style)
|
||
|
else:
|
||
|
ttkstyle = Bootstyle.update_ttk_widget_style(
|
||
|
self, style, **kwargs
|
||
|
)
|
||
|
self.configure(style=ttkstyle)
|
||
|
elif bootstyle:
|
||
|
ttkstyle = Bootstyle.update_ttk_widget_style(
|
||
|
self, bootstyle, **kwargs
|
||
|
)
|
||
|
self.configure(style=ttkstyle)
|
||
|
else:
|
||
|
ttkstyle = Bootstyle.update_ttk_widget_style(
|
||
|
self, "default", **kwargs
|
||
|
)
|
||
|
self.configure(style=ttkstyle)
|
||
|
|
||
|
return __init__
|
||
|
|
||
|
@staticmethod
|
||
|
def override_ttk_widget_configure(func):
|
||
|
"""Overrides the configure method on a ttk widget.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
func (Callable):
|
||
|
The widget class `configure` method
|
||
|
"""
|
||
|
|
||
|
def configure(self, cnf=None, **kwargs):
|
||
|
# get configuration
|
||
|
if cnf in ("bootstyle", "style"):
|
||
|
return self.cget("style")
|
||
|
|
||
|
if cnf is not None:
|
||
|
return func(self, cnf)
|
||
|
|
||
|
# set configuration
|
||
|
if "bootstyle" in kwargs:
|
||
|
bootstyle = kwargs.pop("bootstyle")
|
||
|
else:
|
||
|
bootstyle = ""
|
||
|
|
||
|
if "style" in kwargs:
|
||
|
style = kwargs.get("style")
|
||
|
ttkstyle = Bootstyle.update_ttk_widget_style(
|
||
|
self, style, **kwargs
|
||
|
)
|
||
|
elif bootstyle:
|
||
|
ttkstyle = Bootstyle.update_ttk_widget_style(
|
||
|
self, bootstyle, **kwargs
|
||
|
)
|
||
|
kwargs.update(style=ttkstyle)
|
||
|
|
||
|
# update widget configuration
|
||
|
func(self, cnf, **kwargs)
|
||
|
|
||
|
return configure
|
||
|
|
||
|
@staticmethod
|
||
|
def update_ttk_widget_style(
|
||
|
widget: ttk.Widget = None, style_string: str = None, **kwargs
|
||
|
):
|
||
|
"""Update the ttk style or create if not existing.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (ttk.Widget):
|
||
|
The widget instance being updated.
|
||
|
|
||
|
style_string (str):
|
||
|
The style string to evalulate. May be the `style`, `ttkstyle`
|
||
|
or `bootstyle` argument depending on the context and scenario.
|
||
|
|
||
|
**kwargs:
|
||
|
Other optional keyword arguments.
|
||
|
|
||
|
Returns:
|
||
|
|
||
|
str:
|
||
|
The ttkstyle or empty string if there is none.
|
||
|
"""
|
||
|
style: Style = Style.get_instance() or Style()
|
||
|
|
||
|
# get existing widget style if not provided
|
||
|
if style_string is None:
|
||
|
style_string = widget.cget("style")
|
||
|
|
||
|
# do nothing if the style has not been set
|
||
|
if not style_string:
|
||
|
return ""
|
||
|
|
||
|
if style_string == '.':
|
||
|
return '.'
|
||
|
|
||
|
# build style if not existing (example: theme changed)
|
||
|
ttkstyle = Bootstyle.ttkstyle_name(widget, style_string, **kwargs)
|
||
|
if not style.style_exists_in_theme(ttkstyle):
|
||
|
widget_color = Bootstyle.ttkstyle_widget_color(ttkstyle)
|
||
|
method_name = Bootstyle.ttkstyle_method_name(widget, ttkstyle)
|
||
|
builder: StyleBuilderTTK = style._get_builder()
|
||
|
builder_method = builder.name_to_method(method_name)
|
||
|
builder_method(builder, widget_color)
|
||
|
|
||
|
# subscribe popdown style to theme changes
|
||
|
try:
|
||
|
if widget.winfo_class() == "TCombobox":
|
||
|
builder: StyleBuilderTTK = style._get_builder()
|
||
|
winfo_id = hex(widget.winfo_id())
|
||
|
winfo_pathname = widget.winfo_pathname(winfo_id)
|
||
|
Publisher.subscribe(
|
||
|
name=winfo_pathname,
|
||
|
func=lambda w=widget: builder.update_combobox_popdown_style(
|
||
|
w
|
||
|
),
|
||
|
channel=Channel.STD,
|
||
|
)
|
||
|
builder.update_combobox_popdown_style(widget)
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
return ttkstyle
|
||
|
|
||
|
@staticmethod
|
||
|
def setup_ttkbootstap_api():
|
||
|
"""Setup ttkbootstrap for use with tkinter and ttk. This method
|
||
|
is called when ttkbootstrap is imported to perform all of the
|
||
|
necessary method overrides that implement the bootstyle api."""
|
||
|
from ttkbootstrap.widgets import TTK_WIDGETS
|
||
|
from ttkbootstrap.widgets import TK_WIDGETS
|
||
|
|
||
|
# TTK WIDGETS
|
||
|
for widget in TTK_WIDGETS:
|
||
|
try:
|
||
|
# override widget constructor
|
||
|
_init = Bootstyle.override_ttk_widget_constructor(
|
||
|
widget.__init__
|
||
|
)
|
||
|
widget.__init__ = _init
|
||
|
|
||
|
# override configure method
|
||
|
_configure = Bootstyle.override_ttk_widget_configure(
|
||
|
widget.configure
|
||
|
)
|
||
|
widget.configure = _configure
|
||
|
widget.config = widget.configure
|
||
|
|
||
|
# override get and set methods
|
||
|
_orig_getitem = widget.__getitem
|
||
|
_orig_setitem = widget.__setitem
|
||
|
|
||
|
def __setitem(self, key, val):
|
||
|
if key in ("bootstyle", "style"):
|
||
|
return _configure(self, **{key: val})
|
||
|
return _orig_setitem(key, val)
|
||
|
|
||
|
def __getitem(self, key):
|
||
|
if key in ("bootstyle", "style"):
|
||
|
return _configure(self, cnf=key)
|
||
|
return _orig_getitem(key)
|
||
|
|
||
|
if (
|
||
|
widget.__name__ != "OptionMenu"
|
||
|
): # this has it's own override
|
||
|
widget.__setitem__ = __setitem
|
||
|
widget.__getitem__ = __getitem
|
||
|
except:
|
||
|
# this may fail in python 3.6 for ttk widgets that do not exist
|
||
|
# in that version.
|
||
|
continue
|
||
|
|
||
|
# TK WIDGETS
|
||
|
for widget in TK_WIDGETS:
|
||
|
# override widget constructor
|
||
|
_init = Bootstyle.override_tk_widget_constructor(widget.__init__)
|
||
|
widget.__init__ = _init
|
||
|
|
||
|
@staticmethod
|
||
|
def update_tk_widget_style(widget):
|
||
|
"""Lookup the widget name and call the appropriate update
|
||
|
method
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
widget (object):
|
||
|
The tcl/tk name given by `tkinter.Widget.winfo_name()`
|
||
|
"""
|
||
|
try:
|
||
|
style = Style.get_instance()
|
||
|
method_name = Bootstyle.tkupdate_method_name(widget)
|
||
|
builder = style._get_builder_tk()
|
||
|
builder_method = getattr(StyleBuilderTK, method_name)
|
||
|
builder_method(builder, widget)
|
||
|
except:
|
||
|
"""Must pass here to prevent a failure when the user calls
|
||
|
the `Style`method BEFORE an instance of `Tk` is instantiated.
|
||
|
This will defer the update of the `Tk` background until the end
|
||
|
of the `BootStyle` object instantiation (created by the `Style`
|
||
|
method)"""
|
||
|
pass
|
||
|
|
||
|
@staticmethod
|
||
|
def override_tk_widget_constructor(func):
|
||
|
"""Override widget constructors to apply default style for tk
|
||
|
widgets.
|
||
|
|
||
|
Parameters:
|
||
|
|
||
|
func (Callable):
|
||
|
The `__init__` method for this widget.
|
||
|
"""
|
||
|
|
||
|
def __init__wrapper(self, *args, **kwargs):
|
||
|
|
||
|
# check for autostyle flag
|
||
|
if "autostyle" in kwargs:
|
||
|
autostyle = kwargs.pop("autostyle")
|
||
|
else:
|
||
|
autostyle = True
|
||
|
|
||
|
# instantiate the widget
|
||
|
func(self, *args, **kwargs)
|
||
|
|
||
|
if autostyle:
|
||
|
Publisher.subscribe(
|
||
|
name=str(self),
|
||
|
func=lambda w=self: Bootstyle.update_tk_widget_style(w),
|
||
|
channel=Channel.STD,
|
||
|
)
|
||
|
Bootstyle.update_tk_widget_style(self)
|
||
|
|
||
|
return __init__wrapper
|