renderer: make cursorStyle depend on RenderState
This makes `cursorStyle` utilize `RenderState` to determine the appropriate cursor style. This moves the cursor style logic outside the critical area, although it was cheap to begin with. This always removes `viewport_is_bottom` which had no practical use.pull/9672/head
parent
6e9412cbab
commit
df466f3c73
|
|
@ -1,6 +1,5 @@
|
|||
const std = @import("std");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const State = @import("State.zig");
|
||||
|
||||
/// Available cursor styles for drawing that renderers must support.
|
||||
/// This is a superset of terminal cursor styles since the renderer supports
|
||||
|
|
@ -26,64 +25,65 @@ pub const Style = enum {
|
|||
}
|
||||
};
|
||||
|
||||
pub const StyleOptions = struct {
|
||||
preedit: bool = false,
|
||||
focused: bool = false,
|
||||
blink_visible: bool = false,
|
||||
};
|
||||
|
||||
/// Returns the cursor style to use for the current render state or null
|
||||
/// if a cursor should not be rendered at all.
|
||||
pub fn style(
|
||||
state: *State,
|
||||
focused: bool,
|
||||
blink_visible: bool,
|
||||
state: *const terminal.RenderState,
|
||||
opts: StyleOptions,
|
||||
) ?Style {
|
||||
// Note the order of conditionals below is important. It represents
|
||||
// a priority system of how we determine what state overrides cursor
|
||||
// visibility and style.
|
||||
|
||||
// The cursor is only at the bottom of the viewport. If we aren't
|
||||
// at the bottom, we never render the cursor. The cursor x/y is by
|
||||
// viewport so if we are above the viewport, we'll end up rendering
|
||||
// the cursor in some random part of the screen.
|
||||
if (!state.terminal.screens.active.viewportIsBottom()) return null;
|
||||
// The cursor must be visible in the viewport to be rendered.
|
||||
if (state.cursor.viewport == null) return null;
|
||||
|
||||
// If we are in preedit, then we always show the block cursor. We do
|
||||
// this even if the cursor is explicitly not visible because it shows
|
||||
// an important editing state to the user.
|
||||
if (state.preedit != null) return .block;
|
||||
if (opts.preedit) return .block;
|
||||
|
||||
// If we're at a password input its always a lock.
|
||||
if (state.cursor.password_input) return .lock;
|
||||
|
||||
// If the cursor is explicitly not visible by terminal mode, we don't render.
|
||||
if (!state.terminal.modes.get(.cursor_visible)) return null;
|
||||
if (!state.cursor.visible) return null;
|
||||
|
||||
// If we're not focused, our cursor is always visible so that
|
||||
// we can show the hollow box.
|
||||
if (!focused) return .block_hollow;
|
||||
if (!opts.focused) return .block_hollow;
|
||||
|
||||
// If the cursor is blinking and our blink state is not visible,
|
||||
// then we don't show the cursor.
|
||||
if (state.terminal.modes.get(.cursor_blinking) and !blink_visible) {
|
||||
return null;
|
||||
}
|
||||
if (state.cursor.blinking and !opts.blink_visible) return null;
|
||||
|
||||
// Otherwise, we use whatever style the terminal wants.
|
||||
return .fromTerminal(state.terminal.screens.active.cursor.cursor_style);
|
||||
return .fromTerminal(state.cursor.visual_style);
|
||||
}
|
||||
|
||||
test "cursor: default uses configured style" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
|
||||
var term: terminal.Terminal = try .init(alloc, .{ .cols = 10, .rows = 10 });
|
||||
defer term.deinit(alloc);
|
||||
|
||||
term.screens.active.cursor.cursor_style = .bar;
|
||||
term.modes.set(.cursor_blinking, true);
|
||||
|
||||
var state: State = .{
|
||||
.mutex = undefined,
|
||||
.terminal = &term,
|
||||
.preedit = null,
|
||||
};
|
||||
var state: terminal.RenderState = .empty;
|
||||
defer state.deinit(alloc);
|
||||
try state.update(alloc, &term);
|
||||
|
||||
try testing.expect(style(&state, true, true) == .bar);
|
||||
try testing.expect(style(&state, false, true) == .block_hollow);
|
||||
try testing.expect(style(&state, false, false) == .block_hollow);
|
||||
try testing.expect(style(&state, true, false) == null);
|
||||
try testing.expect(style(&state, .{ .preedit = false, .focused = true, .blink_visible = true }) == .bar);
|
||||
try testing.expect(style(&state, .{ .preedit = false, .focused = false, .blink_visible = true }) == .block_hollow);
|
||||
try testing.expect(style(&state, .{ .preedit = false, .focused = false, .blink_visible = false }) == .block_hollow);
|
||||
try testing.expect(style(&state, .{ .preedit = false, .focused = true, .blink_visible = false }) == null);
|
||||
}
|
||||
|
||||
test "cursor: blinking disabled" {
|
||||
|
|
@ -95,16 +95,14 @@ test "cursor: blinking disabled" {
|
|||
term.screens.active.cursor.cursor_style = .bar;
|
||||
term.modes.set(.cursor_blinking, false);
|
||||
|
||||
var state: State = .{
|
||||
.mutex = undefined,
|
||||
.terminal = &term,
|
||||
.preedit = null,
|
||||
};
|
||||
var state: terminal.RenderState = .empty;
|
||||
defer state.deinit(alloc);
|
||||
try state.update(alloc, &term);
|
||||
|
||||
try testing.expect(style(&state, true, true) == .bar);
|
||||
try testing.expect(style(&state, true, false) == .bar);
|
||||
try testing.expect(style(&state, false, true) == .block_hollow);
|
||||
try testing.expect(style(&state, false, false) == .block_hollow);
|
||||
try testing.expect(style(&state, .{ .focused = true, .blink_visible = true }) == .bar);
|
||||
try testing.expect(style(&state, .{ .focused = true, .blink_visible = false }) == .bar);
|
||||
try testing.expect(style(&state, .{ .focused = false, .blink_visible = true }) == .block_hollow);
|
||||
try testing.expect(style(&state, .{ .focused = false, .blink_visible = false }) == .block_hollow);
|
||||
}
|
||||
|
||||
test "cursor: explicitly not visible" {
|
||||
|
|
@ -117,16 +115,14 @@ test "cursor: explicitly not visible" {
|
|||
term.modes.set(.cursor_visible, false);
|
||||
term.modes.set(.cursor_blinking, false);
|
||||
|
||||
var state: State = .{
|
||||
.mutex = undefined,
|
||||
.terminal = &term,
|
||||
.preedit = null,
|
||||
};
|
||||
var state: terminal.RenderState = .empty;
|
||||
defer state.deinit(alloc);
|
||||
try state.update(alloc, &term);
|
||||
|
||||
try testing.expect(style(&state, true, true) == null);
|
||||
try testing.expect(style(&state, true, false) == null);
|
||||
try testing.expect(style(&state, false, true) == null);
|
||||
try testing.expect(style(&state, false, false) == null);
|
||||
try testing.expect(style(&state, .{ .focused = true, .blink_visible = true }) == null);
|
||||
try testing.expect(style(&state, .{ .focused = true, .blink_visible = false }) == null);
|
||||
try testing.expect(style(&state, .{ .focused = false, .blink_visible = true }) == null);
|
||||
try testing.expect(style(&state, .{ .focused = false, .blink_visible = false }) == null);
|
||||
}
|
||||
|
||||
test "cursor: always block with preedit" {
|
||||
|
|
@ -135,25 +131,24 @@ test "cursor: always block with preedit" {
|
|||
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
|
||||
defer term.deinit(alloc);
|
||||
|
||||
var state: State = .{
|
||||
.mutex = undefined,
|
||||
.terminal = &term,
|
||||
.preedit = .{},
|
||||
};
|
||||
var state: terminal.RenderState = .empty;
|
||||
defer state.deinit(alloc);
|
||||
try state.update(alloc, &term);
|
||||
|
||||
// In any bool state
|
||||
try testing.expect(style(&state, false, false) == .block);
|
||||
try testing.expect(style(&state, true, false) == .block);
|
||||
try testing.expect(style(&state, true, true) == .block);
|
||||
try testing.expect(style(&state, false, true) == .block);
|
||||
try testing.expect(style(&state, .{ .preedit = true, .focused = false, .blink_visible = false }) == .block);
|
||||
try testing.expect(style(&state, .{ .preedit = true, .focused = true, .blink_visible = false }) == .block);
|
||||
try testing.expect(style(&state, .{ .preedit = true, .focused = true, .blink_visible = true }) == .block);
|
||||
try testing.expect(style(&state, .{ .preedit = true, .focused = false, .blink_visible = true }) == .block);
|
||||
|
||||
// If we're scrolled though, then we don't show the cursor.
|
||||
for (0..100) |_| try term.index();
|
||||
try term.scrollViewport(.{ .top = {} });
|
||||
try state.update(alloc, &term);
|
||||
|
||||
// In any bool state
|
||||
try testing.expect(style(&state, false, false) == null);
|
||||
try testing.expect(style(&state, true, false) == null);
|
||||
try testing.expect(style(&state, true, true) == null);
|
||||
try testing.expect(style(&state, false, true) == null);
|
||||
try testing.expect(style(&state, .{ .preedit = true, .focused = false, .blink_visible = false }) == null);
|
||||
try testing.expect(style(&state, .{ .preedit = true, .focused = true, .blink_visible = false }) == null);
|
||||
try testing.expect(style(&state, .{ .preedit = true, .focused = true, .blink_visible = true }) == null);
|
||||
try testing.expect(style(&state, .{ .preedit = true, .focused = false, .blink_visible = true }) == null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1090,7 +1090,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
links: terminal.RenderState.CellSet,
|
||||
mouse: renderer.State.Mouse,
|
||||
preedit: ?renderer.State.Preedit,
|
||||
cursor_style: ?renderer.CursorStyle,
|
||||
scrollbar: terminal.Scrollbar,
|
||||
};
|
||||
|
||||
|
|
@ -1122,19 +1121,8 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
// cross-thread mailbox message within the IO path.
|
||||
const scrollbar = state.terminal.screens.active.pages.scrollbar();
|
||||
|
||||
// Whether to draw our cursor or not.
|
||||
const cursor_style = if (state.terminal.flags.password_input)
|
||||
.lock
|
||||
else
|
||||
renderer.cursorStyle(
|
||||
state,
|
||||
self.focused,
|
||||
cursor_blink_visible,
|
||||
);
|
||||
|
||||
// Get our preedit state
|
||||
const preedit: ?renderer.State.Preedit = preedit: {
|
||||
if (cursor_style == null) break :preedit null;
|
||||
const p = state.preedit orelse break :preedit null;
|
||||
break :preedit try p.clone(arena_alloc);
|
||||
};
|
||||
|
|
@ -1175,7 +1163,6 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
.links = links,
|
||||
.mouse = state.mouse,
|
||||
.preedit = preedit,
|
||||
.cursor_style = cursor_style,
|
||||
.scrollbar = scrollbar,
|
||||
};
|
||||
};
|
||||
|
|
@ -1195,7 +1182,11 @@ pub fn Renderer(comptime GraphicsAPI: type) type {
|
|||
// Build our GPU cells
|
||||
try self.rebuildCells(
|
||||
critical.preedit,
|
||||
critical.cursor_style,
|
||||
renderer.cursorStyle(&self.terminal_state, .{
|
||||
.preedit = critical.preedit != null,
|
||||
.focused = self.focused,
|
||||
.blink_visible = cursor_blink_visible,
|
||||
}),
|
||||
&critical.links,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
|
|||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const fastmem = @import("../fastmem.zig");
|
||||
const color = @import("color.zig");
|
||||
const cursor = @import("cursor.zig");
|
||||
const point = @import("point.zig");
|
||||
const size = @import("size.zig");
|
||||
const page = @import("page.zig");
|
||||
|
|
@ -56,10 +57,6 @@ pub const RenderState = struct {
|
|||
rows: size.CellCountInt,
|
||||
cols: size.CellCountInt,
|
||||
|
||||
/// The viewport is at the bottom of the terminal, viewing the active
|
||||
/// area and scrolling with new output.
|
||||
viewport_is_bottom: bool,
|
||||
|
||||
/// The color state for the terminal.
|
||||
colors: Colors,
|
||||
|
||||
|
|
@ -96,7 +93,6 @@ pub const RenderState = struct {
|
|||
pub const empty: RenderState = .{
|
||||
.rows = 0,
|
||||
.cols = 0,
|
||||
.viewport_is_bottom = false,
|
||||
.colors = .{
|
||||
.background = .{},
|
||||
.foreground = .{},
|
||||
|
|
@ -108,6 +104,10 @@ pub const RenderState = struct {
|
|||
.viewport = null,
|
||||
.cell = .{},
|
||||
.style = undefined,
|
||||
.visual_style = .block,
|
||||
.password_input = false,
|
||||
.visible = true,
|
||||
.blinking = false,
|
||||
},
|
||||
.row_data = .empty,
|
||||
.dirty = .false,
|
||||
|
|
@ -140,6 +140,19 @@ pub const RenderState = struct {
|
|||
/// The style, always valid even if the cell is default style.
|
||||
style: Style,
|
||||
|
||||
/// The visual style of the cursor itself, such as a block or
|
||||
/// bar.
|
||||
visual_style: cursor.Style,
|
||||
|
||||
/// True if the cursor is detected to be at a password input field.
|
||||
password_input: bool,
|
||||
|
||||
/// Cursor visibility state determined by the terminal mode.
|
||||
visible: bool,
|
||||
|
||||
/// Cursor blink state determined by the terminal mode.
|
||||
blinking: bool,
|
||||
|
||||
pub const Viewport = struct {
|
||||
/// The x/y position of the cursor within the viewport.
|
||||
x: size.CellCountInt,
|
||||
|
|
@ -279,11 +292,14 @@ pub const RenderState = struct {
|
|||
// Always set our cheap fields, its more expensive to compare
|
||||
self.rows = s.pages.rows;
|
||||
self.cols = s.pages.cols;
|
||||
self.viewport_is_bottom = s.viewportIsBottom();
|
||||
self.viewport_pin = viewport_pin;
|
||||
self.cursor.active = .{ .x = s.cursor.x, .y = s.cursor.y };
|
||||
self.cursor.cell = s.cursor.page_cell.*;
|
||||
self.cursor.style = s.cursor.style;
|
||||
self.cursor.visual_style = s.cursor.cursor_style;
|
||||
self.cursor.password_input = t.flags.password_input;
|
||||
self.cursor.visible = t.modes.get(.cursor_visible);
|
||||
self.cursor.blinking = t.modes.get(.cursor_blinking);
|
||||
|
||||
// Always reset the cursor viewport position. In the future we can
|
||||
// probably cache this by comparing the cursor pin and viewport pin
|
||||
|
|
|
|||
Loading…
Reference in New Issue