summaryrefslogtreecommitdiff
path: root/src/draw.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/draw.zig')
-rw-r--r--src/draw.zig81
1 files changed, 44 insertions, 37 deletions
diff --git a/src/draw.zig b/src/draw.zig
index 9df76cfd..963d61c1 100644
--- a/src/draw.zig
+++ b/src/draw.zig
@@ -366,6 +366,7 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface {
// the pointer per-text (the macOS I-beam) read this as "target" and
// afford it the arrow instead.
if (p.look_hover_preview != null) s.pointer_shape = .target;
+ if (p.drag == .move or p.drag == .column_move) s.pointer_shape = .box;
for (&s.body_layers) |*layer| layer.rows = 0;
for (&s.tag_layers) |*layer| layer.rows = 0;
s.nimages = 0;
@@ -401,6 +402,7 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface {
// Separators have their own role; a scrollbar need not frame the page.
const whole = s.regions[0].rect;
s.fill(whole.x, whole.y, whole.w, whole.h, .{ .bg = .{ .rgb = chrome.border } });
+ for (0..p.ncol) |c| if (p.col_n[c] == 0) s.fill(p.col_x[c], p.topBarHeight() + p.columnBarHeight(), p.col_w[c], s.rows -| (p.topBarHeight() + p.columnBarHeight()), .{ .bg = .{ .rgb = chrome.empty_col } });
for (&p.panes, 0..) |*slot, id| {
const pane = slot.* orelse continue;
@@ -947,9 +949,9 @@ fn joinPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pane, r
if (r.w <= TAG_TEXT_INSET) return paintPaneTag(p, s, arena, pane, r, id, active, capacity);
const chrome = p.chromeTheme();
const region = s.region(.tag, id).?;
- const tag = try tagline.tagText(p, arena, pane);
+ const tag = (try tagline.shownTag(p, arena, pane)).text;
const rows = pane.tag_rows;
- // As wide as the widest line it shows, and never narrower than its
+ // As wide as the widest row it shows, and never narrower than its
// viewport.
var widest: usize = 0;
var split = std.mem.splitScalar(u8, tag, '\n');
@@ -1093,8 +1095,10 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa
// A row per line shown, from `tag_top`: the tag's first line carries
// the prefix, the rest are the tag's own text alone.
const top = pane.tag_top;
+ // Its lines wrapped at its width (tagline.shownTag): a row per row shown.
+ const shown = try tagline.shownTag(p, arena, pane);
var lines: [MAX_TAG_ROWS][]const u8 = @splat("");
- var split = std.mem.splitScalar(u8, whole, '\n');
+ var split = std.mem.splitScalar(u8, shown.text, '\n');
for (0..top) |_| _ = split.next();
for (lines[0..rows]) |*line| line.* = split.next() orelse "";
// The keyboard's caret, as a row and a byte offset in that row's
@@ -1103,42 +1107,45 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa
const Caret = struct { row: usize, at: usize };
const caret: ?Caret = if (!active)
null
- else if (pane.prompt == .name)
- .{ .row = 0, .at = @min(lines[0].len, @as(usize, @intCast(@max(0, pane.input.cur_col)))) }
- else if (pane.focus == .tag and pane.prompt == .none and pane.tag.cur_row >= top and pane.tag.cur_row < top + rows) caret: {
- const row: usize = @intCast(pane.tag.cur_row - top);
- break :caret .{ .row = row, .at = @min(lines[row].len, @as(usize, @intCast(@max(0, pane.tag.cur_col)))) };
+ else if (pane.prompt == .name) caret: {
+ // A drafted name is the prefix: its caret is on row 0 of the tag,
+ // the rows it wrapped onto included.
+ const at = modal.positionAt(shown.text, shown.fromReal(@intCast(@max(0, pane.input.cur_col))));
+ if (at.row < top or at.row >= top + rows) break :caret null;
+ break :caret .{ .row = at.row - top, .at = @min(lines[at.row - top].len, at.col) };
+ }
+ else if (pane.focus == .tag and pane.prompt == .none) caret: {
+ const real = modal.offsetAt(whole, .{ .row = @intCast(@max(0, pane.tag.cur_row)), .col = @intCast(@max(0, pane.tag.cur_col)) });
+ const at = modal.positionAt(shown.text, shown.fromReal(real));
+ if (at.row < top or at.row >= top + rows) break :caret null;
+ const row: usize = at.row - top;
+ break :caret .{ .row = row, .at = @min(lines[row].len, at.col) };
} else null;
- if (caret) |at| {
- const line = lines[at.row];
- const col = panes.File.rawDisplayCol(line, at.at);
- var scroll: usize = pane.tag_scroll;
- if (col < scroll) scroll = col;
- if (col >= scroll + capacity) scroll = col - capacity + 1;
- var raw = panes.File.rawAtDisplay(line, scroll);
- scroll = panes.File.rawDisplayCol(line, raw);
- if (col >= scroll + capacity) {
- raw = modal.nextGrapheme(line, raw);
- scroll = panes.File.rawDisplayCol(line, raw);
- }
- // A line past a u16 of columns keeps its caret off the band.
- pane.tag_scroll = @intCast(@min(scroll, std.math.maxInt(u16)));
- } else if (pane.focus != .tag) pane.tag_scroll = 0;
+ // Its rows are wrapped to fit: a pane tag never scrolls sideways.
+ _ = capacity;
+ pane.tag_scroll = 0;
for (lines[0..rows], 0..) |line, row| {
// The scroll is the caret's line's: a shorter row shows nothing.
const start = @min(line.len, panes.File.rawAtDisplay(line, pane.tag_scroll));
_ = s.print(tx, tag_y + @as(u16, @intCast(row)), tw, line[start..], tag_style);
}
- const tag = lines[0];
- if (top == 0) if (tagTintRange(pane, tag, prefix_len)) |name| {
- const lo = @max(name.lo, panes.File.rawAtDisplay(tag, pane.tag_scroll));
- const hi = @min(name.hi, tag.len);
- if (lo < hi) {
- var col = panes.File.rawDisplayCol(tag, lo) -| pane.tag_scroll;
- const end = panes.File.rawDisplayCol(tag, hi) -| pane.tag_scroll;
- const name_fg = if (active and p.settings.focus_tint) chrome.tag_active_name_fg else chrome.tag_name_fg;
+ // The name's tint, in the tag's first line, over whichever rows it
+ // wrapped onto.
+ const first_line = whole[0 .. std.mem.indexOfScalar(u8, whole, '\n') orelse whole.len];
+ if (tagTintRange(pane, first_line, prefix_len)) |name| if (name.lo < name.hi) {
+ const name_fg = if (active and p.settings.focus_tint) chrome.tag_active_name_fg else chrome.tag_name_fg;
+ const a = modal.positionAt(shown.text, shown.fromReal(name.lo));
+ const b = modal.positionAt(shown.text, shown.fromReal(@min(name.hi, first_line.len)));
+ var row = @max(a.row, top);
+ while (row <= b.row and row < top + rows) : (row += 1) {
+ const line = lines[row - top];
+ const from = if (row == a.row) a.col else 0;
+ const to = if (row == b.row) b.col else line.len;
+ var col = panes.File.rawDisplayCol(line, @min(from, line.len));
+ const end = panes.File.rawDisplayCol(line, @min(to, line.len));
+ const y = tag_y + @as(u16, @intCast(row - top));
while (col < end and col < tw) : (col += 1)
- s.at(tx + @as(u16, @intCast(col)), tag_y).style.fg = .{ .rgb = name_fg };
+ s.at(tx + @as(u16, @intCast(col)), y).style.fg = .{ .rgb = name_fg };
}
};
if (p.look_hover_preview) |preview| {
@@ -1156,8 +1163,8 @@ pub fn paintPaneTag(p: *Pardes, s: *Surface, arena: std.mem.Allocator, pane: *Pa
const lo = @min(range.anchor, range.head);
const hi = @max(range.anchor, range.head);
if (modal.nextGrapheme(whole, lo) >= hi) continue;
- const first = modal.positionAt(whole, lo);
- const last = modal.positionAt(whole, hi);
+ const first = modal.positionAt(shown.text, shown.fromReal(lo));
+ const last = modal.positionAt(shown.text, shown.fromReal(hi));
var row = @max(first.row, top);
while (row <= last.row and row < top + rows) : (row += 1) {
const line = lines[row - top];
@@ -1299,14 +1306,14 @@ test "place puts every part where the painters and layers put it, in every layou
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 });
defer p.deinit();
_ = try p.setTestFile("placed\n" ** 80);
- p.newScratchColumn(0);
+ p.newColumn(0);
+ p.newScratchIn(0, p.activeColumn().?);
p.newScratchBelow(0);
p.sync();
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena.deinit();
for (0..16) |step| {
p.settings.tag_bottom = step & 1 != 0;
- p.settings.column_tags = step & 2 != 0;
if (step & 4 != 0) try std.testing.expect(p.executeBuiltinLine(p.active, "Msg a placed notice"));
if (step == 8) try std.testing.expect(p.executeBuiltinLine(1, "Collapse"));
if (step == 12) try std.testing.expect(p.executeBuiltinLine(1, "Collapse"));
@@ -1661,7 +1668,7 @@ test "a monkey over notices, prompts, tags and tiny panes at a gui's metrics nev
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena.deinit();
const words = [_][]const u8{ "error:", "\u{754c}", "x", "Shader /tmp/a.glsl:", "e\u{301}", "\t", "long-" ** 20, " " };
- const commands = [_][]const u8{ "New", "Newcol", "Collapse", "Tagbottom", "ColumnTags", "TreeContext", "Wrap", "Find", "Del", "FocusTint", "Debug" };
+ const commands = [_][]const u8{ "New", "Newcol", "Collapse", "Tagbottom", "TreeContext", "Wrap", "Find", "Del", "FocusTint", "Debug" };
var text: std.ArrayListUnmanaged(u8) = .empty;
defer text.deinit(std.testing.allocator);
for (0..steps) |_| {