summaryrefslogtreecommitdiff
path: root/src/mouse.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/mouse.zig')
-rw-r--r--src/mouse.zig127
1 files changed, 72 insertions, 55 deletions
diff --git a/src/mouse.zig b/src/mouse.zig
index 343051b0..33646090 100644
--- a/src/mouse.zig
+++ b/src/mouse.zig
@@ -103,11 +103,11 @@ pub const ChromeTarget = struct { col: u16, row: u16 };
pub fn chromeTarget(p: *const Pardes, col: u16, row: u16) ?ChromeTarget {
if (row < p.topBarHeight() + p.columnBarHeight()) return .{ .col = col, .row = row };
for (p.panes, 0..) |slot, id| {
- if (slot == null) continue;
+ const pane = slot orelse continue;
const rect = p.rects[id];
- const tag = if (p.settings.tag_bottom) rect.y + rect.h -| BOX_H else rect.y;
- if (row == tag and col >= rect.x and col < rect.x + rect.w and col < rect.x + config.GUTTER)
- return .{ .col = col, .row = tag };
+ const tag = p.tagTop(pane, rect);
+ if (row >= tag and row < tag + pane.tag_rows and col >= rect.x and col < rect.x + rect.w and col < rect.x + config.GUTTER)
+ return .{ .col = col, .row = row };
}
for (0..p.ncol -| 1) |column| {
const handle = p.col_x[column] + p.col_w[column] -| 1;
@@ -122,11 +122,11 @@ pub fn chromeTarget(p: *const Pardes, col: u16, row: u16) ?ChromeTarget {
}
}
for (p.panes, 0..) |slot, id| {
- if (slot == null) continue;
+ const pane = slot orelse continue;
const rect = p.rects[id];
- const tag = if (p.settings.tag_bottom) rect.y + rect.h -| BOX_H else rect.y;
- if (row == tag and col >= rect.x and col < rect.x + rect.w)
- return .{ .col = col, .row = tag };
+ const tag = p.tagTop(pane, rect);
+ if (row >= tag and row < tag + pane.tag_rows and col >= rect.x and col < rect.x + rect.w)
+ return .{ .col = col, .row = row };
}
for (0..p.ncol -| 1) |column| {
const handle = p.col_x[column] + p.col_w[column] -| 1;
@@ -182,11 +182,13 @@ pub fn sameTagCell(a: ?TagHit, b: ?TagHit) bool {
return first.kind == second.kind and first.id == second.id and first.serial == second.serial and first.col == second.col;
}
-pub fn tagColumn(p: *const Pardes, kind: TagKind, id: usize, supplied: ?TagHit, clamp: bool) ?u16 {
+/// The tag column a host's pointer is at. Clamped, a point off the tag is
+/// brought back onto line `line` of it, for a drag that leaves it.
+pub fn tagColumn(p: *const Pardes, kind: TagKind, id: usize, supplied: ?TagHit, clamp: bool, line: u16) ?u16 {
var point = supplied orelse return null;
if (point.kind != kind or point.id != id) return null;
if (clamp) for (p.surface.tagLayers()) |*layer| {
- if (layer.cols == 0 or layer.kind != kind or layer.id != id or layer.serial != point.serial) continue;
+ if (layer.cols == 0 or layer.kind != kind or layer.id != id or layer.serial != point.serial or layer.line != line) continue;
const bw: f32 = @floatFromInt(point.metrics.body_w);
const bh: f32 = @floatFromInt(point.metrics.body_h);
const left = @as(f32, @floatFromInt(layer.viewport.x)) * bw;
@@ -298,7 +300,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
};
const x = if (p.header_column) |c| p.col_x[c] + TAG_TEXT_INSET else 0;
const bar = modal.lineSlice(edit.flatSurface(p, t) catch return, 0);
- const at = @min(bar.len, panes.File.rawAtDisplay(bar, (tagColumn(p, if (p.header_column != null) .column else .workspace, p.header_column orelse 0, p.pointer_tag_hit, p.header_drag) orelse (mcol -| x)) + p.header_scroll));
+ const at = @min(bar.len, panes.File.rawAtDisplay(bar, (tagColumn(p, if (p.header_column != null) .column else .workspace, p.header_column orelse 0, p.pointer_tag_hit, p.header_drag, 0) orelse (mcol -| x)) + p.header_scroll));
// the anchor stays where the press put it
t.cur_row = 0;
t.cur_col = @intCast(at);
@@ -330,7 +332,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
const focused = p.header_focus and p.header_column == column;
const bar = modal.lineSlice(edit.flatSurface(p, t) catch return, 0);
const scroll = if (focused) p.header_scroll else 0;
- const at = @min(bar.len, panes.File.rawAtDisplay(bar, (tagColumn(p, if (column != null) .column else .workspace, column orelse 0, p.pointer_tag_hit, p.header_drag) orelse (mcol -| x)) + scroll));
+ const at = @min(bar.len, panes.File.rawAtDisplay(bar, (tagColumn(p, if (column != null) .column else .workspace, column orelse 0, p.pointer_tag_hit, p.header_drag, 0) orelse (mcol -| x)) + scroll));
if (m.button == config.select_button) {
// A clicked header is typed straight into, as a clicked tag is.
if (!focused) p.enterHeader(column);
@@ -417,8 +419,9 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
}
if (hovered) |mid| {
const mr = p.rects[mid];
- const mtag = if (p.settings.tag_bottom) mr.y + mr.h -| BOX_H else mr.y;
- if (mcol < mr.x + config.GUTTER and mrow >= mtag and mrow < mtag + BOX_H) {
+ const mpane = p.panes[mid].?;
+ const mtag = p.tagTop(mpane, mr);
+ if (mcol < mr.x + config.GUTTER and mrow >= mtag and mrow < mtag + mpane.tag_rows) {
p.drag = .{ .move = .{ .id = mid, .cur_x = mcol, .cur_y = mrow } };
return;
}
@@ -449,14 +452,14 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
previous.focus = .body;
previous.closePrompt();
};
- const tag_y = if (p.settings.tag_bottom) r.y + r.h -| BOX_H else r.y;
- const body_y = p.bodyTop(r);
+ const tag_y = p.tagTop(pane, r);
+ const body_y = p.bodyTop(pane, r);
if (mcol < r.x + config.GUTTER) {
// gutter scrollbar: a left click scrolls UP to that row
p.active = id;
pane.scrollBy(-(@as(i32, mrow) - @as(i32, body_y)));
} else {
- const on_tag = mrow >= tag_y and mrow < tag_y + BOX_H;
+ const on_tag = mrow >= tag_y and mrow < tag_y + pane.tag_rows;
if (on_tag and mcol < r.x + TAG_TEXT_INSET) return;
p.active = id;
pane.closePrompt();
@@ -475,8 +478,9 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
pane.tag.msel.active = false;
pane.tag.nsel = 0;
pane.tag.cur_pinned = true;
- pane.tag.cur_row = 0;
- pane.tag.cur_col = @intCast(p.tagOffsetAt(pane, @intCast(@max(0, pointed.sel.c0))) orelse 0);
+ const at = p.tagOffsetAt(pane, @intCast(@max(0, pointed.sel.r0)), @intCast(@max(0, pointed.sel.c0))) orelse modal.Cursor{ .row = 0, .col = 0 };
+ pane.tag.cur_row = @intCast(at.row);
+ pane.tag.cur_col = @intCast(at.col);
}
p.drag = .{ .select = .{
.id = id,
@@ -524,9 +528,9 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
const pane = p.panes[id] orelse return;
// the tag row and the body's first, exactly as the select
// arm above derives them
- const tag_y = if (p.settings.tag_bottom) r.y + r.h -| BOX_H else r.y;
- const body_y = p.bodyTop(r);
- const on_tag = mrow >= tag_y and mrow < tag_y + BOX_H;
+ const tag_y = p.tagTop(pane, r);
+ const body_y = p.bodyTop(pane, r);
+ const on_tag = mrow >= tag_y and mrow < tag_y + pane.tag_rows;
if (mcol < r.x + config.GUTTER and !on_tag) {
const local = @as(i32, mrow) - @as(i32, body_y);
if (m.button == config.look_button) p.active = id;
@@ -605,30 +609,37 @@ pub fn dragUpdate(p: *Pardes, mcol: u16, mrow: u16, body_hit: ?Mouse.BodyHit) vo
if (p.panes[s.id]) |pane| {
const r = p.rects[s.id];
const b = @intFromEnum(s.button);
- if (pane.body_rows > 0 and pane.sel[b].r0 >= BOX_H) if (body_hit) |supplied| {
+ const tag_rows: i32 = pane.tag_rows;
+ if (pane.body_rows > 0 and pane.sel[b].r0 >= tag_rows) if (body_hit) |supplied| {
const mapped_hit = bodyHitForPane(p, s.id, supplied, true) orelse return;
pane.sel[b].c1 = @as(i32, mapped_hit.col) - config.GUTTER;
- pane.sel[b].r1 = @as(i32, mapped_hit.row) + BOX_H;
+ pane.sel[b].r1 = @as(i32, mapped_hit.row) + tag_rows;
return;
};
- if (pane.sel[b].r0 < BOX_H) if (tagColumn(p, .pane, s.id, p.pointer_tag_hit, true)) |value| {
+ // A sweep that began on the tag stays on it, on the tag line
+ // nearest the pointer.
+ const tag_y = p.tagTop(pane, r);
+ const tag_line: u16 = @intCast(std.math.clamp(@as(i32, mrow) - @as(i32, tag_y), 0, tag_rows - 1));
+ if (pane.sel[b].r0 < tag_rows) if (tagColumn(p, .pane, s.id, p.pointer_tag_hit, true, tag_line)) |value| {
pane.sel[b].c1 = @as(i32, value) + pane.tag_scroll;
- pane.sel[b].r1 = 0;
+ pane.sel[b].r1 = tag_line;
return;
};
pane.sel[b].c1 = @as(i32, mcol) - @as(i32, r.x + config.GUTTER);
- // Sel row BOX_H is the body's FIRST row, the same thing
+ // Sel row tag_rows is the body's FIRST row, the same thing
// pointerTextSelection means by it -- so the drag has to
// measure from the body's top, notice bands included, or a
// selection made while a message is up lands rows away
// from the pointer.
- const body_y = p.bodyTop(r);
- const body_h = r.h -| BOX_H;
- pane.sel[b].r1 = if (pane.sel[b].r0 < BOX_H or body_h == 0)
+ const body_y = p.bodyTop(pane, r);
+ const body_h = r.h -| pane.tag_rows;
+ pane.sel[b].r1 = if (pane.sel[b].r0 < tag_rows)
+ tag_line
+ else if (body_h == 0)
0
else
- @as(i32, @min(@max(mrow, body_y), body_y + body_h - 1)) - @as(i32, body_y) + @as(i32, BOX_H);
- if (pane.sel[b].r1 < BOX_H) pane.sel[b].c1 += @as(i32, pane.tag_scroll) - TAG_GAP;
+ @as(i32, @min(@max(mrow, body_y), body_y + body_h - 1)) - @as(i32, body_y) + tag_rows;
+ if (pane.sel[b].r1 < tag_rows) pane.sel[b].c1 += @as(i32, pane.tag_scroll) - TAG_GAP;
if (comptime pdf_enabled)
panes.Pdf.pointerUpdate(&s.pdf, p, pane, mcol, mrow);
}
@@ -668,8 +679,10 @@ fn gestureRange(p: *Pardes, id: usize, text: []const u8, on_tag: bool, operand:
if (on_tag) {
const tag = p.tagText(p.scratch.allocator(), pane) catch return .{};
const sel = operand.expanded orelse operand.preview orelse return .{};
- const lead = @min(sel.c0, sel.c1);
- const at = panes.File.rawAtDisplay(tag, @intCast(@max(0, lead)));
+ const lead = if (sel.r0 < sel.r1 or (sel.r0 == sel.r1 and sel.c0 <= sel.c1)) sel else Pane.Sel{ .r0 = sel.r1, .c0 = sel.c1 };
+ const row: usize = @intCast(@max(0, lead.r0));
+ const start = modal.lineStartOffset(tag, row);
+ const at = start + panes.File.rawAtDisplay(modal.lineSlice(tag, row), @intCast(@max(0, lead.c0)));
const q0: u32 = @intCast(@min(at, tag.len));
return .{ .q0 = q0, .q1 = @intCast(@min(q0 + text.len, tag.len)) };
}
@@ -692,7 +705,7 @@ fn gestureRange(p: *Pardes, id: usize, text: []const u8, on_tag: bool, operand:
else if (operand.expanded orelse operand.preview) |sel| lead: {
// A selection's leading corner in reading order, converted the way
// `pointerOperand` converts the click itself.
- const top = @min(sel.r0, sel.r1) - @as(i32, BOX_H);
+ const top = @min(sel.r0, sel.r1) - @as(i32, pane.tag_rows);
if (top < 0) break :lead null;
const w = pane.wrapAt(top);
const col = edit.paneByteAtDisplay(p, &pane.body, w.line, w.at, @min(sel.c0, sel.c1) - panes.File.gutterWidth(pane));
@@ -825,19 +838,19 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
const sl = pane.sel[sel_slot];
const dragged = sl.c0 != sl.c1 or sl.r0 != sl.r1;
if (!dragged) pane.sel[sel_slot].state = .none;
- if (sl.r0 < BOX_H) {
+ if (sl.r0 < pane.tag_rows) {
// On the tag the caret goes where the button came up. A
// click on the path, if it can be renamed, drafts a name.
- const at = p.tagOffsetAt(pane, @intCast(@max(0, sl.c1)));
- if (at) |col| {
- pane.tag.cur_row = 0;
- pane.tag.cur_col = @intCast(col);
+ const at = p.tagOffsetAt(pane, @intCast(@max(0, sl.r1)), @intCast(@max(0, sl.c1)));
+ if (at) |cursor| {
+ pane.tag.cur_row = @intCast(cursor.row);
+ pane.tag.cur_col = @intCast(cursor.col);
} else if (!dragged) p.draftName(pane, @intCast(@max(0, sl.c1)));
return;
}
if (dragged) edit.capturePointerSelection(p, pane, sel_slot) catch {};
if (dragged and pane.body.mode == .tty) mirrorTtySelection(p, pane);
- const body_vis = sl.r1 - @as(i32, BOX_H);
+ const body_vis = sl.r1 - @as(i32, pane.tag_rows);
if (body_vis >= 0 and pane.body.mode != .tty) {
const w = pane.wrapAt(body_vis);
pane.body.cur_row = w.line;
@@ -864,10 +877,10 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
// A right-click on a line NUMBER looks at that line of
// this file -- a sticky context header's number included,
// since the row map holds each header's own source line.
- if (s.button == config.look_button and clk.r0 >= BOX_H and clk.r0 == clk.r1 and
+ if (s.button == config.look_button and clk.r0 >= pane.tag_rows and clk.r0 == clk.r1 and
clk.c0 == clk.c1 and clk.c0 >= 0 and pane.body.mode != .tty)
if (pane.file) |f| if (f.output == null and clk.c0 < panes.File.gutterWidth(pane)) {
- const line = pane.wrapAt(clk.r0 - BOX_H).line;
+ const line = pane.wrapAt(clk.r0 - pane.tag_rows).line;
if (line >= 0) {
pane.sel[b].state = .none;
var buf: [48]u8 = undefined;
@@ -881,14 +894,14 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
pane.sel[b] = expanded
else if (operand.text == null)
pane.sel[b].state = .none;
- if (s.button == config.look_button and clk.r0 >= BOX_H and pane.body.mode != .tty) {
+ if (s.button == config.look_button and clk.r0 >= pane.tag_rows and pane.body.mode != .tty) {
pane.body.cur_row = operand.row;
pane.body.cur_col = operand.col;
if (edit.clampFileCursor(&pane.body)) pane.ensureCursorVisible();
}
const txt = operand.text;
const cmd = if (s.button == config.look_button) config.look_cmd else config.exec_cmd;
- if (clk.r0 < BOX_H and s.button == config.exec_button and pane.prompt == .name) {
+ if (clk.r0 == 0 and s.button == config.exec_button and pane.prompt == .name) {
const prefix_width = panes.File.displayWidth(p.tagPrefix(pane) catch return);
const on_name = @as(usize, @intCast(@max(0, clk.c0))) < prefix_width;
if (!p.commitTagName(pane)) return;
@@ -909,7 +922,7 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
pane.sel[b].c1 += shift;
}
dispatchPointerBuiltin(p, s.id, cmd, txt, .{
- .on_tag = clk.r0 < BOX_H,
+ .on_tag = clk.r0 < pane.tag_rows,
.operand = operand,
});
}
@@ -931,7 +944,7 @@ fn chordCutPaste(p: *Pardes, cut: bool) void {
if (cut and pv.selection_text.len > 0) edit.setYank(p, pv.selection_text);
return;
};
- if (@min(pane.sel[sel_slot].r0, pane.sel[sel_slot].r1) < BOX_H) {
+ if (@min(pane.sel[sel_slot].r0, pane.sel[sel_slot].r1) < pane.tag_rows) {
// A sweep over the tag cuts from and pastes into the tag's own text;
// the part of it over the prefix is not the tag's to cut.
const t = &pane.tag;
@@ -939,12 +952,16 @@ fn chordCutPaste(p: *Pardes, cut: bool) void {
s.chorded = true;
const sl = pane.sel[sel_slot];
pane.sel[sel_slot].state = .none;
- const from = p.tagOffsetAt(pane, @intCast(@max(0, @min(sl.c0, sl.c1)))) orelse 0;
- const to = p.tagOffsetAt(pane, @intCast(@max(0, @max(sl.c0, sl.c1)))) orelse 0;
- t.cur_row = 0;
- t.cur_col = @intCast(to);
+ const forward = sl.r0 < sl.r1 or (sl.r0 == sl.r1 and sl.c0 <= sl.c1);
+ const start = if (forward) .{ sl.r0, sl.c0 } else .{ sl.r1, sl.c1 };
+ const end = if (forward) .{ sl.r1, sl.c1 } else .{ sl.r0, sl.c0 };
+ const origin: modal.Cursor = .{ .row = 0, .col = 0 };
+ const from = p.tagOffsetAt(pane, @intCast(@max(0, start[0])), @intCast(@max(0, start[1]))) orelse origin;
+ const to = p.tagOffsetAt(pane, @intCast(@max(0, end[0])), @intCast(@max(0, end[1]))) orelse origin;
+ t.cur_row = @intCast(to.row);
+ t.cur_col = @intCast(to.col);
t.cur_pinned = true;
- t.vsel = .{ .active = from != to, .row = 0, .col = @intCast(from), .explicit = false };
+ t.vsel = .{ .active = from.row != to.row or from.col != to.col, .row = @intCast(from.row), .col = @intCast(from.col), .explicit = false };
t.msel.active = false;
t.nsel = 0;
}
@@ -965,7 +982,7 @@ fn chordCutPaste(p: *Pardes, cut: bool) void {
if (y.len == 0) return;
if (panes.Terminal.reportsMouse(pane)) {
const col: u16 = @intCast(std.math.clamp(pane.sel[sel_slot].c1 + 1, 1, 9999));
- const row: u16 = @intCast(std.math.clamp(pane.sel[sel_slot].r1 - @as(i32, BOX_H) + 1, 1, 9999));
+ const row: u16 = @intCast(std.math.clamp(pane.sel[sel_slot].r1 - @as(i32, pane.tag_rows) + 1, 1, 9999));
var mb: [32]u8 = undefined;
if (panes.Terminal.mouseFormatSgr(pane)) {
p.emitWrite(s.id, std.fmt.bufPrint(&mb, "\x1b[<0;{d};{d}M\x1b[<0;{d};{d}m", .{ col, row, col, row }) catch return);
@@ -986,8 +1003,8 @@ fn chordCutPaste(p: *Pardes, cut: bool) void {
const pfx: i32 = if (pane.file != null) panes.File.gutterWidth(pane) else 0;
// both ends of the sweep through the same last-frame map, so a
// chord over wrapped rows cuts the text that was under it
- const w0 = pane.wrapAt(@max(0, sl.r0 - @as(i32, BOX_H)));
- const w1 = pane.wrapAt(@max(0, sl.r1 - @as(i32, BOX_H)));
+ const w0 = pane.wrapAt(@max(0, sl.r0 - @as(i32, pane.tag_rows)));
+ const w1 = pane.wrapAt(@max(0, sl.r1 - @as(i32, pane.tag_rows)));
const row0 = w0.line;
const row1 = w1.line;
const col0 = edit.paneByteAtDisplay(p, &pane.body, w0.line, w0.at, sl.c0 - pfx);