adding-new-widgets

Adding New Widget Types

Adding new widget types isn't difficult, but there are a few steps you'll need to take so that your new widget type fits into the existing structure, plus a few extra if you want to include your new widget type into the base Terminal::Widgets repository (as opposed to shipping it yourself as a separate repo/module distro).

Pick a Class Name

If your new widget will be part of a group of similar widgets, such as the form input widgets or viewer widgets, your new class should be in that namespace, and not include the namespace in the shortname. In other words, prefer Terminal::Widgets::Input::Button over Terminal::Widgets::ButtonInput.

Choose a class name that seems to match the general pattern of existing widget class names and is unlikely to be confusing to readers.

Set Up Your Repo

Whether you intend to contribute to the module ecosystem or directly to T::W itself, you'll need a repo set up properly.

Ecosystem: Create a New Repo

Create a new repo skeleton with mi6 (from App::Mi6):

$ mi6 new Terminal::Widgets::Your::Classname
$ cd Terminal-Widgets-Your-Classname

Add a dependency in META6.json on T::W:

  "depends": [
    "Terminal::Widgets:auth<zef:japhb>:ver<0.3.0+>"
  ],

Terminal::Widgets tries to remain compatible across revisions within a minor version, so make sure you specify a dependency on the actively maintained version series.

While you're here, make sure your auth, authors, description, and license are all accurate too.

Direct Contribution: Fork the Terminal-Widgets Repo

Fork the repo from https://github.com/japhb/Terminal-Widgets to your own GitHub (or other Git hosting service) user account, clone that fork to your local workspace, and create a feature branch so that you aren't working in main:

$ git clone [email protected]:your_user/Terminal-Widgets.git Terminal-Widgets-your_user
$ cd Terminal-Widgets-your_user
$ git checkout -b new-branch-name

A decent default branch name pattern is <github username>-add-<namespaced new widget name>; for example, japhb-add-input-button.

Create the Widget Class

It's usually easiest to start by copying an existing similar widget's class module and making the necessary changes from there. (Don't start from the Widget.rakumod base module; it's mostly common code that you shouldn't need to worry about unless you're planning to override that common behavior.)

Don't forget to change any use, does, or is lines as appropriate. You must inherit from Terminal::Widgets::Widget but you can do so via one of its subclasses or subroles, such as Terminal::Widgets::TopLevel, Terminal::Widgets::Input, or Terminal::Widgets::SpanBuffer.

You'll need to use a few additional modules; you can drop any that don't apply:

use Terminal::Widgets::TextContent;  # To work with spans and span trees
use Terminal::Widgets::Layout;       # To register a layout class
use Terminal::Widgets::Events;       # To respond to input events
use Terminal::Widgets::Focusable;    # To accept keyboard focus

To respond to input events, you'll need to provide multi method handle-event() implementations at least for KeyboardEvent and MouseEvent; see existing examples for ideas, or just leave them empty for now if you prefer to do TDD (Test-Driven Development).

Don't forget to git add the new class module so that tools that key off the Git index will notice it. You don't need to actually git commit yet, this is just making sure there's an index entry at all.

Add a use Test

If you're working in a fork of the Terminal-Widgets repo, just add a line in the proper order in t/00-use.rakutest; in particular, make sure that all its dependencies are earlier in the test file, and any modules likely to make use of it are later. If it has default exports (most widget modules won't), make sure to use Empty for the import list.

If you're working in a fresh repo, you can create a new test file with just the following (if mi6 didn't already create one for you):

use Test;

use Terminal::Widgets::Your::Classname;

pass 'all modules loaded successfully';
done-testing;

In either case, you can now run mi6 test to check for loading errors and to make sure the new module is added to META6.json.

Create a Custom Layout Node

Add a (probably trivial) layout subclass at the top of your file, after the use statement block. You should most likely base your layout subclass on either Terminal::Widgets::Layout::Leaf (for widgets that cannot have children) or Terminal::Widgets::Layout::Node (for widgets that can). You'll also need to name the convenience method created for the layout builder (Terminal::Widgets::Layout::Builder, if you're curious) to use widget as part of your UI.

Here's an example pulled directly from the implementation of the tree viewer widget:

#| Layout node for a tree viewer widget
class Terminal::Widgets::Layout::TreeViewer
   is Terminal::Widgets::Layout::Leaf {
    method builder-name() { 'tree-viewer' }
}

There are a few other methods you can add (see similar widget implementation modules for details), but the above is the bare minimum.

Link the Widget and Layout Classes

Add a method to your widget implementation class that links the widget and layout classes. Here's another example from the tree viewer module:

class Terminal::Widgets::Viewer::Tree
 does Terminal::Widgets::SpanBuffer
 does Terminal::Widgets::Focusable {

    # ... Widget attribute declarations ...

    method layout-class() { Terminal::Widgets::Layout::TreeViewer }

    # ... Actual widget implementation ...
}

Note that there are NO QUOTES around the class name; this is a direct reference to the class type itself.

Self-Register Your Widget

At the bottom of your widget implementation file, add the following:

# Register Your::Classname as a buildable widget type
Terminal::Widgets::Your::Classname.register;

This will cause your widget to self-register into the proper places when the module is loaded, so that it can be transparently used as normal.

Extra for T::W Forks

If you're working in a Terminal-Widgets fork (rather than a new repo) then add a use statement for your new widget module in the proper order in Simple/StandardWidgets.rakumod. This will ensure that the widget is loaded and registered when using the Terminal::Widget::Simple API.

Re-run mi6 test at this point to make sure your additions so far don't have any typos.

Add Example(s)

Add example scripts to the repo's examples/ directory, or modify existing examples to use your new widget as appropriate. For example, if you create a new form input widget, you may want to add it to examples/form.raku to demonstrate its usage. This also gives you an opportunity to make sure its behavior makes sense in context.

Add Tests (if you haven't already)

If you haven't been developing the new widget using TDD, now's the time to write tests for it, and iterate on fixing bugs until the tests are once again clean. Make sure to test error handling as well.

Commit

With all tests passing and examples working, you can now commit your new widget and all the associated changes. Don't forget that you may need to add any new changes to the class module since you first did git add above, otherwise you will commit the original out-of-date version.

If you're contributing back to the core Terminal-Widgets repo, please use a clear title and description for your commit, because it will show up in your pull request.

Contribute Your Widget

The final step depends on whether you are working in a fork or your own repo.

Send a Pull Request

If you're working in a fork, preparing to contribute to the core repo, push your commit to your fork using git push -u origin <the-branch-name>. The commit response from GitHub will include a URL that you can browse to create the PR to include your new widget in Terminal-Widgets. Once you've created the PR, a message will be sent to the maintainer (currently japhb) to review.

Push and Upload Your Module

If you've created your own module, follow your Git hosting provider's instructions for creating the hosted repo and pushing your local repo to it.

Once that's in place, you can run mi6 release to release your module to the zef/fez ecosystem. Once the module has been indexed, please tell everyone about it! To check if it's been indexed, run:

$ zef update && zef search Terminal::Widgets::Your::Classname

Bask in the Praise

Thank you! We really appreciate your contribution!

Now come to #mugs on Libera.Chat so that we can thank you again. :-)

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.