tintkit 0.1.0__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.
tintkit/__init__.py ADDED
@@ -0,0 +1,99 @@
1
+ """TintKit — a small, themeable Tkinter widget kit.
2
+
3
+ A dark/light photo-tool look as reusable controls. One theme drives every
4
+ colour; controls reuse each other; everything is interactive.
5
+
6
+ import tkinter as tk
7
+ from tintkit import Theme, Button, setup_dpi
8
+
9
+ root = tk.Tk()
10
+ setup_dpi(root) # crisp icons on high-DPI screens
11
+ theme = Theme(scheme="dark", accent="#8fae9b")
12
+ Button(root, theme, "Save", icon="save", command=lambda: None).pack()
13
+ root.mainloop()
14
+
15
+ Switch the look at any time — the whole window repaints::
16
+
17
+ theme.set(scheme="light")
18
+ theme.set(accent="#c08457")
19
+
20
+ See ``gallery.py`` for every component rendered together with a live switcher.
21
+ """
22
+
23
+ from . import icons
24
+ from .icons import set_icon_dir
25
+ from .scaling import s, set_scale
26
+ from .theme import (Theme, mix, lighten, darken, on_color,
27
+ SCHEMES, DEFAULT_ACCENT)
28
+ from .primitives import (CanvasControl, Surface, Label, IconLabel,
29
+ rounded_rect, put_icon, font, measure, FONT_FAMILY)
30
+ from .controls import (Button, IconButton, Slider, Toggle, Radio, RadioGroup,
31
+ Checkbox, SegmentedTabs, Badge, Tag, ProgressBar,
32
+ Tooltip, TextField, Dropdown, MultiDropdown)
33
+ from .containers import (Card, SectionHeader, hero_line, callout, dialog,
34
+ v_sash, h_sash, themed_scrollbar)
35
+ from .composites import (toolbar, tool_rail, FolderNav, folder_tree,
36
+ SelectTile, SelectRow, MultiSelectRow,
37
+ multiselect_list, SettingsWindow)
38
+
39
+ __all__ = [
40
+ "Theme", "mix", "lighten", "darken", "on_color", "SCHEMES",
41
+ "DEFAULT_ACCENT", "icons", "set_icon_dir", "setup_dpi",
42
+ "enable_dpi_awareness", "s", "set_scale",
43
+ "CanvasControl", "Surface", "Label", "IconLabel",
44
+ "rounded_rect", "put_icon", "font", "measure", "FONT_FAMILY",
45
+ "Button", "IconButton", "Slider", "Toggle", "Radio", "RadioGroup",
46
+ "Checkbox", "SegmentedTabs", "Badge", "Tag", "ProgressBar", "Tooltip",
47
+ "TextField", "Dropdown", "MultiDropdown",
48
+ "Card", "SectionHeader", "hero_line", "callout", "dialog",
49
+ "v_sash", "h_sash", "themed_scrollbar",
50
+ "toolbar", "tool_rail", "FolderNav", "folder_tree", "SelectTile",
51
+ "SelectRow", "MultiSelectRow", "multiselect_list", "SettingsWindow",
52
+ ]
53
+
54
+
55
+ def enable_dpi_awareness():
56
+ """Tell Windows this process draws at the real screen resolution.
57
+
58
+ MUST run before the first ``tk.Tk()`` — otherwise Tk caches the virtualised
59
+ 96 DPI and everything renders tiny. The kit calls this automatically on
60
+ import, so importing ``tintkit`` before creating the root is enough.
61
+ """
62
+ import sys
63
+ if sys.platform != "win32":
64
+ return
65
+ try:
66
+ import ctypes
67
+ ctypes.windll.shcore.SetProcessDpiAwareness(2) # per-monitor aware
68
+ except Exception:
69
+ try:
70
+ ctypes.windll.user32.SetProcessDPIAware() # older fallback
71
+ except Exception:
72
+ pass
73
+
74
+
75
+ def setup_dpi(root, zoom=1.0):
76
+ """Scale fonts + icons + geometry to the screen. Returns the scale factor.
77
+
78
+ Call once, right after creating the root window and BEFORE building the
79
+ Theme. ``zoom`` is an extra comfort multiplier on top of the screen DPI
80
+ (1.0 = true size; raise it to make the whole UI bigger).
81
+
82
+ * Tk text scaling → ``(screen_dpi / 72) * zoom`` (fonts + text measurement).
83
+ * The kit's geometry scale ``S`` → ``(screen_dpi / 96) * zoom`` (canvas px).
84
+ """
85
+ from . import scaling
86
+ enable_dpi_awareness() # idempotent; real fix is at import
87
+ try:
88
+ fpix = root.winfo_fpixels("1i")
89
+ root.tk.call("tk", "scaling", (fpix / 72.0) * zoom)
90
+ factor = max(1.0, fpix / 96.0) * zoom
91
+ except Exception:
92
+ factor = zoom
93
+ scaling.set_scale(factor)
94
+ icons.DPI = factor
95
+ return factor
96
+
97
+
98
+ # Set DPI awareness at import — before any tk.Tk() the caller creates.
99
+ enable_dpi_awareness()
tintkit/composites.py ADDED
@@ -0,0 +1,459 @@
1
+ """TintKit — composites.
2
+
3
+ Bigger pieces assembled *only* from the kit: a toolbar is a row of
4
+ :class:`IconButton`, the folder nav reuses :class:`Badge` and :class:`IconLabel`,
5
+ the settings window reuses :class:`Toggle`, :class:`Dropdown`, :class:`Button`.
6
+ No composite re-implements a primitive, and every part restyles on theme change.
7
+
8
+ Geometry literals go through ``s()`` to scale to the screen DPI.
9
+ """
10
+
11
+ import tkinter as tk
12
+
13
+ from .scaling import s
14
+ from .primitives import Surface, Label, IconLabel, rounded_rect, put_icon, font
15
+ from . import icons
16
+ from .controls import (IconButton, Toggle, Dropdown, SegmentedTabs, Slider,
17
+ Button, Badge)
18
+ from .containers import Card, SectionHeader
19
+
20
+
21
+ # ----------------------------------------------------------------------------
22
+ # Toolbar — a row of square icon buttons (exclusive active item)
23
+ # ----------------------------------------------------------------------------
24
+ def toolbar(parent, theme, items, active=0, bg="bar", command=None):
25
+ "items: list of icon names. Returns a Surface; one item stays active."
26
+ bar = Surface(parent, theme, bg=bg)
27
+ btns = []
28
+
29
+ def choose(i):
30
+ for j, b in enumerate(btns):
31
+ b.set_active(j == i)
32
+ if command:
33
+ command(i, items[i])
34
+ for i, name in enumerate(items):
35
+ b = IconButton(bar.widget, theme, name, active=(i == active), bg=bg,
36
+ command=lambda i=i: choose(i))
37
+ b.pack(side="left", padx=s(3), pady=s(5))
38
+ btns.append(b)
39
+ return bar
40
+
41
+
42
+ # ----------------------------------------------------------------------------
43
+ # Tool rail — captioned tiles (icon above a label)
44
+ # ----------------------------------------------------------------------------
45
+ def tool_rail(parent, theme, items, active=0, bg="bar", command=None):
46
+ "items: list of (icon, label). Returns a Surface; one tile stays active."
47
+ box = Surface(parent, theme, bg=bg)
48
+ btns = []
49
+
50
+ def choose(i):
51
+ for j, b in enumerate(btns):
52
+ b.set_active(j == i)
53
+ if command:
54
+ command(i, items[i][1])
55
+ for i, (name, label) in enumerate(items):
56
+ b = IconButton(box.widget, theme, name, w=70, h=56, label=label,
57
+ active=(i == active), icon_px=20, bg=bg,
58
+ command=lambda i=i: choose(i))
59
+ b.pack(side="left", padx=s(2))
60
+ btns.append(b)
61
+ return box
62
+
63
+
64
+ # ----------------------------------------------------------------------------
65
+ # Folder navigation — path bar + collapsible folder tree
66
+ # ----------------------------------------------------------------------------
67
+ class FolderNav:
68
+ def __init__(self, parent, theme, crumbs, tree_rows, count_text,
69
+ filter_text="Filter folders…"):
70
+ self.theme = theme
71
+ self.tree_rows = tree_rows
72
+ self.filter_text = filter_text
73
+ self.open = True
74
+ self.box = Surface(parent, theme, bg="bg")
75
+
76
+ outer = Surface(self.box.widget, theme, bg="border")
77
+ outer.widget.pack(fill="x")
78
+ bar = Surface(outer.widget, theme, bg="chip")
79
+ bar.widget.pack(fill="x", padx=s(1), pady=s(1))
80
+ self.bar = bar.widget
81
+
82
+ IconButton(self.bar, theme, "chevron-up", w=32, h=38, bg="chip").pack(
83
+ side="left", padx=(s(4), s(2)))
84
+ Surface(self.bar, theme, bg="border", width=s(1), height=s(22)).pack(
85
+ side="left", padx=s(4))
86
+ for i, (nm, cur) in enumerate(crumbs):
87
+ if i:
88
+ IconLabel(self.bar, theme, "chevron-right", 12, fg="fg_dim",
89
+ bg="chip").pack(side="left", padx=s(2))
90
+ self._crumb(nm, cur)
91
+
92
+ self.toggle_btn = IconButton(self.bar, theme, "chevron-up", w=26, h=38,
93
+ active=True, bg="chip",
94
+ command=self._toggle)
95
+ self.toggle_btn.pack(side="left", padx=(s(2), 0))
96
+ Badge(self.bar, theme, count_text, bg="chip").pack(side="right",
97
+ padx=s(8))
98
+
99
+ self.drop = Surface(self.box.widget, theme, bg="bg")
100
+ self._render()
101
+
102
+ def _crumb(self, name, current):
103
+ lb = Label(self.bar, self.theme, name, fg=("fg" if current else "fg_dim"),
104
+ bg="chip", size=11, bold=current, cursor="hand2",
105
+ padx=s(5), pady=s(9))
106
+ lb.widget.pack(side="left")
107
+ if not current:
108
+ w = lb.widget
109
+ w.bind("<Enter>", lambda e: w.configure(fg=self.theme["accent"]))
110
+ w.bind("<Leave>", lambda e: w.configure(fg=self.theme["fg_dim"]))
111
+
112
+ def _toggle(self):
113
+ self.open = not self.open
114
+ self.toggle_btn.icon_name = "chevron-up" if self.open else "chevron-down"
115
+ self.toggle_btn.set_active(self.open)
116
+ self._render()
117
+
118
+ def _render(self):
119
+ for w in self.drop.widget.winfo_children():
120
+ w.destroy()
121
+ if self.open:
122
+ folder_tree(self.drop.widget, self.theme, self.tree_rows,
123
+ self.filter_text)
124
+ self.drop.widget.pack(fill="x", pady=(s(6), 0))
125
+ else:
126
+ self.drop.widget.pack_forget()
127
+
128
+ def pack(self, **k):
129
+ self.box.pack(**k)
130
+ return self
131
+
132
+ def grid(self, **k):
133
+ self.box.grid(**k)
134
+ return self
135
+
136
+ def place(self, **k):
137
+ self.box.place(**k)
138
+ return self
139
+
140
+
141
+ def folder_tree(parent, theme, rows, filter_text=None):
142
+ outer = Surface(parent, theme, bg="border")
143
+ outer.widget.pack(fill="x")
144
+ panel = Surface(outer.widget, theme, bg="sidebar")
145
+ panel.widget.pack(fill="x", padx=s(1), pady=s(1))
146
+ p = panel.widget
147
+ if filter_text is not None:
148
+ _tree_filter(p, theme, filter_text)
149
+ else:
150
+ Label(p, theme, "Folders", fg="fg_dim", bg="sidebar", size=8,
151
+ bold=True, anchor="w").pack(fill="x", padx=s(12), pady=(s(8), s(4)))
152
+ for depth, name, kind, current in rows:
153
+ _tree_row(p, theme, depth, name, kind, current)
154
+ Surface(p, theme, bg="sidebar", height=s(8)).pack()
155
+ return panel
156
+
157
+
158
+ def _tree_filter(parent, theme, text):
159
+ box = Surface(parent, theme, bg="sidebar")
160
+ box.widget.pack(fill="x", padx=s(10), pady=(s(8), s(6)))
161
+ outer = Surface(box.widget, theme, bg="border")
162
+ outer.widget.pack(fill="x")
163
+ inner = Surface(outer.widget, theme, bg="bg")
164
+ inner.widget.pack(fill="x", padx=s(1), pady=s(1))
165
+ row = Surface(inner.widget, theme, bg="bg")
166
+ row.widget.pack(fill="x", padx=s(8), pady=s(5))
167
+ IconLabel(row.widget, theme, "search", 14, fg="fg_dim", bg="bg").pack(
168
+ side="left", padx=(0, s(6)))
169
+ Label(row.widget, theme, text, fg="fg_dim", bg="bg", size=9).pack(side="left")
170
+
171
+
172
+ def _tree_row(parent, theme, depth, name, kind, current):
173
+ base = "lift" if current else "sidebar"
174
+ row = Surface(parent, theme, bg=base)
175
+ row.widget.pack(fill="x")
176
+ Surface(row.widget, theme, bg=("accent" if current else base),
177
+ width=s(3)).pack(side="left", fill="y")
178
+ Surface(row.widget, theme, bg=base, width=s(4 + depth * 16)).pack(
179
+ side="left")
180
+ if kind in ("open", "closed"):
181
+ IconLabel(row.widget, theme,
182
+ "chevron-down" if kind == "open" else "chevron-right",
183
+ 12, fg="fg_dim", bg=base).pack(side="left")
184
+ else:
185
+ Surface(row.widget, theme, bg=base, width=s(12)).pack(side="left")
186
+ IconLabel(row.widget, theme, "folder-open", 16,
187
+ fg=("accent" if current else "fg"), bg=base).pack(side="left",
188
+ padx=(s(2), 0))
189
+ Label(row.widget, theme, name, fg=("accent" if current else "fg"), bg=base,
190
+ size=10, bold=current, cursor="hand2", padx=s(6)).pack(side="left",
191
+ pady=s(4))
192
+
193
+
194
+ # ----------------------------------------------------------------------------
195
+ # Selection — the same frame in every view (list · thumbnails)
196
+ # ----------------------------------------------------------------------------
197
+ class _Selectable:
198
+ "Common theme handling for the selection demo views."
199
+
200
+ def _bind(self, theme, root):
201
+ self.theme = theme
202
+ self.root = root
203
+ theme.subscribe(self._restyle)
204
+ root.bind("<Destroy>", self._destroyed)
205
+ self._restyle()
206
+
207
+ def _destroyed(self, e):
208
+ if e.widget is self.root:
209
+ self.theme.unsubscribe(self._restyle)
210
+
211
+ def pack(self, **k):
212
+ self.root.pack(**k)
213
+ return self
214
+
215
+ def grid(self, **k):
216
+ self.root.grid(**k)
217
+ return self
218
+
219
+ def place(self, **k):
220
+ self.root.place(**k)
221
+ return self
222
+
223
+
224
+ class SelectTile(_Selectable):
225
+ "A thumbnail with a selection frame; the caption never recolours."
226
+
227
+ def __init__(self, parent, theme, image, name, selected=False, size=120):
228
+ self.selected = selected
229
+ cell = tk.Frame(parent)
230
+ self.cell = cell
231
+ self.holder = tk.Frame(cell, highlightthickness=s(2))
232
+ self.holder.pack(padx=s(4), pady=(s(4), 0))
233
+ self.pic = tk.Label(self.holder, image=image, cursor="hand2")
234
+ self.pic.image = image
235
+ self.pic.pack()
236
+ self.cap = tk.Label(cell, text=name, font=font(7),
237
+ wraplength=s(size + 8))
238
+ self.cap.pack(pady=(s(2), s(4)))
239
+ self._bind(theme, cell)
240
+
241
+ def _restyle(self):
242
+ try:
243
+ t = self.theme
244
+ # selection is the frame alone — an accent border, nothing tinted;
245
+ # the caption never recolours either.
246
+ edge = t["accent"] if self.selected else t["sidebar"]
247
+ self.cell.configure(bg=t["sidebar"])
248
+ self.holder.configure(bg=t["sidebar"], highlightbackground=edge,
249
+ highlightcolor=edge)
250
+ self.pic.configure(bg=t["sidebar"])
251
+ self.cap.configure(bg=t["sidebar"], fg=t["fg"])
252
+ except tk.TclError:
253
+ pass
254
+
255
+
256
+ class SelectRow(_Selectable):
257
+ "A list row with the selection frame; the name never recolours."
258
+
259
+ def __init__(self, parent, theme, image, name, selected=False):
260
+ self.selected = selected
261
+ cell = tk.Frame(parent)
262
+ self.cell = cell
263
+ self.holder = tk.Frame(cell, highlightthickness=s(2))
264
+ self.holder.pack(fill="x")
265
+ self.pic = tk.Label(self.holder, image=image, cursor="hand2")
266
+ self.pic.image = image
267
+ self.pic.pack(side="left", padx=(s(4), s(8)), pady=s(2))
268
+ self.name = tk.Label(self.holder, text=name, anchor="w", cursor="hand2",
269
+ font=font(9))
270
+ self.name.pack(side="left", fill="x", expand=True)
271
+ self._bind(theme, cell)
272
+
273
+ def _restyle(self):
274
+ try:
275
+ t = self.theme
276
+ # selection is the frame alone — an accent border, name never recolours.
277
+ edge = t["accent"] if self.selected else t["sidebar"]
278
+ for w in (self.cell, self.holder, self.pic, self.name):
279
+ w.configure(bg=t["sidebar"])
280
+ self.holder.configure(highlightbackground=edge, highlightcolor=edge)
281
+ self.name.configure(fg=t["fg"])
282
+ except tk.TclError:
283
+ pass
284
+
285
+
286
+ # ----------------------------------------------------------------------------
287
+ # Multi-select list — checkable rows; selected rows tint + name turns accent
288
+ # ----------------------------------------------------------------------------
289
+ class MultiSelectRow(_Selectable):
290
+ def __init__(self, parent, theme, name, selected=False, command=None):
291
+ self.selected = selected
292
+ self.name_text = name
293
+ self.command = command
294
+ row = tk.Frame(parent)
295
+ self.row = row
296
+ self.stripe = tk.Frame(row, width=s(3))
297
+ self.stripe.pack(side="left", fill="y")
298
+ self.box = tk.Canvas(row, width=s(20), height=s(30),
299
+ highlightthickness=0, cursor="hand2")
300
+ self.box.pack(side="left", padx=(s(7), 0))
301
+ self.lbl = tk.Label(row, text=name, anchor="w", font=font(9))
302
+ self.lbl.pack(side="left", padx=(s(8), 0), fill="x", expand=True)
303
+ for w in (row, self.box, self.lbl):
304
+ w.bind("<Button-1>", self._click)
305
+ self._bind(theme, row)
306
+
307
+ def _click(self, _e):
308
+ self.selected = not self.selected
309
+ self._restyle()
310
+ if self.command:
311
+ self.command(self.selected)
312
+
313
+ def _restyle(self):
314
+ try:
315
+ t = self.theme
316
+ base = t["lift"] if self.selected else t["sidebar"]
317
+ self.row.configure(bg=base)
318
+ self.lbl.configure(bg=base,
319
+ fg=t["accent"] if self.selected else t["fg"],
320
+ font=font(9, self.selected))
321
+ self.stripe.configure(bg=t["accent"] if self.selected else base)
322
+ self.box.configure(bg=base)
323
+ self.box.delete("all")
324
+ if self.selected:
325
+ rounded_rect(self.box, s(3), s(8), s(17), s(22), s(3),
326
+ fill=t["accent"])
327
+ put_icon(self.box, s(10), s(15),
328
+ icons.load("check", 12, t["on_accent"]))
329
+ else:
330
+ rounded_rect(self.box, s(3), s(8), s(17), s(22), s(3), fill=base,
331
+ outline=t["ring"], width=s(2))
332
+ except tk.TclError:
333
+ pass
334
+
335
+
336
+ def multiselect_list(parent, theme, rows, width=240):
337
+ "rows: list of (name, selected). Returns the bordered panel Surface."
338
+ holder = Surface(parent, theme, bg="border")
339
+ panel = Surface(holder.widget, theme, bg="sidebar")
340
+ panel.widget.pack(padx=s(1), pady=s(1))
341
+ Surface(panel.widget, theme, bg="sidebar", width=s(width), height=s(1)).pack()
342
+ for name, sel in rows:
343
+ MultiSelectRow(panel.widget, theme, name, selected=sel).pack(fill="x")
344
+ Surface(panel.widget, theme, bg="sidebar", height=s(6)).pack()
345
+ return holder
346
+
347
+
348
+ # ----------------------------------------------------------------------------
349
+ # Settings window — a left tab rail + a swappable pane (kit-built)
350
+ # ----------------------------------------------------------------------------
351
+ class SettingsWindow:
352
+ TABS = ["General", "Export", "Culling", "About"]
353
+
354
+ def __init__(self, parent, theme, width=560, height=400):
355
+ self.theme = theme
356
+ self.active = 0
357
+ self._tabs = []
358
+ card = Card(theme=theme, parent=parent, pad=0, bg="panel", width=s(width))
359
+ self.card = card
360
+ body = card.body
361
+ body.configure(height=s(height))
362
+ body.pack_propagate(False)
363
+ rail = Surface(body, theme, bg="sidebar")
364
+ rail.widget.pack(side="left", fill="y")
365
+ rail.widget.configure(width=s(140))
366
+ rail.widget.pack_propagate(False)
367
+ Label(rail.widget, theme, " SETTINGS", fg="fg_dim", bg="sidebar",
368
+ size=8, bold=True, anchor="w").pack(fill="x", pady=(s(16), s(8)))
369
+ for i, name in enumerate(self.TABS):
370
+ self._tab(rail.widget, i, name)
371
+ self.pane = Surface(body, theme, bg="panel")
372
+ self.pane.widget.pack(side="left", fill="both", expand=True,
373
+ padx=s(20), pady=s(16))
374
+ self._show(0)
375
+
376
+ def _tab(self, parent, i, name):
377
+ row = Surface(parent, self.theme, bg="sidebar")
378
+ row.widget.pack(fill="x")
379
+ stripe = Surface(row.widget, self.theme, bg="sidebar", width=s(3))
380
+ stripe.widget.pack(side="left", fill="y")
381
+ lbl = Label(row.widget, self.theme, name, fg="fg_dim", bg="sidebar",
382
+ size=10, cursor="hand2", anchor="w", padx=s(12), pady=s(8))
383
+ lbl.widget.pack(side="left", fill="x", expand=True)
384
+ self._tabs.append((stripe, lbl))
385
+ for w in (row.widget, lbl.widget):
386
+ w.bind("<Button-1>", lambda e, idx=i: self._show(idx))
387
+
388
+ def _show(self, i):
389
+ self.active = i
390
+ for j, (stripe, lbl) in enumerate(self._tabs):
391
+ on = (j == i)
392
+ stripe._bg = "accent" if on else "sidebar"
393
+ stripe._restyle()
394
+ lbl._fg = "accent" if on else "fg_dim"
395
+ lbl.widget.configure(font=font(10, on))
396
+ lbl._restyle()
397
+ for w in self.pane.widget.winfo_children():
398
+ w.destroy()
399
+ [self._general, self._export, self._culling, self._about][i]()
400
+
401
+ # -- panes -------------------------------------------------------------
402
+ def _row(self, label, control_factory):
403
+ r = Surface(self.pane.widget, self.theme, bg="panel")
404
+ r.widget.pack(fill="x", pady=s(6))
405
+ Label(r.widget, self.theme, label, fg="fg", bg="panel", size=10).pack(
406
+ side="left")
407
+ control_factory(r.widget).pack(side="right")
408
+
409
+ def _general(self):
410
+ SectionHeader(self.pane.widget, self.theme, "General", bg="panel").pack(
411
+ fill="x")
412
+ self._row("Dark theme", lambda p: Toggle(p, self.theme, value=True,
413
+ bg="panel"))
414
+ self._row("Confirm before delete",
415
+ lambda p: Toggle(p, self.theme, value=True, bg="panel"))
416
+ self._row("Thumbnail size",
417
+ lambda p: Dropdown(p, self.theme, ["Small", "Medium", "Large"],
418
+ selected=2, bg="panel"))
419
+
420
+ def _export(self):
421
+ SectionHeader(self.pane.widget, self.theme, "Export", bg="panel").pack(
422
+ fill="x")
423
+ self._row("Format", lambda p: SegmentedTabs(p, self.theme,
424
+ ["JPG", "PNG", "TIFF"],
425
+ bg="panel"))
426
+ Slider(self.pane.widget, self.theme, "Quality", value=85, lo=0, hi=100,
427
+ neutral=0, bg="panel").pack(fill="x", pady=(s(8), 0))
428
+ self._row("Convert to sRGB",
429
+ lambda p: Toggle(p, self.theme, value=True, bg="panel"))
430
+
431
+ def _culling(self):
432
+ SectionHeader(self.pane.widget, self.theme, "Culling", bg="panel").pack(
433
+ fill="x")
434
+ self._row("Keep on right arrow",
435
+ lambda p: Toggle(p, self.theme, value=False, bg="panel"))
436
+ self._row("Reject folder name",
437
+ lambda p: Dropdown(p, self.theme, ["Rejected", "Trash",
438
+ "_cull"], bg="panel"))
439
+
440
+ def _about(self):
441
+ SectionHeader(self.pane.widget, self.theme, "About", bg="panel").pack(
442
+ fill="x")
443
+ Label(self.pane.widget, self.theme, "TintKit — a themeable Tkinter "
444
+ "UI kit.", fg="fg_dim", bg="panel", size=10,
445
+ justify="left").pack(anchor="w", pady=(s(4), s(12)))
446
+ Button(self.pane.widget, self.theme, "Check for updates",
447
+ role="neutral", variant="outline", bg="panel").pack(anchor="w")
448
+
449
+ def pack(self, **k):
450
+ self.card.pack(**k)
451
+ return self
452
+
453
+ def grid(self, **k):
454
+ self.card.grid(**k)
455
+ return self
456
+
457
+ def place(self, **k):
458
+ self.card.place(**k)
459
+ return self