Metadata-Version: 2.4
Name: dkinter
Version: 0.2.1
Summary: Declarative UI library built on tkinter
Author-email: misoftdev5 <misoftdev5@gmail.com>
License-Expression: MIT
Project-URL: Repository, https://github.com/MI-Software-Dev/dkinter
Keywords: tkinter,gui,declarative,ui,desktop
Classifier: Programming Language :: Python :: 3
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: Operating System :: OS Independent
Classifier: Topic :: Software Development :: User Interfaces
Classifier: Topic :: Desktop Environment
Classifier: Intended Audience :: Developers
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# dkinter

Declarative UI library built on top of Python's tkinter. Describe your UI as a tree of nested descriptor objects instead of calling imperative widget methods.

```python
from dkinter import App, Label, Button, State, PackLayout, PackLayoutItem

count = State(0)

App(
    title="Counter",
    width=300, height=150,
    layout=PackLayout(items=[
        PackLayoutItem(widget=Label(text=count, font=("Arial", 24)), pady=20),
        PackLayoutItem(widget=Button(text="Click me", on_click=lambda: count.set(count.get() + 1))),
    ])
).run()
```

---

## Installation

```bash
pip install dkinter
```

Requires Python 3.10+ and tkinter (included with standard Python on Windows and macOS).

---

## Core Concepts

### State

`State[T]` is an observable value. Bind it directly to any widget property — the UI updates automatically when the value changes.

```python
name = State("World")
label = Label(text=name)           # updates whenever name changes
name.set("dkinter")                # label text updates immediately

show = State(True)
frame = Frame(visible=show)        # hide/show the entire frame

title = State.derived(lambda: f"Hello, {name.get()}!")  # computed from other states
```

`fg`, `bg`, and `font` also accept `State` for live styling changes:

```python
color = State("#cc0000")
font  = State(("Arial", 11))

Label(text="Hello", fg=color, font=font)
Button(text="Go", bg=color, fg=State("white"))

color.set("#0055cc")   # all bound widgets update instantly
font.set(("Arial", 14, "bold"))
```

### Ref

`Ref[T]` gives you a handle to a live widget after the app is built.

```python
from dkinter import Ref
from dkinter.live import LiveEntry

entry_ref: Ref[LiveEntry] = Ref()

App(layout=PackLayout(items=[
    PackLayoutItem(widget=Entry(ref=entry_ref)),
    PackLayoutItem(widget=Button(text="Get", on_click=lambda: print(entry_ref.current.value))),
])).run()
```

---

## Layouts

| Layout | Description |
|---|---|
| `PackLayout` | Stack widgets top-to-bottom or side-by-side |
| `GridLayout` | Place widgets in rows and columns |
| `PlaceLayout` | Absolute or relative positioning |

```python
GridLayout(
    column_weights={0: 1, 1: 2},
    items=[
        GridLayoutItem(widget=Label(text="Name:"), row=0, column=0),
        GridLayoutItem(widget=Entry(), row=0, column=1, sticky="ew"),
    ]
)
```

---

## Widgets

### tk widgets (default)

```python
from dkinter import Label, Button, Entry, Text, Frame, LabelFrame
from dkinter import Checkbutton, Radiobutton, Listbox, Spinner, Canvas, Scrollbar
```

| Widget | Key params |
|---|---|
| `Label` | `text`, `font`, `fg`, `bg` |
| `Button` | `text`, `on_click`, `font`, `fg`, `bg` |
| `Entry` | `on_change`, `font`, `fg`, `bg` |
| `Text` | `width`, `height`, `on_change`, `font`, `fg`, `bg` |
| `Frame` | `layout`, `bg` |
| `LabelFrame` | `text`, `layout`, `font`, `fg`, `bg` |
| `Checkbutton` | `text`, `on_change`, `check_bg`, `font`, `fg`, `bg` |
| `Radiobutton` | `text`, `value`, `check_bg`, `font`, `fg`, `bg` |
| `Listbox` | `items`, `select_mode`, `on_select`, `height` |
| `Spinner` | `values` or `from_`/`to`/`increment`, `on_change`, `wrap` |
| `Canvas` | `width`, `height`, `on_click`, `on_mouse_move` |
| `Scrollbar` | `orient`, `target` |

### ttk widgets (themed)

```python
from dkinter.widgets.ttk import Label, Button, Entry, Checkbutton, Frame
from dkinter.widgets.ttk import Combobox, Progressbar, Notebook, Treeview, Menubutton
```

| Widget | Key params |
|---|---|
| `Label` | `text`, `font`, `fg`, `bg` |
| `Button` | `text`, `on_click`, `font`, `fg`, `bg` |
| `Entry` | `on_change`, `font`, `fg`, `bg` |
| `Checkbutton` | `text`, `value`, `on_change`, `font`, `fg`, `bg` |
| `Frame` | `layout`, `bg`, `padding` |
| `Combobox` | `values`, `selected`, `state`, `on_select` |
| `Progressbar` | `value`, `maximum`, `mode`, `orient` |
| `Notebook` | `tabs`, `on_change` |
| `Treeview` | `columns`, `rows`, `show`, `on_select` |
| `Menubutton` | `text`, `menu`, `direction`, `font`, `fg`, `bg` |

> **Note:** ttk `font`/`fg`/`bg` styling requires a non-native theme on Windows.
> Add `theme="clam"` (or `"alt"`) to your `App` to enable full color control.

---

## App

```python
App(
    title="My App",
    width=800,
    height=600,
    resizable=True,
    theme="clam",           # ttk theme: clam, alt, default, classic
    layout=...,
    menu=MenuBar(...),
    on_close=handle_close,
    on_key=handle_key,
    on_resize=handle_resize,
).run()
```

---

## Toplevel

Secondary windows opened on demand. Supports modal (blocks main window) and non-modal modes.

```python
from dkinter import Toplevel

dialog = Toplevel(
    title="Settings",
    width=400,
    height=300,
    modal=True,
    on_close=lambda: print("closed"),
    layout=PackLayout(items=[
        PackLayoutItem(widget=Label(text="Hello from dialog")),
        PackLayoutItem(widget=Button(text="Close", on_click=lambda: dialog.close())),
    ]),
)

Button(text="Open", on_click=dialog.open)
```

| Method / Property | Description |
|---|---|
| `dialog.open()` | Open the window (raises it if already open) |
| `dialog.close()` | Close the window programmatically |
| `dialog.is_open` | `bool` — whether the window is currently open |

---

## Menus

```python
from dkinter import MenuBar, Menu, MenuCommand, MenuSeparator

App(
    menu=MenuBar(menus=[
        Menu(label="File", items=[
            MenuCommand(label="New",  on_click=on_new),
            MenuCommand(label="Open", on_click=on_open),
            MenuSeparator(),
            MenuCommand(label="Exit", on_click=on_exit),
        ]),
    ]),
    ...
)
```

---

## Dialogs

```python
from dkinter import (
    open_file, open_files, save_file, open_directory,
    show_info, show_warning, show_error,
    ask_yes_no, ask_ok_cancel, ask_retry_cancel,
    pick_color,
)

path  = open_file(filetypes=[("Text files", "*.txt")])
color = pick_color(initial="#ffffff")
ok    = ask_yes_no(title="Confirm", message="Are you sure?")
```

---

## Canvas

```python
from dkinter import Ref
from dkinter.live import LiveCanvas

canvas_ref: Ref[LiveCanvas] = Ref()

# after app is built:
c = canvas_ref.current
rect_id = c.draw_rect(10, 10, 100, 60, fill="steelblue")
c.make_draggable(rect_id)
c.make_hoverable(rect_id, hover={"fill": "red"}, normal={"fill": "steelblue"})
```

---

## ttk Checkbutton (two-way binding)

```python
from dkinter.widgets.ttk import Checkbutton
from dkinter import State

checked = State(False)
Checkbutton(text="Enable feature", value=checked, on_change=lambda v: print(v))
# checked.set(True) → checks the box; user click → updates checked
```

---

## ttk Menubutton

```python
from dkinter.widgets.ttk import Menubutton
from dkinter.menu import Menu, MenuCommand, MenuSeparator

Menubutton(
    text="File",
    font=("Arial", 10),
    fg="#003399",
    direction="below",      # above | below | left | right | flush
    menu=Menu(label="File", items=[
        MenuCommand(label="New",  on_click=on_new),
        MenuSeparator(),
        MenuCommand(label="Exit", on_click=on_exit),
    ]),
)
```

---

## Widget Common Params

All widgets inherit these from the base `Widget` class:

| Param | Type | Description |
|---|---|---|
| `visible` | `bool \| State[bool]` | Show/hide the widget |
| `ref` | `Ref` | Access the live widget after build |
| `font` | `tuple \| str \| State` | e.g. `("Arial", 12, "bold")` — supports `State` |
| `fg` | `str \| State[str]` | Foreground/text color — supports `State` |
| `bg` | `str \| State[str]` | Background color — supports `State` |
| `context_menu` | `Menu` | Right-click popup menu |
| `on_click` | `Callable` | Left mouse button |
| `on_right_click` | `Callable` | Right mouse button |
| `on_double_click` | `Callable` | Double click |
| `on_enter` | `Callable` | Mouse enters widget |
| `on_leave` | `Callable` | Mouse leaves widget |
| `on_focus_in` | `Callable` | Widget gains focus |
| `on_focus_out` | `Callable` | Widget loses focus |
| `on_key` | `Callable[[str], None]` | Key pressed while focused |

---

## License

MIT
