diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/draw.zig | 11 | ||||
| -rw-r--r-- | src/gui/gui.zig | 9 | ||||
| -rw-r--r-- | src/layout.zig | 17 | ||||
| -rw-r--r-- | src/mouse.zig | 59 | ||||
| -rw-r--r-- | src/pardes.zig | 5 | ||||
| -rw-r--r-- | src/tutor.txt | 9 |
6 files changed, 89 insertions, 21 deletions
diff --git a/src/draw.zig b/src/draw.zig index fb770b60..4ad1a1fe 100644 --- a/src/draw.zig +++ b/src/draw.zig @@ -504,15 +504,8 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { s.addRegion(.{ .kind = .guide, .rect = .{ .x = p.hover_col, .y = p.topBarHeight(), .w = 1, .h = s.rows -| p.topBarHeight() } }); } } - for (0..p.ncol) |cc| { - if (p.hover_col < p.col_x[cc] or p.hover_col >= p.col_x[cc] + p.col_w[cc]) continue; - // the same seam the h-handle hit test picks (see there) - if (mouse.seamIdxAt(p, cc, p.hover_row) != null) { - var col = p.col_x[cc]; - while (col < p.col_x[cc] + p.col_w[cc]) : (col += 1) s.overlayDash(col, p.hover_row, "╌"); - s.addRegion(.{ .kind = .guide, .rect = .{ .x = p.col_x[cc], .y = p.hover_row, .w = p.col_w[cc], .h = 1 } }); - } - } + // No hint over a pane's last row: a row of text is no handle; two + // panes resize by the rule between them or by the grip. for (0..p.ncol -| 1) |cn| { if (p.hover_col != p.col_x[cn] + p.col_w[cn] -| 1) continue; if (mouse.seamIdxAt(p, cn, p.hover_row) != null) continue; diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 141a802a..ab281cb6 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -1972,7 +1972,11 @@ fn glyphForCodepoint(g: *const Gui, cp: u32) FaceGlyph { } const CellLayout = struct { w: f32, h: f32, x_off: f32, y_off: f32 }; -const MouseCell = struct { col: u16, row: u16, body_hit: ?pardes.Mouse.BodyHit = null, tag_hit: ?pardes.TagHit = null }; +const MouseCell = struct { col: u16, row: u16, body_hit: ?pardes.Mouse.BodyHit = null, tag_hit: ?pardes.TagHit = null, rule_above: bool = false }; + +/// How near a row's top edge a press is on the rule drawn there between two +/// panes: the rule's 2 px and a pixel either side. +const rule_grab_px: f32 = 3; const WindowGeometry = struct { window_w: f32, window_h: f32, @@ -3420,6 +3424,7 @@ fn dispatch(g: *Gui, in: *Input, sev: *const c.SDL_Event) void { .row = mc.row, .tag_hit = mc.tag_hit, .body_hit = mc.body_hit, + .rule_above = mc.rule_above, .ctrl = (c.SDL_GetModState() & c.SDL_KMOD_CTRL) != 0, }, }); @@ -3720,6 +3725,8 @@ fn gridCellAtDimensions( ) MouseCell { const row: u16 = @intFromFloat(@min(@floor(@max(y, 0) / @max(body_h, 1)), 10_000)); var point: MouseCell = .{ .col = @intFromFloat(@min(@floor(@max(x, 0) / @max(body_w, 1)), 10_000)), .row = row }; + // On the rule over this row, which is where two panes meet if they do. + point.rule_above = row > 0 and @max(y, 0) - @as(f32, @floatFromInt(row)) * @max(body_h, 1) < rule_grab_px; if (core) |p| if (p.presentation.pointerFractional(p.screen_w, p.screen_h, x / @max(body_w, 1), y / @max(body_h, 1))) |mapped| { for (p.surface.tagLayers()) |*layer| { if (layer.tagHit(mapped.x * body_w, mapped.y * body_h, body_w, body_h, tagline_w, @floatFromInt(p.row_metrics.tagline_h))) |hit| { diff --git a/src/layout.zig b/src/layout.zig index 6de7a912..25c95d30 100644 --- a/src/layout.zig +++ b/src/layout.zig @@ -162,8 +162,15 @@ pub fn targetColumn(p: *Pardes, cur_x: u16) usize { } pub fn splitRowForExtent(y: u16, h: u16, cur_y: u16) ?u16 { + return splitRowFor(y, h, cur_y, null); +} + +/// As `splitRowForExtent`, each pane keeping at least `least` rows when +/// given: a pane dragged by its grip within its own column is how a +/// terminal resizes, and acme lets either window keep only its tag. +fn splitRowFor(y: u16, h: u16, cur_y: u16, least: ?u16) ?u16 { if (h < 2) return null; - const min_each: u16 = if (h >= config.MINH * 2) config.MINH else 1; + const min_each: u16 = least orelse if (h >= config.MINH * 2) config.MINH else 1; const lo = y +| min_each; const hi = y + h - min_each; if (lo > hi) return y + h / 2; @@ -176,7 +183,11 @@ pub fn movePlacement(p: *Pardes, id: usize, cur_x: u16, cur_y: u16) ?MovePlaceme if (tc == src.col and p.col_n[src.col] == 1) return null; if (tc == src.col) { const sr = p.rects[id]; - if (cur_y >= sr.y and cur_y < sr.y + sr.h) return null; + // Dropped on itself it stays, unless dragged down from its top with + // a pane above: its top moves down there, the pane above taking the + // rows, as acme's window dragged by its button does. With no rule + // between panes, that is how a terminal resizes them. + if (cur_y >= sr.y and cur_y < sr.y + sr.h and (src.idx == 0 or cur_y <= sr.y)) return null; } var heights: [MAX_PANES]u16 = @splat(0); for (0..p.ncol) |c| { @@ -196,7 +207,7 @@ pub fn movePlacement(p: *Pardes, id: usize, cur_x: u16, cur_y: u16) ?MovePlaceme const pid = p.col_panes[tc][k]; if (pid == id) continue; const h = heights[pid]; - const row = splitRowForExtent(y, h, cur_y) orelse { + const row = splitRowFor(y, h, cur_y, if (tc == src.col) BOX_H else null) orelse { y +|= h; continue; }; diff --git a/src/mouse.zig b/src/mouse.zig index 36786612..1ca53017 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -97,13 +97,50 @@ fn cornerFixture(gpa: std.mem.Allocator) !*Pardes { return p; } +test "a pane's last row of text is no resize handle; the rule over the next pane is" { + if (platform == .web) return; + const p = try cornerFixture(std.testing.allocator); + defer p.deinit(); + const seam = seamRowOf(p, 1, 0); + const inside = p.col_x[1] + p.col_w[1] / 2; + p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = inside, .row = seam } }); + try std.testing.expect(p.drag != .border_h); + p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = inside, .row = seam } }); + try std.testing.expect(nudgeRightSeam(p, 2) == seam + 2); +} + +test "a pane's grip dragged up or down its own column moves its top, down to a tag alone above" { + if (platform == .web) return; + const p = try cornerFixture(std.testing.allocator); + defer p.deinit(); + const upper = p.col_panes[1][0]; + const lower = p.col_panes[1][1]; + const grip_x = p.rects[lower].x; + // Up, past the pane above's first row: it keeps one row, its tag. + const top = p.rects[upper].y; + p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = grip_x, .row = p.rects[lower].y } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .drag, .col = grip_x, .row = top } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = grip_x, .row = top } }); + p.sync(); + try std.testing.expectEqual(@as(u16, 1), p.rects[upper].h); + try std.testing.expectEqual(lower, p.col_panes[1][1]); + // Down, inside itself: its top follows, the pane above taking the rows. + const to = p.rects[lower].y + 6; + p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = grip_x, .row = p.rects[lower].y } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .drag, .col = grip_x, .row = to } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = grip_x, .row = to } }); + p.sync(); + try std.testing.expectEqual(to, p.rects[lower].y); +} + fn nudgeRightSeam(p: *Pardes, delta: i32) u16 { const from = seamRowOf(p, 1, 0); const inside = p.col_x[1] + p.col_w[1] / 2; const to: u16 = @intCast(@as(i32, from) + delta); - p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = inside, .row = from } }); - p.update(.{ .mouse = .{ .button = .left, .kind = .drag, .col = inside, .row = to } }); - p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = inside, .row = to } }); + // On the rule a pixel frontend draws over the lower pane's first row. + p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = inside, .row = from + 1, .rule_above = true } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .drag, .col = inside, .row = to + 1 } }); + p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = inside, .row = to + 1 } }); p.sync(); return seamRowOf(p, 1, 0); } @@ -437,13 +474,17 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void { return; } } - for (0..p.ncol) |cc| { + // Two panes are resized on the rule between them, never on a + // row of text; with no rule, by the grip, as acme moves a + // window by its button. + if (m.rule_above and mrow > 0) for (0..p.ncol) |cc| { if (mcol < p.col_x[cc] or mcol >= p.col_x[cc] + p.col_w[cc]) continue; - if (seamIdxAt(p, cc, mrow)) |k| { - p.drag = .{ .border_h = .{ .col = cc, .top_idx = k, .cur_y = mrow } }; + const seam = if (p.settings.tag_bottom) mrow else mrow - 1; + if (seamIdxAt(p, cc, seam)) |k| { + p.drag = .{ .border_h = .{ .col = cc, .top_idx = k, .cur_y = seam } }; return; } - } + }; const id = hovered orelse return; const r = p.rects[id]; const pane = p.panes[id] orelse return; @@ -597,7 +638,9 @@ pub fn dragUpdate(p: *Pardes, mcol: u16, mrow: u16, body_hit: ?Mouse.BodyHit) vo if (k + 1 < p.col_n[cc]) { const a = p.rects[p.col_panes[cc][k]]; const b = p.rects[p.col_panes[cc][k + 1]]; - d.cur_y = clampBorderRow(a.y, a.h, b.h, mrow, p.settings.tag_bottom); + // Held by the rule over the lower pane: the seam is the row above. + const seam = if (p.settings.tag_bottom) mrow else mrow -| 1; + d.cur_y = clampBorderRow(a.y, a.h, b.h, seam, p.settings.tag_bottom); } else d.cur_y = mrow; }, .column_move => |*d| d.cur_x = mcol, diff --git a/src/pardes.zig b/src/pardes.zig index ea03b3eb..8dbe6744 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -3483,6 +3483,11 @@ pub const Mouse = struct { ctrl: bool = false, body_hit: ?BodyHit = null, tag_hit: ?TagHit = null, + /// A pixel frontend's press on the rule it draws at the top of this row, + /// which is where two panes meet if they do: the one grab that resizes + /// them there. A row of text never is one; a terminal, with no rule, + /// resizes by the grip. + rule_above: bool = false, }; pub const Key = struct { diff --git a/src/tutor.txt b/src/tutor.txt index 0893415e..c59deeae 100644 --- a/src/tutor.txt +++ b/src/tutor.txt @@ -132,6 +132,15 @@ DelAbove close it, giving the space to the pane above (Del k) DelBelow close it, giving the space to the pane below (Del j) + RESIZE, AS ACME MOVES A WINDOW + the grip (the box left of a pane's tag) drag it: drop it in another + column to move the pane there, or up or down its own + column to move its top, the pane above taking or giving + the rows -- down to its tag alone + the rule in the GUI, the line between two panes drags their seam + a column's right edge drags the column's width + A pane's last row is text like any other, never a handle. + A new file opens in a new COLUMN only if both panes would still get 100 columns of width. Otherwise it stacks in the one you are in. |
