Skip to content

Commit a423c6c

Browse files
committed
Use TTK Styling
- This patch switches from the simple Tkinter widgets to the *themed* Tkinter widgets. Different systems (ie. Windows, Linux, MacOS) should have a default TTK theme that looks cohesive with the rest of the system. - There are a few other necessary changes. The TTK scales use floats by default, rather than the integers used by the TK scales.
1 parent 00656e1 commit a423c6c

File tree

1 file changed

+61
-37
lines changed

1 file changed

+61
-37
lines changed

kasatk/__main__.py

+61-37
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,26 @@ class LightState(TypedDict):
5454

5555

5656
async def update_bulb(bulb, brightness=None, hue=None, saturation=None):
57-
if brightness != None:
58-
await bulb.set_brightness(brightness)
59-
if hue != None or saturation != None:
60-
state: LightState = await bulb.get_light_state()
61-
await bulb.set_hsv(
62-
hue if hue != None else state["hue"],
63-
saturation if saturation != None else state["saturation"],
64-
brightness if brightness != None else state["brightness"],
65-
)
57+
# note: all arguments need to ints
58+
assert brightness is None or isinstance(brightness, int)
59+
assert hue is None or isinstance(hue, int)
60+
assert saturation is None or isinstance(saturation, int)
61+
62+
state: LightState = await bulb.get_light_state()
63+
await bulb.set_hsv(
64+
hue if hue != None else state["hue"],
65+
saturation if saturation != None else state["saturation"],
66+
brightness if brightness != None else state["brightness"],
67+
)
6668

6769

6870
class ScrollableFrame(tkinter.ttk.Frame):
6971
def __init__(self, container, *args, **kwargs):
7072
super().__init__(container, *args, **kwargs)
71-
self.canvas = tkinter.Canvas(self)
73+
background = tkinter.ttk.Style().lookup("TFrame", "background")
74+
self.canvas = tkinter.Canvas(
75+
self, background=background, bd=0, highlightthickness=0
76+
)
7277
self.scrollable_frame = tkinter.ttk.Frame(self.canvas)
7378
self.scrollable_frame.bind(
7479
"<Configure>",
@@ -126,7 +131,7 @@ def _on_mouse_scroll(self, event):
126131
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
127132

128133

129-
class EditableText(tkinter.Frame):
134+
class EditableText(tkinter.ttk.Frame):
130135
def __init__(self, container, initial_text_value, callback):
131136
super(self.__class__, self).__init__(container)
132137

@@ -149,22 +154,27 @@ def _edit_mode_finish(self, event=None):
149154

150155
def _render_static_mode(self):
151156
"""render non-editable text"""
152-
label_font = tkinter.font.Font(
153-
family="Helvetica", name="appHighlightFont", size=12, weight="bold"
157+
big_label_style_name = "my.TLabel"
158+
style = tkinter.ttk.Style()
159+
default_font_name = style.lookup("TLabel", "font")
160+
default_font = tkinter.font.nametofont(default_font_name)
161+
custom_font = {**default_font.actual(), "size": 12}
162+
style.configure(big_label_style_name, font=custom_font)
163+
164+
text_label = tkinter.ttk.Label(
165+
self, text=self.text.get(), style=big_label_style_name
154166
)
155-
156-
text_label = tkinter.Label(self, text=self.text.get(), font=label_font)
157167
text_label.pack(side=tkinter.LEFT)
158168

159-
edit_label = tkinter.Label(self, image=self.pencil_icon)
169+
edit_label = tkinter.ttk.Label(self, image=self.pencil_icon)
160170
edit_label.bind("<ButtonRelease-1>", self._edit_mode_start)
161171
edit_label.pack(side=tkinter.LEFT)
162172

163173
self.child_widgets.append(text_label)
164174
self.child_widgets.append(edit_label)
165175

166176
def _render_edit_mode(self):
167-
text_entry = tkinter.Entry(self, textvariable=self.text)
177+
text_entry = tkinter.ttk.Entry(self, textvariable=self.text)
168178
text_entry.pack(side=tkinter.LEFT)
169179
text_entry.bind("<Return>", self._edit_mode_finish)
170180

@@ -177,33 +187,46 @@ def pencil_icon(self):
177187
tkinter.Label is not sufficient.
178188
(https://stackoverflow.com/a/31959529/2796349)
179189
"""
190+
style = tkinter.ttk.Style()
191+
foreground = style.lookup("TFrame", "foreground")
192+
background = style.lookup("TFrame", "background")
193+
180194
if not hasattr(self, "_pencil_icon"):
181195
self._pencil_icon = tkinter.BitmapImage(
182-
data=b"#define image_width 16\n#define image_height 16\nstatic char image_bits[] = {\n0x00,0x1c,0x00,0x3e,0x00,0x7f,0x80,0xf7,0xc0,0xf3,0xe0,0x79,0xf0,0x3c,0x78,\n0x1e,0x3c,0x0f,0x9c,0x07,0xcc,0x03,0xfc,0x01,0xfc,0x00,0x7c,0x00,0xff,0xff,\n0xff,0xff\n};"
196+
data=b"#define image_width 16\n#define image_height 16\nstatic char image_bits[] = {\n0x00,0x1c,0x00,0x3e,0x00,0x7f,0x80,0xf7,0xc0,0xf3,0xe0,0x79,0xf0,0x3c,0x78,\n0x1e,0x3c,0x0f,0x9c,0x07,0xcc,0x03,0xfc,0x01,0xfc,0x00,0x7c,0x00,0xff,0xff,\n0xff,0xff\n};",
197+
background=background,
198+
foreground=foreground,
183199
)
184200
return self._pencil_icon
185201

186202

187-
class BulbFrame(tkinter.Frame):
203+
class BulbFrame(tkinter.ttk.Frame):
188204
async def _hue_callback(self):
189-
return await update_bulb(self.bulb, None, self.hue_slider.get())
205+
return await update_bulb(self.bulb, None, int(self.hue_slider.get()))
190206

191207
async def _saturation_callback(self):
192-
return await update_bulb(self.bulb, saturation=self.saturation_slider.get())
208+
return await update_bulb(
209+
self.bulb, int(saturation=self.saturation_slider.get())
210+
)
193211

194212
async def _brightness_callback(self):
195-
return await update_bulb(self.bulb, self.brightness_slider.get())
213+
logger.info("Brightness callback: {}".format(self.brightness_slider.get()))
214+
return await update_bulb(self.bulb, int(self.brightness_slider.get()))
196215

197216
async def _power_callback(self):
198-
self.power_button["state"] = tkinter.DISABLED
217+
self.power_button.state(["disabled"])
199218

200219
try:
201220
await (self.bulb.turn_off() if self.bulb.is_on else self.bulb.turn_on())
202221
await self.bulb.update()
203222
finally:
204-
self.power_button["relief"] = "sunken" if self.bulb.is_on else "raised"
223+
self.power_button.state(
224+
[
225+
"!disabled",
226+
"pressed" if self.bulb.is_on else "!pressed",
227+
]
228+
)
205229
self.power_button["text"] = "Turn Off" if self.bulb.is_on else "Turn On"
206-
self.power_button["state"] = tkinter.NORMAL
207230

208231
@classmethod
209232
def for_bulb(cls, loop, bulb: kasa.SmartBulb, config, *args, **kwargs):
@@ -215,11 +238,11 @@ def for_bulb(cls, loop, bulb: kasa.SmartBulb, config, *args, **kwargs):
215238
self = cls(*args, **kwargs)
216239
self.bulb = bulb
217240

218-
self.hue_label = tkinter.Label(self, text="hue")
219-
self.saturation_label = tkinter.Label(self, text="saturation")
220-
self.brightness_label = tkinter.Label(self, text="brightness")
241+
self.hue_label = tkinter.ttk.Label(self, text="hue")
242+
self.saturation_label = tkinter.ttk.Label(self, text="saturation")
243+
self.brightness_label = tkinter.ttk.Label(self, text="brightness")
221244

222-
self.hue_slider = tkinter.Scale(
245+
self.hue_slider = tkinter.ttk.Scale(
223246
self, from_=0, to=360, orient=tkinter.HORIZONTAL
224247
)
225248
self.hue_slider.bind(
@@ -230,7 +253,7 @@ def for_bulb(cls, loop, bulb: kasa.SmartBulb, config, *args, **kwargs):
230253
)
231254
self.hue_slider.set(self.bulb.hsv[0])
232255

233-
self.saturation_slider = tkinter.Scale(
256+
self.saturation_slider = tkinter.ttk.Scale(
234257
self, from_=0, to=100, orient=tkinter.HORIZONTAL
235258
)
236259
self.saturation_slider.bind(
@@ -241,7 +264,7 @@ def for_bulb(cls, loop, bulb: kasa.SmartBulb, config, *args, **kwargs):
241264
)
242265
self.saturation_slider.set(self.bulb.hsv[1])
243266

244-
self.brightness_slider = tkinter.Scale(
267+
self.brightness_slider = tkinter.ttk.Scale(
245268
self, from_=0, to=100, orient=tkinter.HORIZONTAL
246269
)
247270
self.brightness_slider.bind(
@@ -260,12 +283,13 @@ def for_bulb(cls, loop, bulb: kasa.SmartBulb, config, *args, **kwargs):
260283
self, bulb_name, lambda new_device_name: logger.info(new_device_name)
261284
).grid(column=0, row=0, columnspan=4)
262285

263-
self.power_button = tkinter.Button(
286+
self.power_button = tkinter.ttk.Button(
264287
self,
265288
text="Turn Off" if self.bulb.is_on else "Turn On",
266-
width=12,
267-
relief="sunken" if self.bulb.is_on else "raised",
289+
# relief="sunken" if self.bulb.is_on else "raised",
290+
# pressed=self.bulb.is_on
268291
)
292+
self.power_button.state(["pressed" if self.bulb.is_on else "!pressed"])
269293
self.power_button.bind(
270294
"<ButtonRelease-1>",
271295
lambda event, self=self, loop=loop: asyncio.run_coroutine_threadsafe(
@@ -328,7 +352,7 @@ async def process_devices():
328352

329353
# TODO make sure that self.device_queue exists before exiting this function
330354

331-
self.refresh_button = tkinter.Button(
355+
self.refresh_button = tkinter.ttk.Button(
332356
self, text="Refresh", command=self.start_refresh
333357
)
334358
self.refresh_button.pack(fill=tkinter.X)
@@ -359,11 +383,11 @@ async def add_device(self, device):
359383

360384
async def _do_refresh(self):
361385
logger.info("KasaDevices._do_refresh() called")
362-
self.refresh_button["state"] = tkinter.DISABLED
386+
self.refresh_button.state(["disabled"])
363387
self.refresh_button["text"] = "Refreshing..."
364388
await self.clear_devices()
365389
await kasa.Discover.discover(on_discovered=self.device_queue.put)
366-
self.refresh_button["state"] = tkinter.NORMAL
390+
self.refresh_button.state(["!disabled"])
367391
self.refresh_button["text"] = "Refresh"
368392

369393
async def clear_devices(self):

0 commit comments

Comments
 (0)