Move handle_action to tui.h and use it in functions there

This commit is contained in:
Daniel Schulte 2020-11-22 18:09:17 +01:00
parent 71f9ebaed7
commit 89f0c26484
3 changed files with 192 additions and 192 deletions

124
main.cpp
View File

@ -132,128 +132,6 @@ void draw_no_channels_msg()
} }
} }
static void pad_to_width(std::string &str, const size_t width)
{
const size_t current = string_width(str);
str.append(width-current, ' ');
}
struct action
{
int type;
std::string string;
int modifier;
std::function<void(void)> func;
const std::string help;
};
struct helpitem {
std::string key;
std::string text;
};
void draw_help(const std::vector<helpitem> &items)
{
const size_t rows = termpaint_surface_height(surface);
const size_t rows_per_column = rows / 3 * 2;
std::vector<std::vector<std::string>> column_texts;
const size_t text_columns = 1 + items.size() / rows_per_column;
size_t item = 0;
for(size_t column=0; column<text_columns; column++) {
const size_t items_this_column = std::min(items.size() - item, rows_per_column);
std::vector<std::string> texts;
size_t key_width = 0;
size_t text_width = 0;
for(size_t i=0; i<items_this_column; i++) {
key_width = std::max(string_width(items[item + i].key), key_width);
text_width = std::max(string_width(items[item + i].text), text_width);
}
for(size_t i=0; i<items_this_column; i++) {
std::string s = items[item].key;
pad_to_width(s, key_width);
s.append(" ");
s.append(items[item].text);
pad_to_width(s, key_width + 1 + text_width);
texts.push_back(s);
item++;
}
column_texts.push_back(texts);
}
std::string to_show;
for(size_t i=0; i<rows_per_column; i++) {
for(size_t column=0; column<text_columns; column++) {
if(i >= column_texts[column].size())
continue;
to_show.append(column_texts[column][i]);
if(column+1 < text_columns)
to_show.append(" ");
}
to_show.append("\n");
}
message_box("Help", to_show.c_str());
}
static std::unordered_map<std::string, std::string> key_symbols = {
{"ArrowLeft", ""},
{"ArrowUp", ""},
{"ArrowRight", ""},
{"ArrowDown", ""},
{"Space", "[Space]"},
};
std::string format_key(const action &action) {
std::string str;
if(action.modifier & TERMPAINT_MOD_CTRL)
str.append("C-");
if(action.modifier & TERMPAINT_MOD_ALT)
str.append("M-");
if(action.type == TERMPAINT_EV_KEY) {
auto it = key_symbols.find(action.string);
if(it == key_symbols.end())
str.append(action.string);
else
str.append(it->second);
} else {
str.append(action.string);
}
return str;
}
bool handle_action(const Event &event, const std::vector<action> &actions)
{
if(event.type == EV_TIMEOUT)
return false;
const auto it = std::find_if(actions.cbegin(), actions.cend(), [&](const action &a) {
return a.type == event.type
&& a.string == event.string
&& event.modifier == a.modifier;
});
if(it == actions.cend()) {
if(event.type == TERMPAINT_EV_KEY && event.string == "F1") {
std::vector<helpitem> items = {{"F1", "Display this help"}};
for(const action &action: actions) {
if(action.help.empty())
continue;
items.push_back({format_key(action), action.help});
}
draw_help(items);
}
return false;
}
if(it->func)
it->func();
return true;
}
sqlite3 *db; sqlite3 *db;
void load_videos_for_channel(const std::string &channelId, bool force=false) void load_videos_for_channel(const std::string &channelId, bool force=false)
@ -623,7 +501,7 @@ int main()
if(!event) if(!event)
abort(); abort();
handle_action(*event, actions); tui_handle_action(*event, actions);
} while (!exit); } while (!exit);
tp_shutdown(); tp_shutdown();

232
tui.cpp
View File

@ -158,13 +158,21 @@ int get_selection(const std::string &caption, const std::vector<std::string> &en
const int rows_needed = entries.size()+2; // Number of entries and top/bottom border const int rows_needed = entries.size()+2; // Number of entries and top/bottom border
bool done = false;
std::vector<action> actions = {
{TERMPAINT_EV_KEY, "ArrowUp", 0, [&](){ if(selected > 0) selected--; }, "Previous option"},
{TERMPAINT_EV_KEY, "ArrowDown", 0, [&](){ if(selected < entries.size() - 1) selected++; }, "Next option"},
{TERMPAINT_EV_KEY, "Escape", 0, [&](){ selected = -1; done = true; }, "Abort selection"},
{EV_IGNORE, "1..9", 0, nullptr, "Select option 1..9"},
};
while (!done) {
const int cols = termpaint_surface_width(surface); const int cols = termpaint_surface_width(surface);
const int rows = termpaint_surface_height(surface); const int rows = termpaint_surface_height(surface);
int x, y; int x, y;
resolve_align(align, cols_needed, rows_needed, 0, cols, 0, rows, x, y); resolve_align(align, cols_needed, rows_needed, 0, cols, 0, rows, x, y);
while (true) {
draw_box_with_caption(x, y, cols_needed, rows_needed, caption); draw_box_with_caption(x, y, cols_needed, rows_needed, caption);
int yy = y+1; int yy = y+1;
@ -180,32 +188,21 @@ int get_selection(const std::string &caption, const std::vector<std::string> &en
if(!event) if(!event)
abort(); abort();
if(event->type == TERMPAINT_EV_CHAR) { if(!tui_handle_action(*event, actions)) {
if(event->string.length() == 1) { if(event->type == TERMPAINT_EV_CHAR && event->string.length() == 1) {
char c = event->string[0]; char c = event->string[0];
if(c>'0' && c<='9') { if(c>'0' && c<='9') {
size_t idx = c - '0'; size_t idx = c - '0';
if(idx > entries.size()) if(idx > entries.size())
continue; continue;
return idx - 1; selected = idx - 1;
done = true;
} }
} }
} else if (event->type == TERMPAINT_EV_KEY) {
if (event->string == "Escape") {
return -1;
} else if(event->string == "ArrowUp") {
if(selected > 0)
selected--;
} else if(event->string == "ArrowDown") {
if(selected < entries.size() - 1)
selected++;
} else if(event->string == "Enter" || event->string == "NumpadEnter") {
return static_cast<int>(selected);
}
} }
} }
return -1; return selected;
} }
Align operator|(const Align &a, const Align &b) Align operator|(const Align &a, const Align &b)
@ -241,7 +238,24 @@ std::string get_string(const std::string &caption, const std::string &text, cons
termpaint_terminal_set_cursor_visible(terminal, true); termpaint_terminal_set_cursor_visible(terminal, true);
termpaint_terminal_set_cursor_style(terminal, TERMPAINT_CURSOR_STYLE_BAR, true); termpaint_terminal_set_cursor_style(terminal, TERMPAINT_CURSOR_STYLE_BAR, true);
while(true) { bool done = false;
std::vector<action> actions = {
{TERMPAINT_EV_KEY, "Home", 0, [&](){ input_pos = 0; }, "Go to beginning of input"},
{TERMPAINT_EV_CHAR, "a", TERMPAINT_MOD_CTRL, [&](){ input_pos = 0; }, "Go to beginning of input"},
{TERMPAINT_EV_KEY, "End", 0, [&](){ input_pos = input.size(); }, "Go to end of input"},
{TERMPAINT_EV_CHAR, "e", TERMPAINT_MOD_CTRL, [&](){ input_pos = input.size(); }, "Go to end of input"},
{TERMPAINT_EV_KEY, "ArrowLeft", 0, [&](){ if(input_pos > 0) input_pos--; }, "Move left"},
{TERMPAINT_EV_KEY, "ArrowRight", 0, [&](){ if(input_pos < input.size()) input_pos++; }, "Move right"},
// FIXME: Correctly handle deletion of clusters with more than one codepoint e.g. ° or ä
{TERMPAINT_EV_KEY, "Delete", 0, [&](){ if(input_pos < input.size()) { input.erase(input_pos, 1); } }, "Delete input forward"},
{TERMPAINT_EV_KEY, "Backspace", 0, [&](){ if(!input.empty()) { input.erase(input_pos - 1, 1); input_pos--; }}, "Delete input backward"},
{TERMPAINT_EV_KEY, "Escape", 0, [&](){ input.clear(); done = true; }, "Abort input"},
{TERMPAINT_EV_KEY, "Enter", 0, [&](){ done = true; }, "Confirm input"},
};
while(!done) {
draw_box_with_caption(x, y, cols_needed, rows_needed, caption); draw_box_with_caption(x, y, cols_needed, rows_needed, caption);
if(!text.empty()) if(!text.empty())
termpaint_surface_write_with_attr(surface, x + 1, y + 1, text.c_str(), attributes[ASNormal].normal); termpaint_surface_write_with_attr(surface, x + 1, y + 1, text.c_str(), attributes[ASNormal].normal);
@ -254,40 +268,19 @@ std::string get_string(const std::string &caption, const std::string &text, cons
if(!event) if(!event)
abort(); abort();
if(event->type == TERMPAINT_EV_CHAR) { if(!tui_handle_action(*event, actions) && event->type != EV_TIMEOUT)
if(event->string == "a" && event->modifier & TERMPAINT_MOD_CTRL) { {
input_pos = 0;
} else if (event->string == "e" && event->modifier & TERMPAINT_MOD_CTRL) {
input_pos = input.size();
} else {
if(input_pos + 1 == cols_needed - 1) if(input_pos + 1 == cols_needed - 1)
continue; continue;
if(event->type == TERMPAINT_EV_KEY) {
if(event->string == "Space")
event->string = " ";
else
continue;
}
input.insert(input_pos, event->string); input.insert(input_pos, event->string);
input_pos++; input_pos++;
} }
} else if (event->type == TERMPAINT_EV_KEY) {
if (event->string == "Escape") {
input.clear();
break;
} else if(event->string == "Backspace") {
if(!input.empty()) {
input.erase(input_pos - 1, 1);
input_pos--;
}
} else if(event->string == "Delete") {
if(input_pos < input.size()) {
input.erase(input_pos, 1);
}
} else if(event->string == "ArrowLeft") {
if(input_pos > 0)
input_pos--;
} else if(event->string == "ArrowRight") {
if(input_pos < input.size())
input_pos++;
} else if(event->string == "Enter" || event->string == "NumpadEnter") {
break;
}
}
} }
termpaint_terminal_set_cursor_visible(terminal, false); termpaint_terminal_set_cursor_visible(terminal, false);
@ -358,23 +351,35 @@ Button message_box(const std::string &caption, const std::string &text, const Bu
width = std::max(width, string_width(line)); width = std::max(width, string_width(line));
} }
const size_t cols = termpaint_surface_width(surface);
const size_t rows = termpaint_surface_height(surface);
const size_t rows_needed = 4 + lines.size(); const size_t rows_needed = 4 + lines.size();
const size_t cols_needed = width + 4; const size_t cols_needed = width + 4;
bool done = false;
std::vector<action> actions;
if(active_buttons.size() > 1) {
actions = {
{TERMPAINT_EV_KEY, "Enter", 0, [&](){ done = true; }, "Confirm Selection"},
{TERMPAINT_EV_KEY, "ArrowLeft", 0, [&](){ if(selected_button > 0) selected_button--;}, "Previous option"},
{TERMPAINT_EV_KEY, "ArrowRight", 0, [&](){ if(selected_button < active_buttons.size() - 1) selected_button++; }, "Next option"},
};
} else {
actions = {
{TERMPAINT_EV_KEY, "Enter", 0, [&](){ done = true; }, "Close dialog"},
};
}
while(!done) {
const size_t cols = termpaint_surface_width(surface);
const size_t rows = termpaint_surface_height(surface);
int x, y; int x, y;
resolve_align(align, cols_needed, rows_needed, 0, cols, 0, rows, x, y); resolve_align(align, cols_needed, rows_needed, 0, cols, 0, rows, x, y);
while(true) {
draw_box_with_caption(x, y, cols_needed, rows_needed, caption); draw_box_with_caption(x, y, cols_needed, rows_needed, caption);
for(size_t i=0; i<lines.size(); i++) { for(size_t i=0; i<lines.size(); i++) {
termpaint_surface_write_with_attr(surface, x + 2, y + 1 + i, lines[i].c_str(), attributes[ASNormal].normal); termpaint_surface_write_with_attr(surface, x + 2, y + 1 + i, lines[i].c_str(), attributes[ASNormal].normal);
} }
int button_x = x + 2; int button_x = x + 2;
for(size_t btn=0; btn<active_buttons.size(); btn++) { for(size_t btn=0; btn<active_buttons.size(); btn++) {
const bool button_selected = selected_button == btn; const bool button_selected = selected_button == btn;
@ -393,15 +398,7 @@ Button message_box(const std::string &caption, const std::string &text, const Bu
if(!event) if(!event)
abort(); abort();
if (event->type == TERMPAINT_EV_KEY) { tui_handle_action(*event, actions);
if (event->string == "Escape" || event->string == "Enter" || event->string == "NumpadEnter") {
break;
} else if(event->string == "ArrowLeft" && selected_button > 0) {
selected_button--;
} else if(event->string == "ArrowRight" && selected_button < active_buttons.size() - 1) {
selected_button++;
}
}
} }
return active_buttons[selected_button].button; return active_buttons[selected_button].button;
@ -535,3 +532,116 @@ void tui_abort(std::string message)
message_box("Error", simple_wrap(message, cols/2)); message_box("Error", simple_wrap(message, cols/2));
exit(1); exit(1);
} }
static void pad_to_width(std::string &str, const size_t width)
{
const size_t current = string_width(str);
str.append(width-current, ' ');
}
struct helpitem {
std::string key;
std::string text;
};
static void draw_help(const std::vector<helpitem> &items)
{
const size_t rows = termpaint_surface_height(surface);
const size_t rows_per_column = rows / 3 * 2;
std::vector<std::vector<std::string>> column_texts;
const size_t text_columns = 1 + items.size() / rows_per_column;
size_t item = 0;
for(size_t column=0; column<text_columns; column++) {
const size_t items_this_column = std::min(items.size() - item, rows_per_column);
std::vector<std::string> texts;
size_t key_width = 0;
size_t text_width = 0;
for(size_t i=0; i<items_this_column; i++) {
key_width = std::max(string_width(items[item + i].key), key_width);
text_width = std::max(string_width(items[item + i].text), text_width);
}
for(size_t i=0; i<items_this_column; i++) {
std::string s = items[item].key;
pad_to_width(s, key_width);
s.append(" ");
s.append(items[item].text);
pad_to_width(s, key_width + 1 + text_width);
texts.push_back(s);
item++;
}
column_texts.push_back(texts);
}
std::string to_show;
for(size_t i=0; i<rows_per_column; i++) {
for(size_t column=0; column<text_columns; column++) {
if(i >= column_texts[column].size())
continue;
to_show.append(column_texts[column][i]);
if(column+1 < text_columns)
to_show.append(" ");
}
to_show.append("\n");
}
message_box("Help", to_show.c_str());
}
static std::unordered_map<std::string, std::string> key_symbols = {
{"ArrowLeft", ""},
{"ArrowUp", ""},
{"ArrowRight", ""},
{"ArrowDown", ""},
{"Escape", "Esc"},
};
static std::string format_key(const action &action) {
std::string str;
if(action.modifier & TERMPAINT_MOD_CTRL)
str.append("C-");
if(action.modifier & TERMPAINT_MOD_ALT)
str.append("M-");
if(action.type == TERMPAINT_EV_KEY) {
auto it = key_symbols.find(action.string);
if(it == key_symbols.end())
str.append(action.string);
else
str.append(it->second);
} else {
str.append(action.string);
}
return str;
}
bool tui_handle_action(const Event &event, const std::vector<action> &actions)
{
if(event.type == EV_TIMEOUT)
return false;
const auto it = std::find_if(actions.cbegin(), actions.cend(), [&](const action &a) {
return a.type == event.type
&& a.string == event.string
&& event.modifier == a.modifier;
});
if(it == actions.cend()) {
if(event.type == TERMPAINT_EV_KEY && event.string == "F1") {
std::vector<helpitem> items = {{"F1", "Display this help"}};
for(const action &action: actions) {
if(action.help.empty())
continue;
items.push_back({format_key(action), action.help});
}
draw_help(items);
return true;
}
return false;
}
if(it->func)
it->func();
return true;
}

12
tui.h
View File

@ -2,6 +2,7 @@
#include <termpaintx.h> #include <termpaintx.h>
#define EV_TIMEOUT 0xffff #define EV_TIMEOUT 0xffff
#define EV_IGNORE 0xfffe
#include <functional> #include <functional>
#include <optional> #include <optional>
@ -58,6 +59,17 @@ extern void tp_pause();
extern void tp_unpause(); extern void tp_unpause();
extern std::optional<Event> tp_wait_for_event(); extern std::optional<Event> tp_wait_for_event();
struct action
{
int type;
std::string string;
int modifier;
std::function<void(void)> func;
std::string help;
};
extern bool tui_handle_action(const Event &event, const std::vector<action> &actions);
extern size_t string_width(const std::string &str); extern size_t string_width(const std::string &str);
extern std::pair<size_t, size_t> string_size(const std::string &str); extern std::pair<size_t, size_t> string_size(const std::string &str);
extern void write_multiline_string(const int x, const int y, const std::string &str, termpaint_attr *attr); extern void write_multiline_string(const int x, const int y, const std::string &str, termpaint_attr *attr);