diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/gui/gui.zig | 41 | ||||
| -rw-r--r-- | src/layout.zig | 18 | ||||
| -rw-r--r-- | src/pardes.zig | 86 |
3 files changed, 100 insertions, 45 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index a4cc7672..989f6b25 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -2876,6 +2876,43 @@ const StdinFeed = struct { var seq_start: usize = 0; while (seq_start < len) { + // ESC]777;mouse;<down|up|motion>;<SDL button, 0 for none>;<x>;<y>BEL + // A mouse event at window pixels, built as SDL would and run + // through dispatch, so a driver exercises the real pointer path + // (pixel to cell, tag and body hits). SGR mouse carries only cells. + const mouse_prefix = "\x1b]777;mouse;"; + if (std.mem.startsWith(u8, f.buf[seq_start..len], mouse_prefix)) { + const body = f.buf[seq_start + mouse_prefix.len .. len]; + const end = std.mem.indexOfScalar(u8, body, 0x07) orelse { + std.mem.copyForwards(u8, f.buf[0 .. len - seq_start], f.buf[seq_start..len]); + f.fill = len - seq_start; + break; + }; + if (g) |gp| { + var parts = std.mem.splitScalar(u8, body[0..end], ';'); + const kind = parts.next() orelse ""; + const button = std.fmt.parseInt(u8, parts.next() orelse "", 10) catch 0; + const x = std.fmt.parseFloat(f32, parts.next() orelse "") catch 0; + const y = std.fmt.parseFloat(f32, parts.next() orelse "") catch 0; + var sev = std.mem.zeroes(c.SDL_Event); + if (std.mem.eql(u8, kind, "motion")) { + sev.type = c.SDL_EVENT_MOUSE_MOTION; + sev.motion.x = x; + sev.motion.y = y; + sev.motion.state = if (button == 0 or button > 5) 0 else @as(u32, 1) << @intCast(button - 1); + } else { + sev.type = if (std.mem.eql(u8, kind, "down")) c.SDL_EVENT_MOUSE_BUTTON_DOWN else c.SDL_EVENT_MOUSE_BUTTON_UP; + sev.button.button = button; + sev.button.down = std.mem.eql(u8, kind, "down"); + sev.button.x = x; + sev.button.y = y; + } + dispatch(gp, in, &sev); + out.n_events += 1; + } + seq_start += mouse_prefix.len + end + 1; + continue; + } const prefix = "\x1b]777;finger;"; if (std.mem.startsWith(u8, f.buf[seq_start..len], prefix)) { const body = f.buf[seq_start + prefix.len .. len]; @@ -2909,7 +2946,9 @@ const StdinFeed = struct { seq_start += prefix.len + end + 1; continue; } - if (len - seq_start < prefix.len and std.mem.startsWith(u8, prefix, f.buf[seq_start..len])) { + if ((len - seq_start < prefix.len and std.mem.startsWith(u8, prefix, f.buf[seq_start..len])) or + (len - seq_start < mouse_prefix.len and std.mem.startsWith(u8, mouse_prefix, f.buf[seq_start..len]))) + { std.mem.copyForwards(u8, f.buf[0 .. len - seq_start], f.buf[seq_start..len]); f.fill = len - seq_start; break; diff --git a/src/layout.zig b/src/layout.zig index 8db05a07..3637f30e 100644 --- a/src/layout.zig +++ b/src/layout.zig @@ -713,14 +713,26 @@ pub fn columnBySerial(p: *const Pardes, serial: u32) ?usize { return null; } -pub fn columnMoveTarget(p: *const Pardes, source: usize, x: u16) usize { - if (source >= p.ncol) return source; +/// What releasing column `source`'s grip at `x` does: past a neighbour's +/// middle it takes that column's place; short of it, as in acme, the +/// column's left edge moves to `x - grab`, clamped as setColumnPairWidth +/// clamps it. Still on the grip, or at the leftmost column, nothing moves. +/// The drag preview and the release both ask here, so they cannot disagree. +pub const ColumnDrop = union(enum) { stay, reorder: usize, edge: u16 }; + +pub fn columnDrop(p: *const Pardes, source: usize, x: u16, grab: u16) ColumnDrop { + if (source >= p.ncol) return .stay; var insertion: usize = 0; for (0..p.ncol) |column| { if (column == source) continue; if (x >= p.col_x[column] + p.col_w[column] / 2) insertion += 1; } - return insertion; + if (insertion != source) return .{ .reorder = insertion }; + if (source == 0 or (x >= p.col_x[source] and x < p.col_x[source] + config.GUTTER)) return .stay; + if (p.col_w[source - 1] + p.col_w[source] < 2 * config.MINW) return .stay; + const lo = p.col_x[source - 1] + config.MINW; + const hi = (p.col_x[source] + p.col_w[source]) -| config.MINW; + return .{ .edge = std.math.clamp(x -| grab, lo, @max(lo, hi)) }; } /// Share out the width of columns `left` and `left + 1` so the left one is diff --git a/src/pardes.zig b/src/pardes.zig index f968511a..4786bd70 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -5155,7 +5155,7 @@ const Drag = union(enum) { move: struct { id: usize, cur_x: u16, cur_y: u16 }, /// `grab`: how far into the grip it was pressed, so the column's left /// edge lands at `cur_x - grab`. A release still on the grip moves nothing. - column_move: struct { column: usize, serial: u32, cur_x: u16, grab: u16 = 0 }, + column_move: struct { serial: u32, cur_x: u16, grab: u16 = 0 }, tag: struct { id: usize }, /// chorded: a 1-2/1-3 cut/paste chord fired during this left drag — /// the drag's own release is then inert @@ -7793,7 +7793,7 @@ pub const Pardes = struct { .key => |key| p.handleKey(key), .mouse => |m| { if (m.kind == .release) switch (p.drag) { - .border_v, .border_h, .move => p.presentation.snap_once = true, + .border_v, .border_h, .move, .column_move => p.presentation.snap_once = true, else => {}, }; p.handleMouse(m); @@ -12017,7 +12017,7 @@ pub const Pardes = struct { if (mcol < p.col_x[column] or mcol >= p.col_x[column] + @min(config.GUTTER, p.col_w[column])) continue; if (m.button == config.select_button) { p.header_drag = false; - p.drag = .{ .column_move = .{ .column = column, .serial = p.col_serial[column], .cur_x = mcol, .grab = mcol - p.col_x[column] } }; + p.drag = .{ .column_move = .{ .serial = p.col_serial[column], .cur_x = mcol, .grab = mcol - p.col_x[column] } }; } return; } @@ -12488,15 +12488,10 @@ pub const Pardes = struct { }, .border_h => |d| if (d.cur_y != seamRowOf(p, d.col, d.top_idx)) layout.applyRowSplit(p, d.col, d.top_idx, d.cur_y), - .column_move => |d| if (layout.columnBySerial(p, d.serial)) |column| { - const target = layout.columnMoveTarget(p, column, d.cur_x); - if (target != column) { - layout.reorderColumn(p, column, target); - } else if (column > 0 and (d.cur_x < p.col_x[column] or d.cur_x >= p.col_x[column] + config.GUTTER)) { - // Dropped short of another column's place: as in acme, - // the column's left edge moves to where it was dropped. - layout.setColumnPairWidth(p, column - 1, (d.cur_x -| d.grab) -| p.col_x[column - 1]); - } + .column_move => |d| if (layout.columnBySerial(p, d.serial)) |column| switch (layout.columnDrop(p, column, d.cur_x, d.grab)) { + .stay => {}, + .reorder => |target| layout.reorderColumn(p, column, target), + .edge => |x| layout.setColumnPairWidth(p, column - 1, x - p.col_x[column - 1]), }, .move => |d| { layout.movePane(p, d.id, d.cur_x, d.cur_y); @@ -14091,7 +14086,10 @@ pub const Pardes = struct { const active_col = if (layout.findPane(p, p.active)) |f| f.col == c else false; const bg = if (active_col and p.settings.focus_tint) chrome.tag_active_bg else chrome.tag_bg; const fg = if (active_col and p.settings.focus_tint) chrome.tag_active_fg else chrome.tag_fg; - const grip_bg = chrome.column_box_dim; + // A grip is muted until it is grabbed; then it lights, as a pane's + // box does for the active pane, so the drag shows what it holds. + const grabbed = p.drag == .column_move and layout.columnBySerial(p, p.drag.column_move.serial) == c; + const grip_bg = if (grabbed) chrome.column_box else chrome.column_box_dim; const lum = (@as(u16, grip_bg[0]) * 3 + @as(u16, grip_bg[1]) * 6 + @as(u16, grip_bg[2])) / 10; const ink: [3]u8 = if (lum > 140) .{ 0, 0, 0 } else .{ 255, 255, 255 }; const grip_style: CellStyle = .{ .bg = .{ .rgb = grip_bg }, .fg = .{ .rgb = ink }, .font_role = .tagline }; @@ -14106,7 +14104,8 @@ pub const Pardes = struct { .bg = .{ .rgb = bg }, .font_role = .tagline, }); - if (p.pointer_inside and p.hover_row == p.topBarHeight() and p.hover_col >= x and p.hover_col < x + w) { + // A carried column's pointer is aiming at a place, not a word. + if (p.pointer_inside and p.drag != .column_move and p.hover_row == p.topBarHeight() and p.hover_col >= x and p.hover_col < x + w) { const bar = p.columnTag(c); if (wordBoundsAtCol(bar, panes.File.rawAtDisplay(bar, p.hover_col - x + bar_scroll))) |bounds| { var col = panes.File.rawDisplayCol(bar, bounds.lo) -| bar_scroll; @@ -14154,33 +14153,38 @@ pub const Pardes = struct { var col = p.col_x[d.col]; while (col < p.col_x[d.col] + p.col_w[d.col]) : (col += 1) s.overlayDash(col, d.cur_y, "╌"); }, - .column_move => |d| { - const source = layout.columnBySerial(p, d.serial) orelse d.column; - const target = layout.columnMoveTarget(p, source, d.cur_x); - if (source < p.ncol and target != source) { - const boundary = if (target < source) p.col_x[target] else p.col_x[target] + p.col_w[target]; - const x = @min(boundary, s.cols -| 1); - // Leave the outside insertion rail visible above compact - // text layers. This clips presentation only, never reflows. - if (boundary == s.cols) { - for (&s.body_layers) |*layer| if (layer.rows > 0 and layer.viewport.x + layer.viewport.w == s.cols) { - layer.viewport.w -|= 1; - }; - for (&s.tag_layers) |*layer| if (layer.cols > 0 and layer.viewport.x + layer.viewport.w == s.cols and layer.viewport.y >= p.topBarHeight()) { - layer.viewport.w -|= 1; - }; - } - var row: u16 = p.topBarHeight(); - while (row < s.rows) : (row += 1) s.overlayDash(x, row, "╎"); - } else if (source > 0 and source < p.ncol and (d.cur_x < p.col_x[source] or d.cur_x >= p.col_x[source] + config.GUTTER) and - p.col_w[source - 1] + p.col_w[source] >= 2 * config.MINW) - { - // Where the left edge would land: the border drag's rail. - const lo = p.col_x[source - 1] + config.MINW; - const hi = (p.col_x[source] + p.col_w[source]) -| config.MINW; - const x = @min(std.math.clamp(d.cur_x -| d.grab, lo, @max(lo, hi)), s.cols -| 1); - var row: u16 = p.topBarHeight(); - while (row < s.rows) : (row += 1) s.overlayDash(x, row, "╎"); + .column_move => |d| rail: { + const source = layout.columnBySerial(p, d.serial) orelse break :rail; + // Where the column's left edge would land, reordered or not. + const edge: u16 = switch (layout.columnDrop(p, source, d.cur_x, d.grab)) { + .stay => break :rail, + .reorder => |target| if (target < source) p.col_x[target] else p.col_x[target] + p.col_w[target], + .edge => |x| x, + }; + // Leave the outside insertion rail visible above compact + // text layers. This clips presentation only, never reflows. + if (edge == s.cols) { + for (&s.body_layers) |*layer| if (layer.rows > 0 and layer.viewport.x + layer.viewport.w == s.cols) { + layer.viewport.w -|= 1; + }; + for (&s.tag_layers) |*layer| if (layer.cols > 0 and layer.viewport.x + layer.viewport.w == s.cols and layer.viewport.y >= p.topBarHeight()) { + layer.viewport.w -|= 1; + }; + } + // The rail runs in the seam cell left of the edge, where a + // border drag's does: the edge cell itself is a gutter, which + // the GUI paints as a scroll rail that swallowed the dashes. + // At the window's left edge there is no seam, so it runs just + // past that scroll rail instead. It takes the lit grip's + // color: this is where the grabbed column lands. + const x = @min(if (edge == 0) config.GUTTER - 1 else edge - 1, s.cols -| 1); + var row: u16 = p.topBarHeight(); + while (row < s.rows) : (row += 1) { + // Past the left edge's scroll rail the rail would cut + // through column 0's grip and pane boxes: leave them whole. + if (edge == 0 and s.at(x, row).style.font_role == .tagline) continue; + s.overlayDash(x, row, "╎"); + s.at(x, row).style.fg = .{ .rgb = chrome.column_box }; } }, .move => |d| if (d.cur_x < s.cols) { |
