leontrolski/tkintergalactic: Modern declarative (React-like) Tcl/Tk interface for Python

Date:

Share:

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()

TODO list screencast

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()

Packer screenshot

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 ids 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

Source link

Subscribe to our magazine

━ more like this

Party Down Team Returns This February: Teaser & First-Look Image

Posted in: Preview, Starz, TV | Tagged: adam scott, party down, preview teaser, starzPublished Wed, 14 Dec 2022 09:32:12 -0600 by Ray Flook |...

Newark Airport Suffers Another Radar Outage

An air traffic control facility that guides planes at Newark Liberty International Airport suffered a brief radar outage on Friday morning, the latest technological...

Why Buying a Retiring Business Is the Smartest Move for Young Entrepreneurs

Opinions expressed by Entrepreneur contributors are their own. An upheaval is reshaping the...

God Loses $400 Betting On Cardinal Tagle

THE HEAVENS—Cursing aloud the moment news of Leo XIV’s election arrived on His phone screen, the Lord Almighty told reporters Thursday that he had...

No-Pants Trend Takes Over the 2025 Met Gala

Naomi Parris is the assistant shopping editor at PS, based in New York. With over eight years of experience and previous roles held on...