getting-started

Getting Started with Terminal::Widgets

Overview

As the name implies, Terminal::Widgets (AKA "T-W") is a collection of modules for creating Terminal User Interfaces (TUIs) using various visual widgets. Widgets are rectangular tools that can be placed arbitrarily within the terminal window, each doing a single thing. Simple widgets include text labels, buttons, checkboxes, menus, and specialty add-ons such as scrollbars. More complex widgets include editor inputs, tree navigators, rich text viewers, smoke charts, and more.

The smaller widgets used to design a particular TUI are laid out within a special large widget called a toplevel that covers the entire terminal window/viewport. An application can be made up of many toplevels, each of which lays out a single "screen" within the app, such as the main menu, settings menu, online help docs, and so on.

While a toplevel manages screen layout, another critical object represents the terminal itself -- terminal emulator capabilities, input decoding, active user themes and preferences, the user's current locale and translation context, and the reactive event dispatcher for that terminal session.

Here's a diagram of a trivial T-W UI in use:

USER         WINDOW        OBJECTS
                           App
๐Ÿ‘ค๐Ÿ’ปโ”€โ”€โ”€โ”€โ”€โ•’โ•โ•โ•โ•Helloโ•โ•โ•โ•โ•• โŸต โ”œโ”€Terminal
    โ•ฒ    โ”‚             โ”‚   โ”‚    โ”†
     โ•ฒ   โ”‚             โ”‚ โŸต โ””โ”€TopLevel (HelloUI)
      โ•ฒ  โ”‚Hello, World!โ”‚     โ”œโ”€PlainText
       โ•ฒ โ”‚โŒˆQuitโŒ‹       โ”‚     โ””โ”€Button
        โ•ฒโ”‚             โ”‚
         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Hello, World!

With that short overview in mind, let's start off by creating the classic "Hello, World!" app from the diagram above. Here's what that looks like in Raku code:

use Terminal::Widgets::Simple;

#| A top level UI container based on Terminal::Widgets::Simple::TopLevel
class HelloUI is TopLevel {

    #| Define the initial UI layout when the TopLevel first starts up
    method initial-layout($builder, $width, $height) {

        # Use the layout builder to add a PlainText widget and a quit button,
        # centered in the terminal window and taking minimal space.
        with $builder {
            .center(:vertical, style => %(:minimize-h, :minimize-w),
                     .plain-text(text => 'Hello, World!', color => 'bold'),
                     .button(label => 'Quit',
                             process-input => { $.terminal.quit }),
                    )
        }
    }
}

sub MAIN() {
    # Boot a Terminal::Widgets::Simple::App and jump right to the main screen
    App.new.boot-to-screen('hello-world', HelloUI, title => 'Hello');
}

Using Terminal::Widgets::Simple defines and imports classes that handle all of the basic TUI behaviors, including App (representing the application lifecycle) and TopLevel (representing a full-window UI). The App startup process will eventually call our initial-layout method (more on this in the next section) to define the widget layout constraints, and that's where most of the code in this example resides.

A Layout::Builder object is provided as the first argument, and we use that to request a centered, vertically stacked, minimum-size UI layout, containing a simple plain-text message and a 'Quit' button that when clicked will tell the terminal event reactor to quit (thus exiting the program as a whole).

For your convenience the above program has been saved in the first hello-world example; go ahead and run this to see what the result looks like. You can mouse-click the Quit button or even just press Enter to quit, since the first active input is automatically focused for you.

Startup Behind the Scenes

This section details the startup process and explains why MAIN looks the way it does; feel free to skip to the next section if this doesn't interest you yet.

The trivial MAIN used above creates a default App object and immediately calls its boot-to-screen helper method to start up the application. The arguments are:

  1. An internal moniker for the initial screen (used by multi-screen apps)

  2. The TopLevel UI subclass that the helper method should instantiate first

  3. A title for the terminal window to use while showing the initial screen

As the name implies, boot-to-screen starts a UI bootstrapping sequence, eventually ending with a fully rendered and interactive initial screen.

After some initial housekeeping, the user's terminal is autodetected and a Terminal object created to manage it. Next the specified TopLevel subclass (in this case HelloUI) is instantiated with a reference to its controlling Terminal object. The boot sequence continues by calling Terminal.initialize to blank the terminal window and start its input decoder, and then calling Terminal.set-toplevel to prepare the HelloUI screen for display.

set-toplevel begins by setting the terminal window title and then setting the UI's height and width to the current window size, measured in character cells. Finally it asks HelloUI to perform a relayout on itself.

A relayout begins by computing the actual layout details based on the constraints specified by HelloUI.initial-layout. It then builds the requested child widgets and places them in the layout's computed rectangular layout slots, and sends a LayoutBuilt event to all widgets to let them know their siblings all exist and have been placed. relayout finishes by setting the input focus, then requesting a redraw and recomposite of all placed widgets.

Finally, with the bootup process complete, the App object hands off control to the user by starting Terminal's primary input/event reactor.

Event Handling

T-W event handling is similar to web browser event handling. Events such as KeyboardEvent, MouseEvent, TakeFocus, or LayoutBuilt are injected into the currently visible TopLevel, which trickles each event down to its children recursively until reaching the event's target (if there is one) or the leaves of the widget tree, where it begins a journey of bubbling back up to the TopLevel.

Thus there are three phases to an event's journey that widgets can act on:

  • TrickleDown

  • AtTarget

  • BubbleUp

This allows any widget to choose to handle an event either before or after its children, or only when it is the event target, simply by choosing which phase to listen to.

In order to better control which widgets should receive a particular event, there are several event classes:

  • GlobalEvent - Sent to every widget

  • TargetedEvent - Sent to one particular widget, ignored by others

  • LocalizedEvent - Sent to widgets overlapping a particular X,Y location

  • FocusFollowingEvent - Sent to only the focused widget and its parents

Here's how the standard event types fit into those categories:

  • GlobalEvent - LayoutBuilt

  • TargetedEvent - TakeFocus

  • LocalizedEvent - MouseEvent

  • FocusFollowingEvent - KeyboardEvent

The base Widget class does the EventHandling role, which allows a widget to handle some subset of events by adding a multi method handle-event with sufficiently precise arguments. Here's an example of a mouse event handler shared among several of the Input widget types:

    #| Handle basic mouse click event
    multi method handle-event(Terminal::Widgets::Events::MouseEvent:D
                              $event where !*.mouse.pressed, AtTarget) {
        # Always focus on mouse click, but only perform click action if enabled
        self.toplevel.focus-on(self);
        self.click if $.enabled;
    }

Note how the multi method parameters specify only MouseEvents where the mouse button is being released (the end of a click), and only when the event has reached its target (phase AtTarget).

Event Handling in hello-world

As a concrete case, the hello-world program above has a Quit button. The standard Input::Button class has builtin handle-event multi methods for both keyboard and mouse inputs. When an Event of either type is sent from the Terminal reactor to the current TopLevel, that Event will trickle down through the widget hierarchy until it reaches its target widget.

If the current focus (for a KeyboardEvent) or the current mouse cursor location (for a MouseEvent) target the Quit button, the appropriate handle-event method will be called, which in turn will call the process-input block that was specified in the layout.

In this particular case, our process-input finds the current controlling Terminal object and requests that it quit and shutdown, eventually exiting the program completely.

Widget Layout and the Box Model

Widgets are laid out in a hierarchy of X-Y grids, each laying flat within a stack of Z-planes. Even without any Z-offset, child widgets are assumed to be infinitesimally closer to the viewer than their parent so that the painting and compositing orders are well-defined:

โ”ŒPARENTโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ”ŒCHILDโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”ŒCHILDโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ โ”ŒGRANDCHILDโ” โ”‚ โ”‚ โ”ŒGRANDCHILDโ” โ”‚ โ”‚
โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚
โ”‚ โ”‚ โ”ŒGRANDCHILDโ” โ”‚ โ”‚ โ”ŒGRANDCHILDโ” โ”‚ โ”‚
โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Within each widget, T-W uses a similar layout to the CSS box model. The active content-area sits in the middle and is surrounded by three types of framing -- from innermost to outermost, the padding, border, and margin:

โ”ŒWIDGETโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              margin              โ”‚
โ”‚                                  โ”‚
โ”‚    โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•borderโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—    โ”‚
โ”‚ m  โ•‘                        โ•‘  m โ”‚
โ”‚ a  โ•‘         padding        โ•‘  a โ”‚
โ”‚ r  โ•‘  p โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” p  โ•‘  r โ”‚
โ”‚ g  โ•‘  a โ”‚ Content Area โ”‚ a  โ•‘  g โ”‚
โ”‚ i  โ•‘  d โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ d  โ•‘  i โ”‚
โ”‚ n  โ•‘         padding        โ•‘  n โ”‚
โ”‚    โ•‘                        โ•‘    โ”‚
โ”‚    โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•borderโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•    โ”‚
โ”‚                                  โ”‚
โ”‚              margin              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The upper left corner within a widget grid is at x=0,y=0,z=0, but may be offset by arbitrary integer offsets from its parent (and through the chain of parents, the entire TopLevel screen). Positive values are to the RIGHT, DOWN, and CLOSER to the viewer.

Here's the widget box model again, with coordinates added:

    โ”‚
    โ”‚ +y
    โ–ผ
โ”€โ”€โ”€โ–ถโ”ŒWIDGETโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ•ถโ•ฎ
 +x โ”‚(0,0)         margin              โ”‚ โ”‚
    โ”‚                                  โ”‚ โ”‚
    โ”‚    โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•borderโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—    โ”‚ โ”‚
    โ”‚ m  โ•‘                        โ•‘  m โ”‚ โ”‚
    โ”‚ a  โ•‘         padding        โ•‘  a โ”‚ โ”‚
    โ”‚ r  โ•‘  p โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” p  โ•‘  r โ”‚ โ”‚
    โ”‚ g  โ•‘  a โ”‚ Content Area โ”‚ a  โ•‘  g โ”‚ โ”œ h (height)
    โ”‚ i  โ•‘  d โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ d  โ•‘  i โ”‚ โ”‚
    โ”‚ n  โ•‘         padding        โ•‘  n โ”‚ โ”‚
    โ”‚    โ•‘                        โ•‘    โ”‚ โ”‚
    โ”‚    โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•borderโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•    โ”‚ โ”‚
    โ”‚                                  โ”‚ โ”‚
    โ”‚              margin     (w-1,h-1)โ”‚ โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ•ถโ•ฏ
    โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
                     w (width)

Let's add some framing to our hello-world widgets by replacing the original layout constraint request:

.center(:vertical, style => %(:minimize-h, :minimize-w),
         .plain-text(text => 'Hello, World!', color => 'bold',
                     style => %(border-width  => 1,
                                margin-width  => (0,0,1,0),
                                padding-width => (0,1,2,3))),
         .button(label => 'Quit',
                 style => %(border-width => 1),
                 process-input => { $.terminal.quit }),
        )

When a framing width is specified as a single value, it is applied on all four sides of the widget equally. If it is specified as multiple values, they apply to the top, right, bottom, and left sides respectively.

This version has been saved in the second hello-world example. Here's the result when you run it:

โ•’โ•โ•โ•โ•โ•โ•โ•Helloโ•โ•โ•โ•โ•โ•โ•โ••
โ”‚โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—โ”‚
โ”‚โ•‘   Hello, World! โ•‘โ”‚
โ”‚โ•‘                 โ•‘โ”‚
โ”‚โ•‘                 โ•‘โ”‚
โ”‚โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ”‚
โ”‚                   โ”‚
โ”‚โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—โ”‚
โ”‚โ•‘Quit             โ•‘โ”‚
โ”‚โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Both widgets have a width-1 border all the way around, and they are separated by the width-1 bottom margin of the plain-text widget. The content of the top widget has padding that differs on each side: none on the top, 1 space on the right, 2 spaces on the bottom, and 3 spaces on the left.

There are a couple interesting things to note about the Quit button:

  1. The โŒˆโŒ‹ button corner markers disappeared automatically.

  2. The button has been laid out to be as wide as the plain-text widget.

The first of these is default button behavior in order to avoid placing button corner markers inside of border corners (which looks rather odd).

The second is because our layout has requested only that the center node be minimized, not the individual layout nodes within it. By default widgets stacked vertically (:vertical) are given the same layout width, and widgets stacked horizontally (the default) are given the same layout height.

In this case that looks a bit odd, so we can change the layout request to push the button into the left portion of the area reserved for it:

.center(:vertical, style => %(:minimize-h, :minimize-w),
         .plain-text(text => 'Hello, World!', color => 'bold',
                     style => %(border-width  => 1,
                                margin-width  => (0,0,1,0),
                                padding-width => (0,1,2,3))),
         .push-left(style => %(:minimize-w),
                    .button(label => 'Quit',
                            style => %(border-width => 1),
                            process-input => { $.terminal.quit })),
        )

Here we've requested a new push-left node with minimize-w set, meaning that the button within it will be pushed to the left and shrunk horizontally as much as possible.

This is now the third hello-world example. Here's the new result:

โ•’โ•โ•โ•โ•โ•โ•โ•Helloโ•โ•โ•โ•โ•โ•โ•โ••
โ”‚โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—โ”‚
โ”‚โ•‘   Hello, World! โ•‘โ”‚
โ”‚โ•‘                 โ•‘โ”‚
โ”‚โ•‘                 โ•‘โ”‚
โ”‚โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ”‚
โ”‚                   โ”‚
โ”‚โ•”โ•โ•โ•โ•โ•—             โ”‚
โ”‚โ•‘Quitโ•‘             โ”‚
โ”‚โ•šโ•โ•โ•โ•โ•             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Drawing Sequence

Overall Refresh Sequence

To fully refresh its contents, a widget goes through a simple sequence:

  1. clear-frame

  2. draw-frame

  3. composite

Each of these has several sub-steps; clear-frame is the simplest:

  1. Clear widget's underlying grid

  2. Mark the entire widget dirty, so the widget will composite later

We'll get back to draw-frame in a moment, since it varies considerably for different widgets. For now let's take a look at the final step (composite), which copies the widget's newly drawn content to its parent and/or the terminal screen. Most of its complexity comes from optimizing for several different possible paths:

  1. Snapshot this widget's current dirty areas (and implicitly clear them)

  2. Depending on the widget's parent:

    • If parent is the current T-W toplevel:

      • parent.print-to-content-area

    • If parent is a non-toplevel T-W widget:

      • parent.copy-to-content-area

      • parent.add-dirty-rect

    • Otherwise (parent is probably the T-P screen):

      • parent.add-dirty-rect if parent understands the DirtyAreas protocol

      • Invalidate the underlying T-P grid-string cache

      • Hand off to the underlying T-P composite method (AKA the "old path")

Drawing Leaf Widgets

The complexity of draw-frame depends on whether the widget has any children; if none (meaning the widget is a leaf node), it's just two steps:

  1. draw-framing

  2. draw-content

Of course, those unroll a bit; draw-framing breaks down as follows:

  1. Check the widget's computed layout to see which framing has been requested

  2. draw-margin (if requested)

  3. draw-border (if requested)

  4. draw-padding (if requested)

draw-content is specific to each widget type; the version in the Widget base class is just a stub that does nothing.

Summarizing, here's the full refresh sequence for a leaf widget:

  1. clear-frame

    • Clear underlying grid

    • Mark widget all-dirty

  2. draw-frame

    • draw-framing

      • Check requested framing

      • draw-margin

      • draw-border

      • draw-padding

    • draw-content (unique per widget type)

  3. composite

    • Snapshot current dirty areas

    • Ask parent to do one of:

      • print-to-content-area

      • copy-to-content-area + add-dirty-rect

      • add-dirty-rect + invalidate T-P cache + T-P composite

Drawing Parent Widgets

The major difference for parent widgets (those with children) is that draw-frame now has to account for the Z-ordering of those children relative to itself:

  1. Sort children by Z order, back to front

  2. For each child behind the parent widget (Z <= 0):

    • composite the child onto the parent widget's grid

    • Add a dirty rect for the child's area (if it didn't do so itself)

  3. draw-framing for the parent widget itself (same as for leaf)

  4. draw-content for the parent widget itself (same as for leaf)

  5. Repeat step 2 for each child in front of the parent widget (Z > 0)

Widget Builtin Roles

In order to provide many common behaviors and utility methods, the base Widget class is fairly extensive and does numerous roles. Most of these you won't have to think about when building your own apps and custom widget classes, except maybe to use provided helpers and standard boilerplate; they mostly Just Work.

Here's the list for the curious:

  • Terminal::Print::Widget - Use T-P's low-level cells, grid, and compositor

  • Terminal::Print::Animated - Allow timestamp-aware full-hierarchy redraws

  • Terminal::Print::BoxDrawing - Draw borders using various line styles

  • Terminal::Widgets::Common - Provide common debugging and profiling helpers

  • Terminal::Widgets::Themable - Control color and attributes via semantic states

  • Terminal::Widgets::DirtyAreas - Support dirty-area compositing optimization

  • Terminal::Widgets::WidgetRegistry - Register new widget types on load

  • Terminal::Widgets::Events::EventHandling - See Event Handling section

Note that the first three of those are from Terminal::Print ('T-P'), which T-W is based on and interoperable with.

Debugging and Profiling

Because of the highly-interconnected nature of T-W objects, it can be confusing to log, debug, trace, or profile a T-W app's execution; dd or .raku on any widget are likely to end up dumping many pages of output. To make this easier, the Common role and Widget class together provide a number of helper methods:

  • gist - Avoids recursive dumping and summarizes key attributes

  • gist-name - Class name shortened for readability

  • gist-flags - Used by gist to report special flags on the widget

  • gist-dirty-areas - Used by gist to summarize the widget's dirty areas

  • debug - Cache of $*DEBUG verbosity at time of object creation

  • debug-grid - Return an optionally framed snapshot of a single widget

  • debug-elapsed - Write a debug note for elapsed time during an operation

  • toplevel - Chase parent links to find widget's TopLevel

  • terminal - Find this widget's controlling Terminal (via toplevel)

  • default-focus - Find descendent widget that should get focus by default

  • first-widget - FIRST matching widget in subtree, starting at self

  • last-widget - LAST matching widget in subtree, ending with self

  • next-widget - Next matching widget AFTER self in full tree

  • prev-widget - Previous matching widget BEFORE self in full tree

TopLevel Widgets

Standard Widget Classes

The Application Object

The Terminal Object

Terminal Capabilities

Further Reading

Now that you've gotten through this document, you're ready to take a more detailed look at various parts of Terminal::Widgets. Here are a few suggestions:

XXXX: UNUSED PIECES

As simple as it is, our initial-layout method doesn't use its other two arguments, which provide the width and height of the terminal window (measured in character cells) in case the programmer wants to provide entirely different layouts for small or large terminal windows. This isn't needed when simply resizing the same basic layout -- the builder's layout constraint solver does that automatically -- but could for example instead be used to switch between overview and detailed screen layouts based on available terminal real estate.

For example, Widget does the WidgetRegistry role and provides a register helper method, which introspects various subclass declarations to set up a proper call to self.register-widget with all the right arguments. But you don't have to care about how any of that works. You just have to know that when you're creating a new widget type, if you follow the boilerplate in the Adding New Widget Types doc, and put a simple register call at the end of your implementation file like this:

Terminal::Widgets::Your::Classname.register;

... your new widget type can then be used just like any builtin type would be.

Terminal::Widgets v0.3.2

Basic TUI Widgets

Authors

  • Geoffrey Broadwell

License

Artistic-2.0

Dependencies

Color::DirColors:auth<zef:japhb>:ver<0.0.3+>Terminal::ANSIColor:ver<0.14+>:auth<zef:raku-community-modules>Terminal::Capabilities:auth<zef:japhb>:ver<0.0.21+>Terminal::LineEditor:auth<zef:japhb>:ver<0.0.23+>Terminal::Print:auth<zef:terminal-printers>:ver<0.977+>Text::MiscUtils:auth<zef:japhb>:ver<0.0.13+>nano:auth<zef:lizmat>:ver<0.0.2+>

Test Dependencies

Provides

  • Terminal::Widgets
  • Terminal::Widgets::App
  • Terminal::Widgets::ColorTheme
  • Terminal::Widgets::ColorThemes
  • Terminal::Widgets::Common
  • Terminal::Widgets::DirtyAreas
  • Terminal::Widgets::Events
  • Terminal::Widgets::Focusable
  • Terminal::Widgets::Form
  • Terminal::Widgets::I18N::Locale
  • Terminal::Widgets::I18N::Translation
  • Terminal::Widgets::Input
  • Terminal::Widgets::Input::Boolean
  • Terminal::Widgets::Input::Button
  • Terminal::Widgets::Input::Checkbox
  • Terminal::Widgets::Input::Labeled
  • Terminal::Widgets::Input::Menu
  • Terminal::Widgets::Input::RadioButton
  • Terminal::Widgets::Input::SimpleClickable
  • Terminal::Widgets::Input::Text
  • Terminal::Widgets::Input::ToggleButton
  • Terminal::Widgets::Layout
  • Terminal::Widgets::Layout::BoxModel
  • Terminal::Widgets::PlainText
  • Terminal::Widgets::Progress::Tracker
  • Terminal::Widgets::ScrollBar
  • Terminal::Widgets::Scrollable
  • Terminal::Widgets::Simple
  • Terminal::Widgets::Simple::App
  • Terminal::Widgets::Simple::StandardWidgets
  • Terminal::Widgets::Simple::TopLevel
  • Terminal::Widgets::SpanBuffer
  • Terminal::Widgets::Terminal
  • Terminal::Widgets::TextContent
  • Terminal::Widgets::Themable
  • Terminal::Widgets::TopLevel
  • Terminal::Widgets::Utils
  • Terminal::Widgets::Utils::Color
  • Terminal::Widgets::Viewer::DirTree
  • Terminal::Widgets::Viewer::Log
  • Terminal::Widgets::Viewer::RichText
  • Terminal::Widgets::Viewer::Tree
  • Terminal::Widgets::Viz::SmokeChart
  • Terminal::Widgets::Viz::Sparkline
  • Terminal::Widgets::Volatile::DirTree
  • Terminal::Widgets::Volatile::Tree
  • Terminal::Widgets::Widget
  • Terminal::Widgets::WidgetRegistry
  • Terminal::Widgets::WrappableBuffer

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.

Built with Podlite โ€” the markup and publishing tools behind this site.