Extract most ncurses calls into NCursesUI::Window methods

Try to isolate ncurses as much as possible to explore alternate
terminal UI implementation.
This commit is contained in:
Maxime Coste 2019-09-05 00:04:27 +10:00
parent 4c4eb61c18
commit afd45c6755
2 changed files with 283 additions and 227 deletions

View File

@ -29,6 +29,131 @@ using std::max;
struct NCursesWin : WINDOW {}; struct NCursesWin : WINDOW {};
void NCursesUI::Window::create(const DisplayCoord& p, const DisplayCoord& s)
{
pos = p;
size = s;
win = (NCursesWin*)newpad((int)size.line, (int)size.column);
}
void NCursesUI::Window::destroy()
{
delwin(win);
invalidate();
}
void NCursesUI::Window::invalidate()
{
win = nullptr;
pos = DisplayCoord{};
size = DisplayCoord{};
}
void NCursesUI::Window::refresh(bool force)
{
if (not win)
return;
if (force)
redrawwin(win);
DisplayCoord max_pos = pos + size - DisplayCoord{1,1};
pnoutrefresh(win, 0, 0, (int)pos.line, (int)pos.column,
(int)max_pos.line, (int)max_pos.column);
}
void NCursesUI::Window::move_cursor(DisplayCoord coord)
{
wmove(win, (int)coord.line, (int)coord.column);
}
void NCursesUI::Window::add_str(StringView str)
{
waddnstr(win, str.begin(), (int)str.length());
}
void NCursesUI::Window::clear_to_end_of_line()
{
wclrtoeol(win);
}
void NCursesUI::Window::draw_line(Palette& palette,
const DisplayLine& line,
ColumnCount col_index,
ColumnCount max_column,
const Face& default_face)
{
for (const DisplayAtom& atom : line)
{
set_face(palette, atom.face, default_face);
StringView content = atom.content();
if (content.empty())
continue;
const auto remaining_columns = max_column - col_index;
if (content.back() == '\n' and
content.column_length() - 1 < remaining_columns)
{
add_str(content.substr(0, content.length()-1));
waddch(win, ' ');
}
else
{
content = content.substr(0_col, remaining_columns);
add_str(content);
col_index += content.column_length();
}
}
}
void NCursesUI::Window::set_face(Palette& palette, Face face, const Face& default_face)
{
if (m_active_pair != -1)
wattroff(win, COLOR_PAIR(m_active_pair));
face = merge_faces(default_face, face);
if (face.fg != Color::Default or face.bg != Color::Default)
{
m_active_pair = palette.get_color_pair(face);
wattron(win, COLOR_PAIR(m_active_pair));
}
auto set_attribute = [&](Attribute attr, int nc_attr) {
(face.attributes & attr) ? wattron(win, nc_attr) : wattroff(win, nc_attr);
};
set_attribute(Attribute::Underline, A_UNDERLINE);
set_attribute(Attribute::Reverse, A_REVERSE);
set_attribute(Attribute::Blink, A_BLINK);
set_attribute(Attribute::Bold, A_BOLD);
set_attribute(Attribute::Dim, A_DIM);
#if defined(A_ITALIC)
set_attribute(Attribute::Italic, A_ITALIC);
#endif
}
void NCursesUI::Window::mark_dirty(LineCount pos, LineCount count)
{
wredrawln(win, (int)pos, (int)count);
}
void NCursesUI::Window::set_background_color(Palette& palette, Face face)
{
wbkgdset(win, COLOR_PAIR(palette.get_color_pair(face)));
}
int NCursesUI::Window::get_char()
{
return wgetch(win);
}
void NCursesUI::Window::set_blocking(bool blocking)
{
wtimeout(win, blocking ? -1 : 0);
}
constexpr int NCursesUI::default_shift_function_key; constexpr int NCursesUI::default_shift_function_key;
static constexpr StringView assistant_cat[] = static constexpr StringView assistant_cat[] =
@ -66,14 +191,6 @@ static constexpr StringView assistant_dilbert[] =
R"( @ )", R"( @ )",
R"( )"}; R"( )"};
static void set_attribute(WINDOW* window, int attribute, bool on)
{
if (on)
wattron(window, attribute);
else
wattroff(window, attribute);
}
template<typename T> T sq(T x) { return x * x; } template<typename T> T sq(T x) { return x * x; }
constexpr struct { unsigned char r, g, b; } builtin_colors[] = { constexpr struct { unsigned char r, g, b; } builtin_colors[] = {
@ -143,7 +260,28 @@ constexpr struct { unsigned char r, g, b; } builtin_colors[] = {
{0xd0,0xd0,0xd0}, {0xda,0xda,0xda}, {0xe4,0xe4,0xe4}, {0xee,0xee,0xee}, {0xd0,0xd0,0xd0}, {0xda,0xda,0xda}, {0xe4,0xe4,0xe4}, {0xee,0xee,0xee},
}; };
int NCursesUI::get_color(Color color) const std::initializer_list<HashMap<Kakoune::Color, int>::Item>
NCursesUI::Palette::default_colors = {
{ Color::Default, -1 },
{ Color::Black, 0 },
{ Color::Red, 1 },
{ Color::Green, 2 },
{ Color::Yellow, 3 },
{ Color::Blue, 4 },
{ Color::Magenta, 5 },
{ Color::Cyan, 6 },
{ Color::White, 7 },
{ Color::BrightBlack, 8 },
{ Color::BrightRed, 9 },
{ Color::BrightGreen, 10 },
{ Color::BrightYellow, 11 },
{ Color::BrightBlue, 12 },
{ Color::BrightMagenta, 13 },
{ Color::BrightCyan, 14 },
{ Color::BrightWhite, 15 },
};
int NCursesUI::Palette::get_color(Color color)
{ {
auto it = m_colors.find(color); auto it = m_colors.find(color);
if (it != m_colors.end()) if (it != m_colors.end())
@ -181,7 +319,7 @@ int NCursesUI::get_color(Color color)
} }
} }
int NCursesUI::get_color_pair(const Face& face) int NCursesUI::Palette::get_color_pair(const Face& face)
{ {
ColorPair colors{face.fg, face.bg}; ColorPair colors{face.fg, face.bg};
auto it = m_colorpairs.find(colors); auto it = m_colorpairs.find(colors);
@ -195,27 +333,21 @@ int NCursesUI::get_color_pair(const Face& face)
} }
} }
void NCursesUI::set_face(NCursesWin* window, Face face, const Face& default_face) bool NCursesUI::Palette::set_change_colors(bool change_colors)
{ {
if (m_active_pair != -1) bool reset = false;
wattroff(window, COLOR_PAIR(m_active_pair)); if (can_change_color() and m_change_colors != change_colors)
face = merge_faces(default_face, face);
if (face.fg != Color::Default or face.bg != Color::Default)
{ {
m_active_pair = get_color_pair(face); fputs("\033]104\007", stdout); // try to reset palette
wattron(window, COLOR_PAIR(m_active_pair)); fflush(stdout);
m_colorpairs.clear();
m_colors = default_colors;
m_next_color = 16;
m_next_pair = 1;
reset = true;
} }
m_change_colors = change_colors;
set_attribute(window, A_UNDERLINE, face.attributes & Attribute::Underline); return reset;
set_attribute(window, A_REVERSE, face.attributes & Attribute::Reverse);
set_attribute(window, A_BLINK, face.attributes & Attribute::Blink);
set_attribute(window, A_BOLD, face.attributes & Attribute::Bold);
set_attribute(window, A_DIM, face.attributes & Attribute::Dim);
#if defined(A_ITALIC)
set_attribute(window, A_ITALIC, face.attributes & Attribute::Italic);
#endif
} }
static sig_atomic_t resize_pending = 0; static sig_atomic_t resize_pending = 0;
@ -228,30 +360,8 @@ static void signal_handler(int)
EventManager::instance().force_signal(0); EventManager::instance().force_signal(0);
} }
static const std::initializer_list<HashMap<Kakoune::Color, int>::Item>
default_colors = {
{ Color::Default, -1 },
{ Color::Black, 0 },
{ Color::Red, 1 },
{ Color::Green, 2 },
{ Color::Yellow, 3 },
{ Color::Blue, 4 },
{ Color::Magenta, 5 },
{ Color::Cyan, 6 },
{ Color::White, 7 },
{ Color::BrightBlack, 8 },
{ Color::BrightRed, 9 },
{ Color::BrightGreen, 10 },
{ Color::BrightYellow, 11 },
{ Color::BrightBlue, 12 },
{ Color::BrightMagenta, 13 },
{ Color::BrightCyan, 14 },
{ Color::BrightWhite, 15 },
};
NCursesUI::NCursesUI() NCursesUI::NCursesUI()
: m_colors{default_colors}, : m_cursor{CursorMode::Buffer, {}},
m_cursor{CursorMode::Buffer, {}},
m_stdin_watcher{0, FdEvents::Read, m_stdin_watcher{0, FdEvents::Read,
[this](FDWatcher&, FdEvents, EventMode) { [this](FDWatcher&, FdEvents, EventMode) {
if (not m_on_key) if (not m_on_key)
@ -278,7 +388,7 @@ NCursesUI::NCursesUI()
check_resize(true); check_resize(true);
redraw(); redraw(false);
} }
NCursesUI::~NCursesUI() NCursesUI::~NCursesUI()
@ -294,47 +404,20 @@ NCursesUI::~NCursesUI()
set_signal_handler(SIGCONT, SIG_DFL); set_signal_handler(SIGCONT, SIG_DFL);
} }
void NCursesUI::Window::create(const DisplayCoord& p, const DisplayCoord& s) void NCursesUI::redraw(bool force)
{ {
pos = p; m_window.refresh(force);
size = s;
win = (NCursesWin*)newpad((int)size.line, (int)size.column);
}
void NCursesUI::Window::destroy()
{
delwin(win);
win = nullptr;
pos = DisplayCoord{};
size = DisplayCoord{};
}
void NCursesUI::Window::refresh()
{
if (not win)
return;
DisplayCoord max_pos = pos + size - DisplayCoord{1,1};
pnoutrefresh(win, 0, 0, (int)pos.line, (int)pos.column,
(int)max_pos.line, (int)max_pos.column);
}
void NCursesUI::redraw()
{
pnoutrefresh(m_window, 0, 0, 0, 0,
(int)m_dimensions.line + 1, (int)m_dimensions.column);
if (m_menu.columns != 0 or m_menu.pos.column > m_status_len) if (m_menu.columns != 0 or m_menu.pos.column > m_status_len)
m_menu.refresh(); m_menu.refresh(false);
m_info.refresh(); m_info.refresh(false);
Window screen{{}, static_cast<NCursesWin*>(newscr)};
if (m_cursor.mode == CursorMode::Prompt) if (m_cursor.mode == CursorMode::Prompt)
wmove(newscr, m_status_on_top ? 0 : (int)m_dimensions.line, screen.move_cursor({m_status_on_top ? 0 : m_dimensions.line, m_cursor.coord.column});
(int)m_cursor.coord.column);
else else
wmove(newscr, (int)(m_cursor.coord.line + content_line_offset()), screen.move_cursor(m_cursor.coord + content_line_offset());
(int)m_cursor.coord.column);
doupdate(); doupdate();
} }
@ -346,54 +429,18 @@ void NCursesUI::set_cursor(CursorMode mode, DisplayCoord coord)
void NCursesUI::refresh(bool force) void NCursesUI::refresh(bool force)
{ {
if (force)
redrawwin(m_window);
if (m_dirty or force) if (m_dirty or force)
redraw(); redraw(force);
m_dirty = false; m_dirty = false;
} }
void add_str(WINDOW* win, StringView str)
{
waddnstr(win, str.begin(), (int)str.length());
}
void NCursesUI::draw_line(NCursesWin* window, const DisplayLine& line,
ColumnCount col_index, ColumnCount max_column,
const Face& default_face)
{
for (const DisplayAtom& atom : line)
{
set_face(window, atom.face, default_face);
StringView content = atom.content();
if (content.empty())
continue;
const auto remaining_columns = max_column - col_index;
if (content.back() == '\n' and
content.column_length() - 1 < remaining_columns)
{
add_str(window, content.substr(0, content.length()-1));
waddch(window, ' ');
}
else
{
content = content.substr(0_col, remaining_columns);
add_str(window, content);
col_index += content.column_length();
}
}
}
static const DisplayLine empty_line = { String(" "), {} }; static const DisplayLine empty_line = { String(" "), {} };
void NCursesUI::draw(const DisplayBuffer& display_buffer, void NCursesUI::draw(const DisplayBuffer& display_buffer,
const Face& default_face, const Face& default_face,
const Face& padding_face) const Face& padding_face)
{ {
wbkgdset(m_window, COLOR_PAIR(get_color_pair(default_face))); m_window.set_background_color(m_palette, default_face);
check_resize(); check_resize();
@ -402,20 +449,20 @@ void NCursesUI::draw(const DisplayBuffer& display_buffer,
LineCount line_index = line_offset; LineCount line_index = line_offset;
for (const DisplayLine& line : display_buffer.lines()) for (const DisplayLine& line : display_buffer.lines())
{ {
wmove(m_window, (int)line_index, 0); m_window.move_cursor(line_index);
wclrtoeol(m_window); m_window.clear_to_end_of_line();
draw_line(m_window, line, 0, dim.column, default_face); m_window.draw_line(m_palette, line, 0, dim.column, default_face);
++line_index; ++line_index;
} }
wbkgdset(m_window, COLOR_PAIR(get_color_pair(padding_face))); m_window.set_background_color(m_palette, padding_face);
set_face(m_window, padding_face, default_face); m_window.set_face(m_palette, padding_face, default_face);
while (line_index < dim.line + line_offset) while (line_index < dim.line + line_offset)
{ {
wmove(m_window, (int)line_index++, 0); m_window.move_cursor(line_index++);
wclrtoeol(m_window); m_window.clear_to_end_of_line();
waddch(m_window, '~'); m_window.add_str('~');
} }
m_dirty = true; m_dirty = true;
@ -425,13 +472,13 @@ void NCursesUI::draw_status(const DisplayLine& status_line,
const DisplayLine& mode_line, const DisplayLine& mode_line,
const Face& default_face) const Face& default_face)
{ {
const int status_line_pos = m_status_on_top ? 0 : (int)m_dimensions.line; const LineCount status_line_pos = m_status_on_top ? 0 : m_dimensions.line;
wmove(m_window, status_line_pos, 0); m_window.move_cursor(status_line_pos);
wbkgdset(m_window, COLOR_PAIR(get_color_pair(default_face))); m_window.set_background_color(m_palette, default_face);
wclrtoeol(m_window); m_window.clear_to_end_of_line();
draw_line(m_window, status_line, 0, m_dimensions.column, default_face); m_window.draw_line(m_palette, status_line, 0, m_dimensions.column, default_face);
const auto mode_len = mode_line.length(); const auto mode_len = mode_line.length();
m_status_len = status_line.length(); m_status_len = status_line.length();
@ -439,8 +486,8 @@ void NCursesUI::draw_status(const DisplayLine& status_line,
if (mode_len < remaining) if (mode_len < remaining)
{ {
ColumnCount col = m_dimensions.column - mode_len; ColumnCount col = m_dimensions.column - mode_len;
wmove(m_window, status_line_pos, (int)col); m_window.move_cursor({status_line_pos, col});
draw_line(m_window, mode_line, col, m_dimensions.column, default_face); m_window.draw_line(m_palette, mode_line, col, m_dimensions.column, default_face);
} }
else if (remaining > 2) else if (remaining > 2)
{ {
@ -450,8 +497,8 @@ void NCursesUI::draw_status(const DisplayLine& status_line,
kak_assert(trimmed_mode_line.length() == remaining - 1); kak_assert(trimmed_mode_line.length() == remaining - 1);
ColumnCount col = m_dimensions.column - remaining + 1; ColumnCount col = m_dimensions.column - remaining + 1;
wmove(m_window, status_line_pos, (int)col); m_window.move_cursor({status_line_pos, col});
draw_line(m_window, trimmed_mode_line, col, m_dimensions.column, default_face); m_window.draw_line(m_palette, trimmed_mode_line, col, m_dimensions.column, default_face);
} }
if (m_set_title) if (m_set_title)
@ -495,17 +542,17 @@ void NCursesUI::check_resize(bool force)
const bool info = (bool)m_info; const bool info = (bool)m_info;
const bool menu = (bool)m_menu; const bool menu = (bool)m_menu;
if (m_window) delwin(m_window); if (m_window) m_window.destroy();
if (info) m_info.destroy(); if (info) m_info.destroy();
if (menu) m_menu.destroy(); if (menu) m_menu.destroy();
resize_term(ws.ws_row, ws.ws_col); resize_term(ws.ws_row, ws.ws_col);
m_window = (NCursesWin*)newpad(ws.ws_row, ws.ws_col); m_window.create({0, 0}, {ws.ws_row, ws.ws_col});
kak_assert(m_window); kak_assert(m_window);
intrflush(m_window, false); intrflush(m_window.win, false);
keypad(m_window, not m_builtin_key_parser); keypad(m_window.win, not m_builtin_key_parser);
meta(m_window, true); meta(m_window.win, true);
m_dimensions = DisplayCoord{ws.ws_row-1, ws.ws_col}; m_dimensions = DisplayCoord{ws.ws_row-1, ws.ws_col};
@ -531,7 +578,7 @@ Optional<Key> NCursesUI::get_next_key()
{ {
set_signal_handler(SIGWINCH, SIG_DFL); set_signal_handler(SIGWINCH, SIG_DFL);
set_signal_handler(SIGCONT, SIG_DFL); set_signal_handler(SIGCONT, SIG_DFL);
m_window = nullptr; m_window.invalidate();
m_stdin_watcher.disable(); m_stdin_watcher.disable();
return {}; return {};
} }
@ -544,9 +591,9 @@ Optional<Key> NCursesUI::get_next_key()
return resize(dimensions()); return resize(dimensions());
} }
wtimeout(m_window, 0); m_window.set_blocking(false);
const int c = wgetch(m_window); const int c = m_window.get_char();
wtimeout(m_window, -1); m_window.set_blocking(true);
if (c == ERR) if (c == ERR)
return {}; return {};
@ -644,13 +691,13 @@ Optional<Key> NCursesUI::get_next_key()
ungetch(c); ungetch(c);
struct getch_iterator struct getch_iterator
{ {
getch_iterator(WINDOW* win) : window(win) {} getch_iterator(Window& win) : window(win) {}
int operator*() { return wgetch(window); } int operator*() { return window.get_char(); }
getch_iterator& operator++() { return *this; } getch_iterator& operator++() { return *this; }
getch_iterator& operator++(int) { return *this; } getch_iterator& operator++(int) { return *this; }
bool operator== (const getch_iterator&) const { return false; } bool operator== (const getch_iterator&) const { return false; }
WINDOW* window; Window& window;
}; };
return Key{utf8::codepoint(getch_iterator{m_window}, return Key{utf8::codepoint(getch_iterator{m_window},
getch_iterator{m_window})}; getch_iterator{m_window})};
@ -660,12 +707,12 @@ Optional<Key> NCursesUI::get_next_key()
constexpr auto direction = make_array({Key::Up, Key::Down, Key::Right, Key::Left, Key::Home, Key::End}); constexpr auto direction = make_array({Key::Up, Key::Down, Key::Right, Key::Left, Key::Home, Key::End});
auto parse_csi = [this, &direction]() -> Optional<Key> { auto parse_csi = [this, &direction]() -> Optional<Key> {
const Codepoint c1 = wgetch(m_window); const Codepoint c1 = m_window.get_char();
if (c1 >= 'A' and c1 <= 'F') if (c1 >= 'A' and c1 <= 'F')
return Key{direction[c1 - 'A']}; return Key{direction[c1 - 'A']};
if (c1 == '1') if (c1 == '1')
{ {
const Codepoint c2 = wgetch(m_window); const Codepoint c2 = m_window.get_char();
if (c2 >= 'A' and c2 <= 'F') if (c2 >= 'A' and c2 <= 'F')
return Key{direction[c2 - 'A']}; return Key{direction[c2 - 'A']};
if (c2 != ';') if (c2 != ';')
@ -673,13 +720,13 @@ Optional<Key> NCursesUI::get_next_key()
ungetch(c2); ungetch(c1); ungetch(c2); ungetch(c1);
return {}; return {};
} }
const Codepoint c3 = wgetch(m_window); const Codepoint c3 = m_window.get_char();
if (c3 < '2' or c3 > '8') if (c3 < '2' or c3 > '8')
{ {
ungetch(c3); ungetch(c2); ungetch(c1); ungetch(c3); ungetch(c2); ungetch(c1);
return {}; return {};
} }
const Codepoint c4 = wgetch(m_window); const Codepoint c4 = m_window.get_char();
if (c4 < 'A' or c4 > 'F') if (c4 < 'A' or c4 > 'F')
{ {
ungetch(c4); ungetch(c3); ungetch(c2); ungetch(c1); ungetch(c4); ungetch(c3); ungetch(c2); ungetch(c1);
@ -707,14 +754,14 @@ Optional<Key> NCursesUI::get_next_key()
if (c == 27) if (c == 27)
{ {
wtimeout(m_window, 0); m_window.set_blocking(false);
const int new_c = wgetch(m_window); const int new_c = m_window.get_char();
if (new_c == '[') // potential CSI if (new_c == '[') // potential CSI
{ {
if (auto key = parse_csi()) if (auto key = parse_csi())
return key; return key;
} }
wtimeout(m_window, -1); m_window.set_blocking(true);
if (auto key = parse_key(new_c)) if (auto key = parse_key(new_c))
return alt(*key); return alt(*key);
@ -738,9 +785,8 @@ void NCursesUI::draw_menu()
if (not m_menu) if (not m_menu)
return; return;
const auto menu_bg = get_color_pair(m_menu.bg); m_menu.set_face(m_palette, m_menu.bg, {});
wattron(m_menu.win, COLOR_PAIR(menu_bg)); m_menu.set_background_color(m_palette, m_menu.bg);
wbkgdset(m_menu.win, COLOR_PAIR(menu_bg));
const int item_count = (int)m_menu.items.size(); const int item_count = (int)m_menu.items.size();
if (m_menu.columns == 0) if (m_menu.columns == 0)
@ -749,31 +795,31 @@ void NCursesUI::draw_menu()
kak_assert(m_menu.size.line == 1); kak_assert(m_menu.size.line == 1);
ColumnCount pos = 0; ColumnCount pos = 0;
wmove(m_menu.win, 0, 0); m_menu.move_cursor({0, 0});
add_str(m_menu.win, m_menu.first_item > 0 ? "< " : " "); m_menu.add_str(m_menu.first_item > 0 ? "< " : " ");
int i = m_menu.first_item; int i = m_menu.first_item;
for (; i < item_count and pos < win_width; ++i) for (; i < item_count and pos < win_width; ++i)
{ {
const DisplayLine& item = m_menu.items[i]; const DisplayLine& item = m_menu.items[i];
const ColumnCount item_width = item.length(); const ColumnCount item_width = item.length();
draw_line(m_menu.win, item, 0, win_width - pos, m_menu.draw_line(m_palette, item, 0, win_width - pos,
i == m_menu.selected_item ? m_menu.fg : m_menu.bg); i == m_menu.selected_item ? m_menu.fg : m_menu.bg);
if (item_width > win_width - pos) if (item_width > win_width - pos)
add_str(m_menu.win, ""); m_menu.add_str("");
else else
{ {
wattron(m_menu.win, COLOR_PAIR(menu_bg)); m_menu.set_face(m_palette, m_menu.bg, {});
add_str(m_menu.win, String{" "}); m_menu.add_str(String{" "});
} }
pos += item_width + 1; pos += item_width + 1;
} }
set_face(m_menu.win, m_menu.bg, m_menu.bg); m_menu.set_face(m_palette, m_menu.bg, {});
if (pos <= win_width) if (pos <= win_width)
add_str(m_menu.win, String{' ', win_width - pos + 1}); m_menu.add_str(String{' ', win_width - pos + 1});
add_str(m_menu.win, i == item_count ? " " : ">"); m_menu.add_str(i == item_count ? " " : ">");
m_dirty = true; m_dirty = true;
return; return;
} }
@ -794,7 +840,7 @@ void NCursesUI::draw_menu()
for (auto line = 0_line; line < win_height; ++line) for (auto line = 0_line; line < win_height; ++line)
{ {
wmove(m_menu.win, (int)line, 0); m_menu.move_cursor(line);
for (int col = 0; col < m_menu.columns; ++col) for (int col = 0; col < m_menu.columns; ++col)
{ {
int item_idx = (first_col + col) * (int)m_menu.size.line + (int)line; int item_idx = (first_col + col) * (int)m_menu.size.line + (int)line;
@ -802,17 +848,17 @@ void NCursesUI::draw_menu()
continue; continue;
const DisplayLine& item = m_menu.items[item_idx]; const DisplayLine& item = m_menu.items[item_idx];
draw_line(m_menu.win, item, 0, column_width, m_menu.draw_line(m_palette, item, 0, column_width,
item_idx == m_menu.selected_item ? m_menu.fg : m_menu.bg); item_idx == m_menu.selected_item ? m_menu.fg : m_menu.bg);
const ColumnCount pad = column_width - item.length(); const ColumnCount pad = column_width - item.length();
add_str(m_menu.win, String{' ', pad}); m_menu.add_str(String{' ', pad});
} }
const bool is_mark = line >= mark_line and const bool is_mark = line >= mark_line and
line < mark_line + mark_height; line < mark_line + mark_height;
wclrtoeol(m_menu.win); m_menu.clear_to_end_of_line();
wmove(m_menu.win, (int)line, (int)m_menu.size.column - 1); m_menu.move_cursor({line, m_menu.size.column - 1});
wattron(m_menu.win, COLOR_PAIR(menu_bg)); m_menu.set_face(m_palette, m_menu.bg, {});
add_str(m_menu.win, is_mark ? "" : ""); m_menu.add_str(is_mark ? "" : "");
} }
m_dirty = true; m_dirty = true;
} }
@ -835,7 +881,7 @@ void NCursesUI::menu_show(ConstArrayView<DisplayLine> items,
{ {
if (m_menu) if (m_menu)
{ {
mark_dirty(m_menu); m_window.mark_dirty(m_menu.pos.line, m_menu.size.line);
m_menu.destroy(); m_menu.destroy();
m_dirty = true; m_dirty = true;
} }
@ -949,7 +995,7 @@ void NCursesUI::menu_hide()
return; return;
m_menu.items.clear(); m_menu.items.clear();
mark_dirty(m_menu); m_window.mark_dirty(m_menu.pos.line, m_menu.size.line);
m_menu.destroy(); m_menu.destroy();
m_dirty = true; m_dirty = true;
@ -1137,11 +1183,12 @@ void NCursesUI::info_show(StringView title, StringView content,
m_info.create(anchor, info_box.size); m_info.create(anchor, info_box.size);
wbkgd(m_info.win, COLOR_PAIR(get_color_pair(face))); m_info.set_background_color(m_palette, face);
for (auto line = 0_line; line < info_box.size.line; ++line) for (auto line = 0_line; line < info_box.size.line; ++line)
{ {
wmove(m_info.win, (int)line, 0); m_info.move_cursor(line);
add_str(m_info.win, info_box.contents[(int)line]); m_info.clear_to_end_of_line();
m_info.add_str(info_box.contents[(int)line]);
} }
m_dirty = true; m_dirty = true;
} }
@ -1150,16 +1197,11 @@ void NCursesUI::info_hide()
{ {
if (not m_info) if (not m_info)
return; return;
mark_dirty(m_info); m_window.mark_dirty(m_info.pos.line, m_info.size.line);
m_info.destroy(); m_info.destroy();
m_dirty = true; m_dirty = true;
} }
void NCursesUI::mark_dirty(const Window& win)
{
wredrawln(m_window, (int)win.pos.line, (int)win.size.line);
}
void NCursesUI::set_on_key(OnKeyCallback callback) void NCursesUI::set_on_key(OnKeyCallback callback)
{ {
m_on_key = std::move(callback); m_on_key = std::move(callback);
@ -1246,20 +1288,13 @@ void NCursesUI::set_ui_options(const Options& options)
{ {
auto it = options.find("ncurses_change_colors"_sv); auto it = options.find("ncurses_change_colors"_sv);
auto value = it == options.end() or if (m_palette.set_change_colors(it == options.end() or
(it->value == "yes" or it->value == "true"); (it->value == "yes" or it->value == "true")))
if (can_change_color() and m_change_colors != value)
{ {
fputs("\033]104\007", stdout); // try to reset palette m_window.m_active_pair = -1;
fflush(stdout); m_menu.m_active_pair = -1;
m_colorpairs.clear(); m_info.m_active_pair = -1;
m_colors = default_colors;
m_next_color = 16;
m_next_pair = 1;
m_active_pair = -1;
} }
m_change_colors = value;
} }
{ {
@ -1286,7 +1321,7 @@ void NCursesUI::set_ui_options(const Options& options)
m_builtin_key_parser = builtin_key_parser_it != options.end() and m_builtin_key_parser = builtin_key_parser_it != options.end() and
(builtin_key_parser_it->value == "yes" or (builtin_key_parser_it->value == "yes" or
builtin_key_parser_it->value == "true"); builtin_key_parser_it->value == "true");
keypad(m_window, not m_builtin_key_parser); keypad(m_window.win, not m_builtin_key_parser);
} }
} }

View File

@ -24,7 +24,7 @@ public:
NCursesUI(const NCursesUI&) = delete; NCursesUI(const NCursesUI&) = delete;
NCursesUI& operator=(const NCursesUI&) = delete; NCursesUI& operator=(const NCursesUI&) = delete;
bool is_ok() const override { return m_window != nullptr; } bool is_ok() const override { return (bool)m_window; }
void draw(const DisplayBuffer& display_buffer, void draw(const DisplayBuffer& display_buffer,
const Face& default_face, const Face& default_face,
@ -63,39 +63,61 @@ public:
private: private:
void check_resize(bool force = false); void check_resize(bool force = false);
void redraw(); void redraw(bool force);
int get_color(Color color);
int get_color_pair(const Face& face);
void set_face(NCursesWin* window, Face face, const Face& default_face);
void draw_line(NCursesWin* window, const DisplayLine& line,
ColumnCount col_index, ColumnCount max_column,
const Face& default_face);
Optional<Key> get_next_key(); Optional<Key> get_next_key();
NCursesWin* m_window = nullptr; struct Palette
{
DisplayCoord m_dimensions; private:
static const std::initializer_list<HashMap<Kakoune::Color, int>::Item> default_colors;
using ColorPair = std::pair<Color, Color>; using ColorPair = std::pair<Color, Color>;
HashMap<Color, int, MemoryDomain::Faces> m_colors; HashMap<Color, int, MemoryDomain::Faces> m_colors = default_colors;
HashMap<ColorPair, int, MemoryDomain::Faces> m_colorpairs; HashMap<ColorPair, int, MemoryDomain::Faces> m_colorpairs;
int m_next_color = 16; int m_next_color = 16;
int m_next_pair = 1; int m_next_pair = 1;
int m_active_pair = -1; bool m_change_colors = true;
int get_color(Color color);
public:
int get_color_pair(const Face& face);
bool set_change_colors(bool change_colors);
};
Palette m_palette;
struct Window : Rect struct Window : Rect
{ {
void create(const DisplayCoord& pos, const DisplayCoord& size); void create(const DisplayCoord& pos, const DisplayCoord& size);
void destroy(); void destroy();
void refresh(); void invalidate();
void refresh(bool force);
void move_cursor(DisplayCoord coord);
void mark_dirty(LineCount pos, LineCount count);
void draw_line(Palette& palette,
const DisplayLine& line,
ColumnCount col_index,
ColumnCount max_column,
const Face& default_face);
void add_str(StringView str);
void clear_to_end_of_line();
void set_face(Palette& palette, Face face, const Face& default_face);
void set_background_color(Palette& palette, Face face);
int get_char();
void set_blocking(bool blocking);
explicit operator bool() const { return win; } explicit operator bool() const { return win; }
NCursesWin* win = nullptr; NCursesWin* win = nullptr;
int m_active_pair = -1;
}; };
Window m_window;
DisplayCoord m_dimensions;
void mark_dirty(const Window& win); void mark_dirty(const Window& win);
struct Menu : Window struct Menu : Window
@ -146,7 +168,6 @@ private:
int m_shift_function_key = default_shift_function_key; int m_shift_function_key = default_shift_function_key;
bool m_set_title = true; bool m_set_title = true;
bool m_change_colors = true;
bool m_builtin_key_parser = false; bool m_builtin_key_parser = false;
bool m_dirty = false; bool m_dirty = false;