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 |
libcursed, 2019 -- 2022
This file last updated on 29 December, 2021
This is a C++11 library that provides classes for using curses library which don't just copy original C API.
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.
It was created for a couple of projects, has minimally necessary functionality and can change quite a bit in the future.
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")
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:
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 |
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.
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
).
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.
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.
The API consists of classes in cursed
namespace. Things in cursed::guts
namespace as well as files in guts/
subdirectory are implementation details.
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;
}