feat(tui): redesign chrome with unified key hints and help overlay
Introduce one key-hint grammar across every view: bold key, dim label, priority-ordered hints that drop from the end instead of ellipsizing. The month footer becomes a single split status bar with a friendly selected date, the '?' mega-line becomes a real keyboard shortcut panel, and the editor, agenda, search, picker, detail, and delete frames share the same chips. Notifications carry check/warning marks. Adds a live PTY smoke test covering view interaction and terminal state restoration on q and Ctrl-C exits.
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <initializer_list>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
@@ -253,6 +254,49 @@ std::string horizontal_line(const int width, const std::string_view left,
|
||||
return result;
|
||||
}
|
||||
|
||||
struct KeyHint {
|
||||
std::string_view key;
|
||||
std::string_view label;
|
||||
};
|
||||
|
||||
int hints_width(const KeyHint* const hints, const std::size_t count)
|
||||
{
|
||||
int width = 0;
|
||||
for (std::size_t index = 0; index < count; ++index) {
|
||||
if (index != 0) width += 3;
|
||||
width += static_cast<int>(hints[index].key.size() + 1 + hints[index].label.size());
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
// Key bold, label dim, three spaces between hints; the plain layout is
|
||||
// identical with ANSI disabled. Hints drop from the end to fit.
|
||||
std::string render_hints(const std::initializer_list<KeyHint> hints, const int width,
|
||||
const bool ansi)
|
||||
{
|
||||
std::size_t count = hints.size();
|
||||
while (count > 0 && hints_width(hints.begin(), count) > width) --count;
|
||||
std::string result;
|
||||
for (std::size_t index = 0; index < count; ++index) {
|
||||
if (index != 0) result += " ";
|
||||
result += style(std::string{hints.begin()[index].key}, "1", ansi);
|
||||
result += ' ';
|
||||
result += style(std::string{hints.begin()[index].label}, "2", ansi);
|
||||
}
|
||||
const int used = hints_width(hints.begin(), count);
|
||||
if (used < width) result.append(static_cast<std::size_t>(width - used), ' ');
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string hints_interior_line(const std::initializer_list<KeyHint> hints, const int width,
|
||||
const bool ansi)
|
||||
{
|
||||
if (width <= 0) return {};
|
||||
if (width == 1) return "│";
|
||||
if (width <= 3) return interior_line({}, width);
|
||||
return "│ " + render_hints(hints, width - 4, ansi) + "│";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EventEditor::EventEditor(const Date selected_day)
|
||||
@@ -440,8 +484,9 @@ std::string EventEditor::render(const int requested_width, const int requested_h
|
||||
: editing_ ? " NOCAL · EDIT APPOINTMENT"
|
||||
: " NOCAL · NEW APPOINTMENT";
|
||||
lines.push_back(styled_interior_line(heading, width, "1", ansi));
|
||||
lines.push_back(styled_interior_line(" Tab/↓ next Shift-Tab/↑ back Space toggle",
|
||||
width, "2", ansi));
|
||||
lines.push_back(hints_interior_line(
|
||||
{{"Tab", "next field"}, {"Shift-Tab", "previous field"}, {"Space", "toggle"}},
|
||||
width, ansi));
|
||||
lines.push_back(styled_interior_line(
|
||||
original_.time_basis == TimeBasis::zoned && !original_.time_zone.empty()
|
||||
? " Times shown in source zone: " + original_.time_zone
|
||||
@@ -470,8 +515,10 @@ std::string EventEditor::render(const int requested_width, const int requested_h
|
||||
}
|
||||
|
||||
lines.push_back(rule);
|
||||
lines.push_back(interior_line(error_.empty() ? " Ready" : " ! " + error_, width));
|
||||
lines.push_back(styled_interior_line(" Ctrl-S save Esc cancel", width, "1", ansi));
|
||||
lines.push_back(error_.empty()
|
||||
? styled_interior_line(" Ready", width, "2", ansi)
|
||||
: styled_interior_line(" ! " + error_, width, "1;31", ansi));
|
||||
lines.push_back(hints_interior_line({{"Ctrl-S", "save"}, {"Esc", "cancel"}}, width, ansi));
|
||||
lines.push_back(bottom);
|
||||
|
||||
// Keep the active field visible on short terminals by selecting a window
|
||||
|
||||
Reference in New Issue
Block a user