scustomtkinter 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scustomtkinter/__init__.py +81 -0
- scustomtkinter/assets/images/error.png +0 -0
- scustomtkinter/assets/images/info.png +0 -0
- scustomtkinter/assets/images/warning.png +0 -0
- scustomtkinter/assets/sCTkThemes.json +1963 -0
- scustomtkinter/license_morecustomtkinterwidgets.txt +22 -0
- scustomtkinter/sctk_button_primary.py +366 -0
- scustomtkinter/sctk_button_secondary.py +354 -0
- scustomtkinter/sctk_button_tertiary.py +375 -0
- scustomtkinter/sctk_checkbox.py +270 -0
- scustomtkinter/sctk_combobox.py +155 -0
- scustomtkinter/sctk_core.py +15 -0
- scustomtkinter/sctk_dial.py +1472 -0
- scustomtkinter/sctk_dialog.py +701 -0
- scustomtkinter/sctk_dialog_toplevel.py +268 -0
- scustomtkinter/sctk_entry_primary.py +400 -0
- scustomtkinter/sctk_entry_secondary.py +342 -0
- scustomtkinter/sctk_file_explorer.py +879 -0
- scustomtkinter/sctk_frame.py +210 -0
- scustomtkinter/sctk_frame_labeled_primary.py +667 -0
- scustomtkinter/sctk_frame_labeled_secondary.py +598 -0
- scustomtkinter/sctk_frequency_display.py +636 -0
- scustomtkinter/sctk_label_primary.py +283 -0
- scustomtkinter/sctk_label_secondary.py +275 -0
- scustomtkinter/sctk_label_tertiary.py +278 -0
- scustomtkinter/sctk_messagebox.py +275 -0
- scustomtkinter/sctk_notebook.py +1103 -0
- scustomtkinter/sctk_optionmenu_border_mixin.py +203 -0
- scustomtkinter/sctk_optionmenu_primary.py +378 -0
- scustomtkinter/sctk_optionmenu_secondary.py +346 -0
- scustomtkinter/sctk_path_chooser.py +516 -0
- scustomtkinter/sctk_progress_bar.py +274 -0
- scustomtkinter/sctk_radiobutton.py +360 -0
- scustomtkinter/sctk_scroll_mixin.py +546 -0
- scustomtkinter/sctk_scrollable_frame.py +795 -0
- scustomtkinter/sctk_scrollbar.py +313 -0
- scustomtkinter/sctk_segmentedbutton.py +424 -0
- scustomtkinter/sctk_selector.py +581 -0
- scustomtkinter/sctk_separator.py +308 -0
- scustomtkinter/sctk_slider.py +264 -0
- scustomtkinter/sctk_smeter.py +405 -0
- scustomtkinter/sctk_smeter_bar.py +557 -0
- scustomtkinter/sctk_spinbox.py +664 -0
- scustomtkinter/sctk_switch.py +463 -0
- scustomtkinter/sctk_tableview.py +869 -0
- scustomtkinter/sctk_tabview.py +409 -0
- scustomtkinter/sctk_textbox_primary.py +291 -0
- scustomtkinter/sctk_textbox_secondary.py +225 -0
- scustomtkinter/sctk_toplevel.py +153 -0
- scustomtkinter/themeable_widget.py +938 -0
- scustomtkinter-0.2.1.dist-info/METADATA +336 -0
- scustomtkinter-0.2.1.dist-info/RECORD +101 -0
- scustomtkinter-0.2.1.dist-info/WHEEL +5 -0
- scustomtkinter-0.2.1.dist-info/entry_points.txt +2 -0
- scustomtkinter-0.2.1.dist-info/licenses/LICENSE +21 -0
- scustomtkinter-0.2.1.dist-info/licenses/scustomtkinter/license_morecustomtkinterwidgets.txt +22 -0
- scustomtkinter-0.2.1.dist-info/top_level.txt +2 -0
- scustomtkinter_pygubu/__init__.py +1 -0
- scustomtkinter_pygubu/designer/__init__.py +0 -0
- scustomtkinter_pygubu/designer/plugin.py +1854 -0
- scustomtkinter_pygubu/designer/properties.py +247 -0
- scustomtkinter_pygubu/sCTkButtonPrimarybo.py +46 -0
- scustomtkinter_pygubu/sCTkButtonSecondarybo.py +45 -0
- scustomtkinter_pygubu/sCTkButtonTertiarybo.py +44 -0
- scustomtkinter_pygubu/sCTkCheckBoxbo.py +45 -0
- scustomtkinter_pygubu/sCTkComboBoxbo.py +45 -0
- scustomtkinter_pygubu/sCTkCorebo.py +120 -0
- scustomtkinter_pygubu/sCTkDialbo.py +371 -0
- scustomtkinter_pygubu/sCTkDialogbo.py +397 -0
- scustomtkinter_pygubu/sCTkEntryPrimarybo.py +100 -0
- scustomtkinter_pygubu/sCTkEntrySecondarybo.py +98 -0
- scustomtkinter_pygubu/sCTkFileExplorerbo.py +189 -0
- scustomtkinter_pygubu/sCTkFrameLabeledPrimarybo.py +69 -0
- scustomtkinter_pygubu/sCTkFrameLabeledSecondarybo.py +71 -0
- scustomtkinter_pygubu/sCTkFramebo.py +46 -0
- scustomtkinter_pygubu/sCTkFrequencyDisplaybo.py +175 -0
- scustomtkinter_pygubu/sCTkLabelPrimarybo.py +45 -0
- scustomtkinter_pygubu/sCTkLabelSecondarybo.py +44 -0
- scustomtkinter_pygubu/sCTkLabelTertiarybo.py +45 -0
- scustomtkinter_pygubu/sCTkNotebookbo.py +392 -0
- scustomtkinter_pygubu/sCTkOptionMenuPrimarybo.py +45 -0
- scustomtkinter_pygubu/sCTkOptionMenuSecondarybo.py +39 -0
- scustomtkinter_pygubu/sCTkPathChooserbo.py +204 -0
- scustomtkinter_pygubu/sCTkProgressBarbo.py +45 -0
- scustomtkinter_pygubu/sCTkRadioButtonbo.py +45 -0
- scustomtkinter_pygubu/sCTkSMeterBarbo.py +153 -0
- scustomtkinter_pygubu/sCTkSMeterbo.py +101 -0
- scustomtkinter_pygubu/sCTkScrollableFramebo.py +66 -0
- scustomtkinter_pygubu/sCTkScrollbarbo.py +48 -0
- scustomtkinter_pygubu/sCTkSegmentedButtonbo.py +47 -0
- scustomtkinter_pygubu/sCTkSelectorbo.py +167 -0
- scustomtkinter_pygubu/sCTkSeparatorbo.py +141 -0
- scustomtkinter_pygubu/sCTkSliderbo.py +45 -0
- scustomtkinter_pygubu/sCTkSpinboxbo.py +200 -0
- scustomtkinter_pygubu/sCTkSwitchbo.py +45 -0
- scustomtkinter_pygubu/sCTkTableviewbo.py +188 -0
- scustomtkinter_pygubu/sCTkTabviewbo.py +226 -0
- scustomtkinter_pygubu/sCTkTextboxPrimarybo.py +45 -0
- scustomtkinter_pygubu/sCTkTextboxSecondarybo.py +45 -0
- scustomtkinter_pygubu/sCTkToplevelbo.py +55 -0
- scustomtkinter_pygubu/sCTkWidgetSetForPygubuDesigner.py +141 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""
|
|
3
|
+
sCTkButtonSecondary
|
|
4
|
+
|
|
5
|
+
A theme-compliant secondary action button widget -- a lower-emphasis sibling
|
|
6
|
+
of sCTkButtonPrimary (see also sCTkButtonTertiary). Inherits directly from
|
|
7
|
+
ctk.CTkButton so CustomTkinter handles native rendering; this class layers
|
|
8
|
+
automatic light/dark theme resolution and a four-state visual model
|
|
9
|
+
(normal/disabled/pressed) on top. Unlike sCTkButtonPrimary, there is no
|
|
10
|
+
"alarm" state here.
|
|
11
|
+
|
|
12
|
+
Base class order matters here: `class sCTkButtonSecondary(ctk.CTkButton,
|
|
13
|
+
ThemeableWidget)` puts the native CTk class first, so every `super()` call in
|
|
14
|
+
this file's own methods resolves to ctk.CTkButton -- and, beneath it,
|
|
15
|
+
tkinter.Misc -- never to ThemeableWidget. ThemeableWidget's own
|
|
16
|
+
configure()/cget()/_set_appearance_mode() overrides have been removed entirely
|
|
17
|
+
for this reason (see themeable_widget.py's docstring); this widget owns all of
|
|
18
|
+
its own runtime color-swapping logic.
|
|
19
|
+
"""
|
|
20
|
+
from typing import Any, Optional
|
|
21
|
+
import customtkinter as ctk
|
|
22
|
+
from .themeable_widget import ThemeableWidget, defer
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class sCTkButtonSecondary(ctk.CTkButton, ThemeableWidget):
|
|
26
|
+
"""Themeable secondary action button.
|
|
27
|
+
|
|
28
|
+
Adds to native ctk.CTkButton:
|
|
29
|
+
- Automatic light/dark theme resolution from sCTkThemes.json (via
|
|
30
|
+
ThemeableWidget.__init__ -- see that class's docstring for what it does,
|
|
31
|
+
and just as importantly, what it no longer does).
|
|
32
|
+
- A three-state visual model: normal, disabled, and pressed. Precedence
|
|
33
|
+
when both could apply is disabled > pressed (see
|
|
34
|
+
_update_current_visual_state() and the guard clause in set_pressed()).
|
|
35
|
+
- Pygubu Designer property introspection for a handful of properties via
|
|
36
|
+
a single-argument configure() call.
|
|
37
|
+
|
|
38
|
+
Colors are passed through to configure() as raw (light, dark) tuples rather
|
|
39
|
+
than pre-resolved to a single value, so CustomTkinter's own appearance-mode
|
|
40
|
+
tracking repaints them automatically on a light/dark switch -- the same
|
|
41
|
+
approach validated on sCTkComboBox, sCTkSegmentedButton, and sCTkButtonPrimary.
|
|
42
|
+
|
|
43
|
+
Disabling uses CTk's native state="disabled" rather than manually unbinding
|
|
44
|
+
mouse events -- an earlier version of this widget used the manual-unbind
|
|
45
|
+
approach (identical code to what sCTkButtonPrimary originally had), which
|
|
46
|
+
was directly tested and confirmed NOT to block clicks on that widget. The
|
|
47
|
+
fix is carried over here on the strength of both widgets sharing the same
|
|
48
|
+
underlying ctk.CTkButton click-handling; a confirmatory test on this
|
|
49
|
+
specific widget is still worthwhile.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, master: Optional[Any] = None, **kw: Any) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Args:
|
|
55
|
+
master: Parent container.
|
|
56
|
+
**kw: Any native CTkButton argument, or a theme-key override (see
|
|
57
|
+
the "sCTkButtonSecondary" block in sCTkThemes.json, including
|
|
58
|
+
its disabled_map, pressed_map and alarm_map sub-blocks).
|
|
59
|
+
"""
|
|
60
|
+
# 1. Fire our shared theme logic first. This resolves final_kw
|
|
61
|
+
# (construction-time properties) and the disabled/pressed color maps.
|
|
62
|
+
# See ThemeableWidget.__init__ for what actually happens here.
|
|
63
|
+
# NO PLACEHOLDER TEXT. CustomTkinter defaults `text` to the class
|
|
64
|
+
# name, so a CTkButton built without text reads "CTkButton". Nothing wants
|
|
65
|
+
# that in a real interface, and the Designer made it worse: clearing
|
|
66
|
+
# the text field removes the property, the canvas rebuilds the widget
|
|
67
|
+
# from what is left, and the placeholder appeared in place of the
|
|
68
|
+
# blank that was asked for.
|
|
69
|
+
kw.setdefault("text", "")
|
|
70
|
+
ThemeableWidget.__init__(self, kw)
|
|
71
|
+
|
|
72
|
+
# 2. Deep-copy each resolved map onto this instance, so later changes
|
|
73
|
+
# here never leak back into the shared theme registry.
|
|
74
|
+
self._local_defaults = dict(self.final_kw)
|
|
75
|
+
self._custom_disabled_map = dict(self._widget_disabled_map)
|
|
76
|
+
self._custom_pressed_map = dict(self._widget_pressed_map)
|
|
77
|
+
self._custom_alarm_map = dict(self._widget_alarm_map)
|
|
78
|
+
|
|
79
|
+
# 3. Initialize CustomTkinter natively with the clean final kwargs array.
|
|
80
|
+
super().__init__(master, **self.final_kw)
|
|
81
|
+
|
|
82
|
+
self.is_pressed = False
|
|
83
|
+
self.is_alarm = False
|
|
84
|
+
self._custom_current_state = "normal"
|
|
85
|
+
self._update_current_visual_state()
|
|
86
|
+
|
|
87
|
+
# 4. Register lifecycle handshake hook, notifying Pygubu-style consumers
|
|
88
|
+
# that construction is complete.
|
|
89
|
+
self._finalize_themeable_lifecycle()
|
|
90
|
+
|
|
91
|
+
def configure(self, *args: Any, **kwargs: Any) -> Any:
|
|
92
|
+
"""
|
|
93
|
+
Standard widget configuration, with Pygubu/positional-argument handling.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
*args: At most one positional argument is meaningful:
|
|
97
|
+
- a dict: merged into kwargs and processed normally below.
|
|
98
|
+
- the literal string "state": returns a Tkinter-style
|
|
99
|
+
(name, name, name, default, current) tuple.
|
|
100
|
+
- one of "fg_color"/"border_color"/"text_color"/"hover_color":
|
|
101
|
+
returns the same style of tuple, with `current` reflecting
|
|
102
|
+
whichever of disabled/alarm/pressed/normal is currently active.
|
|
103
|
+
Note the returned value is str(value), where value may
|
|
104
|
+
itself be a (light, dark) tuple rather than a single
|
|
105
|
+
resolved color -- a known limitation shared with the wider
|
|
106
|
+
Pygubu-query investigation set aside elsewhere in this
|
|
107
|
+
project, not fixed here.
|
|
108
|
+
- anything else: forwarded directly to the native widget's
|
|
109
|
+
configure(), which does not support single-argument property
|
|
110
|
+
queries for arbitrary properties (same limitation).
|
|
111
|
+
**kwargs: Standard CTkButton configuration options, plus: passing
|
|
112
|
+
`state=...` routes through self.state() rather than being
|
|
113
|
+
forwarded as-is.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
The query tuple described above for the single-argument case, or
|
|
117
|
+
whatever super().configure() returns for the keyword-argument case
|
|
118
|
+
(typically None).
|
|
119
|
+
"""
|
|
120
|
+
# args is always a tuple -- args[0] is the actual value passed, whether
|
|
121
|
+
# that's a string or a dict. An earlier version of this method compared
|
|
122
|
+
# the wrapped tuple directly (`pname = args`) and forwarded it unwrapped
|
|
123
|
+
# to super().configure(), which is not a valid call shape for the
|
|
124
|
+
# native widget. Don't reintroduce that.
|
|
125
|
+
if len(args) == 1:
|
|
126
|
+
if isinstance(args[0], dict):
|
|
127
|
+
kwargs = {**args[0], **kwargs}
|
|
128
|
+
else:
|
|
129
|
+
pname = args[0]
|
|
130
|
+
if pname == "state":
|
|
131
|
+
return ("state", "state", "state", "normal", str(self.state()))
|
|
132
|
+
|
|
133
|
+
if pname in ["fg_color", "border_color", "text_color", "hover_color"]:
|
|
134
|
+
current_state = str(self.state()).lower()
|
|
135
|
+
if current_state == "disabled" and self._custom_disabled_map:
|
|
136
|
+
val = self._custom_disabled_map.get(pname)
|
|
137
|
+
elif getattr(self, "is_alarm", False) and self._custom_alarm_map:
|
|
138
|
+
val = self._custom_alarm_map.get(pname)
|
|
139
|
+
elif getattr(self, "is_pressed", False) and self._custom_pressed_map:
|
|
140
|
+
val = self._custom_pressed_map.get(pname)
|
|
141
|
+
else:
|
|
142
|
+
val = self._local_defaults.get(pname)
|
|
143
|
+
return (pname, pname, pname, self._query_value(self._theme_default(pname)), self._query_value(val))
|
|
144
|
+
|
|
145
|
+
return self._configure_query(pname)
|
|
146
|
+
|
|
147
|
+
# Runtime overrides have to reach the map a repaint reads, or
|
|
148
|
+
# _update_current_visual_state() puts the theme value straight back --
|
|
149
|
+
# see ThemeableWidget._record_theme_overrides().
|
|
150
|
+
self._record_theme_overrides(kwargs)
|
|
151
|
+
|
|
152
|
+
if "state" in kwargs:
|
|
153
|
+
target_state = kwargs.pop("state")
|
|
154
|
+
self.state(target_state)
|
|
155
|
+
|
|
156
|
+
# Theme values cleared to "" are dropped; `text=""` is kept, so a
|
|
157
|
+
# button's caption can actually be blanked. See
|
|
158
|
+
# ThemeableWidget._CONTENT_KEYS.
|
|
159
|
+
self._drop_cleared(kwargs)
|
|
160
|
+
if kwargs:
|
|
161
|
+
return super().configure(**kwargs)
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
# Tkinter/CTk convention binds .config to .configure as a SEPARATE class
|
|
165
|
+
# attribute -- it does not automatically track whichever configure() a
|
|
166
|
+
# subclass defines. Without this line, calling .config(...) on an instance
|
|
167
|
+
# would silently skip this entire override and land on the native widget's
|
|
168
|
+
# configure() directly, bypassing theming and state handling entirely.
|
|
169
|
+
config = configure
|
|
170
|
+
|
|
171
|
+
def _set_appearance_mode(self, mode_string: str) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Forwards CustomTkinter's internal light/dark mode change notification to
|
|
174
|
+
the native widget.
|
|
175
|
+
|
|
176
|
+
No longer manually re-triggers _update_current_visual_state(). That
|
|
177
|
+
method now passes raw (light, dark) tuples straight through to
|
|
178
|
+
configure() instead of pre-resolving to a single color, so CTk's own
|
|
179
|
+
appearance-mode tracking should repaint correctly on its own -- the same
|
|
180
|
+
approach validated on sCTkComboBox, sCTkSegmentedButton, and
|
|
181
|
+
sCTkButtonPrimary.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
mode_string: The new appearance mode ("Light" or "Dark"), as passed
|
|
185
|
+
by CustomTkinter's internal appearance-mode change machinery.
|
|
186
|
+
"""
|
|
187
|
+
if hasattr(super(), "_set_appearance_mode"):
|
|
188
|
+
try:
|
|
189
|
+
super()._set_appearance_mode(mode_string)
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
def get_state(self) -> str:
|
|
194
|
+
"""Equivalent to calling state() with no argument."""
|
|
195
|
+
return self.state()
|
|
196
|
+
|
|
197
|
+
def state(self, mode: Optional[str] = None) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Gets or sets the widget's enabled/disabled state.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
mode: If None, returns the current state without changing anything.
|
|
203
|
+
Otherwise, only the literal string "disabled" (case-insensitive)
|
|
204
|
+
is treated as disabled; anything in ("normal", "enabled",
|
|
205
|
+
"active") is treated as enabled. Any other value falls through
|
|
206
|
+
without matching either branch, leaving the state unchanged.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
The resulting state ("normal" or "disabled", lowercase).
|
|
210
|
+
"""
|
|
211
|
+
if mode is None:
|
|
212
|
+
return str(getattr(self, "_custom_current_state", "normal")).lower()
|
|
213
|
+
|
|
214
|
+
mode = mode.lower()
|
|
215
|
+
if mode in ("normal", "enabled", "active"):
|
|
216
|
+
self._custom_current_state = "normal"
|
|
217
|
+
super().configure(state="normal", hover=True)
|
|
218
|
+
# Re-establish the mouse bindings that disabling below may have
|
|
219
|
+
# torn down.
|
|
220
|
+
if hasattr(self, "_create_bindings"):
|
|
221
|
+
try: self._create_bindings()
|
|
222
|
+
except Exception: pass
|
|
223
|
+
# Deferred via after_idle rather than called immediately: on
|
|
224
|
+
# sCTkButtonPrimary, CTk's own state="normal" transition was found
|
|
225
|
+
# to schedule its own internal repaint that can run AFTER an
|
|
226
|
+
# immediate call here and silently overwrite fresh colors back to
|
|
227
|
+
# something stale. Queuing our reapply via after_idle lets CTk's
|
|
228
|
+
# own pending repaint settle first, so ours wins the final paint.
|
|
229
|
+
defer(self, "idle", self._update_current_visual_state)
|
|
230
|
+
|
|
231
|
+
elif mode == "disabled":
|
|
232
|
+
self._custom_current_state = "disabled"
|
|
233
|
+
# Uses CTk's native state="disabled" -- confirmed necessary (not
|
|
234
|
+
# optional) on sCTkButtonPrimary, which originally used the same
|
|
235
|
+
# manual-unbind code this method used to have. That approach was
|
|
236
|
+
# confirmed broken: clicks still fired the button's command while
|
|
237
|
+
# "disabled", almost certainly because CTkButton binds its click
|
|
238
|
+
# handler at the canvas-ITEM level (tag_bind on the drawn
|
|
239
|
+
# rectangle/text), not the canvas-WIDGET level -- so
|
|
240
|
+
# canvas.unbind() never reached it. Don't reintroduce manual event
|
|
241
|
+
# unbinding here.
|
|
242
|
+
super().configure(state="disabled", hover=False)
|
|
243
|
+
defer(self, "idle", self._update_current_visual_state)
|
|
244
|
+
|
|
245
|
+
return self._custom_current_state
|
|
246
|
+
|
|
247
|
+
def set_pressed(self, pressed: bool) -> None:
|
|
248
|
+
"""
|
|
249
|
+
Toggles the visual "pressed" state.
|
|
250
|
+
|
|
251
|
+
No-op while disabled or in the alarm state -- see the precedence
|
|
252
|
+
order on _update_current_visual_state().
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
pressed: True to show the pressed-state colors, False to return to
|
|
256
|
+
normal (assuming not disabled or in alarm).
|
|
257
|
+
"""
|
|
258
|
+
if getattr(self, "_custom_current_state", "normal") == "disabled" or self.is_alarm:
|
|
259
|
+
return
|
|
260
|
+
self.is_pressed = pressed
|
|
261
|
+
self._update_current_visual_state()
|
|
262
|
+
|
|
263
|
+
def set_alarm_state(self, active: bool) -> None:
|
|
264
|
+
"""
|
|
265
|
+
Forces the button into (or out of) a high-visibility alarm state.
|
|
266
|
+
|
|
267
|
+
No-op while disabled. Turning alarm on clears any current "pressed"
|
|
268
|
+
state, since alarm takes visual precedence -- see
|
|
269
|
+
_update_current_visual_state().
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
active: True to enter the alarm state, False to leave it.
|
|
273
|
+
"""
|
|
274
|
+
if getattr(self, "_custom_current_state", "normal") == "disabled":
|
|
275
|
+
return
|
|
276
|
+
self.is_alarm = active
|
|
277
|
+
if self.is_alarm:
|
|
278
|
+
self.is_pressed = False
|
|
279
|
+
self._update_current_visual_state()
|
|
280
|
+
|
|
281
|
+
def _update_current_visual_state(self) -> None:
|
|
282
|
+
"""
|
|
283
|
+
Recomputes and applies this widget's colors from the theme file, based
|
|
284
|
+
on the current state. Precedence: disabled > alarm > pressed > normal -- only
|
|
285
|
+
the first matching branch applies.
|
|
286
|
+
|
|
287
|
+
Called after construction, on every state()/set_pressed() change, and
|
|
288
|
+
(indirectly, via after_idle) after every state() transition.
|
|
289
|
+
|
|
290
|
+
Passes raw (light, dark) tuples straight through to configure() instead
|
|
291
|
+
of resolving to a single color first, so CTk's native tracking can
|
|
292
|
+
handle appearance-mode repaints without help from _set_appearance_mode.
|
|
293
|
+
Every value here traces back to sCTkThemes.json; there are no
|
|
294
|
+
hardcoded colors in this method.
|
|
295
|
+
|
|
296
|
+
Only the normal (final else) branch also reapplies border_width,
|
|
297
|
+
corner_radius, and font -- these don't vary between
|
|
298
|
+
disabled/alarm/pressed/normal, so there's no need to repeat them in every
|
|
299
|
+
branch; they're set once here and otherwise left alone.
|
|
300
|
+
"""
|
|
301
|
+
if getattr(self, "_custom_current_state", "normal") == "disabled":
|
|
302
|
+
config_payload = {}
|
|
303
|
+
for key in ("fg_color", "hover_color", "border_color"):
|
|
304
|
+
val = self._custom_disabled_map.get(key)
|
|
305
|
+
if val is not None:
|
|
306
|
+
config_payload[key] = val
|
|
307
|
+
|
|
308
|
+
# disabled_map's text_color goes to the NATIVE text_color_disabled.
|
|
309
|
+
#
|
|
310
|
+
# CTkButton has its own text_color_disabled option and its draw
|
|
311
|
+
# code uses that whenever state is "disabled", overriding whatever
|
|
312
|
+
# text_color was set to. Assigning the disabled value to text_color
|
|
313
|
+
# here was therefore discarded at draw time and the theme key had
|
|
314
|
+
# no visible effect at all.
|
|
315
|
+
#
|
|
316
|
+
# text_color keeps the NORMAL value, so the two options do not
|
|
317
|
+
# fight and CTk chooses between them as it is designed to.
|
|
318
|
+
disabled_text = self._custom_disabled_map.get("text_color")
|
|
319
|
+
if disabled_text is not None:
|
|
320
|
+
config_payload["text_color_disabled"] = disabled_text
|
|
321
|
+
normal_text = self._local_defaults.get("text_color")
|
|
322
|
+
if normal_text is not None:
|
|
323
|
+
config_payload["text_color"] = normal_text
|
|
324
|
+
|
|
325
|
+
if config_payload:
|
|
326
|
+
super().configure(**config_payload)
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
if getattr(self, "is_alarm", False):
|
|
330
|
+
config_payload = {}
|
|
331
|
+
for key in ("fg_color", "hover_color", "border_color", "text_color"):
|
|
332
|
+
val = self._custom_alarm_map.get(key)
|
|
333
|
+
if val is not None:
|
|
334
|
+
config_payload[key] = val
|
|
335
|
+
config_payload["hover"] = False
|
|
336
|
+
super().configure(**config_payload)
|
|
337
|
+
|
|
338
|
+
elif self.is_pressed:
|
|
339
|
+
config_payload = {}
|
|
340
|
+
for key in ("fg_color", "hover_color", "border_color", "text_color"):
|
|
341
|
+
val = self._custom_pressed_map.get(key)
|
|
342
|
+
if val is not None:
|
|
343
|
+
config_payload[key] = val
|
|
344
|
+
config_payload["hover"] = False
|
|
345
|
+
super().configure(**config_payload)
|
|
346
|
+
else:
|
|
347
|
+
config_payload = {}
|
|
348
|
+
for key in ("fg_color", "hover_color", "border_color", "text_color", "border_width", "corner_radius", "font"):
|
|
349
|
+
val = self._local_defaults.get(key)
|
|
350
|
+
if val is not None:
|
|
351
|
+
config_payload[key] = val
|
|
352
|
+
config_payload["hover"] = True
|
|
353
|
+
if config_payload:
|
|
354
|
+
super().configure(**config_payload)
|