flow/src/renderer/win32/renderer.zig
Jonathan Marler 21a7106fe3 win32 gui: mouse drag
initial implementation for left mouse drag
2025-01-06 08:35:16 +01:00

430 lines
13 KiB
Zig

const Self = @This();
pub const log_name = "renderer";
const std = @import("std");
const cbor = @import("cbor");
const vaxis = @import("vaxis");
const Style = @import("theme").Style;
const Color = @import("theme").Color;
pub const CursorShape = vaxis.Cell.CursorShape;
pub const Plane = @import("tuirenderer").Plane;
const input = @import("input");
const win32 = @import("win32").everything;
pub const Cell = @import("tuirenderer").Cell;
pub const StyleBits = @import("tuirenderer").style;
const gui = @import("gui");
const DropWriter = gui.DropWriter;
pub const style = StyleBits;
allocator: std.mem.Allocator,
vx: vaxis.Vaxis,
renders_missed: u32 = 0,
handler_ctx: *anyopaque,
dispatch_input: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
dispatch_mouse: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
dispatch_mouse_drag: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
dispatch_event: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
thread: ?std.Thread = null,
const global = struct {
var init_called: bool = false;
};
fn oom(e: error{OutOfMemory}) noreturn {
@panic(@errorName(e));
}
pub fn init(
allocator: std.mem.Allocator,
handler_ctx: *anyopaque,
no_alternate: bool,
) !Self {
std.debug.assert(!global.init_called);
global.init_called = true;
_ = no_alternate;
gui.init();
const opts: vaxis.Vaxis.Options = .{
.kitty_keyboard_flags = .{
.disambiguate = true,
.report_events = true,
.report_alternate_keys = true,
.report_all_as_ctl_seqs = true,
.report_text = true,
},
.system_clipboard_allocator = allocator,
};
var result = .{
.allocator = allocator,
.vx = try vaxis.init(allocator, opts),
.handler_ctx = handler_ctx,
};
result.vx.caps.unicode = .unicode;
result.vx.screen.width_method = .unicode;
return result;
}
pub fn deinit(self: *Self) void {
std.log.warn("TODO: implement win32 renderer deinit", .{});
var drop_writer = DropWriter{};
self.vx.deinit(self.allocator, drop_writer.writer().any());
}
threadlocal var thread_is_panicing = false;
pub fn panic(
msg: []const u8,
error_return_trace: ?*std.builtin.StackTrace,
ret_addr: ?usize,
) noreturn {
if (!thread_is_panicing) {
thread_is_panicing = true;
const msg_z: [:0]const u8 = if (std.fmt.allocPrintZ(
std.heap.page_allocator,
"{s}",
.{msg},
)) |msg_z| msg_z else |_| "failed allocate error message";
_ = win32.MessageBoxA(null, msg_z, "Flow Panic", .{ .ICONASTERISK = 1 });
}
std.builtin.default_panic(msg, error_return_trace, ret_addr);
}
pub fn run(self: *Self) !void {
if (self.thread) |_| return;
// dummy resize to fully init vaxis
const drop_writer = DropWriter{};
try self.vx.resize(
self.allocator,
drop_writer.writer().any(),
.{ .rows = 25, .cols = 80, .x_pixel = 0, .y_pixel = 0 },
);
self.thread = try gui.start();
}
pub fn fmtmsg(buf: []u8, value: anytype) []const u8 {
var fbs = std.io.fixedBufferStream(buf);
cbor.writeValue(fbs.writer(), value) catch |e| switch (e) {
error.NoSpaceLeft => std.debug.panic("buffer of size {} not big enough", .{buf.len}),
};
return buf[0..fbs.pos];
}
pub fn render(self: *Self) error{}!void {
if (!gui.updateScreen(&self.vx.screen)) {
self.renders_missed += 1;
std.log.warn("missed {} renders, no gui window yet", .{self.renders_missed});
}
}
pub fn stop(self: *Self) void {
_ = self;
std.log.warn("TODO: implement stop", .{});
}
pub fn stdplane(self: *Self) Plane {
const name = "root";
var plane: Plane = .{
.window = self.vx.window(),
.name_buf = undefined,
.name_len = name.len,
};
@memcpy(plane.name_buf[0..name.len], name);
return plane;
}
pub fn process_renderer_event(self: *Self, msg: []const u8) !void {
const Input = struct {
kind: u8,
codepoint: u21,
shifted_codepoint: u21,
text: []const u8,
mods: u8,
};
const MousePos = struct {
col: i32,
row: i32,
xoffset: i32,
yoffset: i32,
};
const Winsize = struct {
cell_width: u16,
cell_height: u16,
pixel_width: u16,
pixel_height: u16,
};
{
var args: Input = undefined;
if (try cbor.match(msg, .{
cbor.any,
"I",
cbor.extract(&args.kind),
cbor.extract(&args.codepoint),
cbor.extract(&args.shifted_codepoint),
cbor.extract(&args.text),
cbor.extract(&args.mods),
})) {
var buf: [300]u8 = undefined;
const cbor_msg = fmtmsg(&buf, .{
"I",
args.kind,
args.codepoint,
args.shifted_codepoint,
args.text,
args.mods,
});
if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg);
return;
}
}
{
var args: Winsize = undefined;
if (try cbor.match(msg, .{
cbor.any,
"Resize",
cbor.extract(&args.cell_width),
cbor.extract(&args.cell_height),
cbor.extract(&args.pixel_width),
cbor.extract(&args.pixel_height),
})) {
var drop_writer = DropWriter{};
self.vx.resize(self.allocator, drop_writer.writer().any(), .{
.rows = @intCast(args.cell_height),
.cols = @intCast(args.cell_width),
.x_pixel = @intCast(args.pixel_width),
.y_pixel = @intCast(args.pixel_height),
}) catch |err| std.debug.panic("resize failed with {s}", .{@errorName(err)});
self.vx.queueRefresh();
{
var buf: [200]u8 = undefined;
if (self.dispatch_event) |f| f(self.handler_ctx, fmtmsg(&buf, .{"resize"}));
}
return;
}
}
{
var args: MousePos = undefined;
if (try cbor.match(msg, .{
cbor.any,
"M",
cbor.extract(&args.col),
cbor.extract(&args.row),
cbor.extract(&args.xoffset),
cbor.extract(&args.yoffset),
})) {
var buf: [200]u8 = undefined;
if (self.dispatch_mouse) |f| f(
self.handler_ctx,
@intCast(args.row),
@intCast(args.col),
fmtmsg(&buf, .{
"M",
args.col,
args.row,
args.xoffset,
args.yoffset,
}),
);
return;
}
}
{
var args: struct {
pos: MousePos,
button: struct {
press: u8,
id: u8,
},
} = undefined;
if (try cbor.match(msg, .{
cbor.any,
"B",
cbor.extract(&args.button.press),
cbor.extract(&args.button.id),
cbor.extract(&args.pos.col),
cbor.extract(&args.pos.row),
cbor.extract(&args.pos.xoffset),
cbor.extract(&args.pos.yoffset),
})) {
var buf: [200]u8 = undefined;
if (self.dispatch_mouse) |f| f(
self.handler_ctx,
@intCast(args.pos.row),
@intCast(args.pos.col),
fmtmsg(&buf, .{
"B",
args.button.press,
args.button.id,
input.utils.button_id_string(@enumFromInt(args.button.id)),
args.pos.col,
args.pos.row,
args.pos.xoffset,
args.pos.yoffset,
}),
);
return;
}
}
{
var args: struct {
pos: MousePos,
button_id: u8,
} = undefined;
if (try cbor.match(msg, .{
cbor.any,
"D",
cbor.extract(&args.button_id),
cbor.extract(&args.pos.col),
cbor.extract(&args.pos.row),
cbor.extract(&args.pos.xoffset),
cbor.extract(&args.pos.yoffset),
})) {
var buf: [200]u8 = undefined;
if (self.dispatch_mouse_drag) |f| f(
self.handler_ctx,
@intCast(args.pos.row),
@intCast(args.pos.col),
fmtmsg(&buf, .{
"D",
input.event.press,
args.button_id,
input.utils.button_id_string(@enumFromInt(args.button_id)),
args.pos.col,
args.pos.row,
args.pos.xoffset,
args.pos.yoffset,
}),
);
return;
}
}
return error.UnexpectedRendererEvent;
}
fn setEllipsis(str: []u16) void {
std.debug.assert(str.len >= 3);
str[str.len - 1] = '.';
str[str.len - 2] = '.';
str[str.len - 3] = '.';
}
const ConversionSizes = struct {
src_len: usize,
dst_len: usize,
};
fn calcUtf8ToUtf16LeWithMax(utf8: []const u8, max_dst_len: usize) !ConversionSizes {
var src_len: usize = 0;
var dst_len: usize = 0;
while (src_len < utf8.len) {
if (dst_len >= max_dst_len) break;
const n = try std.unicode.utf8ByteSequenceLength(utf8[src_len]);
const next_src_len = src_len + n;
const codepoint = try std.unicode.utf8Decode(utf8[src_len..next_src_len]);
if (codepoint < 0x10000) {
dst_len += 1;
} else {
if (dst_len + 2 > max_dst_len) break;
dst_len += 2;
}
src_len = next_src_len;
}
return .{ .src_len = src_len, .dst_len = dst_len };
}
pub fn set_terminal_title(self: *Self, title_utf8: []const u8) void {
_ = self;
const max_title_wide = 500;
const conversion_sizes = calcUtf8ToUtf16LeWithMax(title_utf8, max_title_wide) catch {
std.log.err("title is invalid UTF-8", .{});
return;
};
var title_wide_buf: [max_title_wide + 1]u16 = undefined;
const len = @min(max_title_wide, conversion_sizes.dst_len);
title_wide_buf[len] = 0;
const title_wide = title_wide_buf[0..len :0];
const size = std.unicode.utf8ToUtf16Le(title_wide, title_utf8[0..conversion_sizes.src_len]) catch |err| switch (err) {
error.InvalidUtf8 => {
std.log.err("title is invalid UTF-8", .{});
return;
},
};
std.debug.assert(size == conversion_sizes.dst_len);
if (conversion_sizes.src_len != title_utf8.len) {
setEllipsis(title_wide);
}
var win32_err: gui.Win32Error = undefined;
gui.setWindowTitle(title_wide, &win32_err) catch |err| switch (err) {
error.NoWindow => std.log.warn("no window to set the title for", .{}),
error.Win32 => std.log.err("{s} failed with {}", .{ win32_err.what, win32_err.code.fmt() }),
};
}
pub fn set_terminal_style(self: *Self, style_: Style) void {
_ = self;
_ = style_;
std.log.warn("TODO: implement set_terminal_style", .{});
//if (style_.fg) |color|
//self.vx.setTerminalForegroundColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color.color)).rgb) catch {};
//if (style_.bg) |color|
//self.vx.setTerminalBackgroundColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color.color)).rgb) catch {};
}
pub fn set_terminal_cursor_color(self: *Self, color: Color) void {
_ = self;
std.log.warn("TODO: set_terminal_cursor_color '{any}'", .{color});
//self.vx.setTerminalCursorColor(self.tty.anyWriter(), vaxis.Cell.Color.rgbFromUint(@intCast(color.color)).rgb) catch {};
}
pub fn set_terminal_working_directory(self: *Self, absolute_path: []const u8) void {
_ = self;
std.log.warn("TODO: set_terminal_working_directory '{s}'", .{absolute_path});
//self.vx.setTerminalWorkingDirectory(self.tty.anyWriter(), absolute_path) catch {};
}
pub fn copy_to_system_clipboard(self: *Self, text: []const u8) void {
_ = self;
_ = text;
std.log.warn("TODO: copy_to_system_clipboard", .{});
}
pub const copy_to_windows_clipboard = @import("tuirenderer").copy_to_windows_clipboard;
pub const request_windows_clipboard = @import("tuirenderer").request_windows_clipboard;
pub fn request_mouse_cursor_text(self: *Self, push_or_pop: bool) void {
_ = self;
_ = push_or_pop;
//@panic("todo");
}
pub fn request_mouse_cursor_pointer(self: *Self, push_or_pop: bool) void {
_ = self;
_ = push_or_pop;
//@panic("todo");
}
pub fn request_mouse_cursor_default(self: *Self, push_or_pop: bool) void {
_ = self;
_ = push_or_pop;
//@panic("todo");
}
pub fn cursor_enable(self: *Self, y: c_int, x: c_int, shape: CursorShape) !void {
_ = self;
_ = y;
_ = x;
_ = shape;
//@panic("todo");
}
pub fn cursor_disable(self: *Self) void {
_ = self;
//@panic("todo");
}