mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-05 05:22:38 +08:00
[ui] improve CLI connection status and shortcut layout
This commit is contained in:
@@ -19,6 +19,7 @@ struct Viewport {
|
||||
double zoom = 1, center_x = .5, center_y = .5;
|
||||
int pixel_width = 0, pixel_height = 0;
|
||||
bool native = false, pixel_mode = false;
|
||||
int row_offset = 0; // Terminal rows reserved above the video area.
|
||||
};
|
||||
struct Rect {
|
||||
int x, y, width, height;
|
||||
@@ -51,6 +52,12 @@ inline constexpr char kKittyQuery[] =
|
||||
"\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\";
|
||||
inline constexpr char kKittyDelete[] = "\033_Ga=d,d=I,i=31,q=2\033\\";
|
||||
bool KittyReply(const std::string &response);
|
||||
struct StatusBar {
|
||||
std::string progress, compact_progress, error, view, network;
|
||||
bool control = false, command_mode = false;
|
||||
};
|
||||
// Three padded ASCII rows. Shortcuts are compacted as whole groups to fit.
|
||||
std::vector<std::string> StatusLines(const StatusBar &, int columns);
|
||||
// Incremental parser: protocol replies and unknown escapes are consumed
|
||||
// locally. Key taps always release immediately; paste uses the UTF-8 text
|
||||
// protocol. In command_mode, control records the mode to restore while the
|
||||
|
||||
@@ -26,6 +26,7 @@ static uint32_t Pixel(const DecodedFrame &f, int x, int y) {
|
||||
}
|
||||
static void Validate(const DecodedFrame &f, const Viewport &v) {
|
||||
if (v.columns < 1 || v.rows < 1 || v.columns > 240 || v.rows > 100 ||
|
||||
v.row_offset < 0 || v.row_offset > 100 ||
|
||||
!std::isfinite(v.cell_aspect) || v.cell_aspect < .2 ||
|
||||
v.cell_aspect > 2 || !std::isfinite(v.zoom) || v.zoom < 1 ||
|
||||
v.zoom > 32 || !std::isfinite(v.center_x) || !std::isfinite(v.center_y) ||
|
||||
@@ -179,6 +180,7 @@ Grid Convert(const DecodedFrame &f, Viewport v) {
|
||||
}
|
||||
std::optional<std::pair<float, float>> MapPointer(const Grid &g, int x, int y) {
|
||||
x *= g.samples_x;
|
||||
y -= g.viewport.row_offset;
|
||||
y *= g.samples_y;
|
||||
const auto &r = g.content;
|
||||
if (!r.width || !r.height || !g.source_width || !g.source_height || x < r.x ||
|
||||
@@ -254,6 +256,7 @@ std::string Difference(const Grid &g, const std::optional<Grid> &previous) {
|
||||
const bool same = previous &&
|
||||
previous->viewport.columns == g.viewport.columns &&
|
||||
previous->viewport.rows == g.viewport.rows &&
|
||||
previous->viewport.row_offset == g.viewport.row_offset &&
|
||||
previous->viewport.colors == g.viewport.colors &&
|
||||
previous->viewport.utf8 == g.viewport.utf8;
|
||||
for (int y = 0; y < g.viewport.rows; ++y) {
|
||||
@@ -261,7 +264,8 @@ std::string Difference(const Grid &g, const std::optional<Grid> &previous) {
|
||||
if (!same ||
|
||||
!std::equal(start, start + g.viewport.columns,
|
||||
previous->cells.begin() + size_t(y) * g.viewport.columns))
|
||||
out += "\033[" + std::to_string(y + 1) + ";1H" + Line(g, y);
|
||||
out += "\033[" + std::to_string(g.viewport.row_offset + y + 1) +
|
||||
";1H" + Line(g, y);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -323,7 +327,7 @@ std::string Kitty(const DecodedFrame &f, const Viewport &requested) {
|
||||
w = std::max(2, w * 3 / 4);
|
||||
h = std::max(2, h * 3 / 4);
|
||||
}
|
||||
std::string out = "\033[H";
|
||||
std::string out = "\033[" + std::to_string(v.row_offset + 1) + ";1H";
|
||||
for (size_t i = 0; i < b64.size(); i += 4096) {
|
||||
out += "\033_G";
|
||||
if (i == 0)
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
#include "terminal_renderer.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace crossdesk::terminal {
|
||||
namespace {
|
||||
std::string Clip(const std::string &text, size_t width) {
|
||||
if (text.size() <= width)
|
||||
return text;
|
||||
return width > 3 ? text.substr(0, width - 3) + "..." : text.substr(0, width);
|
||||
}
|
||||
|
||||
std::string Compact(const std::vector<std::string> &choices, size_t width) {
|
||||
for (const auto &text : choices)
|
||||
if (text.size() <= width)
|
||||
return text;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string Label(const std::string &label) {
|
||||
std::string text = label;
|
||||
text.resize(5, ' ');
|
||||
return text + " | ";
|
||||
}
|
||||
|
||||
std::string CompactRow(const std::string &label,
|
||||
const std::vector<std::string> &choices, size_t width) {
|
||||
const auto prefix = width >= 12 ? Label(label) : std::string{};
|
||||
return prefix + Compact(choices, width - prefix.size());
|
||||
}
|
||||
|
||||
std::string Columns(const std::string &label,
|
||||
const std::vector<std::string> &cells, size_t width,
|
||||
size_t count) {
|
||||
std::string line = Label(label);
|
||||
const size_t content = width - line.size() - (count - 1) * 3;
|
||||
for (size_t i = 0; i < cells.size(); ++i) {
|
||||
const size_t start = 8 + i * content / count + i * 3;
|
||||
if (i) {
|
||||
line.resize(start - 3, ' ');
|
||||
line += " | ";
|
||||
}
|
||||
line += cells[i];
|
||||
}
|
||||
return line;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<std::string> StatusLines(const StatusBar &bar, int columns) {
|
||||
const size_t width = std::clamp(columns, 1, 240);
|
||||
const size_t margin = width >= 20 ? 1 : 0;
|
||||
const size_t inner = width - 2 * margin;
|
||||
const std::string mode =
|
||||
bar.command_mode
|
||||
? (bar.control
|
||||
? (inner < 36 ? "LOCAL PAUSED" : "LOCAL / CONTROL PAUSED")
|
||||
: (inner < 36 ? "LOCAL READ" : "LOCAL / READ ONLY"))
|
||||
: bar.control ? "CONTROL"
|
||||
: "READ ONLY";
|
||||
const auto progress =
|
||||
bar.error.empty() ? bar.progress : "Error: " + bar.error;
|
||||
auto left = mode + " | " + progress;
|
||||
auto right = bar.view + (bar.network.empty() ? "" : " | " + bar.network);
|
||||
if (left.size() + right.size() + 2 > inner)
|
||||
right = bar.network;
|
||||
if (left.size() + right.size() + 2 > inner && bar.error.empty())
|
||||
left = mode + " | " + bar.compact_progress;
|
||||
if (left.size() + right.size() + 2 > inner)
|
||||
right.clear();
|
||||
std::string status = Clip(left, inner);
|
||||
if (!right.empty()) {
|
||||
status.resize(inner - right.size(), ' ');
|
||||
status += right;
|
||||
}
|
||||
|
||||
std::string keys, view;
|
||||
if (inner >= 63) {
|
||||
const size_t count = inner >= 99 ? 4 : inner >= 77 ? 3 : 2;
|
||||
if (bar.command_mode) {
|
||||
keys =
|
||||
Columns("LOCAL",
|
||||
count >= 3 ? std::vector<std::string>{"<Esc/Ctrl+C> Return",
|
||||
"<c> Toggle control",
|
||||
"<q> Quit"}
|
||||
: std::vector<std::string>{"<Esc/Ctrl+C> Return",
|
||||
"<c> Toggle; <q> Quit"},
|
||||
inner, count);
|
||||
} else if (bar.control) {
|
||||
keys = Columns("KEYS", {"<Ctrl+]> Commands", "Input -> remote"}, inner,
|
||||
count);
|
||||
} else {
|
||||
keys = Columns("KEYS",
|
||||
count >= 3
|
||||
? std::vector<std::string>{"<c> Control", "<q> Quit",
|
||||
"<Ctrl+]> Commands"}
|
||||
: std::vector<std::string>{"<c> Control; <q> Quit",
|
||||
"<Ctrl+]> Commands"},
|
||||
inner, count);
|
||||
}
|
||||
if (bar.control && !bar.command_mode) {
|
||||
view = Columns("THEN", {"<c> Toggle control", "<q> Quit"}, inner, count);
|
||||
} else {
|
||||
view = Columns(
|
||||
"VIEW",
|
||||
count == 4
|
||||
? std::vector<std::string>{"<arrows/ijkl> Pan", "<+/-> Zoom",
|
||||
"<f> Fit", "<d> Detail"}
|
||||
: count == 3
|
||||
? std::vector<std::string>{"<arrows/ijkl> Pan", "<+/-> Zoom",
|
||||
"<f> Fit; <d> Detail"}
|
||||
: std::vector<std::string>{"<arrows> Pan; <+/-> Zoom",
|
||||
"<f> Fit; <d> Detail"},
|
||||
inner, count);
|
||||
}
|
||||
} else {
|
||||
keys = CompactRow(
|
||||
bar.command_mode ? "LOCAL" : "KEYS",
|
||||
bar.command_mode
|
||||
? std::vector<std::string>{"<Esc> Return | <c> Toggle | <q> Quit",
|
||||
"<Esc> Return | <q> Quit",
|
||||
"<Esc> Back <q> Quit", "<q> Quit", "<q>",
|
||||
"q"}
|
||||
: bar.control
|
||||
? std::vector<std::string>{"<Ctrl+]> Commands | Input -> remote",
|
||||
"<Ctrl+]> Commands", "<Ctrl+]> Menu",
|
||||
"<Ctrl+]>"}
|
||||
: std::vector<
|
||||
std::string>{"<c> Control | <q> Quit | <Ctrl+]> Commands",
|
||||
"<c> Control | <q> Quit", "<c> Ctrl <q> Quit",
|
||||
"<q> Quit", "<q>", "q"},
|
||||
inner);
|
||||
view = CompactRow(
|
||||
bar.control && !bar.command_mode ? "THEN" : "VIEW",
|
||||
bar.control && !bar.command_mode
|
||||
? std::vector<std::string>{"<c> Toggle control | <q> Quit",
|
||||
"<c> Toggle; <q> Quit", "<c> / <q>",
|
||||
"<q>", "q"}
|
||||
: std::vector<std::string>{"<arrows> Pan | <+/-> Zoom | <f> Fit | "
|
||||
"<d> Detail",
|
||||
"<+/-> Zoom | <f> Fit | <d> Detail",
|
||||
"<f> Fit | <d> Detail", "<f> Fit", "<f>",
|
||||
"f"},
|
||||
inner);
|
||||
}
|
||||
std::vector<std::string> lines{status, keys, view};
|
||||
for (auto &line : lines) {
|
||||
line.insert(0, margin, ' ');
|
||||
line.resize(width, ' ');
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
} // namespace crossdesk::terminal
|
||||
Reference in New Issue
Block a user