feat(gui): clipboard, title, cursor, attention
This commit is contained in:
parent
273be78055
commit
b1b50b7ff0
2 changed files with 118 additions and 5 deletions
|
|
@ -42,6 +42,26 @@ var font_name_len: usize = 0;
|
||||||
var font_dirty: std.atomic.Value(bool) = .init(true);
|
var font_dirty: std.atomic.Value(bool) = .init(true);
|
||||||
var stop_requested: std.atomic.Value(bool) = .init(false);
|
var stop_requested: std.atomic.Value(bool) = .init(false);
|
||||||
|
|
||||||
|
// Window title (written from TUI thread, applied by wio thread)
|
||||||
|
var title_mutex: std.Thread.Mutex = .{};
|
||||||
|
var title_buf: [512]u8 = undefined;
|
||||||
|
var title_len: usize = 0;
|
||||||
|
var title_dirty: std.atomic.Value(bool) = .init(false);
|
||||||
|
|
||||||
|
// Clipboard write (heap-allocated, transferred to wio thread)
|
||||||
|
var clipboard_mutex: std.Thread.Mutex = .{};
|
||||||
|
var clipboard_write: ?[]u8 = null;
|
||||||
|
|
||||||
|
// Clipboard read request
|
||||||
|
var clipboard_read_pending: std.atomic.Value(bool) = .init(false);
|
||||||
|
|
||||||
|
// Mouse cursor (stored as wio.Cursor tag value)
|
||||||
|
var pending_cursor: std.atomic.Value(u8) = .init(@intFromEnum(wio.Cursor.arrow));
|
||||||
|
var cursor_dirty: std.atomic.Value(bool) = .init(false);
|
||||||
|
|
||||||
|
// Window attention request
|
||||||
|
var attention_pending: std.atomic.Value(bool) = .init(false);
|
||||||
|
|
||||||
// Current font — written and read only from the wio thread (after gpu.init).
|
// Current font — written and read only from the wio thread (after gpu.init).
|
||||||
var wio_font: gpu.Font = .{ .cell_size = .{ .x = 8, .y = 16 } };
|
var wio_font: gpu.Font = .{ .cell_size = .{ .x = 8, .y = 16 } };
|
||||||
|
|
||||||
|
|
@ -136,6 +156,53 @@ pub fn setFontFace(name: []const u8) void {
|
||||||
requestRender();
|
requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setWindowTitle(title: []const u8) void {
|
||||||
|
title_mutex.lock();
|
||||||
|
defer title_mutex.unlock();
|
||||||
|
const copy_len = @min(title.len, title_buf.len);
|
||||||
|
@memcpy(title_buf[0..copy_len], title[0..copy_len]);
|
||||||
|
title_len = copy_len;
|
||||||
|
title_dirty.store(true, .release);
|
||||||
|
wio.cancelWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setClipboard(text: []const u8) void {
|
||||||
|
const allocator = gpa.allocator();
|
||||||
|
const copy = allocator.dupe(u8, text) catch return;
|
||||||
|
clipboard_mutex.lock();
|
||||||
|
defer clipboard_mutex.unlock();
|
||||||
|
if (clipboard_write) |old| allocator.free(old);
|
||||||
|
clipboard_write = copy;
|
||||||
|
wio.cancelWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn requestClipboard() void {
|
||||||
|
clipboard_read_pending.store(true, .release);
|
||||||
|
wio.cancelWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setMouseCursor(shape: vaxis.Mouse.Shape) void {
|
||||||
|
const cursor: wio.Cursor = switch (shape) {
|
||||||
|
.default => .arrow,
|
||||||
|
.text => .text,
|
||||||
|
.pointer => .hand,
|
||||||
|
.help => .arrow,
|
||||||
|
.progress => .arrow_busy,
|
||||||
|
.wait => .busy,
|
||||||
|
.@"ew-resize" => .size_ew,
|
||||||
|
.@"ns-resize" => .size_ns,
|
||||||
|
.cell => .crosshair,
|
||||||
|
};
|
||||||
|
pending_cursor.store(@intFromEnum(cursor), .release);
|
||||||
|
cursor_dirty.store(true, .release);
|
||||||
|
wio.cancelWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn requestAttention() void {
|
||||||
|
attention_pending.store(true, .release);
|
||||||
|
wio.cancelWait();
|
||||||
|
}
|
||||||
|
|
||||||
// ── Internal helpers (wio thread only) ────────────────────────────────────
|
// ── Internal helpers (wio thread only) ────────────────────────────────────
|
||||||
|
|
||||||
// Reload wio_font from current settings. Called only from the wio thread.
|
// Reload wio_font from current settings. Called only from the wio thread.
|
||||||
|
|
@ -324,10 +391,42 @@ fn wioLoop() void {
|
||||||
const row_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(mouse_pos.y)), wio_font.cell_size.y));
|
const row_cell: i32 = @intCast(@divTrunc(@as(i32, @intCast(mouse_pos.y)), wio_font.cell_size.y));
|
||||||
tui_pid.send(.{ "RDR", "B", @as(u8, 1), btn_id, col_cell, row_cell, @as(i32, 0), @as(i32, 0) }) catch {};
|
tui_pid.send(.{ "RDR", "B", @as(u8, 1), btn_id, col_cell, row_cell, @as(i32, 0), @as(i32, 0) }) catch {};
|
||||||
},
|
},
|
||||||
|
.focused => window.enableTextInput(.{}),
|
||||||
|
.unfocused => window.disableTextInput(),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply pending cross-thread requests from the TUI thread.
|
||||||
|
if (title_dirty.swap(false, .acq_rel)) {
|
||||||
|
title_mutex.lock();
|
||||||
|
const t = title_buf[0..title_len];
|
||||||
|
title_mutex.unlock();
|
||||||
|
window.setTitle(t);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
clipboard_mutex.lock();
|
||||||
|
const pending = clipboard_write;
|
||||||
|
clipboard_write = null;
|
||||||
|
clipboard_mutex.unlock();
|
||||||
|
if (pending) |text| {
|
||||||
|
defer allocator.free(text);
|
||||||
|
window.setClipboardText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (clipboard_read_pending.swap(false, .acq_rel)) {
|
||||||
|
if (window.getClipboardText(allocator)) |text| {
|
||||||
|
defer allocator.free(text);
|
||||||
|
tui_pid.send(.{ "RDR", "system_clipboard", text }) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cursor_dirty.swap(false, .acq_rel)) {
|
||||||
|
window.setCursor(@enumFromInt(pending_cursor.load(.acquire)));
|
||||||
|
}
|
||||||
|
if (attention_pending.swap(false, .acq_rel)) {
|
||||||
|
window.requestAttention();
|
||||||
|
}
|
||||||
|
|
||||||
// Paint if the tui pushed new screen data.
|
// Paint if the tui pushed new screen data.
|
||||||
// Take ownership of the snap (set screen_snap = null under the mutex)
|
// Take ownership of the snap (set screen_snap = null under the mutex)
|
||||||
// so the TUI thread cannot free the backing memory while we use it.
|
// so the TUI thread cannot free the backing memory while we use it.
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,18 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var text: []const u8 = undefined;
|
||||||
|
if (try cbor.match(msg, .{
|
||||||
|
cbor.any,
|
||||||
|
"system_clipboard",
|
||||||
|
cbor.extract(&text),
|
||||||
|
})) {
|
||||||
|
if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", text }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return error.UnexpectedRendererEvent;
|
return error.UnexpectedRendererEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,7 +347,7 @@ pub fn set_sgr_pixel_mode_support(self: *Self, enable: bool) void {
|
||||||
|
|
||||||
pub fn set_terminal_title(self: *Self, text: []const u8) void {
|
pub fn set_terminal_title(self: *Self, text: []const u8) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = text;
|
app.setWindowTitle(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_terminal_style(self: *Self, style_: Style) void {
|
pub fn set_terminal_style(self: *Self, style_: Style) void {
|
||||||
|
|
@ -398,23 +410,26 @@ pub fn request_windows_clipboard(self: *Self) void {
|
||||||
|
|
||||||
pub fn request_mouse_cursor(self: *Self, shape: MouseCursorShape, push_or_pop: bool) void {
|
pub fn request_mouse_cursor(self: *Self, shape: MouseCursorShape, push_or_pop: bool) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = shape;
|
|
||||||
_ = push_or_pop;
|
_ = push_or_pop;
|
||||||
|
app.setMouseCursor(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_mouse_cursor_text(self: *Self, push_or_pop: bool) void {
|
pub fn request_mouse_cursor_text(self: *Self, push_or_pop: bool) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = push_or_pop;
|
_ = push_or_pop;
|
||||||
|
app.setMouseCursor(.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_mouse_cursor_pointer(self: *Self, push_or_pop: bool) void {
|
pub fn request_mouse_cursor_pointer(self: *Self, push_or_pop: bool) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = push_or_pop;
|
_ = push_or_pop;
|
||||||
|
app.setMouseCursor(.pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_mouse_cursor_default(self: *Self, push_or_pop: bool) void {
|
pub fn request_mouse_cursor_default(self: *Self, push_or_pop: bool) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = push_or_pop;
|
_ = push_or_pop;
|
||||||
|
app.setMouseCursor(.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cursor_enable(self: *Self, y: i32, x: i32, shape: CursorShape) !void {
|
pub fn cursor_enable(self: *Self, y: i32, x: i32, shape: CursorShape) !void {
|
||||||
|
|
@ -440,11 +455,10 @@ pub fn show_multi_cursor_yx(self: *Self, y: i32, x: i32) !void {
|
||||||
|
|
||||||
pub fn copy_to_system_clipboard(self: *Self, text: []const u8) void {
|
pub fn copy_to_system_clipboard(self: *Self, text: []const u8) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = text;
|
app.setClipboard(text);
|
||||||
// TODO: implement
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_system_clipboard(self: *Self) void {
|
pub fn request_system_clipboard(self: *Self) void {
|
||||||
_ = self;
|
_ = self;
|
||||||
// TODO: implement
|
app.requestClipboard();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue