Skip to main content

Delarative Tcl/Tk

Project description

Tkintergalactic

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.

Hello World

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

Hello world screencast

TODO list

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

Packer example

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

Further work

Functionality

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.

Diffing

  • 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.

Other

  • Sort out distinct naming for custom python commands, TCL commands and subcommands.

Development

uv pip install -e '.[dev]'
mypy .
pytest -vv

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tkintergalactic-0.0.1.tar.gz (13.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tkintergalactic-0.0.1-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

File details

Details for the file tkintergalactic-0.0.1.tar.gz.

File metadata

  • Download URL: tkintergalactic-0.0.1.tar.gz
  • Upload date:
  • Size: 13.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.5

File hashes

Hashes for tkintergalactic-0.0.1.tar.gz
Algorithm Hash digest
SHA256 137eaab430a74e62e86c1f3d34251dbae19184bdf6eaf4cf96ed329356286eed
MD5 136bb5368f2a3cf882b6ad9d154f4b61
BLAKE2b-256 374b47d913fe1266f8b6ce866f418dfd1ccc62b440689ccba7165fea7668debf

See more details on using hashes here.

File details

Details for the file tkintergalactic-0.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for tkintergalactic-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 393d2bab3b5b31eac56d07838750125162c3a9940c978dd23a7d06e15809cb05
MD5 60332c410d4c25b73072dd11970bedfd
BLAKE2b-256 a934bfb2fffa103c88feb082f1ed7db66096e4e5549bc2ec605e606b96a15696

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page