#terminal #graphics #command-line-interface #tui #rustty

rustty-oxide

An easy-to-use and extensible GUI library for Rust terminal applications using rustty

1 unstable release

Uses old Rust 2015

0.1.0 Oct 23, 2017

#541 in GUI

GPL-3.0 license

51KB
853 lines

Oxide: rustty with widgets

An easy-to-use and extensible GUI library for Rust terminal applications.

Intro

Oxide is an add-on extension for rustty that provides a widget based API for building terminal UI applications. The project is heavily inspiried by Pythons TKinter, and aims at simplicity and modularity.

See GameOfLife for a good example of what Oxide is capable of, and feel free to post issues if you run into trouble using Oxide.

For documentation on using rustty, see https://github.com/cpjreynolds/rustty

Design Philosophy

Oxide is built on the philosophy that all GUI elements begin as a Widget, and can later be specialized for certain task. By providing a common ancestor for ALL UI components, Oxide can generalize the components interact with each other and create an easily extensible interface for developers. To visualize this relationship, see fig 1 below

fig 1

The above is not a 100% accurate UML diagram (some arbitrary information and relationships were left out), but gives you a high level overview of how all UI elements are connected. A Widget provides all basic functionality needed for the most basic UI element, like drawing, resizing, and aligning. Buttons are a more advanced Widget and thus inherit from Button, which is a widget with additional functionality.

Information above is not particularily useful for an application using only the default UI components provided, but will come in handy when designing custom widgets.

Widget based programming

If you've ever used Pythons Tkinter, you'll find Oxide has a similar structure to it (albeit less advanced).

To get started with widgets, your most basic and most widely used container will be a Dialog. A dialog's job is to mesh widgets together and act as a aggregator, a widget which takes in other widgets for easier management. Creation of a dialog is simple:

let mut dlg = Dialog::new(60, 10);	// create dialog 60 terminal columns wide, 10 terminal rows long
dlg.draw_box();				        // draw the border or the dialog

Now that we have a dialog, we can add UI elements into it:

let mut b1 = StdButton::new("Quit", 'q', ButtonResult::Ok);		        // Create a stanard button
b1.pack(&maindlg, HorizontalAlign::Left, VerticalAlign::Bottom, (4,2));	// Align button within dialog

// move b1 into dlg, giving ownership to the dialog
dlg.add_button(b1);	

Great! now we have a dialog window with a button inside of it, which we can use during the event polling stage inside our main loop

// Poll events instantly, no delay
while let Some(Event::Key(ch)) = terminal.get_event(Duration::from_secs(0)).unwrap() {
    // Check to see if the event was a button press for anything inside dlg
    match dlg.result_for_key(ch) {
        Some(ButtonResult::Ok) => break 'main,
        _ => {},
    }
}

Widgets can still function as independent objects, but Dialogs helps bring everything together so you can organize your UI better.

A good way to understand oxide widgets are that they are simply specialized frames that own an area of cells, and perform actions based on that specliazation. At their core, widgets implement a frame and some basic trait specialization. Take for example a label:

pub struct Label {
    frame: Frame,
    text: Vec<String>
    // ...
}

Our widget in this case has a frame, and uses text for drawing into that frame. Frames represent an area that a widget owns. Multiple widgets can own the same cells, but each widget is only concered with itself. In the case of a Label, we want to write text to an area of cells. Like other widgets, this Label can be owned by a dialog, packed, and drawn to the screen.

Any widget implements basic functionality: drawing, packing, outlining, resizing, and returning the frame. In most cases the actual widget is the frame, and structs like Label or Button wrap a frame to provide special functionality.

Creating custom widgets

In scenarios where the default set of UI components are not sufficient in covering your GUI needs, Oxide allows you to easily extend existing widgets, or even create entirely new widgets using the core traits.

Lets take the scenario that StdButton just doesn't cut it for you, and you need a button that always prints text as red. in any custom widget you create, you'll need the following requirements:

  1. The new component contains a oxide::core::Frame or a oxide::core::Canvas
  2. The new component implements Widget

Usage Guide

Examples and usage suggestions can be found in the API documentation.

Contact

email me

Dependencies

~2–10MB
~112K SLoC