File Mode Size
guts/ 040000
ColorTree.cpp 100644 9,396B
ColorTree.hpp 100644 6,080B
Expander.cpp 100644 1,033B
Expander.hpp 100644 1,545B
Init.cpp 100644 1,033B
Init.hpp 100644 1,405B
Input.cpp 100644 2,859B
Input.hpp 100644 2,765B
Label.cpp 100644 1,491B
Label.hpp 100644 2,055B
List.cpp 100644 2,981B
List.hpp 100644 2,756B
ListLike.cpp 100644 2,303B
ListLike.hpp 100644 2,226B
Placeholder.cpp 100644 1,575B
Placeholder.hpp 100644 1,862B
Prompt.cpp 100644 1,314B
Prompt.hpp 100644 2,115B
README.md 100644 7,514B
Screen.cpp 100644 2,125B
Screen.hpp 100644 2,038B
Table.cpp 100644 9,494B
Table.hpp 100644 3,535B
Text.cpp 100644 2,114B
Text.hpp 100644 2,531B
Track.cpp 100644 5,805B
Track.hpp 100644 2,468B
utils.cpp 100644 1,540B
utils.hpp 100644 1,190B
xmake.lua 100644 204B

/README.md

libcursed, 2019 -- 2022

This file last updated on 29 December, 2021

Brief Description

This is a C++11 library that provides classes for using curses library which don't just copy original C API.

Goals

This is not a wrapping library, e.g. you won't find much correspondence between what libcursed and curses provide. Instead the goal is to make it easier to write more or less typical applications with text user interface without flickering, the headache of manual handling of screen resizing or computing positions of elements on the screen.

Therefore the library concentrates mainly on functionality for drawing and does not attempt to implement some kind of framework. It doesn't enforce much structure on your application and thus shouldn't be disruptive to code bases. There are means for reading input, however it's completely up to the client to handle it and request the library to update the graphics (including handling of terminal resize events, which count as input).

On the implementation side the library should never pull in <curses.h> via its headers nor clients should have to include it in order to use the library. <curses.h> uses some very generic names, some of which are macros, consequently they can cause conflicts in completely unrelated code.

Implementation state

It was created for a couple of projects, has minimally necessary functionality and can change quite a bit in the future.

Throw-in library state

To use it clone the repository (possibly as a submodule) and handle the building with the build system that's used by the main project. Compile with C++11 enabled and link against cursesw.

Alternatively one can use xmake to consume submodule as a subproject (example assumes it's stored under libs/):

add_includedirs("libs")
includes("libs/*/xmake.lua")

Prerequisites

  • C++11 capable compiler
  • curses library with support of wide characters

Structure

The realm of the library is structuring displayed information by automatically arranging it on the screen as widgets. The client's responsibility is to construct the widgets and fill them in specifying formatting in the process. After that the library will do what it's supposed to do on calls to draw and resize methods.

The client is expected to manage lifetime of objects, the library accepts pointers and uses the objects assuming that they are valid. This doesn't seem to be a burden as having access to all widgets is needed to update their contents anyway.

There are three main structural elements:

  1. Widgets
  2. Layers
  3. Formatted strings

Widgets

Name Description
Expander non-drawing widget that takes up as much space as possible
Label static text field
List list of items
Placeholder proxy container who redirects all calls to a client widget
Prompt text field that displays cursor
Table multicolumn list
Text static text area
Track container that organizes widgets vertically or horizontally

Layers

Widgets can't be drawn at client's will, instead they need to be organized in a hierarchy and root of the hierarchy should be passed to the Screen class.

Multiple roots can be active at the same time forming layers (the same Screen class manages them) which can be drawn one on top of the other. This allows adding elements to the screen while keeping most of what is already there visible as a background.

Formatted strings

The library works with wide strings and provides corresponding utility conversion functions (toNarrow() and toWide()). ColorTree and Format classes are basically responsible for handling all of the formatting.

ColorTree is a type used in place of plain strings in libcursed. Instead of specifying format separately, clients need to build an instance of this type that describes formatting as a hierarchy of nested formatted substrings where final format of a character is formed by merging all formats it's nested within.

Usage example:

cursed::Format fmt;
fmt.setBold(true);
cursed::Format totalFmt;
totalFmt.setReversed(true);

cursed::ColorTree tree = totalFmt(fmt(L"first") + L"middle" + fmt(L"last"));
tree += L"trailing";

Displayed substrings will have the following formatting:

Substring Format
first bold and reversed
middle reversed
last bold and reversed
trailing no formatting

Format class is also used separately for specifying backgrounds of widgets that display content (i.e. not Expander or Track).

Object management

Use of the library requires creation of Init object before the use of any other classes and destroying it at the end of use.

Any number of Screen and Input objects can be created and used at the same time.

Layout and sizing

By default elements request size that's equal or larger than size of their content. This means that several elements in a track will usually have approximately equal size. If there isn't enough space, they'll use the little space there is.

setFixedSize() method can be used to force specific dimensions for an element.

API

The API consists of classes in cursed namespace. Things in cursed::guts namespace as well as files in guts/ subdirectory are implementation details.

Sample

Very minimal application:

#include "cursed/Init.hpp"
#include "cursed/Input.hpp"
#include "cursed/Label.hpp"
#include "cursed/List.hpp"
#include "cursed/Screen.hpp"
#include "cursed/Track.hpp"

int
main()
{
    cursed::Init init;

    cursed::Label title;
    title.setText(L"libcursed sample");

    cursed::List list;
    list.setItems({
        L"j -- move down",
        L"k -- move up",
        L"q -- quit"
    });

    cursed::Track track;
    track.addItem(&title);
    track.addItem(&list);

    cursed::Screen screen;
    screen.replaceTopWidget(&track);
    screen.draw();

    cursed::Input input(cursed::Keypad::Enabled);
    while (cursed::InputElement ie = input.read()) {
        if (ie.isTerminalResize()) {
            screen.resize();
            screen.draw();
            continue;
        }

        if (!ie.isFunctionalKey()) {
            switch (ie) {
                case L'j': list.moveDown(); break;
                case L'k': list.moveUp(); break;
                case L'q': return 0;
            };
        }

        screen.draw();
    }
    return 0;
}

Alternatives

  • C++ bindings shipped with ncurses. It is suffering while using these what prompted creation of libcursed. While complete, the quality of API implementation is low, which makes it very painful to use (needs way to much time to work around its rather unexpected issues). See this post.
  • NDK++ with its two kinds of APIs. Seems abandoned, but probably still works. Unlike libcursed has lots of UI elements.
  • cursespp is a more recent library that seems to be quite high level and sort of framework-like.
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/libcursed

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/libcursed

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master