Metadata-Version: 2.5
Name: snapgui
Version: 0.1.0
Summary: A one-file GUI builder for Python. Every widget is one line, no dependencies.
Author-email: "Chilli co." <mcintoshedward864@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: desktop,gui,gui-builder,tkinter,ttk,ui,widgets
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: MacOS X
Classifier: Environment :: Win32 (MS Windows)
Classifier: Environment :: X11 Applications
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: User Interfaces
Classifier: Topic :: Software Development :: Widget Sets
Classifier: Typing :: Typed
Requires-Python: >=3.8
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Provides-Extra: images
Requires-Dist: pillow>=9.0; extra == 'images'
Description-Content-Type: text/markdown

# snapgui

[![PyPI](https://img.shields.io/pypi/v/snapgui.svg)](https://pypi.org/project/snapgui/)
[![Python versions](https://img.shields.io/pypi/pyversions/snapgui.svg)](https://pypi.org/project/snapgui/)

A one-file GUI builder for Python. One dependency-free module wrapping tkinter,
which already ships with Python.

```bash
pip install snapgui
```

Then see it run:

```bash
snapgui-demo        # or: python -m snapgui
```

```python
from snapgui import App

app = App("Greeter", size=(420, 200))
app.entry("Your name")
app.button("Say hello", lambda: app.info(f"Hello {app['your_name']}!"), primary=True)
app.run()
```

Three ideas make it small:

1. **Every widget is one line.** No frames, no `pack()`, no geometry manager.
2. **Values live under a name.** `app.entry("Your name")` stores its value under
   `"your_name"` (derived from the caption). Read it with `app["your_name"]`,
   write it the same way, or grab the lot with `app.values`.
3. **Callbacks take what they want.** `lambda: ...` and `lambda value: ...` both
   work anywhere a callback is expected.

Run `snapgui-demo` for a working task tracker that uses most of the library.

---

## Layout

Widgets stack top to bottom. Containers are `with` blocks and nest freely.

```python
with app.row():             # side by side
    app.button("Cancel")
    app.button("Save", primary=True)

with app.group("Address"):  # titled box
    app.field("Street")
    app.field("Postcode")

with app.tabs():
    with app.tab("First"):  ...
    with app.tab("Second"): ...

with app.scroll():          # scrolling area
    for i in range(100):
        app.label(f"Row {i}")

with app.card(): ...        # panel with a border
with app.column(): ...
app.space(12)
app.divider()
```

## Widgets

| Call | Value it holds |
|---|---|
| `app.label(text, size=, bold=, color=, wrap=, align=)` | its text |
| `app.title(text)` / `app.caption(text)` | its text |
| `app.button(text, on_click, primary=, danger=, shortcut=, tooltip=)` | — |
| `app.link(text, url_or_fn)` | — |
| `app.entry(caption, value=, placeholder=, password=, on_enter=, on_change=)` | `str` |
| `app.field(caption, value=)` | `str` (caption and box on one line) |
| `app.form(["Name", "Email"])` | creates several fields, returns their keys |
| `app.password(caption)` | `str` |
| `app.textarea(caption, height=, value=)` | `str` |
| `app.checkbox(caption, value=)` | `bool` |
| `app.radio(caption, options, value=)` | `str` |
| `app.dropdown(caption, options, value=, editable=)` | `str` |
| `app.listbox(caption, items, multi=, on_select=, on_double=)` | `str` or `list` |
| `app.slider(caption, minimum, maximum, value=, step=)` | `float` |
| `app.number(caption, value=, minimum=, maximum=, step=)` | `int` / `float` |
| `app.file_picker(caption, save=, folder=, filetypes=)` | path `str` |
| `app.color_picker(caption, value=)` | `"#rrggbb"` |
| `app.table(columns, rows, height=, widths=, on_select=, on_double=)` | list of rows |
| `app.tree(caption)` | selected labels |
| `app.chart(data, kind="line"/"bar"/"scatter", labels=)` | the data |
| `app.log(caption, height=)` | its text |
| `app.progress(caption, value=, maximum=)` | `float` |
| `app.spinner(caption)` | — (`.widget.start()` / `.stop()`) |
| `app.image(path, width=, height=)` | — (PNG/GIF built in, Pillow adds JPEG etc.) |
| `app.canvas(height=)` | — (raw tkinter canvas for custom drawing) |

Pass `key="my_key"` to any widget to name it yourself.

## Reading and writing values

```python
app["email"]              # read
app["email"] = "a@b.c"    # write
app.values                # {"email": "a@b.c", "subscribe": True, ...}
app.set_values({...})
app.on_change("email", lambda v: print(v))
app.widget("email")       # the handle, for anything finer-grained
```

Handles support `.value`, `.get()`, `.set(v)`, `.hide()`, `.show()`,
`.enable()`, `.disable()`, `.focus()`, `.config(**kw)`, `.tooltip(text)`,
`.on(event, fn)` and `.widget` (the real tkinter widget).

### Tables

```python
t = app.table(["Name", "Qty"], [["Pens", 12]])
t.rows                  # read all rows
t.rows = [...]          # replace all rows
t.add_row(["Ink", 7])   # list or dict
t.selected_row, t.selected, t.selected_index
t.delete_selected(); t.clear(); t.sort_by("Qty")
```

Column headers sort on click. Ascending first.

### Charts

```python
c = app.chart([3, 9, 4], kind="bar", labels=["A", "B", "C"])
c.set_data({"Mon": 4, "Tue": 9})       # list, dict, or (label, value) pairs
```

They redraw themselves when the window resizes or the theme changes.

## Popups and files

```python
app.info(msg); app.warn(msg); app.error(msg)
if app.ask("Delete this?"): ...
name = app.prompt("What's your name?")
path = app.open_file(filetypes=[("CSV", "*.csv")])
path = app.save_file(default_ext=".csv")
folder = app.choose_folder()
```

### Your own dialogs

Same widget API, in a second window:

```python
d = app.dialog("Settings", size=(340, 200))
d.entry("Nickname")
d.checkbox("Remember me", value=True)
d.buttons_ok_cancel()
result = d.show()          # dict of values, or None if cancelled
```

## Menus, shortcuts, status bar

```python
app.menu({"File": {"Open": open_file, "-": None, "Quit": app.close}})
app.shortcut("Ctrl+S", save)          # or a raw "<Control-s>"
app.notify("Saved", seconds=3)        # transient line at the bottom
app.status_bar("Connected")           # permanent line
```

## Timers and background work

tkinter is single-threaded, so long jobs freeze the window. `app.background()`
runs the work on a thread and hands the result back safely on the UI thread.

```python
app.after(500, fn)                    # once
timer = app.every(1000, tick)         # repeating; timer.stop()
app.background(slow_job, on_done=lambda result: chart.set_data(result))
app.call_soon(fn, arg)                # from any thread
```

## Window and theme

```python
App(title, size=(w, h), theme="light"|"dark", padding=10,
    resizable=True, min_size=(w, h), icon="icon.png")

app.theme("dark"); app.dark()
app.set_title(...); app.resize(w, h); app.center()
app.fullscreen(); app.always_on_top()
app.on_close(lambda: app.ask("Quit without saving?"))   # False keeps it open
values = app.run(on_start=preload)    # returns the final values dict
```

## When you outgrow it

Nothing is hidden. `app.widget("key").widget` is the tkinter widget,
`app._root` is the `Tk` instance, and `app._parent()` is the current container —
so you can mix raw tkinter or ttk into any part of the window.

## Notes

- Errors inside callbacks are caught, printed, and shown in a dialog, so one bad
  handler doesn't take the window down.
- Duplicate keys get a numeric suffix (`name`, `name_2`) rather than silently
  overwriting each other.
- Python 3.8+ with tkinter 8.6. tkinter is bundled with Python on Windows and
  macOS; on Debian/Ubuntu install it with `sudo apt install python3-tk`.
- `pip install "snapgui[images]"` adds Pillow, which lets `app.image()` handle
  JPEG, WebP and resizing. PNG and GIF work without it.
- MIT licensed.
