fix: Add table clipping + cursor animation API
Table clipping: - Add clip region around table content area - Prevents cell text from drawing outside table bounds - Header and scrollbar render outside clip region Cursor animation API: - Add CURSOR_IDLE_TIMEOUT_MS (5s) and CURSOR_BLINK_PERIOD_MS (500ms) constants - Add needsCursorAnimation() to check if cursor should blink - Add getAnimationTimeout() for dynamic event loop timeout - Update TextInput to use constants from Context The application can now query ctx.getAnimationTimeout() to determine if a short timeout is needed for cursor animation, or if it can wait indefinitely for events. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e98646b442
commit
f3cdb213cf
3 changed files with 41 additions and 6 deletions
|
|
@ -99,6 +99,13 @@ pub const Context = struct {
|
||||||
/// Used for idle detection (e.g., cursor stops blinking after inactivity)
|
/// Used for idle detection (e.g., cursor stops blinking after inactivity)
|
||||||
last_input_time_ms: u64 = 0,
|
last_input_time_ms: u64 = 0,
|
||||||
|
|
||||||
|
/// Idle timeout for cursor blinking (ms). After this time without input,
|
||||||
|
/// cursor becomes solid and no animation frames are needed.
|
||||||
|
pub const CURSOR_IDLE_TIMEOUT_MS: u64 = 5000;
|
||||||
|
|
||||||
|
/// Cursor blink period (ms). Cursor toggles visibility at this rate.
|
||||||
|
pub const CURSOR_BLINK_PERIOD_MS: u64 = 500;
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
/// Frame statistics for performance monitoring
|
/// Frame statistics for performance monitoring
|
||||||
|
|
@ -288,6 +295,27 @@ pub const Context = struct {
|
||||||
return self.frame_delta_ms;
|
return self.frame_delta_ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if cursor animation is needed (for event loop timeout decisions).
|
||||||
|
/// Returns true if we're within the active period where cursor should blink.
|
||||||
|
/// The application should use a short timeout (e.g., 500ms) when this returns true,
|
||||||
|
/// and can use infinite timeout when false.
|
||||||
|
pub fn needsCursorAnimation(self: Self) bool {
|
||||||
|
if (self.current_time_ms == 0) return false;
|
||||||
|
const idle_time = self.current_time_ms -| self.last_input_time_ms;
|
||||||
|
return idle_time < CURSOR_IDLE_TIMEOUT_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get recommended event loop timeout in milliseconds.
|
||||||
|
/// Returns the time until next cursor blink toggle, or null for infinite wait.
|
||||||
|
pub fn getAnimationTimeout(self: Self) ?u32 {
|
||||||
|
if (!self.needsCursorAnimation()) return null;
|
||||||
|
|
||||||
|
// Calculate time until next blink toggle
|
||||||
|
const time_in_period = self.current_time_ms % CURSOR_BLINK_PERIOD_MS;
|
||||||
|
const time_until_toggle = CURSOR_BLINK_PERIOD_MS - time_in_period;
|
||||||
|
return @intCast(@max(time_until_toggle, 16)); // Minimum 16ms to avoid busy loop
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// ID Management
|
// ID Management
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Context = @import("../../core/context.zig").Context;
|
const Context = @import("../../core/context.zig").Context;
|
||||||
|
const Command = @import("../../core/command.zig");
|
||||||
const Layout = @import("../../core/layout.zig");
|
const Layout = @import("../../core/layout.zig");
|
||||||
|
|
||||||
// Re-export types
|
// Re-export types
|
||||||
|
|
@ -152,6 +153,11 @@ pub fn tableRectFull(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Begin clipping to table content area (excludes header)
|
||||||
|
// This prevents cell text from drawing outside the table bounds
|
||||||
|
const content_y = bounds.y + @as(i32, @intCast(header_h));
|
||||||
|
ctx.pushCommand(Command.clip(bounds.x, content_y, bounds.w, content_h));
|
||||||
|
|
||||||
// Calculate visible row range
|
// Calculate visible row range
|
||||||
const first_visible = table_state.scroll_row;
|
const first_visible = table_state.scroll_row;
|
||||||
const last_visible = @min(first_visible + visible_rows, table_state.row_count);
|
const last_visible = @min(first_visible + visible_rows, table_state.row_count);
|
||||||
|
|
@ -189,7 +195,10 @@ pub fn tableRectFull(
|
||||||
if (row_result.cell_edited) result.cell_edited = true;
|
if (row_result.cell_edited) result.cell_edited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw scrollbar if needed
|
// End clipping
|
||||||
|
ctx.pushCommand(Command.clipEnd());
|
||||||
|
|
||||||
|
// Draw scrollbar if needed (outside clip region)
|
||||||
if (table_state.row_count > visible_rows) {
|
if (table_state.row_count > visible_rows) {
|
||||||
render.drawScrollbar(ctx, bounds, table_state, visible_rows, config, colors);
|
render.drawScrollbar(ctx, bounds, table_state, visible_rows, config, colors);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -364,16 +364,14 @@ pub fn textInputRect(
|
||||||
if (ctx.current_time_ms == 0) break :blk true;
|
if (ctx.current_time_ms == 0) break :blk true;
|
||||||
|
|
||||||
// Check idle time (time since last user input)
|
// Check idle time (time since last user input)
|
||||||
const idle_timeout_ms: u64 = 5000; // 5 seconds
|
|
||||||
const idle_time = ctx.current_time_ms -| ctx.last_input_time_ms;
|
const idle_time = ctx.current_time_ms -| ctx.last_input_time_ms;
|
||||||
|
|
||||||
if (idle_time >= idle_timeout_ms) {
|
if (idle_time >= Context.CURSOR_IDLE_TIMEOUT_MS) {
|
||||||
// Idle: cursor always visible (solid, no blink)
|
// Idle: cursor always visible (solid, no blink)
|
||||||
break :blk true;
|
break :blk true;
|
||||||
} else {
|
} else {
|
||||||
// Active: cursor blinks (visible for 500ms, hidden for 500ms)
|
// Active: cursor blinks
|
||||||
const blink_period_ms: u64 = 500;
|
break :blk (ctx.current_time_ms / Context.CURSOR_BLINK_PERIOD_MS) % 2 == 0;
|
||||||
break :blk (ctx.current_time_ms / blink_period_ms) % 2 == 0;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue