Rework ncurses info display, crop content when overlflowing
Optmize the code to avoid allocating like crazy, unify various info style rendering, crop content and display markers that there is more text remaining. Fixes #2257
This commit is contained in:
parent
19e1be8e0d
commit
34f48cc851
|
@ -423,6 +423,8 @@ void Client::info_show(DisplayLine title, DisplayLineList content, BufferCoord a
|
||||||
|
|
||||||
void Client::info_show(StringView title, StringView content, BufferCoord anchor, InfoStyle style)
|
void Client::info_show(StringView title, StringView content, BufferCoord anchor, InfoStyle style)
|
||||||
{
|
{
|
||||||
|
if (not content.empty() and content.back() == '\n')
|
||||||
|
content = content.substr(0, content.length() - 1);
|
||||||
info_show({title.str(), Face{}},
|
info_show({title.str(), Face{}},
|
||||||
content | split<StringView>('\n')
|
content | split<StringView>('\n')
|
||||||
| transform([](StringView s) { return DisplayLine{s.str(), Face{}}; })
|
| transform([](StringView s) { return DisplayLine{s.str(), Face{}}; })
|
||||||
|
|
|
@ -179,7 +179,7 @@ ColumnCount DisplayLine::length() const
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayLine::trim(ColumnCount first_col, ColumnCount col_count)
|
bool DisplayLine::trim(ColumnCount first_col, ColumnCount col_count)
|
||||||
{
|
{
|
||||||
for (auto it = begin(); first_col > 0 and it != end(); )
|
for (auto it = begin(); first_col > 0 and it != end(); )
|
||||||
{
|
{
|
||||||
|
@ -199,11 +199,13 @@ void DisplayLine::trim(ColumnCount first_col, ColumnCount col_count)
|
||||||
for (; it != end() and col_count > 0; ++it)
|
for (; it != end() and col_count > 0; ++it)
|
||||||
col_count -= it->length();
|
col_count -= it->length();
|
||||||
|
|
||||||
|
bool did_trim = it != end() || col_count < 0;
|
||||||
if (col_count < 0)
|
if (col_count < 0)
|
||||||
(it-1)->trim_end(-col_count);
|
(it-1)->trim_end(-col_count);
|
||||||
m_atoms.erase(it, end());
|
m_atoms.erase(it, end());
|
||||||
|
|
||||||
compute_range();
|
compute_range();
|
||||||
|
return did_trim;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BufferRange init_range{ {INT_MAX, INT_MAX}, {INT_MIN, INT_MIN} };
|
const BufferRange init_range{ {INT_MAX, INT_MAX}, {INT_MIN, INT_MIN} };
|
||||||
|
|
|
@ -125,7 +125,7 @@ public:
|
||||||
|
|
||||||
// remove first_col from the begining of the line, and make sure
|
// remove first_col from the begining of the line, and make sure
|
||||||
// the line is less that col_count character
|
// the line is less that col_count character
|
||||||
void trim(ColumnCount first_col, ColumnCount col_count);
|
bool trim(ColumnCount first_col, ColumnCount col_count);
|
||||||
|
|
||||||
// Merge together consecutive atoms sharing the same display attributes
|
// Merge together consecutive atoms sharing the same display attributes
|
||||||
void optimize();
|
void optimize();
|
||||||
|
|
|
@ -1029,97 +1029,6 @@ static DisplayCoord compute_pos(DisplayCoord anchor, DisplayCoord size,
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InfoBox
|
|
||||||
{
|
|
||||||
DisplayCoord size;
|
|
||||||
DisplayLineList contents;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
void append_atoms(DisplayLine& line, Args&&... args)
|
|
||||||
{
|
|
||||||
auto append = overload(
|
|
||||||
[](DisplayLine& line, String str) {
|
|
||||||
line.push_back(DisplayAtom{std::move(str)});
|
|
||||||
},
|
|
||||||
[](DisplayLine& line, const DisplayLine& atoms) {
|
|
||||||
for (auto& atom : atoms)
|
|
||||||
line.push_back(atom);
|
|
||||||
});
|
|
||||||
|
|
||||||
(append(line, args), ...);
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoBox make_info_box(const DisplayLine& title, const DisplayLineList& content,
|
|
||||||
ColumnCount max_width, ConstArrayView<StringView> assistant)
|
|
||||||
{
|
|
||||||
DisplayCoord assistant_size;
|
|
||||||
if (not assistant.empty())
|
|
||||||
assistant_size = { (int)assistant.size(), assistant[0].column_length() };
|
|
||||||
|
|
||||||
InfoBox result{};
|
|
||||||
|
|
||||||
const ColumnCount max_bubble_width = max_width - assistant_size.column - 6;
|
|
||||||
if (max_bubble_width < 4)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
ColumnCount bubble_width = title.length() + 2;
|
|
||||||
for (auto& line : content)
|
|
||||||
bubble_width = max(bubble_width, line.length());
|
|
||||||
|
|
||||||
auto line_count = max(assistant_size.line-1, LineCount{(int)content.size()} + 2);
|
|
||||||
result.size = DisplayCoord{line_count, bubble_width + assistant_size.column + 4};
|
|
||||||
const auto assistant_top_margin = (line_count - assistant_size.line+1) / 2;
|
|
||||||
for (LineCount i = 0; i < line_count; ++i)
|
|
||||||
{
|
|
||||||
constexpr Codepoint dash{L'─'};
|
|
||||||
DisplayLine line;
|
|
||||||
if (not assistant.empty())
|
|
||||||
{
|
|
||||||
StringView assistant_line = (i >= assistant_top_margin) ?
|
|
||||||
assistant[(int)min(i - assistant_top_margin, assistant_size.line-1)]
|
|
||||||
: assistant[(int)assistant_size.line-1];
|
|
||||||
|
|
||||||
append_atoms(line, assistant_line.str());
|
|
||||||
}
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
if (title.atoms().empty())
|
|
||||||
append_atoms(line, "╭─" + String{dash, bubble_width} + "─╮");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto dash_count = bubble_width - title.length() - 2;
|
|
||||||
String left{dash, dash_count / 2};
|
|
||||||
String right{dash, dash_count - dash_count / 2};
|
|
||||||
append_atoms(line, "╭─" + left + "┤", title, "├" + right +"─╮");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (i < content.size() + 1)
|
|
||||||
{
|
|
||||||
auto& info_line = content[(int)i - 1];
|
|
||||||
const ColumnCount padding = bubble_width - info_line.length();
|
|
||||||
append_atoms(line, "│ ", info_line, String{' ', padding} + " │");
|
|
||||||
}
|
|
||||||
else if (i == content.size() + 1)
|
|
||||||
append_atoms(line, "╰─" + String(dash, bubble_width) + "─╯");
|
|
||||||
|
|
||||||
result.contents.push_back(std::move(line));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoBox make_simple_info_box(const DisplayLineList& content, ColumnCount max_width)
|
|
||||||
{
|
|
||||||
InfoBox info_box{};
|
|
||||||
for (auto& line : content)
|
|
||||||
{
|
|
||||||
++info_box.size.line;
|
|
||||||
info_box.size.column = std::max(line.length(), info_box.size.column);
|
|
||||||
info_box.contents.push_back(line);
|
|
||||||
}
|
|
||||||
return info_box;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NCursesUI::info_show(const DisplayLine& title, const DisplayLineList& content,
|
void NCursesUI::info_show(const DisplayLine& title, const DisplayLineList& content,
|
||||||
DisplayCoord anchor, Face face, InfoStyle style)
|
DisplayCoord anchor, Face face, InfoStyle style)
|
||||||
{
|
{
|
||||||
|
@ -1131,60 +1040,104 @@ void NCursesUI::info_show(const DisplayLine& title, const DisplayLineList& conte
|
||||||
m_info.face = face;
|
m_info.face = face;
|
||||||
m_info.style = style;
|
m_info.style = style;
|
||||||
|
|
||||||
|
DisplayCoord max_size = m_dimensions;
|
||||||
|
if (style == InfoStyle::MenuDoc)
|
||||||
|
max_size.column = std::max(m_dimensions.column - (m_menu.pos.column + m_menu.size.column),
|
||||||
|
m_menu.pos.column);
|
||||||
|
else if (style != InfoStyle::Modal)
|
||||||
|
max_size.line -= m_menu.size.line;
|
||||||
|
|
||||||
|
const bool framed = style == InfoStyle::Prompt or style == InfoStyle::Modal;
|
||||||
|
const bool assisted = style == InfoStyle::Prompt and m_assistant.size() != 0;
|
||||||
|
DisplayCoord size{(int)content.size(), accumulate(content, 0_col, [](ColumnCount c, const DisplayLine& l) {
|
||||||
|
return std::max(c, l.length());
|
||||||
|
})};
|
||||||
|
if (framed)
|
||||||
|
size += {2, 4};
|
||||||
|
if (assisted)
|
||||||
|
size = {std::max(LineCount{(int)m_assistant.size()-1}, size.line), size.column + m_assistant[0].column_length()};
|
||||||
|
|
||||||
|
size = {std::min(max_size.line, size.line), std::min(max_size.column, size.column)};
|
||||||
|
|
||||||
|
const auto content_width = size.column - (framed ? 4 : 2) - (assisted ? m_assistant[0].column_length() : 0);
|
||||||
|
if (content_width < 4 or (framed and size.line < 3) or size.line <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
const Rect rect = {content_line_offset(), m_dimensions};
|
const Rect rect = {content_line_offset(), m_dimensions};
|
||||||
InfoBox info_box;
|
|
||||||
if (style == InfoStyle::Prompt)
|
if (style == InfoStyle::Prompt)
|
||||||
{
|
{
|
||||||
info_box = make_info_box(m_info.title, m_info.content, m_dimensions.column, m_assistant);
|
|
||||||
anchor = DisplayCoord{m_status_on_top ? 0 : m_dimensions.line, m_dimensions.column-1};
|
anchor = DisplayCoord{m_status_on_top ? 0 : m_dimensions.line, m_dimensions.column-1};
|
||||||
anchor = compute_pos(anchor, info_box.size, rect, m_menu, style == InfoStyle::InlineAbove);
|
anchor = compute_pos(anchor, size, rect, m_menu, style == InfoStyle::InlineAbove);
|
||||||
}
|
}
|
||||||
else if (style == InfoStyle::Modal)
|
else if (style == InfoStyle::Modal)
|
||||||
{
|
{
|
||||||
info_box = make_info_box(m_info.title, m_info.content, m_dimensions.column, {});
|
|
||||||
auto half = [](const DisplayCoord& c) { return DisplayCoord{c.line / 2, c.column / 2}; };
|
auto half = [](const DisplayCoord& c) { return DisplayCoord{c.line / 2, c.column / 2}; };
|
||||||
anchor = rect.pos + half(rect.size) - half(info_box.size);
|
anchor = rect.pos + half(rect.size) - half(size);
|
||||||
}
|
}
|
||||||
else if (style == InfoStyle::MenuDoc)
|
else if (style == InfoStyle::MenuDoc)
|
||||||
{
|
{
|
||||||
if (not m_menu)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto right_max_width = m_dimensions.column - (m_menu.pos.column + m_menu.size.column);
|
const auto right_max_width = m_dimensions.column - (m_menu.pos.column + m_menu.size.column);
|
||||||
const auto left_max_width = m_menu.pos.column;
|
const auto left_max_width = m_menu.pos.column;
|
||||||
const auto max_width = std::max(right_max_width, left_max_width);
|
|
||||||
if (max_width < 4)
|
|
||||||
return;
|
|
||||||
|
|
||||||
info_box = make_simple_info_box(m_info.content, max_width);
|
|
||||||
anchor.line = m_menu.pos.line;
|
anchor.line = m_menu.pos.line;
|
||||||
if (info_box.size.column <= right_max_width or right_max_width >= left_max_width)
|
if (size.column <= right_max_width or right_max_width >= left_max_width)
|
||||||
anchor.column = m_menu.pos.column + m_menu.size.column;
|
anchor.column = m_menu.pos.column + m_menu.size.column;
|
||||||
else
|
else
|
||||||
anchor.column = m_menu.pos.column - info_box.size.column;
|
anchor.column = m_menu.pos.column - size.column;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const ColumnCount max_width = m_dimensions.column - anchor.column;
|
anchor = compute_pos(anchor, size, rect, m_menu, style == InfoStyle::InlineAbove);
|
||||||
if (max_width < 4)
|
|
||||||
return;
|
|
||||||
|
|
||||||
info_box = make_simple_info_box(m_info.content, max_width);
|
|
||||||
anchor = compute_pos(anchor, info_box.size, rect, m_menu, style == InfoStyle::InlineAbove);
|
|
||||||
|
|
||||||
anchor.line += content_line_offset();
|
anchor.line += content_line_offset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The info box does not fit
|
m_info.create(anchor, size);
|
||||||
if (anchor < rect.pos or anchor + info_box.size > rect.pos + rect.size)
|
auto draw_atoms = [&](auto&&... args) {
|
||||||
return;
|
auto draw = overload(
|
||||||
|
[&](String str) { m_info.draw(m_palette, DisplayAtom{std::move(str)}, face); },
|
||||||
|
[&](const DisplayLine& atoms) { m_info.draw(m_palette, atoms.atoms(), face); });
|
||||||
|
|
||||||
m_info.create(anchor, info_box.size);
|
(draw(args), ...);
|
||||||
|
};
|
||||||
|
|
||||||
for (auto line = 0_line; line < info_box.size.line; ++line)
|
for (auto line = 0_line; line < size.line; ++line)
|
||||||
{
|
{
|
||||||
|
constexpr Codepoint dash{L'─'};
|
||||||
|
constexpr Codepoint dotted_dash{L'┄'};
|
||||||
m_info.move_cursor(line);
|
m_info.move_cursor(line);
|
||||||
m_info.draw(m_palette, info_box.contents[(int)line].atoms(), face);
|
if (assisted)
|
||||||
|
{
|
||||||
|
const auto assistant_top_margin = (size.line - m_assistant.size()+1) / 2;
|
||||||
|
StringView assistant_line = (line >= assistant_top_margin) ?
|
||||||
|
m_assistant[(int)min(line - assistant_top_margin, LineCount{(int)m_assistant.size()}-1)]
|
||||||
|
: m_assistant[(int)m_assistant.size()-1];
|
||||||
|
|
||||||
|
draw_atoms(assistant_line.str());
|
||||||
|
}
|
||||||
|
if (not framed)
|
||||||
|
draw_atoms(content[(int)line]);
|
||||||
|
else if (line == 0)
|
||||||
|
{
|
||||||
|
if (title.atoms().empty())
|
||||||
|
draw_atoms("╭─" + String{dash, content_width} + "─╮");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto trimmed_title = title;
|
||||||
|
trimmed_title.trim(0, content_width - 2);
|
||||||
|
auto dash_count = content_width - trimmed_title.length() - 2;
|
||||||
|
String left{dash, dash_count / 2};
|
||||||
|
String right{dash, dash_count - dash_count / 2};
|
||||||
|
draw_atoms("╭─" + left + "┤", trimmed_title, "├" + right +"─╮");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (line < size.line - 1 and line <= content.size())
|
||||||
|
{
|
||||||
|
auto info_line = content[(int)line - 1];
|
||||||
|
const bool trimmed = info_line.trim(0, content_width);
|
||||||
|
const ColumnCount padding = content_width - info_line.length();
|
||||||
|
draw_atoms("│ ", info_line, String{' ', padding} + (trimmed ? "…│" : " │"));
|
||||||
|
}
|
||||||
|
else if (line == std::min<LineCount>((int)content.size() + 1, size.line - 1))
|
||||||
|
draw_atoms("╰─" + String(line > content.size() ? dash : dotted_dash, content_width) + "─╯");
|
||||||
}
|
}
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user