Skip to main content

Python bindings for libui-ng

Project description

python-libui-ng

PyPI Version Python License: MIT GitHub Issues

Native GUI toolkit for Python. Lightweight bindings for libui-ng — real native widgets on Linux (GTK+3), macOS (Cocoa), and Windows (Win32).

No electron. No web views. Just native controls.

Features

  • 30+ native widgets — buttons, entries, sliders, tables, color pickers, drawing surfaces, and more
  • Declarative API — reactive state, composable components, two-way data binding
  • Imperative API — direct low-level control when you need it
  • Async-first — built-in asyncio integration with thread-safe UI updates
  • Cross-platform — one codebase, native look and feel everywhere

Quick Start

pip install libui

Hello World

import libui
from libui.declarative import App, Window, VBox, Label, Button, State

async def main():
    app = App()
    count = State(0)

    app.build(window=Window(
        "Hello", 400, 300,
        child=VBox(
            Label(text=count.map(lambda n: f"Count: {n}")),
            Button("Click me", on_clicked=lambda: count.update(lambda n: n + 1)),
        ),
    ))

    app.show()
    await app.wait()

libui.run(main())

Declarative API

The declarative API is the recommended way to build UIs. Describe your interface as a tree of components with reactive state — the framework handles synchronization.

State Management

State is a reactive container. When its value changes, all subscribers and bound widgets update automatically.

import libui
from libui.declarative import App, Window, VBox, Label, Button, State

async def main():
    app = App()

    name = State("World")
    count = State(0)

    # Derived state (read-only, auto-updates)
    greeting = name.map(lambda n: f"Hello, {n}!")

    # Subscribe to changes
    unsub = count.subscribe(lambda: print(count.value))

    def on_click():
        count.update(lambda n: n + 1)
        name.value = "Python"    # triggers greeting update

    app.build(window=Window("State Demo", 400, 300, child=VBox(
        Label(text=greeting),
        Label(text=count.map(lambda n: f"Clicks: {n}")),
        Button("Click", on_clicked=on_click),
    )))

    app.show()
    await app.wait()

libui.run(main())

Layout Containers

import libui
from libui.declarative import (
    App, Window, VBox, HBox, Group, Form, Tab, Grid, GridCell,
    Label, Button, Entry, MultilineEntry, stretchy,
)

async def main():
    app = App()

    app.build(window=Window("Layouts", 700, 500, child=Tab(
        # Vertical / Horizontal stacking
        ("Boxes", VBox(
            Label("Title"),
            Button("Click me"),
            stretchy(MultilineEntry()),  # stretchy = fills available space
            padded=True,
        )),

        # Form — two-column label + control layout
        ("Form", Form(
            ("Name:", Entry()),
            ("Password:", Entry(type="password")),
            ("Bio:", MultilineEntry(), True),  # True = stretchy
            padded=True,
        )),

        # Grid — precise positioning
        ("Grid", Grid(
            GridCell(Label("X:"), left=0, top=0, halign=libui.Align.END),
            GridCell(Entry(),      left=1, top=0, hexpand=True),
            GridCell(Label("Y:"), left=0, top=1, halign=libui.Align.END),
            GridCell(Entry(),      left=1, top=1, hexpand=True),
            GridCell(Button("OK"), left=0, top=2, xspan=2, halign=libui.Align.CENTER),
            padded=True,
        )),

        # Grouped container
        ("Group", Group("Connection", child=Form(
            ("Host:", Entry()),
            ("Port:", Entry()),
            padded=True,
        ), margined=True)),
    )))

    app.show()
    await app.wait()

libui.run(main())

Widgets

All widgets support reactive binding with State:

import libui
from libui.declarative import (
    App, Window, VBox, Form, Group, Separator, State, stretchy,
    Label, Button, Entry, MultilineEntry, Checkbox,
    Slider, Spinbox, ProgressBar,
    Combobox, RadioButtons, EditableCombobox,
    ColorButton, FontButton, DateTimePicker,
)

async def main():
    app = App()
    status = State("Ready.")
    value = State(50)
    dark_mode = State(False)

    app.build(window=Window("Widgets", 600, 500, child=VBox(
        Label(text=status),

        Group("Text Input", Form(
            ("Normal:", Entry(on_changed=lambda t: status.set(f"Entry: {t}"))),
            ("Password:", Entry(type="password")),
            ("Search:", Entry(type="search")),
            padded=True,
        )),

        Group("Controls", Form(
            ("Button:", Button("Save", on_clicked=lambda: status.set("Saved!"))),
            ("Checkbox:", Checkbox("Dark mode", checked=dark_mode,
                                   on_toggled=lambda c: status.set(f"Dark: {c}"))),
            ("Slider:", Slider(0, 100, value=value,
                               on_changed=lambda v: status.set(f"Value: {v}"))),
            ("Spinbox:", Spinbox(0, 999, value=value)),
            ("Progress:", ProgressBar(value=value)),
            padded=True,
        )),

        Group("Selection", Form(
            ("Combobox:", Combobox(items=["Red", "Green", "Blue"], selected=0)),
            ("Radio:", RadioButtons(items=["Low", "Medium", "High"])),
            ("Editable:", EditableCombobox(items=["Apple", "Banana"])),
            padded=True,
        )),

        Group("Pickers", Form(
            ("Color:", ColorButton(on_changed=lambda rgba: status.set(f"Color: {rgba}"))),
            ("Font:", FontButton(on_changed=lambda f: status.set(f"{f['family']} {f['size']}pt"))),
            ("DateTime:", DateTimePicker(type="datetime")),
            padded=True,
        )),

        Separator(),
        stretchy(MultilineEntry(wrapping=True)),
    )))

    app.show()
    await app.wait()

libui.run(main())

Data Tables

Tables use ListState — an observable list that automatically syncs insertions, deletions, and edits with the UI.

import libui
from libui.declarative import (
    App, Window, VBox, HBox, Label, Button, State, stretchy,
    DataTable, ListState,
    TextColumn, CheckboxTextColumn, ProgressColumn, ButtonColumn,
)

async def main():
    app = App()
    status = State("Click a row.")

    data = ListState([
        {"checked": 1, "name": "Alice", "role": "Engineer",  "score": 85, "action": "View"},
        {"checked": 0, "name": "Bob",   "role": "Designer",  "score": 72, "action": "View"},
        {"checked": 1, "name": "Carol", "role": "Manager",   "score": 90, "action": "View"},
    ])

    def on_button(row):
        d = data[row]
        app.msg_box("Details", f"{d['name']}{d['role']}\nScore: {d['score']}")

    table = DataTable(
        data,
        CheckboxTextColumn("Name", checkbox_key="checked", text_key="name",
                           checkbox_editable=True),
        TextColumn("Role", key="role"),
        ProgressColumn("Score", key="score"),
        ButtonColumn("Action", text_key="action", on_click=on_button),
        on_row_clicked=lambda row: status.set(f"Clicked: {data[row]['name']}"),
    )

    counter = State(len(data))

    def add_row():
        counter.update(lambda n: n + 1)
        data.append({"checked": 0, "name": f"Person {counter.value}",
                      "role": "New", "score": 50, "action": "View"})

    app.build(window=Window("Table", 600, 400, child=VBox(
        Label(text=status),
        stretchy(table),
        HBox(
            Button("Add Row", on_clicked=add_row),
            Button("Remove Last", on_clicked=lambda: data.pop() if len(data) else None),
        ),
    )))

    app.show()
    await app.wait()

libui.run(main())

Menus

Menus are defined before the window and passed to App.build():

import libui
from libui.declarative import (
    App, Window, VBox, Label, State,
    MenuDef, MenuItem, CheckMenuItem, MenuSeparator,
    QuitItem, PreferencesItem, AboutItem,
)

async def main():
    app = App()
    dark_state = State(False)
    status = State("Ready.")

    menus = [
        MenuDef("File",
            MenuItem("Open...", on_click=lambda: app.open_file()),
            MenuItem("Save As...", on_click=lambda: app.save_file()),
            MenuSeparator(),
            QuitItem(),
        ),
        MenuDef("Edit",
            CheckMenuItem("Dark Mode", checked=dark_state,
                          on_click=lambda: status.set(f"Dark: {dark_state.value}")),
            PreferencesItem(),
        ),
        MenuDef("Help",
            AboutItem(),
        ),
    ]

    app.build(
        menus=menus,
        window=Window("Menus", 500, 300, has_menubar=True, child=VBox(
            Label(text=status),
        )),
    )

    app.show()
    await app.wait()

libui.run(main())

Dialogs

import libui
from libui.declarative import App, Window, VBox, Button, Label, State

async def main():
    app = App()
    status = State("")

    def do_open():
        path = app.open_file()
        if path:
            status.set(f"Opened: {path}")

    async def do_open_async():
        path = await app.open_file_async()
        if path:
            status.set(f"Opened: {path}")

    app.build(window=Window("Dialogs", 400, 200, child=VBox(
        Label(text=status),
        Button("Open File (sync)", on_clicked=do_open),
        Button("Open File (async)", on_clicked=do_open_async),
        Button("Message Box", on_clicked=lambda: app.msg_box("Info", "Hello!")),
        Button("Error Box", on_clicked=lambda: app.msg_box_error("Error", "Something failed.")),
    )))

    app.show()
    await app.wait()

libui.run(main())

Custom Drawing

import math
import libui
from libui.declarative import App, Window, VBox, DrawArea, stretchy

def on_draw(ctx, area_w, area_h, clip_x, clip_y, clip_w, clip_h):
    # Filled rectangle
    path = libui.DrawPath()
    path.add_rectangle(20, 20, 200, 100)
    path.end()

    brush = libui.DrawBrush()
    brush.r, brush.g, brush.b, brush.a = 0.2, 0.6, 0.9, 1.0
    ctx.fill(path, brush)

    # Stroked circle
    circle = libui.DrawPath()
    circle.new_figure_with_arc(300, 70, 50, 0, 2 * math.pi, False)
    circle.end()

    stroke = libui.DrawStrokeParams()
    stroke.thickness = 3.0
    ctx.stroke(circle, brush, stroke)

    # Gradient
    grad_path = libui.DrawPath()
    grad_path.add_rectangle(20, 150, 200, 80)
    grad_path.end()

    grad = libui.DrawBrush()
    grad.type = libui.BrushType.LINEAR_GRADIENT
    grad.x0, grad.y0 = 20, 150
    grad.x1, grad.y1 = 220, 230
    grad.set_stops([(0.0, 1, 0, 0, 1), (0.5, 1, 1, 0, 1), (1.0, 0, 0, 1, 1)])
    ctx.fill(grad_path, grad)

    # Styled text
    text = libui.AttributedString("Hello Drawing!")
    text.set_attribute(libui.weight_attribute(libui.TextWeight.BOLD), 0, 5)
    text.set_attribute(libui.color_attribute(0.8, 0.0, 0.0, 1.0), 6, 14)
    layout = libui.DrawTextLayout(text, {"family": "sans-serif", "size": 18.0}, area_w)
    ctx.text(layout, 20, 260)

async def main():
    app = App()

    app.build(window=Window("Drawing", 500, 350,
        child=VBox(stretchy(DrawArea(on_draw=on_draw))),
    ))

    app.show()
    await app.wait()

libui.run(main())

The drawing API supports paths, fills, strokes, gradients (linear/radial), bezier curves, transforms (translate, rotate, scale, skew), clipping, and rich attributed text.

Full Declarative Example

import libui
from libui.declarative import *

async def main():
    app = App()
    status = State("Ready.")
    value = State(50)
    value.subscribe(lambda: status.set(f"Value: {value.value}"))

    app.build(
        menus=[
            MenuDef("File",
                MenuItem("About...", on_click=lambda: app.msg_box("About", "Demo v1.0")),
                MenuSeparator(),
                QuitItem(),
            ),
        ],
        window=Window("Demo", 600, 400, has_menubar=True, child=VBox(
            Label(text=status),
            Group("Controls", Form(
                ("Slider:", Slider(0, 100, value=value)),
                ("Spinbox:", Spinbox(0, 100, value=value)),
                ("Progress:", ProgressBar(value=value)),
                padded=True,
            )),
            Group("Selection", Form(
                ("Quality:", RadioButtons(items=["Low", "Medium", "High"])),
                ("Color:", Combobox(items=["Red", "Green", "Blue"], selected=0)),
                padded=True,
            )),
            Separator(),
            Group("Notes", stretchy(MultilineEntry(wrapping=True))),
        )),
    )

    app.show()
    await app.wait()

libui.run(main())

Imperative API

The imperative API gives you direct control over every widget. It maps closely to the underlying libui-ng C library and is useful when you need fine-grained control, custom event loops, or want to integrate with existing code.

All imperative widgets are thread-safe proxies — mutations are automatically marshalled to the UI thread.

Basic Example

import asyncio
import libui

async def main():
    window = libui.Window("Hello", 400, 300)

    box = libui.VerticalBox(padded=True)
    window.set_child(box)

    label = libui.Label("Count: 0")
    box.append(label)

    count = 0

    def on_click():
        nonlocal count
        count += 1
        label.text = f"Count: {count}"

    button = libui.Button("Click me!")
    button.on_clicked(on_click)
    box.append(button)

    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()

    await asyncio.Event().wait()

libui.run(main())

Containers

import asyncio
import libui

async def main():
    window = libui.Window("Containers", 600, 400)

    # Vertical / Horizontal box
    vbox = libui.VerticalBox(padded=True)
    hbox = libui.HorizontalBox(padded=True)
    vbox.append(hbox)
    hbox.append(libui.Button("Left"), stretchy=True)
    hbox.append(libui.Button("Right"), stretchy=True)

    # Group
    group = libui.Group("Settings")
    group.margined = True
    vbox.append(group)

    # Form — label + control pairs
    form = libui.Form(padded=True)
    form.append("Name:", libui.Entry())
    form.append("Bio:", libui.MultilineEntry(wrapping=True), stretchy=True)
    group.set_child(form)

    # Tab
    tab = libui.Tab()
    tab.append("Page 1", vbox)
    tab.append("Page 2", libui.Label("Second page"))
    tab.set_margined(0, True)
    tab.set_margined(1, True)

    # Grid
    grid = libui.Grid(padded=True)
    grid.append(libui.Label("Name:"), 0, 0, 1, 1, False, libui.Align.END, False, libui.Align.FILL)
    grid.append(libui.Entry(), 1, 0, 1, 1, True, libui.Align.FILL, False, libui.Align.FILL)

    window.set_child(tab)
    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()

    await asyncio.Event().wait()

libui.run(main())

Widgets

import asyncio
import libui

async def main():
    window = libui.Window("Widgets", 500, 600)
    vbox = libui.VerticalBox(padded=True)
    window.set_child(vbox)

    # Label
    label = libui.Label("Hello")
    vbox.append(label)

    # Button
    btn = libui.Button("Click")
    btn.on_clicked(lambda: setattr(label, "text", "Clicked!"))
    vbox.append(btn)

    # Entry (text input)
    entry = libui.Entry()                    # normal (also: "password", "search")
    entry.on_changed(lambda: setattr(label, "text", f"Entry: {entry.text}"))
    vbox.append(entry)

    # Multiline Entry
    mle = libui.MultilineEntry(wrapping=True)
    mle.text = "Type here..."
    vbox.append(mle, stretchy=True)

    # Checkbox
    cb = libui.Checkbox("Enable feature")
    cb.on_toggled(lambda: setattr(label, "text", f"Checked: {cb.checked}"))
    vbox.append(cb)

    # Slider
    slider = libui.Slider(0, 100)
    slider.on_changed(lambda: setattr(label, "text", f"Slider: {slider.value}"))
    vbox.append(slider)

    # Spinbox
    spinbox = libui.Spinbox(0, 100)
    spinbox.on_changed(lambda: setattr(label, "text", f"Spinbox: {spinbox.value}"))
    vbox.append(spinbox)

    # Progress Bar
    progress = libui.ProgressBar()
    progress.value = 75
    vbox.append(progress)

    # Combobox
    combo = libui.Combobox()
    for item in ("Red", "Green", "Blue"):
        combo.append(item)
    combo.selected = 0
    combo.on_selected(lambda: setattr(label, "text", f"Combo: {combo.selected}"))
    vbox.append(combo)

    # Radio Buttons
    radio = libui.RadioButtons()
    for item in ("Option A", "Option B"):
        radio.append(item)
    radio.on_selected(lambda: setattr(label, "text", f"Radio: {radio.selected}"))
    vbox.append(radio)

    # Separator
    vbox.append(libui.Separator())

    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()

    await asyncio.Event().wait()

libui.run(main())

Pickers

import asyncio
import libui

async def main():
    window = libui.Window("Pickers", 400, 300)
    form = libui.Form(padded=True)
    window.set_child(form)

    label = libui.Label("Pick something...")
    form.append("Status:", label)

    # Color picker
    color_btn = libui.ColorButton()
    color_btn.on_changed(lambda: setattr(
        label, "text", "Color: R={:.2f} G={:.2f} B={:.2f}".format(*color_btn.color[:3])))
    form.append("Color:", color_btn)

    # Font picker
    font_btn = libui.FontButton()
    font_btn.on_changed(lambda: setattr(
        label, "text", f"Font: {font_btn.font['family']} {font_btn.font['size']}pt"))
    form.append("Font:", font_btn)

    # Date/Time pickers
    dtp = libui.DateTimePicker()              # datetime
    dtp.on_changed(lambda: setattr(
        label, "text", "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}".format(*dtp.time[:5])))
    form.append("DateTime:", dtp)

    dtp_date = libui.DateTimePicker(type="date")
    form.append("Date:", dtp_date)

    dtp_time = libui.DateTimePicker(type="time")
    form.append("Time:", dtp_time)

    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()

    await asyncio.Event().wait()

libui.run(main())

Tables

import asyncio
import libui

async def main():
    window = libui.Window("Table", 500, 300)

    data = [
        ["Alice", "Engineer", 85],
        ["Bob",   "Designer", 72],
        ["Carol", "Manager",  90],
    ]

    model = libui.TableModel(
        num_columns=lambda: 3,
        column_type=lambda col: libui.TableValueType.STRING if col < 2 else libui.TableValueType.INT,
        num_rows=lambda: len(data),
        cell_value=lambda row, col: str(data[row][col]) if col < 2 else int(data[row][col]),
        set_cell_value=lambda row, col, val: data[row].__setitem__(col, val),
    )

    table = libui.Table(model)
    table.append_text_column("Name", 0)
    table.append_text_column("Role", 1)
    table.append_progress_bar_column("Score", 2)

    table.on_row_clicked(lambda row: print(f"Clicked: {data[row][0]}"))

    # Dynamic updates
    data.append(["Dave", "Intern", 50])
    model.row_inserted(len(data) - 1)

    window.set_child(table)
    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()

    await asyncio.Event().wait()

libui.run(main())

Available column types:

table.append_text_column(name, col, editable=NEVER_EDITABLE)
table.append_checkbox_column(name, col, editable=ALWAYS_EDITABLE)
table.append_checkbox_text_column(name, cb_col, cb_editable, text_col)
table.append_progress_bar_column(name, col)
table.append_button_column(name, col, clickable=ALWAYS_EDITABLE)
table.append_image_column(name, col)
table.append_image_text_column(name, img_col, text_col)

Menus

Menus must be created before the window:

import asyncio
import libui

async def main():
    # Menus first
    file_menu = libui.Menu("File")
    item = file_menu.append_item("Open...")
    item.on_clicked(lambda: print("Open clicked"))
    file_menu.append_separator()
    file_menu.append_quit_item()

    edit_menu = libui.Menu("Edit")
    edit_menu.append_preferences_item()
    toggle = edit_menu.append_check_item("Dark Mode")
    toggle.on_clicked(lambda: print(f"Dark: {toggle.checked}"))

    help_menu = libui.Menu("Help")
    help_menu.append_about_item()

    # Then the window with has_menubar=True
    window = libui.Window("Menus", 500, 300, has_menubar=True)
    window.set_child(libui.Label("Menu demo"))
    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()

    await asyncio.Event().wait()

libui.run(main())

Drawing

import asyncio
import math
import libui

def on_draw(ctx, area_w, area_h, clip_x, clip_y, clip_w, clip_h):
    # Path + fill
    path = libui.DrawPath()
    path.add_rectangle(10, 10, 200, 100)
    path.end()

    brush = libui.DrawBrush()
    brush.r, brush.g, brush.b, brush.a = 0.8, 0.2, 0.2, 1.0
    ctx.fill(path, brush)

    # Stroke
    circle = libui.DrawPath()
    circle.new_figure_with_arc(160, 200, 50, 0, 2 * math.pi, False)
    circle.end()

    params = libui.DrawStrokeParams()
    params.thickness = 3.0
    params.cap = libui.LineCap.ROUND
    ctx.stroke(circle, brush, params)

    # Gradient
    rect = libui.DrawPath()
    rect.add_rectangle(250, 10, 150, 100)
    rect.end()

    grad = libui.DrawBrush()
    grad.type = libui.BrushType.LINEAR_GRADIENT
    grad.x0, grad.y0 = 250, 10
    grad.x1, grad.y1 = 400, 110
    grad.set_stops([(0.0, 1, 0, 0, 1), (1.0, 0, 0, 1, 1)])
    ctx.fill(rect, grad)

    # Transform
    matrix = libui.DrawMatrix()
    matrix.set_identity()
    matrix.rotate(300, 200, 30)
    ctx.save()
    ctx.transform(matrix)
    p = libui.DrawPath()
    p.add_rectangle(270, 185, 60, 30)
    p.end()
    ctx.fill(p, brush)
    ctx.restore()

    # Attributed text
    text = libui.AttributedString("Styled text")
    text.set_attribute(libui.weight_attribute(libui.TextWeight.BOLD), 0, 6)
    text.set_attribute(libui.color_attribute(0.0, 0.5, 0.0, 1.0), 7, 11)
    layout = libui.DrawTextLayout(text, {"family": "sans-serif", "size": 14.0}, area_w)
    ctx.text(layout, 10, 130)

async def main():
    window = libui.Window("Drawing", 500, 350)

    vbox = libui.VerticalBox()
    area = libui.Area(on_draw)
    vbox.append(area, stretchy=True)
    window.set_child(vbox)

    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()

    await asyncio.Event().wait()

libui.run(main())

Async Integration

libui.run() sets up a two-thread architecture: the main thread pumps the native event loop while an asyncio event loop runs on a background thread.

import asyncio
import libui

async def main():
    window = libui.Window("Async Demo", 500, 400)
    vbox = libui.VerticalBox(padded=True)
    window.set_child(vbox)

    label = libui.Label("Starting...")
    vbox.append(label)

    # Async callbacks work naturally
    async def on_click():
        label.text = "Fetching..."
        await asyncio.sleep(1)
        label.text = "Done!"

    btn = libui.Button("Go")
    btn.on_clicked(on_click)
    vbox.append(btn)

    # Background tasks
    async def ticker():
        n = 0
        while True:
            await asyncio.sleep(1)
            n += 1
            label.text = f"Tick #{n}"

    asyncio.create_task(ticker())

    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()
    await asyncio.Event().wait()

libui.run(main())

Dialogs

import asyncio
import libui

async def main():
    window = libui.Window("Dialogs", 400, 200)
    vbox = libui.VerticalBox(padded=True)
    window.set_child(vbox)

    label = libui.Label("")
    vbox.append(label)

    def do_open():
        path = libui.open_file(window)
        if path:
            label.text = f"Opened: {path}"

    btn_open = libui.Button("Open File")
    btn_open.on_clicked(do_open)

    btn_folder = libui.Button("Open Folder")
    btn_folder.on_clicked(lambda: setattr(
        label, "text", f"Folder: {libui.open_folder(window) or '(cancelled)'}"))

    btn_msg = libui.Button("Message Box")
    btn_msg.on_clicked(lambda: libui.msg_box(window, "Info", "Operation complete."))

    btn_err = libui.Button("Error Box")
    btn_err.on_clicked(lambda: libui.msg_box_error(window, "Error", "Something failed."))

    for b in (btn_open, btn_folder, btn_msg, btn_err):
        vbox.append(b)

    window.on_closing(lambda: (libui.quit(), True)[-1])
    window.show()

    await asyncio.Event().wait()

libui.run(main())

Widget Reference

All Controls

Widget Description
Label Static or reactive text display
Button Clickable button
Entry Single-line text input (normal, password, search)
MultilineEntry Multi-line text editor
Checkbox Toggle with label
Slider Horizontal slider with range
Spinbox Numeric spinner with range
ProgressBar Progress indicator (0–100)
Combobox Dropdown selection
EditableCombobox Dropdown with text input
RadioButtons Mutually exclusive choices
ColorButton Color picker
FontButton Font picker
DateTimePicker Date, time, or datetime picker
Separator Visual divider (horizontal/vertical)
Area / DrawArea Custom 2D drawing surface
Table / DataTable Data grid with columns

Containers

Container Description
VBox / VerticalBox Stack children vertically
HBox / HorizontalBox Stack children horizontally
Group Titled container with border
Form Two-column label + control pairs
Tab Tabbed pages
Grid Position-based grid layout
Window Top-level window

Enumerations

Enum Values
libui.Align FILL, START, CENTER, END
libui.BrushType SOLID, LINEAR_GRADIENT, RADIAL_GRADIENT
libui.LineCap FLAT, ROUND, SQUARE
libui.LineJoin MITER, ROUND, BEVEL
libui.TextWeight THIN ... NORMAL ... BOLD ... MAXIMUM
libui.TextItalic NORMAL, OBLIQUE, ITALIC
libui.Underline NONE, SINGLE, DOUBLE, SUGGESTION
libui.SelectionMode NONE, ONE, ZERO_OR_ONE, MULTI
libui.SortIndicator NONE, ASCENDING, DESCENDING
libui.TableValueType INT, STRING, IMAGE
libui.TableModelColumn NEVER_EDITABLE, ALWAYS_EDITABLE

Examples

Run the examples from the project root:

python examples/hello.py                # minimal button + label
python examples/showcase_declarative.py # full declarative showcase (all widgets)
python examples/showcase.py             # full imperative showcase
python examples/example_async.py        # async widgets + background tasks
python examples/draw.py                 # custom drawing
python examples/table.py               # data table

Requirements

  • Python >= 3.13
  • Linux: GTK+-3.0
  • macOS: Cocoa (built-in)
  • Windows: Win32 API (built-in)

License

MIT — see LICENSE.

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

libui-0.1.10.tar.gz (906.4 kB view details)

Uploaded Source

Built Distributions

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

libui-0.1.10-cp314-cp314-win_amd64.whl (177.0 kB view details)

Uploaded CPython 3.14Windows x86-64

libui-0.1.10-cp314-cp314-manylinux_2_28_x86_64.whl (12.3 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ x86-64

libui-0.1.10-cp314-cp314-macosx_10_15_universal2.whl (353.2 kB view details)

Uploaded CPython 3.14macOS 10.15+ universal2 (ARM64, x86-64)

libui-0.1.10-cp313-cp313-win_amd64.whl (172.3 kB view details)

Uploaded CPython 3.13Windows x86-64

libui-0.1.10-cp313-cp313-manylinux_2_28_x86_64.whl (12.3 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.28+ x86-64

libui-0.1.10-cp313-cp313-macosx_10_13_universal2.whl (353.2 kB view details)

Uploaded CPython 3.13macOS 10.13+ universal2 (ARM64, x86-64)

libui-0.1.10-cp312-cp312-win_amd64.whl (172.3 kB view details)

Uploaded CPython 3.12Windows x86-64

libui-0.1.10-cp312-cp312-manylinux_2_28_x86_64.whl (12.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.28+ x86-64

libui-0.1.10-cp312-cp312-macosx_10_13_universal2.whl (353.2 kB view details)

Uploaded CPython 3.12macOS 10.13+ universal2 (ARM64, x86-64)

libui-0.1.10-cp311-cp311-win_amd64.whl (172.3 kB view details)

Uploaded CPython 3.11Windows x86-64

libui-0.1.10-cp311-cp311-manylinux_2_28_x86_64.whl (12.3 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.28+ x86-64

libui-0.1.10-cp311-cp311-macosx_10_9_universal2.whl (352.9 kB view details)

Uploaded CPython 3.11macOS 10.9+ universal2 (ARM64, x86-64)

libui-0.1.10-cp310-cp310-win_amd64.whl (172.3 kB view details)

Uploaded CPython 3.10Windows x86-64

libui-0.1.10-cp310-cp310-manylinux_2_28_x86_64.whl (12.3 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.28+ x86-64

libui-0.1.10-cp310-cp310-macosx_10_9_universal2.whl (352.9 kB view details)

Uploaded CPython 3.10macOS 10.9+ universal2 (ARM64, x86-64)

File details

Details for the file libui-0.1.10.tar.gz.

File metadata

  • Download URL: libui-0.1.10.tar.gz
  • Upload date:
  • Size: 906.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for libui-0.1.10.tar.gz
Algorithm Hash digest
SHA256 61d08bfbd440c12a755585bcd8681deb918ed19479627ec8e10e2b59c52fc45f
MD5 cfead80d8189461174fb0da2cd8f7af6
BLAKE2b-256 a9d145798ceb1ea270d0534618be6afa64c65783b3a2f0ba68c2a8448e709b03

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10.tar.gz:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: libui-0.1.10-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 177.0 kB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for libui-0.1.10-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 3c84674098cdea864bfdfdf8f8c8db7f5d676433c9c5425cd805be2dc92dc49c
MD5 c32a2620aedfbb9fb86a7abe23ddce63
BLAKE2b-256 ac8da9fe042b4b3c41dad4d4cb48beb4f5c31397ff8f3850c7721ab6f4a218d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp314-cp314-win_amd64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp314-cp314-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp314-cp314-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 274b6e32d5d54536bcf3d21b863fc8726fa128e454a5c33260b9f49cec0d6dae
MD5 410b327ca469a8f1f602774c992370fc
BLAKE2b-256 83561b785067b133e759c8950d81a182dd0e86ededa05c3a2f6cabf2d9b3d2a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp314-cp314-manylinux_2_28_x86_64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp314-cp314-macosx_10_15_universal2.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp314-cp314-macosx_10_15_universal2.whl
Algorithm Hash digest
SHA256 a1449eefb7b8694323040f083eee3b8891b9d1d45ee87b212bd337a1caaa7428
MD5 c1650d068bd2329ba3c51fda0be87425
BLAKE2b-256 90247e735b45f5f78d35d522d0d6a131dc3b5c125833abdeb40971d427b82ea3

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp314-cp314-macosx_10_15_universal2.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: libui-0.1.10-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 172.3 kB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for libui-0.1.10-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 26cc2d8b63e4e2c82761085ed11a39a147a55cc90f5e5e147229097816e218ca
MD5 bd0d1b96b63617e66debab6781da89c5
BLAKE2b-256 d6507fdeedb3ca6ca00af5cabc06e197bdab599429ee5649e08115df4801bfba

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp313-cp313-win_amd64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp313-cp313-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp313-cp313-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 cc1b414e62d8cca0abf8b792bffb91c5372d346cb40270afa05831e418d1bfdb
MD5 15bfc2eafe85c1f5bb37ca921731ac68
BLAKE2b-256 354d3dde47b6ee91964142ce8ecf3dbcb4d5a5775a6da09af690c00820908bec

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp313-cp313-manylinux_2_28_x86_64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp313-cp313-macosx_10_13_universal2.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp313-cp313-macosx_10_13_universal2.whl
Algorithm Hash digest
SHA256 78d40403812f0447dfc16e6b8a797074a2e07fcea02418f72cb7dd77edf0754e
MD5 18f1d95672f5e5b1ee7258f5170e4108
BLAKE2b-256 0b9a190e2c89ed26d795dacf8f2393b77deb63770df6cb5c4ee677291d5e0e36

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp313-cp313-macosx_10_13_universal2.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: libui-0.1.10-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 172.3 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for libui-0.1.10-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 c4c068018b33fb75d4d1e38a7055600ca931111ce985d459ccfd3e5a4854d2ec
MD5 4958bd7225d25f0d583a4e1ca3b34c0b
BLAKE2b-256 b3e898f692e32e6cbdcd65ac89c8c957f1ae46f4ab1cc888d1ea416c3f39aeec

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp312-cp312-win_amd64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 1e1351f10a9455a59759256999b8c4c07295823ff02ec2abb5d407098fb92fe6
MD5 32fb78059b900abeef6ce9cf4ad58b7b
BLAKE2b-256 dc1ff09b50d1606dc7f9d3caed88f6895eb7d7bfb30a7e15d9353d8812c646cf

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp312-cp312-manylinux_2_28_x86_64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp312-cp312-macosx_10_13_universal2.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp312-cp312-macosx_10_13_universal2.whl
Algorithm Hash digest
SHA256 6187b4799d2befd5538675eb95dd5212d9e0f73fae43d0b8dff2514683c05ef5
MD5 86fab93b4978ecd59e2b751b69a8c0b3
BLAKE2b-256 fe1e34edf17b249648fa8f4caaa7e4b8c41a0ea5ca12fdb19f9728612f303954

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp312-cp312-macosx_10_13_universal2.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: libui-0.1.10-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 172.3 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for libui-0.1.10-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 4001c973cbe6ee4e71acbf32ec5b05dcb01b8bfd72163665a84067a855c9f8cf
MD5 77dd81a60faf06a2f8d13e12ea9a080a
BLAKE2b-256 0689451dddd702087b078d8c2d8fda422656cb1dc7fe1d995f6a79dbf5cb9d47

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp311-cp311-win_amd64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 0cb0132e5fa481ef93157303cf360c6153aae652e07ff19b009c1e2eea3946e2
MD5 7c0eb5b99c00bd4040eb2b24f30ddd80
BLAKE2b-256 40b2b1ffcd662b996fcf057f7d34400be4dae7b326c21424d88b886d645f13d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp311-cp311-manylinux_2_28_x86_64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp311-cp311-macosx_10_9_universal2.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp311-cp311-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 3d3ebcfc6fc4cf5af4da8cd309ad34e06e7bbf9defc9bb7a99da70b79137361b
MD5 76309366b3c968088fd5fd0d52e29964
BLAKE2b-256 68ec671513a0e7d35efed7cb02c230b5dabe60e69effb5e114aef46275f3a49b

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp311-cp311-macosx_10_9_universal2.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: libui-0.1.10-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 172.3 kB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for libui-0.1.10-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 bc3709333090de40a6fa55fe6306d0a79325ff91fbb33ab0dc133f807dd739c3
MD5 316f8b5f82ddddcfcc64a538c5faf957
BLAKE2b-256 9eb4f687f5d03ce9b613bf7ec2e1d23b95152660f4e4cb46c56ec3b15a9be0ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp310-cp310-win_amd64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp310-cp310-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp310-cp310-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b63a1895a10fe22372c0adc2d390e6d5308a37c83ff139491c365674c61a0ee1
MD5 fcd3dea93acd8e4acb232913b94bc9dd
BLAKE2b-256 26a94689c6331c5511f7e341b1cd8eff7110cef4e3e733242540335f5bcf82b0

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp310-cp310-manylinux_2_28_x86_64.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file libui-0.1.10-cp310-cp310-macosx_10_9_universal2.whl.

File metadata

File hashes

Hashes for libui-0.1.10-cp310-cp310-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 2fa507a4ada161728ac79573162abb14be86694dc4c65269259f500d217fde8c
MD5 02c70b153f55d96286aa89292ec8580e
BLAKE2b-256 1d2fc77efb0a4c54aef79fe1f1a294187654bf73f9bb5d4e64de86f7f8f1fbc7

See more details on using hashes here.

Provenance

The following attestation bundles were made for libui-0.1.10-cp310-cp310-macosx_10_9_universal2.whl:

Publisher: release.yml on mosquito/libui-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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