Metadata-Version: 2.4
Name: guile
Version: 0.7.0
Summary: A lightweight Python framework for building desktop apps.
Author-email: Andres Patrignani <andrespatrignani@ksu.edu>
License: MIT
Project-URL: Homepage, https://andpatrig.github.io/guile
Project-URL: Repository, https://github.com/andpatrig/guile
Project-URL: Bug Tracker, https://github.com/andpatrig/guile/issues
Project-URL: Changelog, https://github.com/andpatrig/guile/releases
Keywords: gui,desktop,app,pywebview,reactive
Classifier: Programming Language :: Python :: 3
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: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: User Interfaces
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Developers
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: pywebview>=5.0
Requires-Dist: matplotlib>=3.5
Provides-Extra: science
Requires-Dist: numpy>=1.21; extra == "science"
Requires-Dist: pandas>=1.3; extra == "science"
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"

# gui**le**

A lightweight Python framework for building desktop apps.

---

## Philosophy

Guile started as a personal tool for building lab and research apps — the kind of quick internal dashboards, data explorers, and parameter tools that are too specific to justify a full web stack, but too interactive for a script. It does two things and tries to do them well. First, it layers interactivity on top of the Python you already have: your functions stay ordinary Python, and guile just wires them to widgets. Second, it follows a single reactive rule — change a state value and the interface updates, patching only the parts that actually changed, so text stays in inputs and focus is never lost. The app is one Python process that opens a native window: no server, no ports, no browser tab.

---

## Install

```bash
pip install guile
```

Requires `pywebview`. On Windows, WebView2 ships with Windows 10/11 — nothing extra to install.

---

## Quick start

```python
import guile as gui

count = gui.state(0)

@gui.app("Counter", width=400, height=300)
def ui():
    with gui.col(align="center", justify="center", style="height:100vh"):
        with gui.card(gap=14):
            gui.title("Counter")
            with gui.row(gap=16, align="center", justify="center"):
                gui.button("−", variant="secondary",
                           on_click=lambda: count.update(lambda x: x - 1))
                gui.text(count.value, size="2xl", bold=True,
                         style="min-width:64px;text-align:center")
                gui.button("+",
                           on_click=lambda: count.update(lambda x: x + 1))

gui.run()
```

`@gui.app()` defines the app; `gui.run()` opens the window and blocks until it is closed. Code written after `gui.run()` executes once the window closes — a natural place to save the session or continue a processing pipeline (see `examples/field_notes.py`).

While building a UI, use `gui.run(dev=True)`: guile watches your script and reloads the app inside the open window every time you save the file. Errors show in the window without killing the session; each reload resets state to its initial values.

---

## How it works

- `gui.state(value)` — a reactive value; setting it re-renders the UI automatically. Read it through `.value`, always
- `with gui.card():` / `with gui.col():` / `with gui.row():` — layout containers; everything indented goes inside
- `gui.button()`, `gui.slider()`, `gui.input()`, `gui.table()` — widgets that take `on_click=` or return their current value
- `gui.figure(fig)` — embed a matplotlib figure inline
- `gui.leaflet(center, markers=...)` — embed an interactive map
- `gui.task(fn, on_done=...)` — run slow work on a background thread; the window stays responsive
- `gui.run()` — opens the window; `gui.run(dev=True)` adds hot reload while you build
- `gui.package("my_app.py")` — build a shareable executable in one call

---

## Examples

| File | What it shows |
|------|--------------|
| `counter.py`              | State, buttons, badges                       |
| `todo.py`                 | Lists, dynamic rendering, checkboxes         |
| `settings.py`             | Sliders, selects, form layout                |
| `field_notes.py`          | Save on exit — code after `gui.run()`        |
| `mesonet_map.py`          | Leaflet map with markers                     |
| `mesonet_interactive.py`  | Live mesonet station data                    |
| `weather_explorer.py`     | Table, date picker, file picker              |
| `soils_lab.py`            | Lab data entry form                          |
| `soil_water_retention.py` | Sliders driving a live chart                 |
| `upload_weather_data.py`  | File picker, DataFrame, table                |
| `canopeo.py`              | Image analysis                               |
| `map_draw.py`             | Leaflet with draw tools                      |

---

## Dependencies

| Package | Purpose |
|---------|---------|
| `pywebview` | The window (installed with guile) |
| `matplotlib` | `gui.figure()` (installed with guile) |
| `numpy`, `pandas` | Optional — `pip install guile[science]` |

Everything else is Python standard library.

---

## Files

| File | Role |
|------|------|
| `state.py` | Reactive value class |
| `ui.py` | Render engine + all widgets |
| `_app.py` | Window lifecycle, pywebview bridge |
| `_template.py` | Embedded HTML/CSS/JS |
| `_package.py` | `gui.package()` — PyInstaller wrapper |
| `_dev.py` | `gui.run(dev=True)` — hot reload |
| `__init__.py` | Public API (`gui.*`) |

---

## Changelog
**v0.7.0**
- `@gui.app` now only *defines* the app; add `gui.run()` at the end of your script to open the window. Code after `gui.run()` executes when the window closes — a natural place to save the session or continue a pipeline (see `examples/field_notes.py`).
- Hot reload: `gui.run(dev=True)` watches your script and reloads the app inside the open window on every save. Errors show in the window without killing the session; each reload resets state to initial values.
- `State` is now fully explicit: read and compare through `.value` (`if count.value > 0:`). Comparing or operating on the State object itself raises a clear `TypeError` — this fixes silent misbehavior with numpy arrays and DataFrames.
- New `gui.task(fn, on_done=, on_error=, busy=)` — run slow work on a background thread; clicks and renders keep flowing, and state set inside the task drives live progress bars.
- Errors raised in callbacks now show a danger toast in the window (previously invisible in packaged, windowed apps).
- DOM patcher fix: programmatic updates to inputs, checkboxes, textareas, and selects now render correctly after the user has interacted with them; the caret no longer jumps in `live=True` inputs.
- `gui.number_input()` keeps `.value` current per keystroke but re-renders on commit (Enter, focus leave, spinner); an empty or invalid commit keeps the current value.
- Maps rendered conditionally (e.g. inside a tab) now load Leaflet lazily instead of staying blank.
- Duplicate `key=` values print a one-time warning; `gui.progress(value, max=0)` no longer raises.
- New example `field_notes.py` (load → run → save-on-exit); test suite expanded to 14 tests.

**v0.6.0** — Improved map tile presets. Added `gui.package()` for one-call PyInstaller builds.

**v0.5.0** — Added center=True to app window. Improved code structure in how-to page.

**v0.4.0** — Added tabs. Fixed `datetime-local` input to display in 24-hour format.

**v0.3.0** — Added `notify` and `modal` widgets.

**v0.2.0** — Added `max_height` to `gui.scroll()`. Fixed `multiselect` change event.

**v0.1.0** — First release. 27 widgets.

---

MIT License
