Add the nctabbed widget (#1431)

Tabs for the people!

fixes #986
This commit is contained in:
MasFlam 2021-03-24 22:27:29 +01:00 committed by GitHub
parent ee93d1cf2c
commit c677a4fd8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1578 additions and 2 deletions

View File

@ -2,6 +2,7 @@ Copyright 2019-2021 Nick Black
Copyright 2019-2021 Marek Habersack
Copyright 2020-2021 José Luis Cruz
Copyright 2020-2021 igo95862
Copyright 2021 Łukasz Drukała
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

187
USAGE.md
View File

@ -9,7 +9,7 @@ to backwards compatibility.
* [Planes](#planes) ([Plane Channels API](#plane-channels-api))
* [Cells](#cells) ([Cell Channels API](#cell-channels-api))
* [Reels](#reels) ([ncreel Examples](#ncreel-examples))
* [Widgets](#widgets) ([Plots](#plots)) ([Readers](#readers)) ([Progbars](#progbars))
* [Widgets](#widgets) ([Plots](#plots)) ([Readers](#readers)) ([Progbars](#progbars)) ([Tabs](#tabs))
* [Channels](#channels)
* [Visuals](#visuals) ([QR codes](#qrcodes)) ([Multimedia](#multimedia)) ([Pixels](#pixels))
* [Stats](#stats)
@ -2486,7 +2486,7 @@ the time of each redraw), with the horizontal length scaled by 2 for
purposes of comparison. I.e. for a plane of 20 rows and 50 columns, the
progress will be to the right (50 > 40) or left with `OPTION_RETROGRADE`.
```
```c
// Takes ownership of the ncplane 'n', which will be destroyed by
// ncprogbar_destroy(). The progress bar is initially at 0%.
struct ncprogbar* ncprogbar_create(struct ncplane* n, const ncprogbar_options* opts);
@ -2514,6 +2514,189 @@ double ncprogbar_progress(const struct ncprogbar* n);
void ncprogbar_destroy(struct ncprogbar* n);
```
### Tabs
Tabbed widgets. The tab list is displayed at the top or at the bottom of the
plane, and only one tab is visible at a time.
```c
// Display the tab list at the bottom instead of at the top of the plane
#define NCTABBED_OPTION_BOTTOM 0x0001ull
typedef struct nctabbed_options {
uint64_t selchan; // channel for the selected tab header
uint64_t hdrchan; // channel for unselected tab headers
uint64_t sepchan; // channel for the tab separator
char* separator; // separator string
uint64_t flags; // bitmask of NCTABBED_OPTION_*
} nctabbed_options;
// Tab content drawing callback. Takes the tab it was associated to, the ncplane
// on which tab content is to be drawn, and the user pointer of the tab.
// It is called during nctabbed_redraw().
typedef void (*tabcb)(struct nctab* t, struct ncplane* ncp, void* curry);
// Creates a new nctabbed widget, associated with the given ncplane 'n', and with
// additional options given in 'opts'. When 'opts' is NULL, it acts as if it were
// called with an all-zero opts. The widget takes ownership of 'n', and destroys
// it when the widget is destroyed. Returns the newly created widget. Returns
// NULL on failure, also destroying 'n'.
struct nctabbed* nctabbed_create(struct ncplane* n, const nctabbed_options* opts);
// Destroy an nctabbed widget. All memory belonging to 'nt' is deallocated,
// including all tabs and their names. The plane associated with 'nt' is also
// destroyed. Calling this with NULL does nothing.
void nctabbed_destroy(struct nctabbed* nt);
// Redraw the widget. This calls the tab callback of the currently selected tab
// to draw tab contents, and draws tab headers. The tab content plane is not
// modified by this function, apart from resizing the plane is necessary.
void nctabbed_redraw(struct nctabbed* nt);
// Make sure the tab header of the currently selected tab is at least partially
// visible. (by rotating tabs until at least one column is displayed)
// Does nothing if there are no tabs.
void nctabbed_ensure_selected_header_visible(struct nctabbed* nt);
// Returns the currently selected tab, or NULL if there are no tabs.
struct nctab* nctabbed_selected(struct nctabbed* nt);
// Returns the leftmost tab, or NULL if there are no tabs.
struct nctab* nctabbed_leftmost(struct nctabbed* nt);
// Returns the number of tabs in the widget.
int nctabbed_tabcount(struct nctabbed* nt);
// Returns the plane associated to 'nt'.
struct ncplane* nctabbed_plane(struct nctabbed* nt);
// Returns the tab content plane.
struct ncplane* nctabbed_content_plane(struct nctabbed* nt);
// Returns the tab callback.
tabcb nctab_cb(struct nctab* t)
__attribute__ ((nonnull (1)));
// Returns the tab name. This is not a copy and it should not be stored.
const char* nctab_name(struct nctab* t);
// Returns the width (in columns) of the tab's name.
int nctab_name_width(struct nctab* t);
// Returns the tab's user pointer.
void* nctab_userptr(struct nctab* t);
// Returns the tab to the right of 't'. This does not change which tab is selected.
struct nctab* nctab_next(struct nctab* t);
// Returns the tab to the left of 't'. This does not change which tab is selected.
struct nctab* nctab_prev(struct nctab* t);
// Add a new tab to 'nt' with the given tab callback, name, and user pointer.
// If both 'before' and 'after' are NULL, the tab is inserted after the selected
// tab. Otherwise, it gets put after 'after' (if not NULL) and before 'before'
// (if not NULL). If both 'after' and 'before' are given, they must be two
// neighboring tabs (the tab list is circular, so the last tab is immediately
// before the leftmost tab), otherwise the function returns NULL. If 'name' is
// NULL or a string containing illegal characters, the function returns NULL.
// On all other failures the function also returns NULL. If it returns NULL,
// none of the arguments are modified, and the widget state is not altered.
struct nctab* nctabbed_add(struct nctabbed* nt, struct nctab* after,
struct nctab* before, tabcb tcb,
const char* name, void* opaque);
// Remove a tab 't' from 'nt'. Its neighboring tabs become neighbors to each
// other. If 't' if the selected tab, the tab after 't' becomes selected.
// Likewise if 't' is the leftmost tab, the tab after 't' becomes leftmost.
// If 't' is the only tab, there will no more be a selected or leftmost tab,
// until a new tab is added. Returns -1 if 't' is NULL, and 0 otherwise.
int nctabbed_del(struct nctabbed* nt, struct nctab* t);
// Move 't' after 'after' (if not NULL) and before 'before' (if not NULL).
// If both 'after' and 'before' are NULL, the function returns -1, otherwise
// it returns 0.
int nctab_move(struct nctabbed* nt, struct nctab* t, struct nctab* after,
struct nctab* before);
// Move 't' to the right by one tab, looping around to become leftmost if needed.
void nctab_move_right(struct nctabbed* nt, struct nctab* t);
// Move 't' to the right by one tab, looping around to become the last tab if needed.
void nctab_move_left(struct nctabbed* nt, struct nctab* t);
// Rotate the tabs of 'nt' right by 'amt' tabs, or '-amt' tabs left if 'amt' is
// negative. Tabs are rotated only by changing the leftmost tab; the selected tab
// stays the same. If there are no tabs, nothing happens.
void nctabbed_rotate(struct nctabbed* nt, int amt);
// Select the tab after the currently selected tab, and return the newly selected
// tab. Returns NULL if there are no tabs.
struct nctab* nctabbed_next(struct nctabbed* nt);
// Select the tab before the currently selected tab, and return the newly selected
// tab. Returns NULL if there are no tabs.
struct nctab* nctabbed_prev(struct nctabbed* nt);
// Change the selected tab to be 't'. Returns the previously selected tab.
struct nctab* nctabbed_select(struct nctabbed* nt, struct nctab* t);
// Write the channels for tab headers, the selected tab header, and the separator
// to '*hdrchan', '*selchan', and '*sepchan' respectively.
void nctabbed_channels(struct nctabbed* nt, uint64_t* RESTRICT hdrchan,
uint64_t* RESTRICT selchan, uint64_t* RESTRICT sepchan);
static inline uint64_t
nctabbed_hdrchan(struct nctabbed* nt){
uint64_t ch;
nctabbed_channels(nt, &ch, NULL, NULL);
return ch;
}
static inline uint64_t
nctabbed_selchan(struct nctabbed* nt){
uint64_t ch;
nctabbed_channels(nt, NULL, &ch, NULL);
return ch;
}
static inline uint64_t
nctabbed_sepchan(struct nctabbed* nt){
uint64_t ch;
nctabbed_channels(nt, NULL, NULL, &ch);
return ch;
}
// Returns the tab separator. This is not a copy and it should not be stored.
// This can be NULL, if the separator was set to NULL in ncatbbed_create() or
// nctabbed_set_separator().
const char* nctabbed_separator(struct nctabbed* nt);
// Returns the tab separator width, or zero if there is no separator.
int nctabbed_separator_width(struct nctabbed* nt);
// Set the tab headers channel for 'nt'.
void nctabbed_set_hdrchan(struct nctabbed* nt, uint64_t chan);
// Set the selected tab header channel for 'nt'.
void nctabbed_set_selchan(struct nctabbed* nt, uint64_t chan);
// Set the tab separator channel for 'nt'.
void nctabbed_set_sepchan(struct nctabbed* nt, uint64_t chan);
// Set the tab callback function for 't'. Returns the previous tab callback.
tabcb nctab_set_cb(struct nctab* t, tabcb newcb);
// Change the name of 't'. Returns -1 if 'newname' is NULL, and 0 otherwise.
int nctab_set_name(struct nctab* t, const char* newname);
// Set the user pointer of 't'. Returns the previous user pointer.
void* nctab_set_userptr(struct nctab* t, void* newopaque);
// Change the tab separator for 'nt'. Returns -1 if 'separator' is not NULL and
// is not a valid string, and 0 otherwise.
int nctabbed_set_separator(struct nctabbed* nt, const char* separator);
```
## Channels
A channel encodes 24 bits of RGB color, using 8 bits for each component. It

View File

@ -73,6 +73,7 @@
<a href="notcurses_stats.3.html">notcurses_stats</a>—notcurses runtime statistics<br/>
<a href="notcurses_stdplane.3.html">notcurses_stdplane</a>—acquire the standard <tt>ncplane</tt><br/>
<a href="notcurses_stop.3.html">notcurses_stop</a>—collapse the context<br/>
<a href="notcurses_tabbed.3.html">notcurses_tabbed</a>—tabbed interface widget<br/>
<a href="notcurses_tree.3.html">notcurses_tree</a>—line-based widget for hierarchical data<br/>
<a href="notcurses_visual.3.html">notcurses_visual</a>—operations on <tt>ncvisual</tt> objects<br/>
</div>

View File

@ -117,6 +117,7 @@ A few high-level widgets are included, all built atop ncplanes:
* **notcurses_progbar(3)** for drawing progress bars
* **notcurses_reader(3)** for free-form input data
* **notcurses_reel(3)** for hierarchal display of block-based data
* **notcurses_tabbed(3)** for tabbed interfaces
* **notcurses_selector(3)** for selecting one item from a set
* **notcurses_tree(3)** for hierarchal display of line-based data
@ -187,6 +188,7 @@ order to turn most error returns into exceptions.
**notcurses_stats(3)**,
**notcurses_stdplane(3)**,
**notcurses_stop(3)**,
**notcurses_tabbed(3)**,
**notcurses_tree(3)**,
**notcurses_visual(3)**,
**terminfo(5)**, **ascii(7)**, **utf-8(7)**,

View File

@ -0,0 +1,161 @@
% notcurses_tabbed(3)
% v2.2.3
# NAME
notcurses_tabbed tabbed interface widget
# SYNOPSIS
**#include <notcurses/notcurses.h>**
```c
#define NCTABBED_OPTION_BOTTOM 0x0001
struct nctabbed;
struct ncplane;
struct nctab;
typedef struct nctabbed_options {
uint64_t selchan; // channel for the selected tab header
uint64_t hdrchan; // channel for unselected tab headers
uint64_t sepchan; // channel for the tab separator
char* separator; // separator string (copied by nctabbed_create())
uint64_t flags; // bitmask of NCTABBED_OPTION_*
} nctabbed_options;
typedef void (*tabcb)(struct nctab* t, struct ncplane* ncp, void* userptr);
```
**struct nctabbed* nctabbed_create(struct ncplane* ***ncp***, const nctabbed_options* ***opts***);**
**void nctabbed_destroy(struct nctabbed* ***nt***);**
**void nctabbed_redraw(struct nctabbed* ***nt***);**
**void nctabbed_ensure_selected_header_visible(struct nctabbed* ***nt***);**
**struct nctab* nctabbed_selected(struct nctabbed* ***nt***);**
**struct nctab* nctabbed_leftmost(struct nctabbed* ***nt***);**
**int nctabbed_tabcount(struct nctabbed* ***nt***);**
**struct ncplane* nctabbed_plane(struct nctabbed* ***nt***);**
**struct ncplane* nctabbed_content_plane(struct nctabbed* ***nt***);**
**tabcb nctab_cb(struct nctab* ***t***);**
**const char* nctab_name(struct nctab* ***t***);**
**int nctab_name_width(struct nctab* ***t***);**
**void* nctab_userptr(struct nctab* ***t***);**
**struct nctab* nctab_next(struct nctab* ***t***);**
**struct nctab* nctab_prev(struct nctab* ***t***);**
**struct nctab* nctabbed_add(struct nctabbed* ***nt***, struct nctab* ***after***, struct nctab* ***before***, tabcb ***tcb***, const char* ***name***, void* ***opaque***);
**int nctabbed_del(struct nctabbed* ***nt***, struct nctab* ***t***);**
**int nctab_move(struct nctabbed* ***nt***, struct nctab* ***t***, struct nctab* ***after***,
struct nctab* ***before***);**
**void nctab_move_right(struct nctabbed* ***nt***, struct nctab* ***t***);**
**void nctab_move_left(struct nctabbed* ***nt***, struct nctab* ***t***);**
**void nctabbed_rotate(struct nctabbed* ***nt***, int ***amt***);**
**struct nctab* nctabbed_next(struct nctabbed* ***nt***);**
**struct nctab* nctabbed_prev(struct nctabbed* ***nt***);**
**struct nctab* nctabbed_select(struct nctabbed* ***nt***, struct nctab* ***t***);**
**void nctabbed_channels(struct nctabbed* ***nt***, uint64_t* RESTRICT ***hdrchan***, uint64_t* RESTRICT ***selchan***, uint64_t* RESTRICT ***sepchan***);**
**uint64_t nctabbed_hdrchan(struct nctabbed* ***nt***);**
**uint64_t nctabbed_selchan(struct nctabbed* ***nt***);**
**uint64_t nctabbed_sepchan(struct nctabbed* ***nt***);**
**const char* nctabbed_separator(struct nctabbed* ***nt***);**
**int nctabbed_separator_width(struct nctabbed* ***nt***);**
**void nctabbed_set_hdrchan(struct nctabbed* ***nt***, uint64_t ***chan***);**
**void nctabbed_set_selchan(struct nctabbed* ***nt***, uint64_t ***chan***);**
**void nctabbed_set_sepchan(struct nctabbed* ***nt***, uint64_t ***chan***);**
**tabcb nctab_set_cb(struct nctab* ***t***, tabcb ***newcb***);**
**int nctab_set_name(struct nctab* ***t***, const char* ***newname***);**
**void* nctab_set_userptr(struct nctab* ***t***, void* ***newopaque***);**
**int nctabbed_set_separator(struct nctabbed* ***nt***, const char* ***separator***);**
# DESCRIPTION
An **nctabbed** is a widget with one area for data display and a bar with
names of tabs. Unless there are no tabs, exactly one tab is "selected", and
exactly one tab is "leftmost". The selected tab is the one that controls
the tab content plane. The leftmost tab is the one of which the header is
visible furthest to the left. Any tab can be moved to and from anywhere in the
list. The tabs can be "rotated", which really means the leftmost tab gets
shifted. The widget is drawn only when **nctabbed_redraw** or **ntabbed_create**
are called.
## LAYOUT
The widget has a tab list either on the top or the bottom, 1 row thick. The tab
list contains tab headers (i.e. their names), separated with the separator
specified in **nctabbed_create** or **nctabbed_set_separator**. The channels
for the selected tab's header, other tab headers and the separator can be
set independently of each other. The tab separator can be 0-length, or NULL,
in which case there is no visible separator between tab headers. The selected
tab can be made sure to be visible when drawn (by changing the leftmost tab)
by calling the very long-named **nctabbed_ensure_selected_header_visible**.
The rest of the widget is an **ncplane** housing the selected tab content. (if any)
## THE TAB CALLBACK
The tab callback (of type **tabcb**) takes a tab, the tab content plane, and
the opaque pointer given to **nctabbed_add** or **nctabbed_set_userptr**.
It is called when the tab content is supposed to be drawn, that is when
the whole widget is redrawn. It should draw the tab content and possibly
make other actions, but it should not assume anything about the current state
of the tab content plane, nor should it modify the widget's or the tab's state.
# RETURN VALUES
**nctabbed_create** returns the newly created widget, or **NULL** when the widget
failed to be created. This destroys the **ncplane** given to it even if it fails.
**nctabbed_selected** and **nctabbed_leftmost** return the selected and
leftmost tabs, respectively. If there are no tabs, these return **NULL**.
**nctab_name** returns the tab's name. This is not a copy, and it should not be
stored, since it is freed when the tab's name is changed or the tab is deleted.
**nctabbed_next**, **nctabbed_prev** and **nctabbed_select** return the newly
selected tab.
**nctabbed_separator** returns the tab separator. This is not a copy, and it
should not be stored, since it is freed when the separator is changed or the
widget is deleted.
Functions returning **int** return **-1** on failure.
# SEE ALSO
**notcurses(3)**,
**notcurses_channels(3)**,
**notcurses_plane(3)**

View File

@ -55,6 +55,8 @@ struct ncreader; // widget supporting free string input ala readline
struct ncfadectx; // context for a palette fade operation
struct nctablet; // grouped item within an ncreel
struct ncreel; // hierarchical block-based data browser
struct nctab; // grouped item within an nctabbed
struct nctabbed; // widget with one tab visible at a time
// we never blit full blocks, but instead spaces (more efficient) with the
// background set to the desired foreground.
@ -3188,6 +3190,217 @@ API double ncprogbar_progress(const struct ncprogbar* n)
// Destroy the progress bar and its underlying ncplane.
API void ncprogbar_destroy(struct ncprogbar* n);
// Tabbed widgets. The tab list is displayed at the top or at the bottom of the
// plane, and only one tab is visible at a time.
// Display the tab list at the bottom instead of at the top of the plane
#define NCTABBED_OPTION_BOTTOM 0x0001ull
typedef struct nctabbed_options {
uint64_t selchan; // channel for the selected tab header
uint64_t hdrchan; // channel for unselected tab headers
uint64_t sepchan; // channel for the tab separator
char* separator; // separator string (copied by nctabbed_create())
uint64_t flags; // bitmask of NCTABBED_OPTION_*
} nctabbed_options;
// Tab content drawing callback. Takes the tab it was associated to, the ncplane
// on which tab content is to be drawn, and the user pointer of the tab.
// It is called during nctabbed_redraw().
typedef void (*tabcb)(struct nctab* t, struct ncplane* ncp, void* curry);
// Creates a new nctabbed widget, associated with the given ncplane 'n', and with
// additional options given in 'opts'. When 'opts' is NULL, it acts as if it were
// called with an all-zero opts. The widget takes ownership of 'n', and destroys
// it when the widget is destroyed. Returns the newly created widget. Returns
// NULL on failure, also destroying 'n'.
API ALLOC struct nctabbed* nctabbed_create(struct ncplane* n, const nctabbed_options* opts)
__attribute ((nonnull (1)));
// Destroy an nctabbed widget. All memory belonging to 'nt' is deallocated,
// including all tabs and their names. The plane associated with 'nt' is also
// destroyed. Calling this with NULL does nothing.
API void nctabbed_destroy(struct nctabbed* nt);
// Redraw the widget. This calls the tab callback of the currently selected tab
// to draw tab contents, and draws tab headers. The tab content plane is not
// modified by this function, apart from resizing the plane is necessary.
API void nctabbed_redraw(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Make sure the tab header of the currently selected tab is at least partially
// visible. (by rotating tabs until at least one column is displayed)
// Does nothing if there are no tabs.
API void nctabbed_ensure_selected_header_visible(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Returns the currently selected tab, or NULL if there are no tabs.
API struct nctab* nctabbed_selected(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Returns the leftmost tab, or NULL if there are no tabs.
API struct nctab* nctabbed_leftmost(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Returns the number of tabs in the widget.
API int nctabbed_tabcount(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Returns the plane associated to 'nt'.
API struct ncplane* nctabbed_plane(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Returns the tab content plane.
API struct ncplane* nctabbed_content_plane(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Returns the tab callback.
API tabcb nctab_cb(struct nctab* t)
__attribute__ ((nonnull (1)));
// Returns the tab name. This is not a copy and it should not be stored.
API const char* nctab_name(struct nctab* t)
__attribute__ ((nonnull (1)));
// Returns the width (in columns) of the tab's name.
API int nctab_name_width(struct nctab* t)
__attribute__ ((nonnull (1)));
// Returns the tab's user pointer.
API void* nctab_userptr(struct nctab* t)
__attribute__ ((nonnull (1)));
// Returns the tab to the right of 't'. This does not change which tab is selected.
API struct nctab* nctab_next(struct nctab* t)
__attribute__ ((nonnull (1)));
// Returns the tab to the left of 't'. This does not change which tab is selected.
API struct nctab* nctab_prev(struct nctab* t)
__attribute__ ((nonnull (1)));
// Add a new tab to 'nt' with the given tab callback, name, and user pointer.
// If both 'before' and 'after' are NULL, the tab is inserted after the selected
// tab. Otherwise, it gets put after 'after' (if not NULL) and before 'before'
// (if not NULL). If both 'after' and 'before' are given, they must be two
// neighboring tabs (the tab list is circular, so the last tab is immediately
// before the leftmost tab), otherwise the function returns NULL. If 'name' is
// NULL or a string containing illegal characters, the function returns NULL.
// On all other failures the function also returns NULL. If it returns NULL,
// none of the arguments are modified, and the widget state is not altered.
API ALLOC struct nctab* nctabbed_add(struct nctabbed* nt, struct nctab* after,
struct nctab* before, tabcb tcb,
const char* name, void* opaque)
__attribute__ ((nonnull (1, 5)));
// Remove a tab 't' from 'nt'. Its neighboring tabs become neighbors to each
// other. If 't' if the selected tab, the tab after 't' becomes selected.
// Likewise if 't' is the leftmost tab, the tab after 't' becomes leftmost.
// If 't' is the only tab, there will no more be a selected or leftmost tab,
// until a new tab is added. Returns -1 if 't' is NULL, and 0 otherwise.
API int nctabbed_del(struct nctabbed* nt, struct nctab* t)
__attribute__ ((nonnull (1)));
// Move 't' after 'after' (if not NULL) and before 'before' (if not NULL).
// If both 'after' and 'before' are NULL, the function returns -1, otherwise
// it returns 0.
API int nctab_move(struct nctabbed* nt, struct nctab* t, struct nctab* after,
struct nctab* before)
__attribute__ ((nonnull (1, 2)));
// Move 't' to the right by one tab, looping around to become leftmost if needed.
API void nctab_move_right(struct nctabbed* nt, struct nctab* t)
__attribute__ ((nonnull (1, 2)));
// Move 't' to the right by one tab, looping around to become the last tab if needed.
API void nctab_move_left(struct nctabbed* nt, struct nctab* t)
__attribute__ ((nonnull (1, 2)));
// Rotate the tabs of 'nt' right by 'amt' tabs, or '-amt' tabs left if 'amt' is
// negative. Tabs are rotated only by changing the leftmost tab; the selected tab
// stays the same. If there are no tabs, nothing happens.
API void nctabbed_rotate(struct nctabbed* nt, int amt)
__attribute__ ((nonnull (1)));
// Select the tab after the currently selected tab, and return the newly selected
// tab. Returns NULL if there are no tabs.
API struct nctab* nctabbed_next(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Select the tab before the currently selected tab, and return the newly selected
// tab. Returns NULL if there are no tabs.
API struct nctab* nctabbed_prev(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Change the selected tab to be 't'. Returns the previously selected tab.
API struct nctab* nctabbed_select(struct nctabbed* nt, struct nctab* t)
__attribute__ ((nonnull (1, 2)));
// Write the channels for tab headers, the selected tab header, and the separator
// to '*hdrchan', '*selchan', and '*sepchan' respectively.
API void nctabbed_channels(struct nctabbed* nt, uint64_t* RESTRICT hdrchan,
uint64_t* RESTRICT selchan, uint64_t* RESTRICT sepchan)
__attribute__ ((nonnull (1)));
static inline uint64_t
nctabbed_hdrchan(struct nctabbed* nt){
uint64_t ch;
nctabbed_channels(nt, &ch, NULL, NULL);
return ch;
}
static inline uint64_t
nctabbed_selchan(struct nctabbed* nt){
uint64_t ch;
nctabbed_channels(nt, NULL, &ch, NULL);
return ch;
}
static inline uint64_t
nctabbed_sepchan(struct nctabbed* nt){
uint64_t ch;
nctabbed_channels(nt, NULL, NULL, &ch);
return ch;
}
// Returns the tab separator. This is not a copy and it should not be stored.
// This can be NULL, if the separator was set to NULL in ncatbbed_create() or
// nctabbed_set_separator().
API const char* nctabbed_separator(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Returns the tab separator width, or zero if there is no separator.
API int nctabbed_separator_width(struct nctabbed* nt)
__attribute__ ((nonnull (1)));
// Set the tab headers channel for 'nt'.
API void nctabbed_set_hdrchan(struct nctabbed* nt, uint64_t chan)
__attribute__ ((nonnull (1)));
// Set the selected tab header channel for 'nt'.
API void nctabbed_set_selchan(struct nctabbed* nt, uint64_t chan)
__attribute__ ((nonnull (1)));
// Set the tab separator channel for 'nt'.
API void nctabbed_set_sepchan(struct nctabbed* nt, uint64_t chan)
__attribute__ ((nonnull (1)));
// Set the tab callback function for 't'. Returns the previous tab callback.
API tabcb nctab_set_cb(struct nctab* t, tabcb newcb)
__attribute__ ((nonnull (1)));
// Change the name of 't'. Returns -1 if 'newname' is NULL, and 0 otherwise.
API int nctab_set_name(struct nctab* t, const char* newname)
__attribute__ ((nonnull (1, 2)));
// Set the user pointer of 't'. Returns the previous user pointer.
API void* nctab_set_userptr(struct nctab* t, void* newopaque)
__attribute__ ((nonnull (1)));
// Change the tab separator for 'nt'. Returns -1 if 'separator' is not NULL and
// is not a valid string, and 0 otherwise.
API int nctabbed_set_separator(struct nctabbed* nt, const char* separator)
__attribute__ ((nonnull (1, 2)));
// Plots. Given a rectilinear area, an ncplot can graph samples along some axis.
// There is some underlying independent variable--this could be e.g. measurement
// sequence number, or measurement time. Samples are tagged with this variable, which

View File

@ -256,6 +256,28 @@ typedef struct ncprogbar {
bool retrograde;
} ncprogbar;
typedef struct nctab {
struct nctabbed* nt; // The nctabbed this belongs to
tabcb cb; // tab callback
char* name; // tab name
int namecols; // tab name width in columns
void* curry; // user pointer
struct nctab* prev;
struct nctab* next;
} nctab;
typedef struct nctabbed {
ncplane* ncp; // widget ncplane
ncplane* p; // tab content ncplane
ncplane* hp; // tab headers ncplane
// a doubly-linked circular list of tabs
nctab* leftmost; // the tab most to the left
nctab* selected; // the currently selected tab
int tabcount; // tab separator (can be NULL)
int sepcols; // separator with in columns
nctabbed_options opts; // copied in nctabbed_create()
} nctabbed;
// terminfo cache
typedef struct tinfo {
unsigned colors;// number of colors terminfo reported usable for this screen

456
src/lib/tabbed.c Normal file
View File

@ -0,0 +1,456 @@
#include "internal.h"
void nctabbed_redraw(nctabbed* nt){
nctab* t;
int drawn_cols = 0;
int rows, cols;
if(nt->tabcount == 0){
// no tabs = nothing to draw
ncplane_erase(nt->hp);
return;
}
// update sizes for planes
ncplane_dim_yx(nt->ncp, &rows, &cols);
if(nt->opts.flags & NCTABBED_OPTION_BOTTOM){
ncplane_resize_simple(nt->hp, -1, cols);
ncplane_resize_simple(nt->p, rows - 1, cols);
ncplane_move_yx(nt->hp, rows - 2, 0);
}else{
ncplane_resize_simple(nt->hp, -1, cols);
ncplane_resize_simple(nt->p, rows - 1, cols);
}
// the callback draws the tab contents
if(nt->selected->cb){
nt->selected->cb(nt->selected, nt->p, nt->selected->curry);
}
// now we draw the headers
t = nt->leftmost;
ncplane_erase(nt->hp);
ncplane_set_channels(nt->hp, nt->opts.hdrchan);
do{
if(t == nt->selected){
ncplane_set_channels(nt->hp, nt->opts.selchan);
drawn_cols += ncplane_putstr(nt->hp, t->name);
ncplane_set_channels(nt->hp, nt->opts.hdrchan);
}else{
drawn_cols += ncplane_putstr(nt->hp, t->name);
}
// avoid drawing the separator after the last tab, or when we
// ran out of space, or when it's not set
if((t->next != nt->leftmost || drawn_cols >= cols) && nt->opts.separator){
ncplane_set_channels(nt->hp, nt->opts.sepchan);
drawn_cols += ncplane_putstr(nt->hp, nt->opts.separator);
ncplane_set_channels(nt->hp, nt->opts.hdrchan);
}
t = t->next;
}while(t != nt->leftmost && drawn_cols < cols);
}
void nctabbed_ensure_selected_header_visible(nctabbed* nt){
nctab* t = nt->leftmost;
int cols = ncplane_dim_x(nt->hp);
int takencols = 0;
if(!t){
return;
}
//fprintf(stderr, "ensuring selected header visible\n");
do{
if(t == nt->selected){
break;
}
takencols += t->namecols + nt->sepcols;
if(takencols >= cols){
//fprintf(stderr, "not enough space, rotating\n");
takencols -= nt->leftmost->namecols + nt->sepcols;
nctabbed_rotate(nt, -1);
}
t = t->next;
//fprintf(stderr, "iteration over: takencols = %d, cols = %d\n", takencols, cols);
}while(t != nt->leftmost);
//fprintf(stderr, "ensuring done\n");
}
static bool
nctabbed_validate_opts(ncplane* n, const nctabbed_options* opts){
notcurses* nc = ncplane_notcurses(n);
if(opts->flags > NCTABBED_OPTION_BOTTOM){
logwarn(nc, "Provided unsupported flags 0x%016jx\n", (uint64_t)opts->flags);
}
if(opts->sepchan && !opts->separator){
logwarn(nc, "Provided non-zero separator channel when separator is NULL")
}
return true;
}
nctab* nctabbed_selected(nctabbed* nt){
return nt->selected;
}
nctab* nctabbed_leftmost(nctabbed* nt){
return nt->leftmost;
}
int nctabbed_tabcount(nctabbed* nt){
return nt->tabcount;
}
ncplane* nctabbed_plane(nctabbed* nt){
return nt->ncp;
}
ncplane* nctabbed_content_plane(nctabbed* nt){
return nt->p;
}
tabcb nctab_cb(nctab* t){
return t->cb;
}
const char* nctab_name(nctab* t){
return t->name;
}
int nctab_name_width(nctab* t){
return t->namecols;
}
void* nctab_userptr(nctab* t){
return t->curry;
}
nctab* nctab_next(nctab* t){
return t->next;
}
nctab* nctab_prev(nctab* t){
return t->prev;
}
nctabbed* nctabbed_create(ncplane* n, const nctabbed_options* topts){
nctabbed_options zeroed = {};
ncplane_options nopts = {};
int nrows, ncols;
nctabbed* nt;
notcurses* nc = ncplane_notcurses(n);
if(!topts){
topts = &zeroed;
}
if(!nctabbed_validate_opts(n, topts)){
return NULL;
}
if((nt = malloc(sizeof(*nt))) == NULL){
logerror(nc, "Couldn't allocate nctabbed");
return NULL;
}
nt->ncp = n;
nt->leftmost = nt->selected = NULL;
nt->tabcount = 0;
memcpy(&nt->opts, topts, sizeof(*topts));
if(nt->opts.separator){
if((nt->opts.separator = strdup(nt->opts.separator)) == NULL){
logerror(nc, "Couldn't allocate nctabbed separator");
free(nt);
return NULL;
}
if((nt->sepcols = ncstrwidth(nt->opts.separator)) < 0){
logerror(nc, "Separator string contains illegal characters");
free(nt->opts.separator);
free(nt);
return NULL;
}
}else{
nt->sepcols = 0;
}
ncplane_dim_yx(n, &nrows, &ncols);
if(topts->flags & NCTABBED_OPTION_BOTTOM){
nopts.y = nopts.x = 0;
nopts.cols = ncols;
nopts.rows = nrows - 1;
if((nt->p = ncplane_create(n, &nopts)) == NULL){
logerror(nc, "Couldn't create the tab content plane");
ncplane_genocide(n);
free(nt);
return NULL;
}
nopts.y = nrows - 2;
nopts.rows = 1;
if((nt->hp = ncplane_create(n, &nopts)) == NULL){
logerror(nc, "Couldn't create the tab headers plane");
ncplane_genocide(n);
free(nt);
return NULL;
}
}else{
nopts.y = nopts.x = 0;
nopts.cols = ncols;
nopts.rows = 1;
if((nt->hp = ncplane_create(n, &nopts)) == NULL){
logerror(nc, "Couldn't create the tab headers plane");
ncplane_genocide(n);
free(nt);
return NULL;
}
nopts.y = 1;
nopts.rows = nrows - 1;
if((nt->p = ncplane_create(n, &nopts)) == NULL){
logerror(nc, "Couldn't create the tab content plane");
ncplane_genocide(n);
free(nt);
return NULL;
}
}
nctabbed_redraw(nt);
return nt;
}
nctab* nctabbed_add(nctabbed* nt, nctab* after, nctab* before, tabcb cb,
const char* name, void* opaque){
nctab* t;
notcurses* nc = ncplane_notcurses(nt->ncp);
if(after && before){
if(after->prev != before || before->next != after){
logerror(ncplane_notcurses(nt->ncp), "bad before (%p) / after (%p) spec\n", before, after);
return NULL;
}
}else if(!after && !before){
// add it to the right of the selected tab
after = nt->selected;
}
if((t = malloc(sizeof(*t))) == NULL){
logerror(nc, "Couldn't alocate nctab")
return NULL;
}
if((t->name = strdup(name)) == NULL){
logerror(nc, "Couldn't allocate the tab name");
free(t);
return NULL;
}
if((t->namecols = ncstrwidth(name)) < 0){
logerror(nc, "Tab name contains illegal characters")
free(t->name);
free(t);
return NULL;
}
if(after){
t->next = after->next;
t->prev = after;
after->next = t;
t->next->prev = t;
}else if(before){
t->next = before;
t->prev = before->prev;
before->prev = t;
t->prev->next = t;
}else{
// the first tab
t->prev = t->next = t;
nt->leftmost = nt->selected = t;
}
t->nt = nt;
t->cb = cb;
t->curry = opaque;
++nt->tabcount;
return t;
}
int nctabbed_del(nctabbed* nt, nctab* t){
if(!t){
logerror(ncplane_notcurses(nt->ncp), "Provided NULL nctab");
return -1;
}
if(nt->tabcount == 1){
nt->leftmost = nt->selected = NULL;
}else{
if(nt->selected == t){
nt->selected = t->next;
}
if(nt->leftmost == t){
nt->leftmost = t->next;
}
t->next->prev = t->prev;
t->prev->next = t->next;
}
free(t);
--nt->tabcount;
return 0;
}
int nctab_move(nctabbed* nt, nctab* t, nctab* after, nctab* before){
if(after && before){
if(after->prev != before || before->next != after){
logerror(ncplane_notcurses(nt->ncp), "bad before (%p) / after (%p) spec\n", before, after);
return -1;
}
}else if(!after && !before){
logerror(ncplane_notcurses(nt->ncp), "bad before (%p) / after (%p) spec\n", before, after);
return -1;
}
// bad things would happen
if(t == after || t == before){
logerror(ncplane_notcurses(nt->ncp), "Cannot move a tab before or after itself.");
return -1;
}
t->prev->next = t->next;
t->next->prev = t->prev;
if(after){
t->next = after->next;
t->prev = after;
after->next = t;
t->next->prev = t;
}else{
t->next = before;
t->prev = before->prev;
before->prev = t;
t->prev->next = t;
}
return 0;
}
void nctab_move_right(nctabbed* nt, nctab* t){
if(t == nt->leftmost->prev){
nctab_move(nt, t, NULL, nt->leftmost);
nt->leftmost = t;
return;
}else if(t == nt->leftmost){
nt->leftmost = t->next;
}
nctab_move(nt, t, t->next, NULL);
}
void nctab_move_left(nctabbed* nt, nctab* t){
if(t == nt->leftmost){
nt->leftmost = t->next;
nctab_move(nt, t, nt->leftmost->prev, NULL);
return;
}else if(t == nt->leftmost->next){
nt->leftmost = t;
}
nctab_move(nt, t, NULL, t->prev);
}
void nctabbed_rotate(nctabbed* nt, int amt){
if(amt > 0){
for(int i = 0 ; i < amt ; ++i){
nt->leftmost = nt->leftmost->prev;
}
}else{
for(int i = 0 ; i < -amt ; ++i){
nt->leftmost = nt->leftmost->next;
}
}
}
nctab* nctabbed_next(nctabbed* nt){
if(nt->tabcount == 0){
return NULL;
}
nt->selected = nt->selected->next;
return nt->selected;
}
nctab* nctabbed_prev(nctabbed* nt){
if(nt->tabcount == 0){
return NULL;
}
nt->selected = nt->selected->prev;
return nt->selected;
}
nctab* nctabbed_select(nctabbed* nt, nctab* t){
nctab* prevsel = nt->selected;
nt->selected = t;
return prevsel;
}
void nctabbed_channels(nctabbed* nt, uint64_t* RESTRICT hdrchan,
uint64_t* RESTRICT selchan, uint64_t* RESTRICT sepchan){
memcpy(&nt->opts.hdrchan, hdrchan, sizeof(*hdrchan));
memcpy(&nt->opts.selchan, selchan, sizeof(*selchan));
memcpy(&nt->opts.sepchan, sepchan, sizeof(*sepchan));
}
const char* nctabbed_separator(nctabbed* nt){
return nt->opts.separator;
}
int nctabbed_separator_width(nctabbed* nt){
return nt->sepcols;
}
void nctabbed_destroy(nctabbed* nt){
if(!nt){
return;
}
nctab* t = nt->leftmost;
nctab* tmp;
t->prev->next = NULL;
while(t){
tmp = t->next;
free(t->name);
free(t);
t = tmp;
}
ncplane_genocide(nt->ncp);
free(nt->opts.separator);
free(nt);
}
void nctabbed_set_hdrchan(nctabbed* nt, uint64_t chan){
nt->opts.hdrchan = chan;
}
void nctabbed_set_selchan(nctabbed* nt, uint64_t chan){
nt->opts.selchan = chan;
}
void nctabbed_set_sepchan(nctabbed* nt, uint64_t chan){
nt->opts.sepchan = chan;
}
tabcb nctab_set_cb(nctab* t, tabcb newcb){
tabcb prevcb = t->cb;
t->cb = newcb;
return prevcb;
}
int nctab_set_name(nctab* t, const char* newname){
int newnamecols;
char* prevname = t->name;
notcurses* nc = ncplane_notcurses(t->nt->ncp);
if((newnamecols = ncstrwidth(newname)) < 0){
logerror(nc, "New tab name contains illegal characters");
return -1;
}
if((t->name = strdup(newname)) == NULL){
logerror(nc, "Couldn't allocate new tab name");
t->name = prevname;
return -1;
}
free(prevname);
t->namecols = newnamecols;
return 0;
}
void* nctab_set_userptr(nctab* t, void* newopaque){
void* prevcurry = t->curry;
t->curry = newopaque;
return prevcurry;
}
int nctabbed_set_separator(nctabbed* nt, const char* separator){
int newsepcols;
char* prevsep = nt->opts.separator;
notcurses* nc = ncplane_notcurses(nt->ncp);
if((newsepcols = ncstrwidth(separator)) < 0){
logerror(nc, "New tab separator contains illegal characters");
return -1;
}
if((nt->opts.separator = strdup(separator)) == NULL){
logerror(nc, "Couldn't allocate new tab separator");
nt->opts.separator = prevsep;
return -1;
}
free(prevsep);
nt->sepcols = newsepcols;
return 0;
}

143
src/poc/tabbed.c Normal file
View File

@ -0,0 +1,143 @@
#include <notcurses/notcurses.h>
#define REDRAW() \
do{ \
nctabbed_redraw(nct); \
if(notcurses_render(nc) < 0){ \
goto ded; \
} \
notcurses_getc_blocking(nc, NULL); \
}while(0)
void tabcbfn(struct nctab* t, struct ncplane* ncp, void* curry){
(void) t;
(void) curry;
ncplane_erase(ncp);
ncplane_puttext(ncp, -1, NCALIGN_LEFT,
"Use left/right arrow keys for navigation, "
"'[' and ']' to rotate tabs, "
"'a' to add a tab, 'r' to remove a tab, "
"',' and '.' to move the selected tab, "
"and 'q' to quit",
NULL);
}
void print_usage(char** argv){
printf("Usage: %s [ -bht | --bottom | --help | --top ]...\n", argv[0]);
}
int main(int argc, char** argv){
struct notcurses* nc;
bool bottom = false;
for(int i = 1 ; i < argc ; ++i){
if(strcmp(argv[i], "--help") == 0){
print_usage(argv);
return EXIT_SUCCESS;
}else if(strcmp(argv[i], "--bottom") == 0){
bottom = true;
}else if(strcmp(argv[i], "--top") == 0){
bottom = false;
}else if(argv[i][0] == '-'){
for(char* c = &argv[i][1] ; *c ; ++c){
switch(*c){
case 'h':
print_usage(argv);
return EXIT_SUCCESS;
case 'b':
bottom = true;
break;
case 't':
bottom = false;
break;
default:
print_usage(argv);
return EXIT_FAILURE;
}
}
}else{
print_usage(argv);
return EXIT_FAILURE;
}
}
nc = notcurses_core_init(NULL, NULL);
if(!nc){
return EXIT_FAILURE;
}
int rows, cols;
struct ncplane* stdp = notcurses_stddim_yx(nc, &rows, &cols);
struct ncplane_options popts = {
.y = 3,
.x = 5,
.rows = rows - 10,
.cols = cols - 10
};
struct ncplane* ncp = ncplane_create(stdp, &popts);
struct nctabbed_options topts = {
.hdrchan = CHANNELS_RGB_INITIALIZER(255, 0, 0, 60, 60, 60),
.selchan = CHANNELS_RGB_INITIALIZER(0, 255, 0, 0, 0, 0),
.sepchan = CHANNELS_RGB_INITIALIZER(255, 255, 255, 100, 100, 100),
.separator = " || ",
.flags = bottom ? NCTABBED_OPTION_BOTTOM : 0
};
struct nctabbed* nct = nctabbed_create(ncp, &topts);
struct nctab* t_; // stupid unused result warnings
(void) t_;
ncplane_set_base(nctabbed_content_plane(nct), " ", 0, CHANNELS_RGB_INITIALIZER(255, 255, 255, 15, 60, 15));
REDRAW();
t_ = nctabbed_add(nct, NULL, NULL, tabcbfn, "Tab #1", NULL);
REDRAW();
t_ = nctabbed_add(nct, NULL, NULL, tabcbfn, "Tab #2", NULL);
REDRAW();
t_ = nctabbed_add(nct, NULL, NULL, tabcbfn, "Tab #3", NULL);
REDRAW();
t_ = nctabbed_add(nct, NULL, NULL, tabcbfn, "alpha", NULL);
REDRAW();
t_ = nctabbed_add(nct, NULL, NULL, tabcbfn, "beta", NULL);
REDRAW();
t_ = nctabbed_add(nct, NULL, NULL, tabcbfn, "gamma", NULL);
REDRAW();
char32_t c;
while((c = notcurses_getc_blocking(nc, NULL)) != 'q'){
switch(c){
case NCKEY_RIGHT:
nctabbed_next(nct);
break;
case NCKEY_LEFT:
nctabbed_prev(nct);
break;
case '[':
nctabbed_rotate(nct, -1);
break;
case ']':
nctabbed_rotate(nct, 1);
break;
case ',':
nctab_move_left(nct, nctabbed_selected(nct));
break;
case '.':
nctab_move_right(nct, nctabbed_selected(nct));
break;
case 'a':
t_ = nctabbed_add(nct, NULL, NULL, tabcbfn, "added tab", NULL);
break;
case 'r':
nctabbed_del(nct, nctabbed_selected(nct));
break;
default:;
}
nctabbed_ensure_selected_header_visible(nct);
nctabbed_redraw(nct);
if(notcurses_render(nc)){
goto ded;
}
}
goto fin;
ded:
notcurses_stop(nc);
return EXIT_FAILURE;
fin:
if(notcurses_stop(nc) < 0){
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

394
src/tests/tabbed.cpp Normal file
View File

@ -0,0 +1,394 @@
#include "main.h"
void tabbedcb(struct nctab*, struct ncplane*, void*){
}
TEST_CASE("Tabbed") {
auto nc_ = testing_notcurses();
if(!nc_){
return;
}
struct ncplane* n_ = notcurses_stdplane(nc_);
REQUIRE(n_);
REQUIRE(0 == ncplane_cursor_move_yx(n_, 0, 0));
SUBCASE("CreateNullOpts") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
REQUIRE(nullptr != ncp);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
CHECK(0 == nctabbed_hdrchan(nt));
CHECK(0 == nctabbed_selchan(nt));
CHECK(0 == nctabbed_sepchan(nt));
CHECK(nullptr == nctabbed_separator(nt));
nctabbed_destroy(nt);
}
SUBCASE("Create") {
struct nctabbed_options opts = {
.selchan = CHANNELS_RGB_INITIALIZER(0, 255, 0, 70, 70, 70),
.hdrchan = CHANNELS_RGB_INITIALIZER(255, 0, 0, 60, 60, 60),
.sepchan = CHANNELS_RGB_INITIALIZER(0, 0, 255, 60, 60, 60),
.separator = const_cast<char*>("-separator-"),
.flags = NCTABBED_OPTION_BOTTOM
};
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, &opts);
REQUIRE(nullptr != nt);
CHECK(opts.hdrchan == nctabbed_hdrchan(nt));
CHECK(opts.selchan == nctabbed_selchan(nt));
CHECK(opts.sepchan == nctabbed_sepchan(nt));
CHECK(0 == strcmp("-separator-", nctabbed_separator(nt)));
nctabbed_destroy(nt);
}
SUBCASE("Add") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
CHECK(0 == nctabbed_tabcount(nt));
auto t1 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "amazingtab123", nullptr);
CHECK(nullptr != t1);
CHECK(t1 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
CHECK(1 == nctabbed_tabcount(nt));
CHECK(t1 == nctab_next(t1));
CHECK(t1 == nctab_prev(t1));
CHECK(nullptr == nctab_userptr(t1));
CHECK(0 == strcmp("amazingtab123", nctab_name(t1)));
auto t2 = nctabbed_add(nt, nullptr, nullptr, nullptr, "nullcbtab", nullptr);
CHECK(nullptr != t2);
CHECK(t1 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
CHECK(2 == nctabbed_tabcount(nt));
CHECK(t2 == nctab_next(t1));
CHECK(t2 == nctab_prev(t1));
CHECK(t1 == nctab_next(t2));
CHECK(t1 == nctab_prev(t2));
// this one gets put in the middle
auto t3 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab3", nullptr);
CHECK(nullptr != t3);
CHECK(3 == nctabbed_tabcount(nt));
CHECK(t2 == nctab_next(t3));
CHECK(t1 == nctab_prev(t3));
// this one at the end
auto t4 = nctabbed_add(nt, t2, t1, tabbedcb, "tab4", nullptr);
CHECK(nullptr != t4);
CHECK(4 == nctabbed_tabcount(nt));
CHECK(t1 == nctab_next(t4));
CHECK(t2 == nctab_prev(t4));
// second to last
auto t5 = nctabbed_add(nt, nullptr, t4, tabbedcb, "tab5", nullptr);
CHECK(nullptr != t5);
CHECK(t4 == nctab_next(t5));
CHECK(t2 == nctab_prev(t5));
// second
auto t6 = nctabbed_add(nt, t1, nullptr, tabbedcb, "tab6", nullptr);
CHECK(nullptr != t6);
CHECK(t3 == nctab_next(t6));
CHECK(t1 == nctab_prev(t6));
nctabbed_destroy(nt);
}
SUBCASE("Del") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
auto t1 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab1", nullptr);
auto t5 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab5", nullptr);
auto t4 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab4", nullptr);
auto t3 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab3", nullptr);
auto t2 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab2", nullptr);
CHECK(5 == nctabbed_tabcount(nt));
// tabs: t1 t2 t3 t4 t5
CHECK(0 == nctabbed_del(nt, t5));
CHECK(4 == nctabbed_tabcount(nt));
CHECK(t1 == nctab_next(t4));
CHECK(t4 == nctab_prev(t1));
// tabs: t1 t2 t3 t4
CHECK(0 == nctabbed_del(nt, t1));
CHECK(3 == nctabbed_tabcount(nt));
CHECK(t2 == nctab_next(t4));
CHECK(t4 == nctab_prev(t2));
CHECK(t2 == nctabbed_selected(nt));
CHECK(t2 == nctabbed_leftmost(nt));
// tabs: t2 t3 t4
CHECK(0 == nctabbed_del(nt, t3));
CHECK(2 == nctabbed_tabcount(nt));
CHECK(t2 == nctab_next(t4));
CHECK(t4 == nctab_prev(t2));
// tabs: t2 t4
CHECK(0 == nctabbed_del(nt, t2));
CHECK(1 == nctabbed_tabcount(nt));
CHECK(t4 == nctab_next(t4));
CHECK(t4 == nctab_prev(t4));
CHECK(t4 == nctabbed_selected(nt));
CHECK(t4 == nctabbed_leftmost(nt));
// only t4 left
CHECK(0 == nctabbed_del(nt, t4));
CHECK(0 == nctabbed_tabcount(nt));
CHECK(nullptr == nctabbed_selected(nt));
CHECK(nullptr == nctabbed_leftmost(nt));
nctabbed_destroy(nt);
}
SUBCASE("Move") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
auto t1 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab1", nullptr);
auto t5 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab5", nullptr);
auto t4 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab4", nullptr);
auto t3 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab3", nullptr);
auto t2 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab2", nullptr);
// tabs: t1 t2 t3 t4 t5
nctab_move(nt, t4, t5, nullptr);
CHECK(t1 == nctab_next(t4));
CHECK(t5 == nctab_prev(t4));
CHECK(t2 == nctab_next(t1));
CHECK(t4 == nctab_prev(t1));
CHECK(t4 == nctab_next(t5));
CHECK(t3 == nctab_prev(t5));
// tabs: t1 t2 t3 t5 t4
nctab_move(nt, t1, t2, nullptr);
CHECK(t3 == nctab_next(t1));
CHECK(t2 == nctab_prev(t1));
CHECK(t1 == nctab_next(t2));
CHECK(t4 == nctab_prev(t2));
CHECK(t5 == nctab_next(t3));
CHECK(t1 == nctab_prev(t3));
CHECK(t1 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
// tabs: t1 t3 t5 t4 t2
nctab_move(nt, t2, nullptr, t3);
CHECK(t2 == nctab_next(t1));
CHECK(t4 == nctab_prev(t1));
CHECK(t3 == nctab_next(t2));
CHECK(t1 == nctab_prev(t2));
CHECK(t5 == nctab_next(t3));
CHECK(t2 == nctab_prev(t3));
// tabs: t1 t2 t3 t5 t4
nctab_move(nt, t1, nullptr, t4);
CHECK(t1 == nctab_next(t5));
CHECK(t3 == nctab_prev(t5));
CHECK(t4 == nctab_next(t1));
CHECK(t5 == nctab_prev(t1));
CHECK(t2 == nctab_next(t4));
CHECK(t1 == nctab_prev(t4));
CHECK(t1 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
// tabs: t1 t4 t2 t3 t5
nctabbed_destroy(nt);
}
SUBCASE("Rotate") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
auto t1 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab1", nullptr);
auto t5 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab5", nullptr);
auto t4 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab4", nullptr);
auto t3 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab3", nullptr);
auto t2 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab2", nullptr);
// tabs: t1 t2 t3 t4 t5
nctabbed_rotate(nt, 1);
CHECK(t5 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t5 t1 t2 t3 t4
nctabbed_rotate(nt, 7);
CHECK(t3 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t3 t4 t5 t1 t2
nctabbed_rotate(nt, -1);
CHECK(t4 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t4 t5 t1 t2 t3
nctabbed_rotate(nt, -13);
CHECK(t2 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t2 t3 t4 t5 t1
nctabbed_destroy(nt);
}
SUBCASE("MoveLeftRight") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
auto t1 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab1", nullptr);
auto t5 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab5", nullptr);
auto t4 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab4", nullptr);
auto t3 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab3", nullptr);
auto t2 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab2", nullptr);
// tabs: t1 t2 t3 t4 t5
nctab_move_right(nt, t1);
CHECK(t3 == nctab_next(t1));
CHECK(t2 == nctab_prev(t1));
CHECK(t2 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t2 t1 t3 t4 t5
nctab_move_right(nt, t1);
CHECK(t4 == nctab_next(t1));
CHECK(t3 == nctab_prev(t1));
CHECK(t2 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t2 t3 t1 t4 t5
nctab_move_right(nt, t5);
CHECK(t2 == nctab_next(t5));
CHECK(t4 == nctab_prev(t5));
CHECK(t5 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t5 t2 t3 t1 t4
nctab_move_right(nt, t5);
CHECK(t3 == nctab_next(t5));
CHECK(t2 == nctab_prev(t5));
CHECK(t2 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t2 t5 t3 t1 t4
nctab_move_left(nt, t5);
CHECK(t2 == nctab_next(t5));
CHECK(t4 == nctab_prev(t5));
CHECK(t5 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t5 t2 t3 t1 t4
nctab_move_left(nt, t5);
CHECK(t2 == nctab_next(t5));
CHECK(t4 == nctab_prev(t5));
CHECK(t2 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t2 t3 t1 t4 t5
nctab_move_left(nt, t5);
CHECK(t4 == nctab_next(t5));
CHECK(t1 == nctab_prev(t5));
CHECK(t2 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t2 t3 t1 t5 t4
nctab_move_left(nt, t5);
CHECK(t1 == nctab_next(t5));
CHECK(t3 == nctab_prev(t5));
CHECK(t2 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_selected(nt));
// tabs: t2 t3 t5 t1 t4
nctabbed_destroy(nt);
}
SUBCASE("Select") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
auto t1 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab1", nullptr);
auto t5 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab5", nullptr);
auto t4 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab4", nullptr);
auto t3 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab3", nullptr);
auto t2 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab2", nullptr);
(void)t3;
(void)t4;
CHECK(t1 == nctabbed_selected(nt));
nctabbed_select(nt, t2);
CHECK(t2 == nctabbed_selected(nt));
nctabbed_select(nt, t5);
CHECK(t5 == nctabbed_selected(nt));
nctabbed_select(nt, t5);
CHECK(t5 == nctabbed_selected(nt));
nctabbed_select(nt, t1);
CHECK(t1 == nctabbed_selected(nt));
nctabbed_destroy(nt);
}
SUBCASE("NextPrev") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
auto t1 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab1", nullptr);
auto t5 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab5", nullptr);
auto t4 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab4", nullptr);
auto t3 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab3", nullptr);
auto t2 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab2", nullptr);
CHECK(t1 == nctabbed_selected(nt));
CHECK(t2 == nctabbed_next(nt));
CHECK(t2 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
CHECK(t3 == nctabbed_next(nt));
CHECK(t3 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
CHECK(t4 == nctabbed_next(nt));
CHECK(t4 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
CHECK(t5 == nctabbed_next(nt));
CHECK(t5 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
CHECK(t1 == nctabbed_next(nt));
CHECK(t1 == nctabbed_selected(nt));
CHECK(t1 == nctabbed_leftmost(nt));
nctabbed_destroy(nt);
}
SUBCASE("Setters") {
struct ncplane_options nopts = {
.y = 1, .x = 2, .rows = ncplane_dim_y(n_) - 2, .cols = ncplane_dim_x(n_) - 4,
.userptr = nullptr, .name = nullptr, .resizecb = nullptr, .flags = 0
};
auto ncp = ncplane_create(n_, &nopts);
auto nt = nctabbed_create(ncp, nullptr);
REQUIRE(nullptr != nt);
uint64_t hdrchan = CHANNELS_RGB_INITIALIZER(255, 127, 63, 31, 15, 7);
uint64_t selchan = CHANNELS_RGB_INITIALIZER(127, 63, 31, 15, 7, 3);
uint64_t sepchan = CHANNELS_RGB_INITIALIZER(63, 31, 15, 7, 3, 1);
nctabbed_set_hdrchan(nt, hdrchan);
nctabbed_set_selchan(nt, selchan);
nctabbed_set_sepchan(nt, sepchan);
uint64_t hdrchan2, selchan2, sepchan2;
nctabbed_channels(nt, &hdrchan2, &selchan2, &sepchan2);
CHECK(hdrchan == hdrchan2);
CHECK(selchan == selchan2);
CHECK(sepchan == sepchan2);
const char* sep = "separateur";
nctabbed_set_separator(nt, sep);
CHECK(0 == strcmp(sep, nctabbed_separator(nt)));
auto t1 = nctabbed_add(nt, nullptr, nullptr, tabbedcb, "tab1", nullptr);
nctab_set_cb(t1, tabbedcb);
CHECK(tabbedcb == nctab_cb(t1));
nctab_set_userptr(t1, (void*) sep);
CHECK((void*) sep == nctab_userptr(t1));
const char* tname = "tab name";
nctab_set_name(t1, tname);
CHECK(0 == strcmp(tname, nctab_name(t1)));
nctabbed_destroy(nt);
}
}