S or /

egui

Talon's egui module provides Python bindings for the egui graphics library. See egui's official documentation for more features.

A function for showing egui widgets should be defined with the async keyword and take a ui object as an argument. Widgets can be drawn on the ui using methods. Keep in mind that the framework will call the ui function repeatedly at a rapid pace.

Basic Widgets

Mutable

A Mutable object wraps a value so it can be set with .set() and accessed with .get(). This is used by widgets that set a value based on user input. The following example uses a Mutable to wrap a string editable through a text field.

        ...
        self.single_line_text_input = egui.Mutable("")
        ...

        ui.add(egui.TextEdit.singleline(self.single_line_text_input))
        ui.add_space(10)
        ui.label("Lowercase text:")
        ui.label(self.single_line_text_input.get().lower())
        ...

Button

You create a button with ui.button() and a string argument for the button's label. This returns an object you can use to check if the button has been clicked with .clicked(). The following example code sets a Mutable to the empty string when a button is clicked.

    ...
    if ui.button("Clear Text").clicked():
        self.single_line_text_input.set("")
    ...

Label

You create a label with ui.label() with a string argument for the label text. This shows text but does not allow editing it.

Example:

    ...
    ui.label(f"Page {self.page_number}")
    ...

Checkbox

A checkbox allows toggling a Mutable wrapped boolean. You create a checkbox with ui.checkbox().

Arguments:

1: a Mutable wrapping a boolean

2: a string for the label text

Example:

    ...
    self.make_lowercase = egui.Mutable(False)
    ...
    if self.make_lowercase.get():
        ...
    else:
        ...
    ui.checkbox(self.make_lowercase, "Show the text in lowercase")
    ...

Single Line Text Field

You create a single line text field for editing text with ui.add(egui.TextEdit.singleline(...)). TextEdit.singleline takes a Mutable wrapped string as its argument.

Example:

    ...
    self.single_line_text_input = egui.Mutable("")
    ...
    ui.add(egui.TextEdit.singleline(self.single_line_text_input))
    ui.label(self.single_line_text_input.get().lower())
    ...

Multiline Text Field

You create a multiline text field for editing text with ui.add(egui.TextEdit.multiline(...)). TextEdit.multiline takes a Mutable wrapped string as its argument.

    ...
    self.multiline_text_input = egui.Mutable("This one allows multiple lines.\nThis is another line.")
    ...
    ui.add(egui.TextEdit.multiline(self.multiline_text_input))
    ...

Separator

You create a separator with ui.separator() to visually separate widgets with a line.

Example:

        ...
        ui.label("This Is a Title")
        ui.separator()
        ui.label("this is clearly separated from the title")
        ...

Space

You can create space in between widgets to visually separate them with ui.add_space(). This takes the amount of space to add between the widgets as an argument.

Example:

        ...
        ui.label("This Is a Title")
        ui.add_space(10)
        ui.label("this is clearly separated from the title")
        ...

Selectable Label

A selectable label is a text label that is highlighted when a boolean argument passed to it is true. These can be useful for letting users pick options. You create one with ui.selectable_label(). This returns an object you can use to check if the selectable_label has been clicked with .clicked().

Arguments:

1: a boolean indicating if it is currently selected

2: a string for the label text

Example:

        ...
        for i in range(1, 4):
            is_selected = self.chosen_option.get() == i
            label_text = f"Choose {i}"
            if ui.selectable_label(is_selected, label_text).clicked():
                self.chosen_option.set(i)
        ...

Radio Button

You can create a radio button withui.radio(). A radio button shows a circle by a label. The circle is filled in when the boolean passed to it is true. This returns an object you can use to check if the radio button has been clicked with .clicked().

Arguments:

1: a boolean indicating if it is currently selected

2: a string for the label text

Example:

        ...
        for i in range(1, 4):
            is_selected = self.chosen_option.get() == i
            label_text = f"Choose {i}"
            if ui.radio(is_selected, label_text).clicked():
                self.chosen_option.set(i)
        ...

Combo Box

A combo box allows showing options in a dropdown.

You can create a combo box with egui.ComboBox.from_label() with a string argument for the label. You can call the .selected_text() method with a string argument for the currently selected text to display it when the dropdown is collapsed.

To show the combo box, call .show() wrapped within async with (...) as combo_box:. You can use another variable name instead of combo_box.

You can use combo_box.shown to check if the combo box is being shown and combo_box.ui to access a ui that can be used to show widgets inside the combo box.

One way to show an option inside a combo box is with combo_box.ui.selectable_value().

Arguments for selectable_value:

1: A Mutable to set when the option is clicked

2: the value corresponding to the option

3: the string to display for the option in the dropdown

Example:

    ...
    combo_box_label = "Pick an option"
    currently_chosen_option = str(self.chosen_option.get())
    async with (
        egui.ComboBox.from_label(combo_box_label)
        .selected_text(currently_chosen_option)
        .show()
    ) as combo_box:
        if combo_box.shown:
            combo_ui = combo_box.ui
            for i in range(1, 4):
                label_text = f"Choose {i}"
                option_value = i
                combo_ui.selectable_value(self.chosen_option, option_value, label_text)
    ...

Slider

You create a slider for adjusting a numeric value with egui.Slider() and then add it to a ui with ui.add(). By default, this constrains the numeric value even if the value is edited with another widget.

Arguments:

1: a Mutable wrapping a numeric value

2: the lower bound as a float

3: the upper bound as a float

You can call .text(string) on the slider to add an optional label.

Example:

        ...
        self.numeric_value = egui.Mutable(10.0)
        ...
        minimum_acceptable_value = 0.0
        maximum_acceptable_value = 100.0
        slider_label_text = "Value"
        slider = egui.Slider(
            self.numeric_value,
            minimum_acceptable_value,
            maximum_acceptable_value).text(slider_label_text)
        ui.add(slider)
        ...

Drag Value

A Drag Value widget shows a numeric value and allows changing the value through either (1) dragging the widget or (2) clicking the widget and then typing a new value. You create a Drag Value with egui.DragValue() and then add it to a ui with ui.add(). egui.DragValue() takes a Mutable wrapping a numeric value as its argument.

Example:

        ...
        self.numeric_value = egui.Mutable(10.0)
        ...
        ui.add(egui.DragValue(self.numeric_value))
        ...

Layouts

Layouts allow deciding how widgets are arranged. ui.horizontal() and ui.vertical() allow making widgets arranged horizontally and vertically respectively. Layout objects can be used with ui.with_layout for more control.

Creating Layout Objects

A Layout object has the following fields: - main_dir: Direction. The main direction of the layout, such as left to right. - main_wrap: bool. Decides if components should wrap along the main direction. - main_align: Align. Decides the alignment of the main axis, such as aligning components at the top or bottom of each row. - main_justify: bool. Decides if the main axis should be justified. - cross_align: Align. Decides the alignment of the cross axis. - cross_justify: bool. Decides if the cross axis should be justified.

An Align object has the following possible values: - egui.Align.Min (equivalent to egui.Align.TOP and egui.Align.LEFT) - egui.Align.Center - egui.Align.Max (equivalent to egui.Align.BOTTOM and egui.Align.RIGHT)

It is usually convenient to create Layout objects using chainable construction methods.

Layout Construction Methods

method arguments description
left_to_right (vertical_alignment: Align) Place widgets from left to right.
right_to_left (vertical_alignment: Align) Place widgets from right to left.
top_down (horizontal_alignment: Align) Place widgets from top to bottom
bottom_up (horizontal_alignment: Align) Place widgets from bottom to top
with_main_wrap (main_wrap: bool) sets main_wrap
with_main_align (main_align: Align) sets main_align
with_cross_align (cross_align: Align) sets cross_align
with_main_justify (main_justify: bool) sets main_justify
with_cross_justify (cross_justify: bool) sets cross_justify

For more information on creating Layout objects, see the rust egui Layout documentation.

Grids

Grids arrange widgets in a series of cells organized within rows and columns going left to right and top to bottom. Cell contents are left and center aligned by default.

You must group multiple widgets with a layout or container to put them in the same cell.

Creating Grid Objects

Create a Grid object with the egui.Grid constructor using a locally unique string as the argument.

Grid Construction Methods

You configure a Grid with chainable construction methods similar to Layout objects.

method arguments description
num_columns (number_of_columns: int) This lets the last column take up the rest of the available space.
min_col_width (minimum_column_width: float) Set the minimum column width.
max_col_width (maximum_column_width: float) Set the maximum column width.
spacing (spacing: egui.Vec2) Set the amount of space between columns and rows.
min_row_height (minimum_row_height: float) Set the minimum row height.

Showing a Grid

To show a Grid, call .show() wrapped within async with (...) as grid_ui:. You can use another variable name instead of grid_ui. You can then create widgets inside the Grid's ui as if it was a regular ui.

More Information

For more information on Grids, see the rust egui Grid documentation.

Styling

egui allows customizing the style of widgets and text. You can set a default style and also override it temporarily.

Responses

ui methods for adding widgets to the ui return a Response object. A Response provides methods for responding to user interactions with widgets. You have already seen this used to handle button click events.

Useful Response methods include: - clicked. Returns True if the user left clicked the widget this frame. - secondary_clicked. Returns True if the user right clicked the widget this frame. - double_clicked. Returns True if the user double left clicked the widget this frame. - hovered. Returns True if the cursor is hovering over the widget this frame. - has_focus. Returns True if the widget is focused. - on_hover_text. Takes a String argument and shows the string when the widget is hovered and enabled. - on_disabled_hover_text. Works the same as on_hover_text but shows the string when the widget is hovered and disabled.

Containers

A container wraps widget(s). Useful containers include ScrollAreas, CollapsingHeaders, and Panels. See the egui containers documentation for more.

ScrollArea

A scroll area lets the user scroll vertically and/or horizontally to navigate contained widgets.

You create a ScrollArea with egui.ScrollArea.vertical() to create a ScrollArea that can be vertically scrolled, egui.ScrollArea.horizontal() to create a ScrollArea that can be horizontally scrolled, and egui.ScrollArea.both() to create a ScrollArea that can be both horizontally and vertically scrolled.

You can customize a ScrollArea with chainable methods including:

method arguments description
max_width (max_width: float) Set the maximum width of the scroll area.
max_height (max_height: float) Set the maximum height of the scroll area.
auto_shrink (bool) Determines if the scroll area should shrink to fit the contents if small.

To show a ScrollArea, call .show() wrapped within async with (...) as scroll_ui:. You can use another variable name instead of scroll_ui. You can then create widgets inside the ScrollArea's ui as if it was a regular ui.

See the egui ScrollArea documentation for more information and customization options.

CollapsingHeader

Users can expand and collapse the widgets wrapped by a CollapsingHeader by clicking on the label at the top of the header. To create a CollapsingHeader, use egui.CollapsingHeader() with a string for the top label text as the argument.

You can customize a CollapsingHeader with chainable methods including:

method arguments description
default_open (open: bool) Sets if the CollapsingHeader should be expanded by default
open (open: bool) Sets if the CollapsingHeader should be expanded this frame

To show a CollapsingHeader, call .show() wrapped within async with (...) as collapsing_ui:. You can use another variable name instead of collapsing_ui. You can then create widgets inside the CollapsingHeader's ui as if it was a regular ui.

For more information, see the egui documentation for CollapsingHeaders

Panels

Panels allow putting widgets on different parts of the ui, including the left, right, top, bottom, and center.

Use one of the following functions to create a panel. Note that a CentralPanel should always be added after the other panels.

function argument description
egui.Panel.top a String representing a unique id a panel on the top of the ui
egui.Panel.bottom a String representing a unique id a panel on the bottom of the ui
egui.Panel.left a String representing a unique id a panel on the left of the ui
egui.Panel.right a String representing a unique id a panel on the right of the ui
egui.CentralPanel.default N/A a panel in the center of the ui

To show a Panel with variable name "panel" within a ui named "ui", use async with (panel.show(ui.ctx())) as panel_ui:. You can use another variable name instead of panel_ui. You can then create widgets inside the Panel's ui as if it was a regular ui.

For more information, see the egui documentation for Panels.

Window Customization

Recall that a Window is what contains the ui. Window object properties you can set include the following:

property name type description
title str The title to show at the top of the window
rect skia.Rect The window's rectangle setting its initial size and position
decorated bool Decides if the window should have a surrounding border including buttons
toplevel bool Decides if the window should be toplevel, which means it is never hidden behind another window
autosize bool Decides if the window should set its own size based on the contents
draggable bool Decides if you can move the window by dragging on the window background

Understanding How to Write Python Based on the Rust Documentation

Referencing the official egui library documentation is useful when working with the Python bindings, but keep in mind that the library was written in Rust, which has different syntax.

Use "." in Python instead of "::".

Use a Mutable object in Python instead of "&mut ".

"Typename::new()" is how a constructor is typically written in Rust. You should instead use "Typename()" to create an instance of "Typename" in Python.

Rust denotes code blocks with curly braces.

"||" at the start of a code block inside a function call in Rust denotes a closure, which is similar to a Python lambda expression. Instead of putting a lambda inside the corresponding function call, in the Python bindings you write "async with call_goes_here() as variable_that_was_originally_in_the_vertical_bars_goes_here:".

Examples

The following examples show transforming Rust from the official egui documentation to Talon's Python bindings.

The following in Rust

ui.add(egui::Label::new("Hello World!"));

becomes the following in Python

ui.add(egui.Label("Hello World!"))

This is because egui::Label becomes egui.Label and Label::new() becomes Label().

The following in Rust

ui.horizontal(|ui| {
    ui.label("Add widgets");
    if ui.button("on the same row!").clicked() {
        /* … */
    }
});

becomes the following in Python

async with ui.horizontal():
    ui.label("Add widgets")
    if ui.button("on the same row!").clicked():
        pass

The closure |ui| {} was converted to async with ui.horizontal():. We could have written this as async with ui.horizontal() as horizontal_ui:, but we can just reuse the original "ui" variable when using layouts.

The following in Rust

let enabled = true;
...
ui.checkbox(&mut enabled, "Enable subsection");

becomes the following in Python

enabled = egui.Mutable(True)
...
ui.checkbox(enabled, "Enable subsection")

Because the function call included "&mut" before the "enabled" argument, the corresponding Python uses a Mutable object for the argument.

Source

Reference