CLUI

repository·master·Indexed 20 days ago

https://github.com/vladimirmarkelov/clui

A Command Line User Interface library for Go inspired by TurboVision. It provides a variety of terminal-based UI controls including windows, frames, buttons, edit fields, progress bars, and specialized dialogs like FilePicker and LoginDialog. The library features built-in theme support, automatic layout scaling, and a comprehensive set of global and control-specific hotkeys for window manipulation and navigation.

Tokens
6.5K
Snippets
21
Records
32
Agent score
71%

What's inside clui

  1. Use the File Picker dialog

    master
    The File Picker is used to select a single file or directory. The dialog provides a list of files/directories, an edit field for quick search or entering new names, and buttons for Open (to enter a directory), Select (to confirm selection), and Cancel (to abort).
  2. How widget positioning and scaling work

    master

    CLUI handles layout automatically based on terminal size and parent constraints. You do not need to set positions manually. Key concepts include:

    • Minimal Sizing: You should set the minimal width and height for a widget. If you pass the constant AutoSize, the library calculates minimal sizes automatically (e.g., a Label's minimal width is based on its text length).
    • Scaling Coefficients: When creating a widget, you can set a scale coefficient to control how it reacts to terminal/window resizing:
      • Autoscaling: The widget automatically resizes when the terminal or parent window changes size.
      • Fixed: Setting the coefficient to Fixed prevents the widget from resizing, forcing it to maintain its minimal size. This is useful for elements like Button.
    • Constraints: While Window elements can overlap each other, a widget cannot overlap another widget.
  3. Core concepts of CLUI controls and hierarchy

    master

    CLUI organizes its UI elements into four distinct categories that define how they behave and how they must be structured in your application:

    1. Top level controls: The Window is the only top-level control. It is a visual element that does not have a parent and cannot be a child of another control. Every application must have at least one visible Window. If the last Window is closed, the application terminates. Windows can be modal.
    2. Widgets: Most controls fall into this category. A widget must be a child of a Window or another widget; it cannot exist without a parent. Widgets can act as both parents and children.
    3. Invisible controls: These are logical helpers that do not render visually. For example, RadioGroup is an invisible control used to manage the selection logic of Radio buttons.
    4. Dialogs: These are ready-to-use modal Window implementations for common user interactions, such as ConfirmationDialog and SelectDialog.
  4. How CLUI layout management works

    master

    CLUI uses a simplified layout model where any control becomes a container (layout) if it has children. Unlike toolkits like Qt, CLUI does not have dedicated layout objects; instead, the container's direction is determined by its children.

    Layouts are strictly linear: controls are arranged either from left to right or from top to bottom in the order they are added. Consequently, a container is always either one control high or one control wide.

    Key constraints to remember:

    • No Fixed/Grid Layouts: Only automatic horizontal and vertical layouts are supported.
    • Minimal Size Calculation: A container's real minimal size is the maximum of its own minimal values and the total space required by its children (including gaps and paddings). You cannot force a container to be smaller than the space required by its children.
    • Alignment Trick: Since there is no explicit alignment API, to align a control to the bottom or right side, add a frameless Frame with scale set to 1, then add the target control with scale set to Fixed. This allows the frame to expand with the parent while the control maintains its size and sticks to the edge.
    // Example of the alignment trick for bottom/right alignment
    // 1. Add a frameless Frame with scale 1 (to expand)
    // 2. Add the control with scale Fixed (to stay at edge)
    container.AddChild(new Frame(..., scale: 1))
    container.GetLastChild().AddChild(new MyControl(..., scale: Fixed))
  5. Window interaction and hotkeys

    master

    Windows are the only controls in CLUI that can be manually moved or resized using a mouse or keyboard.

    Keyboard Hotkeys

    Hotkeys are executed as key sequences: press the first combination, release it, and then press the second key.

    ActionHotkey Sequence
    Resize WindowCtrl+S followed by an arrow key
    Move WindowCtrl+P followed by an arrow key
    Maximize/RestoreCtrl+W then Ctrl+M
    Hide/Move to BackgroundCtrl+W then Ctrl+H (moves window to bottom of stack and activates next window)

    Mouse and Visual Cues

    • Borders: The currently active window is indicated by a double border, while inactive windows have a single border.
    • Window Icons: Located at the bottom-right corner, these allow for mouse manipulation:
      • Move to background
      • Maximize/Restore
      • Close (Note: Closing the last remaining window will terminate the application).
    • Navigation: Windows capture the TAB key to allow users to move to the next child control using the keyboard.
  6. How widget scaling and layout works

    master

    The library uses a scaling coefficient to distribute extra space (Delta) when a container is resized.

    1. Starting Size: Calculated as the maximum of the container's minimal size and the sum of its children's minimal sizes.
    2. Delta Calculation: Delta = NewSize - StartingSize.
    3. Total Scale: The sum of all children's scale coefficients (Fixed children have a scale of 0).
    4. Distribution: Each child (except the last one with scale > 0) increases by: child.Scale * Delta / TotalScaleSize. The remaining Delta is assigned to the last child.

    Example: If two children both have scale: 1 and the parent size increases by 3, the first child grows by floor(1 * (3 / 2)) = 1, and the second child grows by the remainder: 3 - 1 = 2.

  7. Use color tags in widget text

    master

    You can colorize text in widgets like Label, Frame, or ListBox items using HTML-like tags: <Letter:ColorValue>.

    Tags:

    • b: Background color
    • c, t, f: Text/Foreground color (c is a general color tag)

    Supported Colors: black, white, green, yellow, blue, magenta, cyan, red, and default (uses the widget's theme color).

    Modifiers: bold (or bright), underline (or underlined), and reverse.

    Usage Examples:

    • <t:red bold>: Red, bold text.
    • <t:underline+blue>: Blue, underlined text.
    • <c:green>text<c:>: Resets text color to the widget's default after the word "text".
    • <b:> or <c:>: Shortcuts for resetting background or foreground to default.

    Colors are temporary and only apply until the end of the string or until another tag is encountered. You do not need to manually reset colors at the end of a string.

    "The <c:green>green<c:> text"
    "<t:red bold>Warning!<c:>"
  8. Create a widget

    master

    To create a widget, you must provide a parent container at creation time; otherwise, the widget will be invisible and won't receive messages.

    Common arguments for widget creation functions include:

    • parent: The container the widget belongs to.
    • minimalWidth / minimalHeight: Limits for widget sizes when the parent is resized. Use the constant AutoSize to allow the widget to calculate its own size (e.g., a Label uses its title length) or use library defaults (e.g., Frame defaults to 5x3).
    • title: Text displayed on the widget (not supported by all widgets).
    • scale: Defines how fast the widget grows relative to its siblings when the parent is resized. Use the constant Fixed to prevent resizing.

    Note: Frame requires an additional argument frameWidth which can be BorderThick or BorderThin.

    // Conceptual example of a widget creation signature
    CreateWidget(parent, minimalWidth, minimalHeight, title, scale)
  9. Initialize and finalize the CLUI library

    master

    CLUI must be initialized before creating any controls. ui.InitLibrary() sets up control and theme managers, initializes the underlying termbox library, and prepares the main event loop.

    To prevent the terminal cursor from disappearing (which happens because the library turns off the cursor at start), you must call ui.DeinitLibrary() to clean up the terminal before the application exits. Using defer is the recommended way to ensure finalization occurs even if the application logic completes or crashes.

    func main() {
        ui.InitLibrary()
        defer ui.DeinitLibrary()
        // ... your other code ...
    }