Welcome to NionUI documentation!

NionUI is a toolkit for building desktop user interfaces (UI) in Python. It includes tools for declarative UI programming through simple Python dicts. It was primarily created to implement the Nion Swift application.

NionUI works in conjunction with a host application. The host application is responsible for initializing a Python environment, calling functions in this library in response to UI events, and providing idle time for this library to perform periodic maintenance tasks.

NionUI is designed to be compatible with multiple host applications, but includes built-in support for NionUI-Launcher, a high-performance Qt-based host application.

In conjunction with the host application, NionUI provides an application framework that includes:

  • Windows

  • Docked panels

  • Menus

  • Application management

  • Widget and layout system

  • Declarative UI system

  • Clipboard handling

  • Drag and drop handling

  • Threaded low-level drawing system

  • High-performance canvas items

  • Logging

  • Font handling

  • File dialogs

  • Screen and color scheme handling

  • Preferences

The widget and layout system includes:

  • Labels

  • Push buttons

  • Check boxes

  • Combo boxes

  • Line edits

  • Text edits

  • Text browsers

  • Progress bars

  • Radio buttons

  • Sliders

  • Groups

  • Tabs

  • Stacks

  • Splitters

  • Scroll areas

NionUI also provides canvas items, which are special high-performance widgets that can be displayed using explicit drawing commands similar to HTML canvas items.

NionUI also includes a declarative UI that allows you to construct your UI by defining a Python dict that describes the UI structure and connects it to Python handlers.

Installation

Configuring a Development Environment for Nion UI

It is recommended to create a Python virtual environment for Nion UI development, either one virtual environment per project or a single virtual environment for all Nion UI development.

For macOS and Linux, you can create a Python virtual environment using either pyenv and pyenv-virtualenv or uv.

For Windows, you can create a Python virtual environment using uv.

Using pyenv (macOS and Linux)

Find the latest version of Python using pyenv install --list. Install the latest version with pyenv install <version>.

Create a new virtual environment with pyenv virtualenv <version> <env-name> where env-name is the name of your development virtual environment, something like nionui-dev. Activate the environment with pyenv activate <env-name>.

Configure your virtual environment with nionui and nionui-tool using pip:

pyenv install 3.13.11
pyenv virtualenv 3.13.11 nionui-dev
pyenv activate nionui-dev
python -m pip install nionui nionui-tool

Using uv (macOS, Windows, and Linux)

Find the latest version of Python by updating uv and listing available versions with uv python list.

Install the latest version of Python using uv python install <version>.

Create a new virtual environment with uv venv <env-name> where env-name is the name of your development virtual environment, something like nionui-dev. With uv, you can also just run uv venv to create a virtual environment for the current directory. Activate the environment (see uv documentation).

uv python install 3.13.11
uv venv nionui-dev
.venv\nionui-dev\Scripts\activate
python -m pip install nionui nionui-tool

To install on macOS, run:

uv python install 3.13.11
uv venv nionui-dev
source .venv/nionui-dev/bin/activate
python -m pip install nionui nionui-tool

Running an Application

Run an application using the nionui command line tool.

You can run a specific single file application using:

nionui my_app.py

You can run an application in your current Python environment by providing the module name:

nionui nionui_app.my_app_module

You can run the examples:

nionui nionui_app.nionui_examples.hello_world
nionui nionui_app.nionui_examples.ui_demo
nionui nionui_app.nionui_examples.canvas_demo

Thread Handling

The host application responds to UI events by calling functions in NionUI. It also regularly calls periodic to provide idle time to perform maintenance tasks.

Your code needs to ensure it runs quickly in response to those events to maintain UI responsiveness. If your code needs to do a long running task or something periodically, you can use the techniques in this section to handle that.

Async Functions

NionUI works seamlessly with async tasks. The host application regularly calls periodic which runs any pending async tasks.

To start an async task in response to a UI event, you can use code like:

def handle_ui_event(window: Window.Window) -> None:
    window.event_loop.create_task(my_async_function())

Your async task should not do anything that takes more than approximately 10ms to run. If you need to do something that takes longer, you can use various techniques below to run the time consuming or blocking task in a separate thread.

For short blocking tasks where total thread exhaustion is not an issue, you can use asyncio.to_thread to run the task in a separate thread without blocking the UI.

import asyncio

def blocking_task() -> None:
    # do something that takes a while to run
    ...

async def my_async_function():
    await asyncio.to_thread(blocking_task)

For long running tasks or tasks that need to limit their thread usage, you can use a ThreadPoolExecutor to run the task in a separate thread.

import asyncio
import concurrent.futures

executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)

def blocking_task() -> None:
    # do something that takes a while to run
    ...

async def my_async_function(window: Window.Window) -> None:
    await window.event_loop.run_in_executor(executor, blocking_task)

From within a thread, you can call specific functions in the main thread using call_soon_threadsafe on the event loop.

def thread_function(event_loop: asyncio.AbstractEventLoop) -> None:
    # do something in the thread
    ...
    event_loop.call_soon_threadsafe(some_function_in_main_thread)

You can also run async functions from a thread by using run_coroutine_threadsafe on the event loop. Using this technique, you can also wait for return values from the async function.

import asyncio

def thread_function(event_loop: asyncio.AbstractEventLoop) -> None:
    # do something in the thread
    ...
    future = asyncio.run_coroutine_threadsafe(my_async_function(), event_loop)
    result = future.result()  # this will block the thread until the async function is done

Very few UI related functions are thread safe. If you need to interact with the UI, use one of the techniques above to call a function in the main thread that interacts with the UI. The exception is calling the update method on a canvas item. The update method is thread safe and can be called from a thread to schedule a redraw of the canvas item.

As much as possible, functions running on threads should minimize their footprint. As an example, instead of passing the window object to a thread, only pass the event loop. This minimizes the chance of accidentally calling a non-thread safe function from the thread.

As much as possible, functions running on threads should be self contained and not access any external objects. Ideally, they can be functional in nature, taking all of their input as parameters and returning all of their output as return values. This minimizes the chance of accidentally accessing non-thread safe objects from the thread and also makes it easier to test the functions.

Long running tasks will typically need to be cancellable and should check for cancellation regularly. Cancellation can occur in response to user actions such as closing a window or clicking a cancel button. It is important to ensure that your long running tasks are cancelled properly when the window or application closes.

async tasks are automatically cancelled when the window or application closes. However, within the task itself, you will need to handle any cleanup of any threads that have been launched from the task. The async task will immediately throw an asyncio.CancelledError at the next await point, but the thread itself will continue to run until it finishes. You can handle the asyncio.CancelledError in your async task and perform any necessary cleanup of the thread, such as cancelling any long running operations or joining the thread.

Here is an example:

import asyncio
import concurrent.futures
import threading

executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)

def blocking_task(cancel_event: threading.Event) -> None:
    # do something that takes a while to run
    while not cancel_event.is_set():
        # do some work
        ...

async def my_async_function(window: Window.Window) -> None:
    cancel_event = threading.Event()
    try:
        thread_future = window.event_loop.run_in_executor(executor, blocking_task, cancel_event)
        await thread_future
    except asyncio.CancelledError:
        # perform any necessary cleanup of the thread here
        cancel_event.set()  # signal the thread to stop
        await thread_future  # wait for the thread to finish
        raise

Introduction to the Declarative UI

The declarative features allow you to have a clean separation between the UI and your code for handling the UI.

Put this code into a file named hello_world.py and then run it using nionui hello_world.py.

import gettext
import typing

from nion.ui import Application
from nion.ui import Declarative
from nion.ui import UserInterface

_ = gettext.gettext

class Handler(Declarative.HandlerLike):
    def __init__(self) -> None:
        self.label_item = None
        self.click_count = 0

    def button_clicked(self, widget: UserInterface.PushButtonWidget) -> None:
        self.click_count += 1
        self.label_item.text = _("Clicked") + " " + str(self.click_count)

def main(*args: typing.Any, **kwargs: typing.Any) -> Application.BaseApplication:
    u = Declarative.DeclarativeUI()
    button = u.create_push_button(text=_("Hello World"), on_clicked="button_clicked")
    label = u.create_label(name="label_item", text=_("Not Clicked"))
    column = u.create_column(button, label, spacing=8)
    window = u.create_window(column, title=_("Hello World"), margin=12)
    handler = Handler()
    return Application.run_window(args[0], args[1], window, handler)

if __name__ == "__main__":
    print("This script cannot be run directly. Use the nionui command line tool.")

At the top, a Handler class is defined which includes a property label_item and a method button_clicked which will be connected to the UI. It also includes a property click_count that will be used separately.

The main function begins by creating a u object which will help construct the Python dict representing the layout.

With the u object, a button is declared. The button declares that the on_clicked event will be connected to the button_clicked method of the handler.

Next, a label is declared. By specifying name to the label, the declarative UI engine will store the label widget into the handler when the app is run (the label_item property in the handler).

Next, a column is declared with the button and label in the column. Both are put into a window declaration.

Next, an instance of Handler is created.

Finally, the window and handler are passed to Application.run_window which constructs the window user interface, connects it to the handler, and presents the user window to the user.

The window variable is a Python dictionary. It gets connected to the Handler in the Application.run_window method. The dictionary looks like this:

{'content': {'children': [{'on_clicked': 'button_clicked',
                           'text': 'Hello World',
                           'type': 'push_button'},
                          {'name': 'label_item',
                           'text': 'Not Clicked',
                           'type': 'text_label'}],
             'spacing': 8,
             'type': 'column'},
 'margin': 12,
 'title': 'Hello World',
 'type': 'window'}

User Interface Backends

NionUI is intended to provide a Python oriented user interface API. You might consider it to be a wrapper on a lower level API such as Qt. However, it is more general in that other low level UI’s could be used as long as a specific implementation of UserInterface is provided for those lower level UI’s.

Currently only two low level implementations are provided:

  • A NionUI-Launcher implementation in C++ based directly on Qt.

  • A TestUI user interface, used for testing.

An HTML/JavaScript implementation is also in the works.

Organizing a Nion UI Application

Although it is possible to write a complete Nion UI application in a single file, it is recommended to organize your code into multiple files and modules.

A typical organization might look like this:

nionui_app/
    my_app/
        main.py

By convention, the top level module of your application is named nionui_app. Inside that module, you can have sub-modules for your specific application.

Putting your application into the nionui_app module allows future tooling to recognize your application as a Nion UI application.

The main entry point for your application is typically a file named main.py. This file contains the main function which is called by the nionui command line tool to start your application.

Declarative UI

The declarative features allow you to have a clean separation between the UI and your code for handling the UI.

A declaration is split into a description (a Python dict, serializable to JSON) and a handler (code). This tookit takes care of connecting the declaration to the handler when the declaration is attached to a window or other real UI.

Since the declaration is a Python dict (serializable to JSON), the declarative part of the UI can be stored in a JSON text file. This opens the possibility of having a graphical editor for the declarative part, which reads and write the JSON text file. A graphical editor is not yet available.

The declarative UI can be built explicitly using methods of a DeclarativeUI class or by providing an explicit dict. This guide assumes it will be built using the DeclarativeUI class and explicit methods.

The declarative UI provides top level support for windows and dialogs.

It also provides support for layout into rows, columns, and stacks.

It provides labels, push buttons, check boxes, combo boxes, line edits, progress bars, radio buttons, sliders, groups, and tabs.

It provides experimental support for compositions (reusable UI sections).

And finally it provides support for binding UI elements to your application specific code using bindings and converters.

Handlers

The goal of the UI is to connect user actions to your code. The declarative UI does this by an object that you provide called the handler.

When a declarative UI is instantiated it is turned into layouts and widgets that are displayed to the user. You can get access to specific widgets, bind to parts of those widgets such as a text label, and respond to events such as a button click.

To do all of these things, your declaration specifies how to make these connections to your handler.

Handler Lifecycle

For your top level handler, you will typically write the class yourself, initializing properties and providing methods as explained below.

You will instantiate your handler yourself and pass the instance to the declarative UI engine along with the Python dict which describes your UI.

The declarative UI engine will build the widgets and put them in the appropriate container (such as a window) and then connect the widgets, events, bindings, etc. to your handler.

For the most part, your handler can be initialized in its __init__ method. However, at the point the handler is created, it will not have access to any of the UI widgets.

Assuming your UI description is in a variable layout and that your handler class is named Handler, initializing the declarative UI might look something like this:

layout = build_ui_layout()
handler = Handler()
Declarative.run_dialog(layout, handler)

During run_dialog, the declarative UI engine will populate properties of your handler connected to the UI, inject useful objects such as _event_loop, and then call the method init_handler which can be used to initialize any values that require the UI to be in place.

Your handler can do any further initialization of the widgets in init_handler.

Connecting Widgets

Most widgets declarations provide an optional name parameter. If you pass a string value for name, the widget asssociated with that declaration will be stored into your handler under the name property.

For instance, let’s say you create a button with the following method:

button = ui.create_push_button(text="Push Me", name="my_button")

Once your handler has been initialized, the PushButtonWidget associated with your declaration will be stored in the property my_button of your handler.

This gives you direct access to the widget in your handler. Sometimes this is necessary, although direct widget access may limit future compatibility, so it should be used sparingly.

Connecting Strings

Some widgets, such as a label and group, display text. For those widgets, you can provide that text directly when creating the widget.

button = ui.create_push_button(text="Push Me", name="my_button")

In the code above, the text “Push Me” is passed to the create_push_button method and will appear as the text of the button.

This may seem obvious, but be sure to note the distinction between the string passed as text and the string passed as name. The string passed as text is used directly in the UI and displayed to the user. The string passed as name is used as a property name on your handler in which to store the PushButtonWidget instance.

In the binding section described below, you will see how to bind the text of the label to a variable in your handler.

Connecting References

Many widgets, such as check boxes, have associated values that can be set from code or changed by the user. For those widgets, you can provide a handler property which holds the value.

class Handler:
    def __init__(self) -> None:
        self.enabled = False

check_box = ui.create_check_box(text="Enable", name="enable_cb", checked="enabled")

As before, the label string displayed to the user is passed as text. The CheckBoxWidget is stored into the handler under the property enable_cb using the name property.

But in this case, by passing “enabled” as the checked parameter, the state of the checkbox will be initialized with the enabled value in the handler. And when the user clicks on the checkbox, the enabled property of the handler will be changed to reflect the checked state.

Events

Many widgets, such as buttons, trigger events that can invoke methods in the handler.

class Handler:
    def handle_click(self, widget: UserInterface.PushButtonWidget) -> None:
        print("Clicked!")

push_button = ui.create_push_button(text="Push Me", on_clicked="handle_click")

In this case, the string “handle_click” passed as the parameter on_clicked indicates that the event on_clicked should call the method handle_click on the handler when the user clicks the button.

Each widget defines its own set of events and each event may have unique parameters. Refer the documentation for the specific widget for detailed information.

Bindings

As you use string values, references, and events, a pattern will emerge where a value associated with a UI element will be connected to the handler, you will listen for the changed event for that value, and you will update the widget when the value changes.

The declarative UI engine provides a mechanism called binding which streamlines this pattern through the use of property models, converters, and events.

class Handler:
    def __init__(self) -> None:
        self.enabled_model = Model.PropertyModel(False)
        self.enabled_model.on_value_changed = self.enabled_changed

    def enabled_changed(self, widget: UserInterface.CheckBoxWidget, new_enabled: bool) -> None:
        print(f"Enabled changed to {new_enabled}")

check_box = ui.create_check_box(text="Enabled", value="@binding(enabled_model.value)")

In the example above, the handler provides a model named enabled_model to hold the value of the enabled property. The handler attaches a listener to the model that gets called when the value of the model changes. Finally, the declaration for the check box indicates that the value will bind to the value of the model enabled_model.value.

Using this technique, interacting with the enabled value is easy. Just get and set the value from the enabled_model.

If you wanted to connect a button to reset the value of enabled, you could add a method to the handler and connect it to a button.

class Handler:
    ...

    def button_pushed(self, widget: UserInterface.PushButtonWidget) -> None:
        self.enabled_model.value = True

Most places where you use a string value or a reference can be replaced with a binding.

Converters

Often when using bindings, you will need to convert the value from one format to another. For instance, you might want the user to enter an integer into a text field. You can do this by attaching a converter to a binding.

class Handler:
    def __init__(self) -> None:
        self.year_model = Model.PropertyModel(2001)
        self.year_converter = Converter.IntegerToStringConverter()

year_field = ui.create_line_edit(text="Enabled", text="@binding(year_model.value, converter=year_converter)")

Now the value in year_model will be stored as an integer, but the line edit requires a string. The converter will do the conversion automatically.

Bindings Technical Details

This section refers to classes defined in the project NionUtils.

In the examples above, the bindings use a Model.PropertyModel to store the value. The PropertyModel class interacts with bindings using a simple protocol.

First, it supports getting and setting a property, named value in this case.

Next, it provides a Event.Event named “property_changed” that gets fired whenever the value is set.

You can explicitly provide this functionality and bind to objects other than a Model.PropertyModel.

class Handler:
    def __init__(self) -> None:
        self.property_changed_event = Event.Event()
        self.__name = "March"

    @property
    def name(self) -> str:
        return self.__name

    @name.setter
    def name(self, value: str) -> None:
        self.__name = value
        self.property_changed_event.fire("name")

name_field = ui.create_line_edit(text="Your Name?", text="@binding(name)")

Application and Windows

The declarative UI provides top level support for windows and dialogs.

You can declare a window using ui.create_window (see below). Once you have a window declaration, you can run the window using Application.run_window in your main function.

def main(*args: typing.Any, **kwargs: typing.Any) -> Application.BaseApplication:
    u = Declarative.DeclarativeUI()
    content = u.create_column(u.create_label(text="Hello World"))
    window = u.create_window(content, title=_("Hello World"), margin=12)
    handler = object()  # a dummy handler in this example
    return Application.run_window(args[0], args[1], window, handler)
class DeclarativeUI
create_modeless_dialog(content: Mapping[str, Any], *, title: str | None = None, resources: Mapping[str, Any] | None = None, **kwargs: Any) Dict[str, Any]

Create a modeless dialog UI description with content, title, resources, and margin.

Parameters:

content – UI description of the content

Keyword Arguments:
  • title – title of the window

  • resources – additional resources

  • margin – margin in points

Returns:

a UI description of the dialog

create_window(content: Mapping[str, Any], *, title: str | None = None, resources: Mapping[str, Any] | None = None, window_style: str | None = None, **kwargs: Any) Dict[str, Any]

Create a window UI description with content, title, resources, and margin.

Parameters:

content – UI description description of the content

Keyword Arguments:
  • title – title of the window

  • resources – additional resources

  • margin – margin in points

Returns:

a UI description of the window

Layout

Content layout is done using rows, columns, groups, and stacks. Tabs and groups participate in layout, but will be discussed below under the widgets section since they can’t be used as the top-most layout.

A row, column, or stack can be used as a base for additional content. Each element contains other elements (referred to as children in this guide).

Rows and columns layout their children horizontally or vertically. The children can optionally be separated by spacing and the entire row or column can have a margin spacing.

In addition, you can explicitly add spacing or stretches to rows and columns. Spacing ensures the spacing for that item never goes smaller than the spacing value. And stretch attempts to take up free space in the layout, effectly squishing the other items to their minimum sizes. If there are more than one stretches, the free space will be distributed equally between them.

Stacks layout their children on top of one another with only a single child visible at any given time. Stacks are useful when presenting optional content where the specific content is controlled by another UI element. Stacks can have a margin spacing.

Scroll areas layout their content in a scrollable area with scroll bars.

class DeclarativeUI
create_column(*children: Mapping[str, Any], name: str | None = None, items: str | None = None, item_component_id: str | None = None, spacing: int | None = None, **kwargs: Any) Dict[str, Any]

Create a column UI description with children or dynamic items, spacing, and margin.

The children can be passed as parameters or constructed from an observable list specified by items and item_component_id.

If the children are constructed from an observable list, a component instance will be created for each item in the list. Adding or removing items from the list will dynamically update the children. The associated handler must implement the resources property with a value for the key item_component_id describing the component UI. It must also implement create_handler to create a handler for each component. The create_handler call will receive two extra keyword arguments container and item in additional to the component_id.

Parameters:

children – children to put into the column

Keyword Arguments:
  • items – handler observable list property from which to build child components

  • item_component_id – resource identifier of component ui description for child components

  • spacing – spacing between items, in points

  • margin – margin, in points

Returns:

a UI description of the column

create_row(*children: Mapping[str, Any], name: str | None = None, items: str | None = None, item_component_id: str | None = None, spacing: int | None = None, **kwargs: Any) Dict[str, Any]

Create a row UI description with children or dynamic items, spacing, and margin.

The children can be passed as parameters or constructed from an observable list specified by items and item_component_id.

If the children are constructed from an observable list, a component instance will be created for each item in the list. Adding or removing items from the list will dynamically update the children. The associated handler must implement the resources property with a value for the key item_component_id describing the component UI. It must also implement create_handler to create a handler for each component. The create_handler call will receive two extra keyword arguments container and item in additional to the component_id.

Parameters:

children – children to put into the row

Keyword Arguments:
  • items – handler observable list property from which to build child components

  • item_component_id – resource identifier of component ui description for child components

  • spacing – spacing between items, in points

  • margin – margin, in points

Returns:

a UI description of the row

create_scroll_area(content: Mapping[str, Any], name: str | None = None, **kwargs: Any) Dict[str, Any]

Create a scroll area UI description with content and a name.

Parameters:

content – UI description of the content

Keyword Arguments:

name – handler property in which to store widget (optional)

Returns:

UI description of the scroll area

create_spacing(size: int) Dict[str, Any]

Create a spacing UI description for a row or column.

Keyword Arguments:

size – spacing, in points

Returns:

a UI description of the spacing

create_stack(*children: Mapping[str, Any], items: str | None = None, item_component_id: str | None = None, name: str | None = None, current_index: str | None = None, on_current_index_changed: str | None = None, **kwargs: Any) Dict[str, Any]

Create a stack UI description with children or dynamic items, the current index and optional changed event.

The children can be passed as parameters or constructed from an observable list specified by items and item_component_id.

If the children are constructed from an observable list, a component instance will be created for each item in the list. Adding or removing items from the list will dynamically update the children. The associated handler must implement the resources property with a value for the key item_component_id describing the component UI. It must also implement create_handler to create a handler for each component. The create_handler call will receive two extra keyword arguments container and item in additional to the component_id.

The current_index controls which child is displayed.

The on_current_index_changed callback reference takes widget and current_index parameters. The type signature in the handler should be typing.Callable[[UIWidget, int], None].

Parameters:

children – stack items

Keyword Arguments:
  • items – handler observable list property from which to build child components

  • item_component_id – resource identifier of component ui description for child components

  • name – handler property in which to store widget (optional)

  • current_index – current index handler reference (bindable, optional)

  • on_current_index_changed – callback when current index changes (optional)

Returns:

a UI description of the stack

create_stretch() Dict[str, Any]

Create a stretch UI description for a row or column.

Returns:

a UI description of the stretch

Widgets

You can create labels, push buttons, check boxes, combo boxes, line edits, progress bars, radio buttons, sliders, groups, and tabs.

Each is described below.

class DeclarativeUI
create_check_box(*, text: str | None = None, icon: str | None = None, name: str | None = None, checked: str | None = None, check_state: str | None = None, tristate: bool | None = None, on_checked_changed: str | None = None, on_check_state_changed: str | None = None, **kwargs: Any) Dict[str, Any]

Create a check box UI description with text, name, state information, and events.

The checked and check_state both refer to the check state. Some callers may choose to use the simpler checked which is a simple boolean. check_state is a string and must be one of ‘checked’, ‘unchecked’, or ‘partial’. ‘partial’ is only valid if tristate is True.

The on_checked_changed callback is invoked when the user changes the state of the check box. The widget and the new state of the check box are passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget, bool], None].

The on_check_state_changed callback is invoked when the user changes the state of the check box, but it also includes the ‘partial’ state if enabled. The widget and the new state of the check box are passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget, str], None].

Keyword Arguments:
  • text – text of the label (bindable)

  • name – handler property in which to store widget (optional)

  • checked – checked state (bool)

  • check_state – checked state (string: checked, unchecked, or partial)

  • tristate – whether the check box is tristate or not

  • on_checked_changed – callback when checked changes (optional)

  • on_check_state_changed – callback when check state changes (optional)

Returns:

UI description of the check box

create_combo_box(*, name: str | None = None, items: Sequence[str] | None = None, items_ref: str | None = None, current_index: str | None = None, on_current_index_changed: str | None = None, **kwargs: Any) Dict[str, Any]

Create a combo box UI description with name, items, current index, and events.

The on_current_index_changed callback is invoked when the user changes the selected item in the combo box. The widget and the new index of the selected item are passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget, int], None].

Keyword Arguments:
  • name – handler property in which to store widget (optional)

  • items – list combo box items (strings, optional)

  • items_ref – handler reference of combo box items (bindable, optional)

  • current_index – current index handler reference (bindable, optional)

  • on_current_index_changed – callback when current index changes (optional)

Returns:

UI description of the combo box

create_group(content: Mapping[str, Any], name: str | None = None, title: str | None = None, **kwargs: Any) Dict[str, Any]

Create a group UI description with content, a name, a title, and a margin.

Parameters:

content – UI description of the content

Keyword Arguments:
  • name – handler property in which to store widget (optional)

  • title – title of the group

  • margin – margin in points

Returns:

UI description of the group

create_label(*, text: str | None = None, name: str | None = None, **kwargs: Any) Dict[str, Any]

Create a label UI description with text and an optional name.

Keyword Arguments:
  • text – text of the label (bindable)

  • name – handler property in which to store widget (optional)

  • width – width in points (optional, default None)

Returns:

UI description of the label

create_line_edit(*, text: str | None = None, name: str | None = None, editable: bool | None = None, placeholder_text: str | None = None, clear_button_enabled: bool | None = None, on_editing_finished: str | None = None, on_escape_pressed: str | None = None, on_return_pressed: str | None = None, on_key_pressed: str | None = None, on_text_edited: str | None = None, **kwargs: Any) Dict[str, Any]

Create a line edit UI description with text, name, placeholder, options, and events.

The on_editing_finished callback is invoked when the user presses return or escape or when they change keyboard focus away from the line edit. The line edit widget and string are passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget, str], None].

The on_escape_pressed and on_return_pressed callbacks are invoked when the user presses escape or return. The line edit widget is passed and these methods must return True if they handle the key or False otherwise. Their type signatures in the handler should be typing.Callable[[UIWidget], bool].

The on_key_pressed callback is invoked when the user types a key. The line edit widget and a key instance are passed. This method should return True if the key is handled (it will not go into the line edit field) and return False if not handled (it will be entered as regular text). The type signature in the handler should be typing.Callable[[UIWidget, UIKey], bool].

The on_text_edited callback is invoked when the user changes the text. The line edit widget and the new text are passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget, str], None].

Keyword Arguments:
  • text – handler reference to line edit text (bindable, required)

  • name – handler property in which to store widget (optional)

  • editable – whether the line edit text is editable (optional, default True)

  • placeholder_text – text to display when line edit is empty (optional)

  • clear_button_enabled – whether the clear button is enabled (optional, default False)

  • width – width in points (optional, default None)

  • on_editing_finished – callback when editing is finished (return or blur focus)

  • on_escape_pressed – callback when escape is pressed, return true if handled

  • on_return_pressed – callback when return is pressed, return true if handled

  • on_key_pressed – callback when a key is pressed, return true if handled

  • on_text_edited – callback when text is edited

Returns:

UI description of the line edit

create_progress_bar(*, name: str | None = None, value: str | None = None, minimum: int | None = None, maximum: int | None = None, **kwargs: Any) Dict[str, Any]

Create a progress bar UI description with name, value, and limits.

Keyword Arguments:
  • name – handler property in which to store widget (optional)

  • value – handler reference to the current value (required, bindable)

  • minimum – minimum value (default 0)

  • maximum – maximum value (default 100)

Returns:

UI description of the progress bar

create_push_button(*, text: str | None = None, icon: str | None = None, name: str | None = None, on_clicked: str | None = None, **kwargs: Any) Dict[str, Any]

Create a push button UI description with text, name, an event.

The on_clicked callback is invoked when the user clicks the button. The widget is passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget], None].

Keyword Arguments:
  • text – text of the label (bindable)

  • icon – icon, bindable to numpy uint32 bgra array

  • name – handler property in which to store widget (optional)

  • width – width in points (optional, default None)

  • on_clicked – callback when button clicked

Returns:

UI description of the push button

create_radio_button(*, name: str | None = None, text: str | None = None, icon: str | None = None, value: Any = None, group_value: str | None = None, **kwargs: Any) Dict[str, Any]

Create a radio button UI description with text, name, value, and group value.

A set of radio buttons should be created such that each has a different value but shares a common group_value. The type of value must match the type of group_value.

Keyword Arguments:
  • name – handler property in which to store widget (optional)

  • text – text of the label (bindable)

  • value – unique value within its group (required)

  • group_value – common value handler reference (bindable, required)

Returns:

UI description of the radio button

create_slider(*, name: str | None = None, value: str | None = None, minimum: int | None = None, maximum: int | None = None, on_value_changed: str | None = None, on_slider_pressed: str | None = None, on_slider_released: str | None = None, on_slider_moved: str | None = None, **kwargs: Any) Dict[str, Any]

Create a slider UI description with name, value, limits, and events.

The on_value_changed callback is invoked whenever the slider value changes, including if set programmatically. The widget and the new value are passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget, int], None].

The on_slider_pressed callback is invoked when the user begins dragging the slider. The widget is passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget], None].

The on_slider_released callback is invoked when the user stops dragging the slider. The widget is passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget], None].

The on_slider_moved callback is invoked whenever the slider value changes while the user is dragging. The widget and the new value are passed to the callback. The type signature in the handler should be typing.Callable[[UIWidget, int], None].

Keyword Arguments:
  • name – handler property in which to store widget (optional)

  • value – handler reference to the current value (required, bindable)

  • minimum – minimum value (default 0)

  • maximum – maximum value (default 100)

  • on_value_changed – callback when value changes, any source (optional)

  • on_slider_pressed – callback when slider is pressed (optional)

  • on_slider_released – callback when slider is released (optional)

  • on_slider_moved – callback when slider moves, user initiated only (optional)

Returns:

UI description of the slider

create_tab(label: str, content: Mapping[str, Any]) Dict[str, Any]

Create a tab UI description with a label and content.

Parameters:
  • label – label for the tab

  • content – UI description of the content

Returns:

a UI description of the tab

create_tabs(*tabs: Mapping[str, Any], name: str | None = None, current_index: str | None = None, on_current_index_changed: str | None = None, **kwargs: Any) Dict[str, Any]

Create a tabs UI description with children, the current index and optional changed event.

The children must be tabs created by create_tab().

The current_index controls which tab is displayed.

The on_current_index_changed callback reference takes widget and current_index parameters. The type signature in the handler should be typing.Callable[[UIWidget, int], None].

Parameters:

children – child tabs

Keyword Arguments:
  • name – handler property in which to store widget (optional)

  • current_index – current index handler reference (bindable, optional)

  • on_current_index_changed – callback when current index changes (optional)

Returns:

a UI description of the tabs

Components

It provides experimental support for compositions (reusable UI sections).

Common Properties

Many widgets and layouts share common properties. These common properties are described here.

enabled

A boolean value indicating whether the widget is enabled or disabled. Bindable.

visible

A boolean value indicating whether the widget is visible or hidden. Bindable.

tool_tip

A string value indicating the tooltip text for the widget. Bindable.

color

A string value indicating the color for the widget. Bindable.

background_color

A string value indicating the background color for the widget. Bindable.

font

A string value indicating the font for the widget. Bindable.

border_color

A string value indicating the border color for the widget. Used for debugging. Bindable.

Resources

Widgets that construct their content dynamically get the dynamic content via resources. Resources can be returned by as a dict from the associated handler’s resources property, or by implementing get_resources on the handler, or both.

class Handler:
    def __init__(self) -> None:
        u = Declarative.DeclarativeUI()
        self.resources = {"component": u.define_component(u.create_label(text="Component"))}

    def get_resource(resource_id: str, **kwargs: typing.Any) -> UIDescription | None:
        if resource_id == "component2":
            return u.define_component(u.create_label(text="Component 2"))
        return None

Indices and tables