diff options
Diffstat (limited to 'src/draw.zig')
| -rw-r--r-- | src/draw.zig | 81 |
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) |_| { |
