Declarative Tcl/Tk UI library for Python.
- Somewhat React-like (there is effectively a Tk VDOM).
- Well typed.
- Maps very closely to the underlying Tcl/Tk for ease of debugging.
- Zero dependency.
- On mac sometimes you have to start by wiggling the window.
- Small enough to understand how it works.
- In an incomplete state – much functionality missing.
After pip install tkintergalactic
, just run:
import tkintergalactic as tk
counter = 0
@tk.command()
def inc_counter() -> None:
global counter
counter += 1
tk.Window(
app=lambda: tk.Frame(
tk.Button(text="Hello World!", onbuttonrelease=inc_counter),
tk.Text(content=f"Button clicked {counter} times"),
),
).run()
from dataclasses import dataclass, field
import tkintergalactic as tk
@dataclass
class Task:
description: str
complete: bool = False
@dataclass
class State:
tasks: list[Task] = field(default_factory=list)
new_task_description: str = ""
state = State()
@tk.command()
def add_task() -> None:
state.tasks.append(Task(state.new_task_description))
state.new_task_description = ""
@tk.command()
def delete_task(i: int) -> None:
state.tasks.pop(i)
@tk.command()
def toggle_class_complete(i: int) -> None:
state.tasks[i].complete = not state.tasks[i].complete
@tk.command(with_event=True)
def set_new_task_description(e: tk.EventKeyRelease) -> None:
state.new_task_description = e.value
tk.Window(
title="TODO List",
h=600,
w=500,
app=lambda: tk.Frame(
tk.Frame(
[
tk.Frame(
tk.Entry(
value=task.description,
side="left",
font=tk.Font(styles=["overstrike"]) if task.complete else tk.Font(),
expand=True,
),
tk.Button(
text="✗" if task.complete else "✔",
onbuttonrelease=toggle_class_complete.partial(i=i),
),
tk.Button(text="Delete", onbuttonrelease=delete_task.partial(i=i), side="right"),
fill="x",
expand=True,
)
for i, task in enumerate(state.tasks)
],
fill="x",
expand=True,
),
tk.Frame(
tk.Entry(
value=state.new_task_description,
onkeyrelease=set_new_task_description,
side="left",
expand=True,
),
tk.Button(
text="New Task",
onbuttonrelease=add_task,
),
fill="x",
),
tk.Text(
content=f"Total number of tasks: {len(state.tasks)}\nComplete: {sum(t.complete for t in state.tasks)}",
),
),
).run()
The packer is the main way of arranging Widgets.
import tkintergalactic as tk
tk.Window(
title="Packer",
w=200,
h=300,
app=lambda: tk.Frame(
tk.Button(text="t", side="top", fill="x"),
tk.Button(text="b", side="bottom", fill="x"),
tk.Button(text="l", side="left"),
tk.Button(text="r", side="right"),
tk.Text(content="mid", expand=True, fill="both"),
fill="both",
expand=True,
),
).run()
The majority of the functionality in the Tk Docs is still not implemented, most of it is just a case of padding out existing functionality in widgets.py
, however there are some complicated text buffer bits that would take a lot more work.
- The diffing algorithm could be made more efficient – see eg. the referenced alogrithms in the
mithril
code. - Could allow passing optional
id
s to widgets to make diffing long lists more efficient. - More complicated state management a la React could be done. I’d have a preference for a simpler “this widget tree is the same as the other, don’t diff” approach that the user can opt in to.
- Potentially a lot of the diffing code coudld be offloaded to Rust.
- Before doing any of the above, set up benchmarking.
- Sort out distinct naming for custom python commands, TCL commands and subcommands.
uv pip install -e '.[dev]'
mypy .
pytest -vv