V UI Documentation

repository·master·Indexed 25 days ago

https://github.com/vlang/ui

A cross-platform, declarative UI toolkit for the V programming language. V UI provides a lightweight way to build native-feeling interfaces for Windows, macOS, Linux, and Android, with upcoming support for iOS and Web (JS/WASM). It features a declarative API for composing components like windows, rows, columns, and textboxes, and supports both standalone applications and multi-application environments via the `ui.apps` interface.

Tokens
9.4K
Snippets
30
Records
47
Agent score
82%

What's inside V UI

  1. Use complex components from the `uic` module

    master

    The uic (Components) module provides pre-built, complex UI elements that combine multiple basic widgets into functional stacks. Common components include:

    • uic.accordion_stack: Collapsible sections using titles ([]string) and children ([]ui.Widget).
    • uic.alpha_stack: A slider and textbox for selecting transparency (0-255).
    • uic.colorbutton: A button displaying a color that can trigger callbacks via on_click or on_changed.
    • uic.colorsliders_stack: RGB sliders and textboxes. Use .color() to get the current gx.Color or .set_color(gx.Color) to update it.
    • uic.doublelistbox_stack: Two listboxes for moving items between them; use .values() to retrieve items from the right listbox.
    • uic.filebrowser_stack: A tree-view file/directory browser. Use .selected_full_title() to get the selected path.
    • uic.hideable_stack: A wrapper for layouts that can be toggled using hideable_toggle(window, id), hideable_show(window, id), or hideable_hide(window, id).
  2. What is V UI and how does it work?

    master

    V UI is a cross-platform, declarative UI toolkit written in V.

    Platform Support:

    • Windows, macOS, Linux, Android.
    • Upcoming: iOS and Web (JS/WASM).

    Rendering Model:

    • Windows and macOS: Uses native widgets.
    • Other platforms: Widgets are drawn by V UI itself. Note that currently, only non-native widgets are available.

    Key Characteristics:

    • Declarative API: UI is defined by composing components in a tree structure.
    • Zero Dependencies: Binaries built with V UI have no external dependencies.
    • Lightweight: On Linux, it aims to be a lightweight alternative to GTK and Qt.
  3. Create menus and menubars

    master

    V UI provides components to build hierarchical menus:

    • ui.menuitem: An individual item. Can have an action callback or a submenu.
    • ui.menu: A collection of menuitems.
    • ui.menubar: A top-level bar containing multiple menu items.
    // Structure definition
    menu_items := [
        ui.menuitem(
            text:    'File',
            submenu: ui.menu(
                items: [
                    ui.menuitem(text: 'Open', action: menu_click),
                    ui.menuitem(text: 'Save', action: menu_click),
                    ui.menuitem(text: 'Exit', action: menu_click),
                ]
            )
        ),
        // ... other top-level menus
    ]
    
    // Usage in layout
    ui.menubar(
        id:    'menubar',
        items: menu_items
    )
  4. Use `ui.box_layout` for absolute and relative positioning

    master

    The ui.box_layout provides flexible positioning and sizing. Unlike rows/columns, it uses a map[string]Widget for its children, where the key is a string containing the child's id and its bounding_spec.

    Bounding Box Syntax

    Format: 'child_id: bounding_spec'

    Bounding Spec Options:

    • (x, y): Top-left coordinates.
    • (w, h): Size.
    • (x, y) -> (x2, y2): Defined by top-left and bottom-right corners.
    • (x, y) ++ (w, h): Defined by top-left corner and size.
    • stretch: Equivalent to (0, 0) -> (100%, 100%).
    • hidden: Makes the child invisible and excluded from layout.

    Coordinate/Size Types:

    • Pixels: e.g., 10 or -5 (offset from bottom/right).
    • Percentage: e.g., 50%.
    • Relative: e.g., @other_id.x + 5 or @other_id.w. Uses ui.calculate internally.
    ui.box_layout(
        id:       'bl',
        children: {
            // Top-left corner, 30x30 pixels
            'id1: (0,0) ++ (30,30)': ui.rectangle(...),
            // From (30,30) to 30.5 pixels from the right/bottom edges
            'id2: (30,30) -> (-30.5,-30.5)': ui.rectangle(...),
            // From center (50%, 50%) to bottom-right corner (100%, 100%)
            'id3: (50%,50%) ->  (100%,100%)': ui.rectangle(...),
            // Bottom-right corner, 30x30 pixels (size defined from bottom-right)
            'id4: (-30.5, -30.5) ++ (30,30)': ui.rectangle(...),
            // Position relative to id4, size 20x20
            'id5: (@id4.x + 5, @id4.y+5) ++ (20,20)': ui.rectangle(...)
        }
    )
  5. Custom drawing with `ui.canvas` and `ui.canvas_plus`

    master

    For custom graphics, use ui.canvas or ui.canvas_plus. canvas_plus provides additional styling like bg_color and bg_radius.

    How to use:

    1. Define a drawing function that accepts a DrawDevice and a CanvasLayout.
    2. Pass this function to the on_draw (or draw_fn) parameter.
    3. Use the provided CanvasLayout methods (e.g., draw_device_rect_empty, draw_device_text) to perform drawing operations.
    ui.canvas_plus(
        width:     400,
        height:    275,
        on_draw:   app.draw, // Custom drawing function in the App struct
        bg_color:  gx.Color{255, 220, 220, 150},
        bg_radius: 10
    )
    
    // Inside the app.draw function:
    fn (app &State) draw(mut d ui.DrawDevice, c &ui.CanvasLayout) {
        // Use methods like c.draw_device_rect_empty, c.draw_device_line, c.draw_device_text
        c.draw_device_rect_empty(d, marginx, y, table_width, cell_height, gx.gray)
        c.draw_device_text(d, marginx + 5, y + 5, user.first_name)
    }
  6. Use `ui.canvas_layout` for custom drawing and manual placement

    master

    The ui.canvas_layout is used for custom drawing and manual widget placement. Instead of a map, it uses a children array where widgets are typically wrapped in ui.at(x, y, widget) to specify their coordinates.

    Key Parameters:

    • on_draw: A callback function fn(mut DrawDevice, &CanvasLayout) for custom background or element drawing.
    • ui.at(x, y, widget): Used within the children array to place a widget at specific coordinates.
    • scrollview: Enable scrolling.
    • full_width, full_height: Define the total scrollable area size if it differs from content bounds.
    ui.canvas_layout(
        id:              'demo_cl',
        on_draw:         draw, // Custom background drawing
        scrollview:      true,
        children:        [
            ui.at(10, 10, ui.button(id:'b_thm', ...)), // Place button at (10, 10)
            ui.at(120, 10, ui.dropdown(...)),         // Place dropdown at (120, 10)
            // ... other widgets placed with ui.at()
        ]
    )
  7. Implement the standard `ui.Application` pattern

    master

    For complex applications, follow this structural pattern to encapsulate state and UI construction:

    1. Define State: Create an AppUI struct marked @[heap] to hold application state (e.g., references to &ui.Window, &ui.Layout, and specific widgets).
    2. Define Parameters: Create an AppUIParams struct marked @[params] for initialization.
    3. Initialize: Implement a new(params) function to create the AppUI instance and call make_layout().
    4. Define Entrypoint: Implement an app(params) function that returns &ui.Application(&AppUI).
    5. Construct UI: Implement a make_layout() method on AppUI that builds the UI using ui.row, ui.column, and components, assigning the result to app.layout.
    6. Setup Callbacks: Optionally assign an on_init callback (fn [mut app] (w &ui.Window)) to app.on_init for post-window-creation setup (like adding shortcuts).
  8. Use `ui.row` and `ui.column` for linear layouts

    master

    Use ui.row and ui.column to arrange children linearly (horizontally or vertically). These are implemented using ui.stack internally. You can control how children share space using widths (for rows) or heights (for columns) with the following sizing modes:

    • ui.stretch: Child takes a proportional amount of remaining space (default weight 1.0). Use 2 * ui.stretch for double weight.
    • ui.compact: Child takes its natural/minimum size.
    • > 1: Fixed size in pixels.
    • 0 < size <= 1: Proportional size relative to the parent's dimension (e.g., 0.5 is 50%).

    Other key parameters:

    • spacing: Space between children (pixels if >=1, relative if <1).
    • margin_: Uniform margin around the stack.
    • margin: Specific ui.Margin for top, right, bottom, left.
    • alignment: Default alignment for children on the cross axis.
    • scrollview: Set to true to enable scrolling if content exceeds bounds.
    // Row with compact buttons and spacing
    ui.row(
        id:       'btn_row',
        widths:   ui.compact, // Children take their own width
        heights:  20.0,       // Fixed height for the row
        spacing:  80,         // 80px spacing between buttons
        children: [ /* ... buttons ... */ ]
    )
    
    // Column with mixed height children
    ui.column(
        spacing:    10,
        widths:     ui.compact, // Column takes width of widest child
        heights:    ui.compact, // Children take their own height
        scrollview: true,      // Enable vertical scrolling if needed
        children:   [ /* ... textboxes, checkboxes, etc. ... */ ]
    )
  9. Develop applications as self-contained modules

    master

    The ui.apps interface allows you to develop applications as self-contained modules. This approach enables two primary usage patterns:

    1. Multi-application environments: You can launch multiple instances of different applications within a single Window Manager (wm) instance, allowing them to interact with each other.
    2. Standalone applications: A module can be used as a simple, independent application by calling its .app() method.

    To use applications as modules within a Window Manager, use wm.add(position_string, app_instance) where the position string defines the window name and geometry (e.g., 'name: (x,y) ++ (width,height)').

    import ui
    import ui.apps.users
    import ui.apps.editor
    
    fn main() {
    	mut wm := ui.wm()
    	mut app := users.new()
    	wm.add('appusers: (20,20) ++ (600,400)', mut app)
    	mut app2 := editor.new()
    	wm.add('editor: (400,10) ++ (600,400)', mut app2)
    	wm.run()
    }
  10. Understand the difference between Widgets and Components

    master

    V UI distinguishes between low-level building blocks and high-level encapsulated elements:

    Widgets

    Basic building blocks like ui.button, ui.textbox, and ui.label. They are typically created via direct function calls.

    Components (ui.component or uic)

    Higher-level elements that encapsulate state and behavior (e.g., uic.filebrowser_stack). Components typically follow a three-part pattern:

    1. Factory Function: Creates the component's root layout (e.g., uic.accordion_stack).
    2. State Retrieval: A companion function retrieves the component's state struct from its layout (e.g., uic.accordion_component).
    3. State Struct: A struct that holds references to the component's internal widgets and manages its internal state.
  11. How V UI applications are structured

    master

    A V UI application is built around a ui.window which serves as the root container. The application lifecycle is managed by the ui.run(window) function, which starts the main event loop. To build an interface, you define a root layout (such as ui.column or ui.row) within the window configuration and populate it with widgets or components.

    import ui
    
    fn main() {
        // Create the main window
        window := ui.window(
            width: 800,
            height: 600,
            title: 'My App',
            layout: ui.column( // Add your root layout here
                children: [
                    ui.label(text: 'Hello, V UI!')
                ]
            )
        )
        // Start the event loop
        ui.run(window)
    }
  12. Animate values with `ui.transition`

    master

    The ui.transition widget manages animated transitions for integer properties (like widget offsets).

    Workflow:

    1. Initialize: Create transition widgets for the properties you want to animate (e.g., x_transition, y_transition) and add them to your window layout.
    2. Set Target: Use set_value(&int) to link the transition to a specific integer variable.
    3. Trigger: Set the target_value to the desired destination. The transition widget's internal draw() method handles the frame-by-frame animation.
    // Initialization
    app.x_transition = ui.transition(duration: 750, easing: ui.easing(.ease_in_out_cubic))
    app.y_transition = ui.transition(duration: 750, easing: ui.easing(.ease_in_out_quart))
    
    // In window layout:
    children: [
        app.picture,
        app.x_transition, // Add transition widgets to the window
        app.y_transition,
    ]
    
    // To start animation:
    fn (mut app App) btn_toggle_click(button &ui.Button) {
        if app.x_transition.animated_value == 0 {
            app.x_transition.set_value(&app.picture.offset_x)
            app.y_transition.set_value(&app.picture.offset_y)
        }
        app.x_transition.target_value = new_x_position
        app.y_transition.target_value = new_y_position
    }