win32 standalone gui
This commit is contained in:
parent
1ad6b8db3d
commit
1a595871dd
10 changed files with 1773 additions and 1 deletions
66
build.zig
66
build.zig
|
@ -8,6 +8,7 @@ pub fn build(b: *std.Build) void {
|
||||||
const strip = b.option(bool, "strip", "Disable debug information (default: no)");
|
const strip = b.option(bool, "strip", "Disable debug information (default: no)");
|
||||||
const use_llvm = b.option(bool, "use_llvm", "Enable llvm backend (default: none)");
|
const use_llvm = b.option(bool, "use_llvm", "Enable llvm backend (default: none)");
|
||||||
const pie = b.option(bool, "pie", "Produce an executable with position independent code (default: none)");
|
const pie = b.option(bool, "pie", "Produce an executable with position independent code (default: none)");
|
||||||
|
const gui = b.option(bool, "gui", "Standalone GUI mode") orelse false;
|
||||||
|
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the app");
|
||||||
const check_step = b.step("check", "Check the app");
|
const check_step = b.step("check", "Check the app");
|
||||||
|
@ -25,6 +26,7 @@ pub fn build(b: *std.Build) void {
|
||||||
strip,
|
strip,
|
||||||
use_llvm,
|
use_llvm,
|
||||||
pie,
|
pie,
|
||||||
|
gui,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@ fn build_development(
|
||||||
strip: ?bool,
|
strip: ?bool,
|
||||||
use_llvm: ?bool,
|
use_llvm: ?bool,
|
||||||
pie: ?bool,
|
pie: ?bool,
|
||||||
|
gui: bool,
|
||||||
) void {
|
) void {
|
||||||
const target = b.standardTargetOptions(.{ .default_target = .{ .abi = if (builtin.os.tag == .linux and !tracy_enabled) .musl else null } });
|
const target = b.standardTargetOptions(.{ .default_target = .{ .abi = if (builtin.os.tag == .linux and !tracy_enabled) .musl else null } });
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
@ -57,6 +60,7 @@ fn build_development(
|
||||||
strip orelse false,
|
strip orelse false,
|
||||||
use_llvm,
|
use_llvm,
|
||||||
pie,
|
pie,
|
||||||
|
gui,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +75,7 @@ fn build_release(
|
||||||
strip: ?bool,
|
strip: ?bool,
|
||||||
use_llvm: ?bool,
|
use_llvm: ?bool,
|
||||||
pie: ?bool,
|
pie: ?bool,
|
||||||
|
gui: bool,
|
||||||
) void {
|
) void {
|
||||||
const targets: []const std.Target.Query = &.{
|
const targets: []const std.Target.Query = &.{
|
||||||
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
|
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
|
||||||
|
@ -112,6 +117,7 @@ fn build_release(
|
||||||
strip orelse true,
|
strip orelse true,
|
||||||
use_llvm,
|
use_llvm,
|
||||||
pie,
|
pie,
|
||||||
|
gui,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,11 +136,13 @@ pub fn build_exe(
|
||||||
strip: bool,
|
strip: bool,
|
||||||
use_llvm: ?bool,
|
use_llvm: ?bool,
|
||||||
pie: ?bool,
|
pie: ?bool,
|
||||||
|
gui: bool,
|
||||||
) void {
|
) void {
|
||||||
const options = b.addOptions();
|
const options = b.addOptions();
|
||||||
options.addOption(bool, "enable_tracy", tracy_enabled);
|
options.addOption(bool, "enable_tracy", tracy_enabled);
|
||||||
options.addOption(bool, "use_tree_sitter", use_tree_sitter);
|
options.addOption(bool, "use_tree_sitter", use_tree_sitter);
|
||||||
options.addOption(bool, "strip", strip);
|
options.addOption(bool, "strip", strip);
|
||||||
|
options.addOption(bool, "gui", gui);
|
||||||
|
|
||||||
const options_mod = options.createModule();
|
const options_mod = options.createModule();
|
||||||
|
|
||||||
|
@ -266,7 +274,7 @@ pub fn build_exe(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderer_mod = b.createModule(.{
|
const tui_renderer_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/renderer/vaxis/renderer.zig"),
|
.root_source_file = b.path("src/renderer/vaxis/renderer.zig"),
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "vaxis", .module = vaxis_mod },
|
.{ .name = "vaxis", .module = vaxis_mod },
|
||||||
|
@ -280,6 +288,51 @@ pub fn build_exe(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const renderer_mod = blk: {
|
||||||
|
if (gui) switch (target.result.os.tag) {
|
||||||
|
.windows => {
|
||||||
|
const direct2d_dep = b.lazyDependency("direct2d", .{}) orelse break :blk tui_renderer_mod;
|
||||||
|
|
||||||
|
const win32_dep = direct2d_dep.builder.dependency("win32", .{});
|
||||||
|
const win32_mod = win32_dep.module("zigwin32");
|
||||||
|
const gui_mod = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/win32/gui.zig"),
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "win32", .module = win32_mod },
|
||||||
|
.{ .name = "ddui", .module = direct2d_dep.module("ddui") },
|
||||||
|
.{ .name = "cbor", .module = cbor_mod },
|
||||||
|
.{ .name = "thespian", .module = thespian_mod },
|
||||||
|
.{ .name = "input", .module = input_mod },
|
||||||
|
// TODO: we should be able to work without these modules
|
||||||
|
.{ .name = "vaxis", .module = vaxis_mod },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
gui_mod.addIncludePath(b.path("src/win32"));
|
||||||
|
|
||||||
|
const mod = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/renderer/win32/renderer.zig"),
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "theme", .module = themes_dep.module("theme") },
|
||||||
|
.{ .name = "win32", .module = win32_mod },
|
||||||
|
.{ .name = "cbor", .module = cbor_mod },
|
||||||
|
.{ .name = "thespian", .module = thespian_mod },
|
||||||
|
.{ .name = "input", .module = input_mod },
|
||||||
|
.{ .name = "gui", .module = gui_mod },
|
||||||
|
// TODO: we should be able to work without these modules
|
||||||
|
.{ .name = "tuirenderer", .module = tui_renderer_mod },
|
||||||
|
.{ .name = "vaxis", .module = vaxis_mod },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
break :blk mod;
|
||||||
|
},
|
||||||
|
else => |tag| {
|
||||||
|
std.log.err("OS '{s}' does not support -Dgui mode", .{@tagName(tag)});
|
||||||
|
std.process.exit(0xff);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break :blk tui_renderer_mod;
|
||||||
|
};
|
||||||
|
|
||||||
const keybind_mod = b.createModule(.{
|
const keybind_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/keybind/keybind.zig"),
|
.root_source_file = b.path("src/keybind/keybind.zig"),
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
|
@ -392,6 +445,7 @@ pub fn build_exe(
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.strip = strip,
|
.strip = strip,
|
||||||
|
.win32_manifest = b.path("src/win32/flow.manifest"),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (use_llvm) |value| {
|
if (use_llvm) |value| {
|
||||||
|
@ -411,6 +465,16 @@ pub fn build_exe(
|
||||||
exe.root_module.addImport("input", input_mod);
|
exe.root_module.addImport("input", input_mod);
|
||||||
exe.root_module.addImport("syntax", syntax_mod);
|
exe.root_module.addImport("syntax", syntax_mod);
|
||||||
exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file }));
|
exe.root_module.addImport("version_info", b.createModule(.{ .root_source_file = version_info_file }));
|
||||||
|
|
||||||
|
if (target.result.os.tag == .windows) {
|
||||||
|
exe.addWin32ResourceFile(.{
|
||||||
|
.file = b.path("src/win32/flow.rc"),
|
||||||
|
});
|
||||||
|
if (gui) {
|
||||||
|
exe.subsystem = .Windows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const exe_install = b.addInstallArtifact(exe, exe_install_options);
|
const exe_install = b.addInstallArtifact(exe, exe_install_options);
|
||||||
b.getInstallStep().dependOn(&exe_install.step);
|
b.getInstallStep().dependOn(&exe_install.step);
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,11 @@
|
||||||
.url = "https://github.com/rockorager/zeit/archive/9cca8ec620a54c3b07cd249f25e5bcb3153d03d7.tar.gz",
|
.url = "https://github.com/rockorager/zeit/archive/9cca8ec620a54c3b07cd249f25e5bcb3153d03d7.tar.gz",
|
||||||
.hash = "1220755ea2a5aa6bb3713437aaafefd44812169fe43f1da755c3ee6101b85940f441",
|
.hash = "1220755ea2a5aa6bb3713437aaafefd44812169fe43f1da755c3ee6101b85940f441",
|
||||||
},
|
},
|
||||||
|
.direct2d = .{
|
||||||
|
.url = "https://github.com/marler8997/direct2d-zig/archive/0d031389a26653bb71f81c2340d1b8ba6bd339c3.tar.gz",
|
||||||
|
.hash = "122069b40656962c6ba9b9b3f9f882ba2e9cf4c5e1afebac7b7501404129e6bb4705",
|
||||||
|
.lazy = true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"include",
|
"include",
|
||||||
|
|
363
src/renderer/win32/renderer.zig
Normal file
363
src/renderer/win32/renderer.zig
Normal file
|
@ -0,0 +1,363 @@
|
||||||
|
const Self = @This();
|
||||||
|
pub const log_name = "renderer";
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const cbor = @import("cbor");
|
||||||
|
const thespian = @import("thespian");
|
||||||
|
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.vx.queueRefresh();
|
||||||
|
//if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"resize"}));
|
||||||
|
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 input_fd_blocking(self: Self) i32 {
|
||||||
|
_ = self;
|
||||||
|
@panic("todo");
|
||||||
|
}
|
||||||
|
pub fn leave_alternate_screen(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
@panic("todo");
|
||||||
|
}
|
||||||
|
pub fn process_gui_event(self: *Self, m: thespian.message) !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 m.match(.{
|
||||||
|
thespian.any,
|
||||||
|
"I",
|
||||||
|
thespian.extract(&args.kind),
|
||||||
|
thespian.extract(&args.codepoint),
|
||||||
|
thespian.extract(&args.shifted_codepoint),
|
||||||
|
thespian.extract(&args.text),
|
||||||
|
thespian.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 m.match(.{
|
||||||
|
thespian.any,
|
||||||
|
"Resize",
|
||||||
|
thespian.extract(&args.cell_width),
|
||||||
|
thespian.extract(&args.cell_height),
|
||||||
|
thespian.extract(&args.pixel_width),
|
||||||
|
thespian.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 m.match(.{
|
||||||
|
thespian.any,
|
||||||
|
"M",
|
||||||
|
thespian.extract(&args.col),
|
||||||
|
thespian.extract(&args.row),
|
||||||
|
thespian.extract(&args.xoffset),
|
||||||
|
thespian.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 m.match(.{
|
||||||
|
thespian.any,
|
||||||
|
"B",
|
||||||
|
thespian.extract(&args.button.press),
|
||||||
|
thespian.extract(&args.button.id),
|
||||||
|
thespian.extract(&args.pos.col),
|
||||||
|
thespian.extract(&args.pos.row),
|
||||||
|
thespian.extract(&args.pos.xoffset),
|
||||||
|
thespian.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return thespian.unexpected(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_input_event(self: *Self, input_: []const u8, text: ?[]const u8) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = input_;
|
||||||
|
_ = text;
|
||||||
|
@panic("todo");
|
||||||
|
}
|
||||||
|
pub fn set_terminal_title(self: *Self, text: []const u8) void {
|
||||||
|
_ = self;
|
||||||
|
std.log.warn("TODO: set_terminal_title '{s}'", .{text});
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
@panic("todo");
|
||||||
|
}
|
||||||
|
pub fn request_system_clipboard(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
@panic("todo");
|
||||||
|
}
|
||||||
|
pub fn request_windows_clipboard(self: *Self) ![]u8 {
|
||||||
|
_ = self;
|
||||||
|
@panic("todo");
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
pub fn ucs32_to_utf8(ucs32: []const u32, utf8: []u8) !usize {
|
||||||
|
_ = ucs32;
|
||||||
|
_ = utf8;
|
||||||
|
@panic("todo");
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const build_options = @import("build_options");
|
||||||
const tp = @import("thespian");
|
const tp = @import("thespian");
|
||||||
const cbor = @import("cbor");
|
const cbor = @import("cbor");
|
||||||
const log = @import("log");
|
const log = @import("log");
|
||||||
|
@ -256,6 +257,13 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (build_options.gui) {
|
||||||
|
if (try m.match(.{ "GUI", tp.more })) {
|
||||||
|
try self.rdr.process_gui_event(m);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (self.message_filters.filter(from, m) catch |e| return self.logger.err("filter", e))
|
if (self.message_filters.filter(from, m) catch |e| return self.logger.err("filter", e))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
1
src/win32/ResourceNames.h
Normal file
1
src/win32/ResourceNames.h
Normal file
|
@ -0,0 +1 @@
|
||||||
|
#define ID_ICON_FLOW 1
|
BIN
src/win32/flow.ico
Normal file
BIN
src/win32/flow.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
14
src/win32/flow.manifest
Normal file
14
src/win32/flow.manifest
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
</assembly>
|
34
src/win32/flow.rc
Normal file
34
src/win32/flow.rc
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include "ResourceNames.h"
|
||||||
|
|
||||||
|
// LANG_NEUTRAL(0), SUBLANG_NEUTRAL(0)
|
||||||
|
LANGUAGE 0, 0
|
||||||
|
|
||||||
|
ID_ICON_FLOW ICON "flow.ico"
|
||||||
|
|
||||||
|
VS_VERSION_INFO VERSIONINFO
|
||||||
|
//FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,VERSION_COMMIT_HEIGHT
|
||||||
|
//PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,VERSION_COMMIT_HEIGHT
|
||||||
|
//FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||||
|
//FILEFLAGS VER_DBG
|
||||||
|
//FILEOS VOS_NT
|
||||||
|
//FILETYPE VFT_APP
|
||||||
|
//FILESUBTYPE VFT2_UNKNOWN
|
||||||
|
BEGIN
|
||||||
|
BLOCK "StringFileInfo"
|
||||||
|
BEGIN
|
||||||
|
BLOCK "040904b0"
|
||||||
|
BEGIN
|
||||||
|
//VALUE "CompanyName", "???"
|
||||||
|
//VALUE "FileDescription", "???"
|
||||||
|
//VALUE "FileVersion", VERSION
|
||||||
|
//VALUE "LegalCopyright", "(C) 2024 ???"
|
||||||
|
VALUE "OriginalFilename", "flow.exe"
|
||||||
|
VALUE "ProductName", "Flow Control"
|
||||||
|
//VALUE "ProductVersion", VERSION
|
||||||
|
END
|
||||||
|
END
|
||||||
|
BLOCK "VarFileInfo"
|
||||||
|
BEGIN
|
||||||
|
VALUE "Translation", 0x0409,1200
|
||||||
|
END
|
||||||
|
END
|
918
src/win32/gui.zig
Normal file
918
src/win32/gui.zig
Normal file
|
@ -0,0 +1,918 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const c = @cImport({
|
||||||
|
@cInclude("ResourceNames.h");
|
||||||
|
});
|
||||||
|
|
||||||
|
const win32 = @import("win32").everything;
|
||||||
|
const ddui = @import("ddui");
|
||||||
|
|
||||||
|
const cbor = @import("cbor");
|
||||||
|
const thespian = @import("thespian");
|
||||||
|
const vaxis = @import("vaxis");
|
||||||
|
|
||||||
|
const input = @import("input");
|
||||||
|
const windowmsg = @import("windowmsg.zig");
|
||||||
|
|
||||||
|
const HResultError = ddui.HResultError;
|
||||||
|
|
||||||
|
pub const DropWriter = struct {
|
||||||
|
pub const WriteError = error{};
|
||||||
|
pub const Writer = std.io.Writer(DropWriter, WriteError, write);
|
||||||
|
pub fn writer(self: DropWriter) Writer {
|
||||||
|
return .{ .context = self };
|
||||||
|
}
|
||||||
|
pub fn write(self: DropWriter, bytes: []const u8) WriteError!usize {
|
||||||
|
_ = self;
|
||||||
|
return bytes.len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn oom(e: error{OutOfMemory}) noreturn {
|
||||||
|
@panic(@errorName(e));
|
||||||
|
}
|
||||||
|
fn onexit(e: error{Exit}) void {
|
||||||
|
switch (e) {
|
||||||
|
error.Exit => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const global = struct {
|
||||||
|
var mutex: std.Thread.Mutex = .{};
|
||||||
|
|
||||||
|
var init_called: bool = false;
|
||||||
|
var start_called: bool = false;
|
||||||
|
var icons: Icons = undefined;
|
||||||
|
var dwrite_factory: *win32.IDWriteFactory = undefined;
|
||||||
|
var d2d_factory: *win32.ID2D1Factory = undefined;
|
||||||
|
var window_class: u16 = 0;
|
||||||
|
var hwnd: ?win32.HWND = null;
|
||||||
|
};
|
||||||
|
const window_style_ex = win32.WINDOW_EX_STYLE{
|
||||||
|
//.ACCEPTFILES = 1,
|
||||||
|
};
|
||||||
|
const window_style = win32.WS_OVERLAPPEDWINDOW;
|
||||||
|
|
||||||
|
pub fn init() void {
|
||||||
|
std.debug.assert(!global.init_called);
|
||||||
|
global.init_called = true;
|
||||||
|
|
||||||
|
global.icons = getIcons();
|
||||||
|
|
||||||
|
{
|
||||||
|
const hr = win32.DWriteCreateFactory(
|
||||||
|
win32.DWRITE_FACTORY_TYPE_SHARED,
|
||||||
|
win32.IID_IDWriteFactory,
|
||||||
|
@ptrCast(&global.dwrite_factory),
|
||||||
|
);
|
||||||
|
if (hr < 0) fatalHr("DWriteCreateFactory", hr);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var err: HResultError = undefined;
|
||||||
|
global.d2d_factory = ddui.createFactory(
|
||||||
|
.SINGLE_THREADED,
|
||||||
|
.{},
|
||||||
|
&err,
|
||||||
|
) catch std.debug.panic("{}", .{err});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Icons = struct {
|
||||||
|
small: win32.HICON,
|
||||||
|
large: win32.HICON,
|
||||||
|
};
|
||||||
|
fn getIcons() Icons {
|
||||||
|
const small_x = win32.GetSystemMetrics(.CXSMICON);
|
||||||
|
const small_y = win32.GetSystemMetrics(.CYSMICON);
|
||||||
|
const large_x = win32.GetSystemMetrics(.CXICON);
|
||||||
|
const large_y = win32.GetSystemMetrics(.CYICON);
|
||||||
|
std.log.info("icons small={}x{} large={}x{}", .{
|
||||||
|
small_x, small_y,
|
||||||
|
large_x, large_y,
|
||||||
|
});
|
||||||
|
const small = win32.LoadImageW(
|
||||||
|
win32.GetModuleHandleW(null),
|
||||||
|
@ptrFromInt(c.ID_ICON_FLOW),
|
||||||
|
.ICON,
|
||||||
|
small_x,
|
||||||
|
small_y,
|
||||||
|
win32.LR_SHARED,
|
||||||
|
) orelse fatalWin32("LoadImage for small icon", win32.GetLastError());
|
||||||
|
const large = win32.LoadImageW(
|
||||||
|
win32.GetModuleHandleW(null),
|
||||||
|
@ptrFromInt(c.ID_ICON_FLOW),
|
||||||
|
.ICON,
|
||||||
|
large_x,
|
||||||
|
large_y,
|
||||||
|
win32.LR_SHARED,
|
||||||
|
) orelse fatalWin32("LoadImage for large icon", win32.GetLastError());
|
||||||
|
return .{ .small = @ptrCast(small), .large = @ptrCast(large) };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn d2dColorFromVAxis(color: vaxis.Cell.Color) win32.D2D_COLOR_F {
|
||||||
|
return switch (color) {
|
||||||
|
.default => .{ .r = 0, .g = 0, .b = 0, .a = 0 },
|
||||||
|
.index => @panic("todo: color index"),
|
||||||
|
.rgb => |rgb| .{
|
||||||
|
.r = @as(f32, @floatFromInt(rgb[0])) / 255.0,
|
||||||
|
.g = @as(f32, @floatFromInt(rgb[1])) / 255.0,
|
||||||
|
.b = @as(f32, @floatFromInt(rgb[2])) / 255.0,
|
||||||
|
.a = 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dpi = struct {
|
||||||
|
value: u32,
|
||||||
|
pub fn eql(self: Dpi, other: Dpi) bool {
|
||||||
|
return self.value == other.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn createTextFormatEditor(dpi: Dpi) *win32.IDWriteTextFormat {
|
||||||
|
var err: HResultError = undefined;
|
||||||
|
return ddui.createTextFormat(global.dwrite_factory, &err, .{
|
||||||
|
.size = win32.scaleDpi(f32, 14, dpi.value),
|
||||||
|
.family_name = win32.L("Cascadia Code"),
|
||||||
|
.center_x = true,
|
||||||
|
.center_y = true,
|
||||||
|
}) catch std.debug.panic("{s} failed, hresult=0x{x}", .{ err.context, err.hr });
|
||||||
|
}
|
||||||
|
|
||||||
|
const D2d = struct {
|
||||||
|
target: *win32.ID2D1HwndRenderTarget,
|
||||||
|
brush: *win32.ID2D1SolidColorBrush,
|
||||||
|
pub fn init(hwnd: win32.HWND, err: *HResultError) error{HResult}!D2d {
|
||||||
|
var target: *win32.ID2D1HwndRenderTarget = undefined;
|
||||||
|
const target_props = win32.D2D1_RENDER_TARGET_PROPERTIES{
|
||||||
|
.type = .DEFAULT,
|
||||||
|
.pixelFormat = .{
|
||||||
|
.format = .B8G8R8A8_UNORM,
|
||||||
|
.alphaMode = .PREMULTIPLIED,
|
||||||
|
},
|
||||||
|
.dpiX = 0,
|
||||||
|
.dpiY = 0,
|
||||||
|
.usage = .{},
|
||||||
|
.minLevel = .DEFAULT,
|
||||||
|
};
|
||||||
|
const hwnd_target_props = win32.D2D1_HWND_RENDER_TARGET_PROPERTIES{
|
||||||
|
.hwnd = hwnd,
|
||||||
|
.pixelSize = .{ .width = 0, .height = 0 },
|
||||||
|
.presentOptions = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
const hr = global.d2d_factory.CreateHwndRenderTarget(
|
||||||
|
&target_props,
|
||||||
|
&hwnd_target_props,
|
||||||
|
&target,
|
||||||
|
);
|
||||||
|
if (hr < 0) return err.set(hr, "CreateHwndRenderTarget");
|
||||||
|
}
|
||||||
|
errdefer _ = target.IUnknown.Release();
|
||||||
|
|
||||||
|
{
|
||||||
|
var dc: *win32.ID2D1DeviceContext = undefined;
|
||||||
|
{
|
||||||
|
const hr = target.IUnknown.QueryInterface(win32.IID_ID2D1DeviceContext, @ptrCast(&dc));
|
||||||
|
if (hr < 0) return err.set(hr, "GetDeviceContext");
|
||||||
|
}
|
||||||
|
defer _ = dc.IUnknown.Release();
|
||||||
|
// just make everything DPI aware, all applications should just do this
|
||||||
|
dc.SetUnitMode(win32.D2D1_UNIT_MODE_PIXELS);
|
||||||
|
}
|
||||||
|
|
||||||
|
var brush: *win32.ID2D1SolidColorBrush = undefined;
|
||||||
|
{
|
||||||
|
const color: win32.D2D_COLOR_F = .{ .r = 0, .g = 0, .b = 0, .a = 0 };
|
||||||
|
const hr = target.ID2D1RenderTarget.CreateSolidColorBrush(&color, null, &brush);
|
||||||
|
if (hr < 0) return err.set(hr, "CreateSolidBrush");
|
||||||
|
}
|
||||||
|
errdefer _ = brush.IUnknown.Release();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.target = target,
|
||||||
|
.brush = brush,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *D2d) void {
|
||||||
|
_ = self.brush.IUnknown.Release();
|
||||||
|
_ = self.target.IUnknown.Release();
|
||||||
|
}
|
||||||
|
pub fn solid(self: *const D2d, color: win32.D2D_COLOR_F) *win32.ID2D1Brush {
|
||||||
|
self.brush.SetColor(&color);
|
||||||
|
return &self.brush.ID2D1Brush;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const State = struct {
|
||||||
|
pid: thespian.pid,
|
||||||
|
maybe_d2d: ?D2d = null,
|
||||||
|
erase_bg_done: bool = false,
|
||||||
|
text_format_editor: ddui.TextFormatCache(Dpi, createTextFormatEditor) = .{},
|
||||||
|
|
||||||
|
// these fields should only be accessed inside the global mutex
|
||||||
|
shared_screen_arena: std.heap.ArenaAllocator,
|
||||||
|
shared_screen: vaxis.Screen = .{},
|
||||||
|
pub fn deinit(self: *State) void {
|
||||||
|
{
|
||||||
|
global.mutex.lock();
|
||||||
|
defer global.mutex.unlock();
|
||||||
|
self.shared_screen.deinit(self.shared_screen_arena.allocator());
|
||||||
|
self.shared_screen_arena.deinit();
|
||||||
|
}
|
||||||
|
if (self.maybe_d2d) |*d2d| {
|
||||||
|
d2d.deinit();
|
||||||
|
}
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fn stateFromHwnd(hwnd: win32.HWND) *State {
|
||||||
|
const addr: usize = @bitCast(win32.GetWindowLongPtrW(hwnd, @enumFromInt(0)));
|
||||||
|
if (addr == 0) @panic("window is missing it's state!");
|
||||||
|
return @ptrFromInt(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
d2d: *const D2d,
|
||||||
|
dpi: u32,
|
||||||
|
screen: *const vaxis.Screen,
|
||||||
|
text_format_editor: *win32.IDWriteTextFormat,
|
||||||
|
) void {
|
||||||
|
{
|
||||||
|
const color = ddui.rgb8(31, 31, 31);
|
||||||
|
d2d.target.ID2D1RenderTarget.Clear(&color);
|
||||||
|
}
|
||||||
|
const cell_size = getCellSize(dpi, text_format_editor);
|
||||||
|
for (0..screen.height) |y| {
|
||||||
|
const row_y = cell_size.y * @as(i32, @intCast(y));
|
||||||
|
for (0..screen.width) |x| {
|
||||||
|
const column_x = cell_size.x * @as(i32, @intCast(x));
|
||||||
|
const cell_index = screen.width * y + x;
|
||||||
|
const cell = &screen.buf[cell_index];
|
||||||
|
|
||||||
|
const cell_rect: win32.RECT = .{
|
||||||
|
.left = column_x,
|
||||||
|
.top = row_y,
|
||||||
|
.right = column_x + cell_size.x,
|
||||||
|
.bottom = row_y + cell_size.y,
|
||||||
|
};
|
||||||
|
ddui.FillRectangle(
|
||||||
|
&d2d.target.ID2D1RenderTarget,
|
||||||
|
cell_rect,
|
||||||
|
d2d.solid(d2dColorFromVAxis(cell.style.bg)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: pre-caclulate the buffer size needed, for now this should just
|
||||||
|
// cause out-of-bounds access
|
||||||
|
var buf_wtf16: [100]u16 = undefined;
|
||||||
|
const grapheme_len = std.unicode.wtf8ToWtf16Le(&buf_wtf16, cell.char.grapheme) catch |err| switch (err) {
|
||||||
|
error.InvalidWtf8 => @panic("TODO: handle invalid wtf8"),
|
||||||
|
};
|
||||||
|
ddui.DrawText(
|
||||||
|
&d2d.target.ID2D1RenderTarget,
|
||||||
|
buf_wtf16[0..grapheme_len],
|
||||||
|
text_format_editor,
|
||||||
|
ddui.rectFloatFromInt(cell_rect),
|
||||||
|
d2d.solid(d2dColorFromVAxis(cell.style.fg)),
|
||||||
|
.{},
|
||||||
|
.NATURAL,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateWindowArgs = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
pid: thespian.pid,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn start() !std.Thread {
|
||||||
|
std.debug.assert(!global.start_called);
|
||||||
|
global.start_called = true;
|
||||||
|
const pid = thespian.self_pid().clone();
|
||||||
|
return try std.Thread.spawn(.{}, entry, .{pid});
|
||||||
|
}
|
||||||
|
fn entry(pid: thespian.pid) !void {
|
||||||
|
var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
|
defer arena_instance.deinit();
|
||||||
|
|
||||||
|
const CLASS_NAME = win32.L("Flow");
|
||||||
|
|
||||||
|
// we only need to register the window class once per process
|
||||||
|
if (global.window_class == 0) {
|
||||||
|
const wc = win32.WNDCLASSEXW{
|
||||||
|
.cbSize = @sizeOf(win32.WNDCLASSEXW),
|
||||||
|
.style = .{},
|
||||||
|
.lpfnWndProc = WndProc,
|
||||||
|
.cbClsExtra = 0,
|
||||||
|
.cbWndExtra = @sizeOf(*State),
|
||||||
|
.hInstance = win32.GetModuleHandleW(null),
|
||||||
|
.hIcon = global.icons.large,
|
||||||
|
.hCursor = win32.LoadCursorW(null, win32.IDC_ARROW),
|
||||||
|
.hbrBackground = null,
|
||||||
|
.lpszMenuName = null,
|
||||||
|
.lpszClassName = CLASS_NAME,
|
||||||
|
.hIconSm = global.icons.small,
|
||||||
|
};
|
||||||
|
global.window_class = win32.RegisterClassExW(&wc);
|
||||||
|
if (global.window_class == 0) fatalWin32(
|
||||||
|
"RegisterClass for main window",
|
||||||
|
win32.GetLastError(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var create_args = CreateWindowArgs{
|
||||||
|
.allocator = arena_instance.allocator(),
|
||||||
|
.pid = pid,
|
||||||
|
};
|
||||||
|
const hwnd = win32.CreateWindowExW(
|
||||||
|
window_style_ex,
|
||||||
|
CLASS_NAME, // Window class
|
||||||
|
win32.L("Flow"),
|
||||||
|
window_style,
|
||||||
|
win32.CW_USEDEFAULT, // x
|
||||||
|
win32.CW_USEDEFAULT, // y
|
||||||
|
win32.CW_USEDEFAULT, // width
|
||||||
|
win32.CW_USEDEFAULT, // height
|
||||||
|
null, // Parent window
|
||||||
|
null, // Menu
|
||||||
|
win32.GetModuleHandleW(null),
|
||||||
|
@ptrCast(&create_args),
|
||||||
|
) orelse fatalWin32("CreateWindow", win32.GetLastError());
|
||||||
|
defer if (0 == win32.DestroyWindow(hwnd)) fatalWin32("DestroyWindow", win32.GetLastError());
|
||||||
|
|
||||||
|
{
|
||||||
|
global.mutex.lock();
|
||||||
|
defer global.mutex.unlock();
|
||||||
|
std.debug.assert(global.hwnd == null);
|
||||||
|
global.hwnd = hwnd;
|
||||||
|
}
|
||||||
|
defer {
|
||||||
|
global.mutex.lock();
|
||||||
|
defer global.mutex.unlock();
|
||||||
|
std.debug.assert(global.hwnd == hwnd);
|
||||||
|
global.hwnd = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// TODO: maybe use DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 if applicable
|
||||||
|
// see https://stackoverflow.com/questions/57124243/winforms-dark-title-bar-on-windows-10
|
||||||
|
//int attribute = DWMWA_USE_IMMERSIVE_DARK_MODE;
|
||||||
|
const dark_value: c_int = 1;
|
||||||
|
const hr = win32.DwmSetWindowAttribute(
|
||||||
|
hwnd,
|
||||||
|
win32.DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||||||
|
&dark_value,
|
||||||
|
@sizeOf(@TypeOf(dark_value)),
|
||||||
|
);
|
||||||
|
if (hr < 0) std.log.warn(
|
||||||
|
"DwmSetWindowAttribute for dark={} failed, error={}",
|
||||||
|
.{ dark_value, win32.GetLastError() },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == win32.UpdateWindow(hwnd)) fatalWin32("UpdateWindow", win32.GetLastError());
|
||||||
|
_ = win32.ShowWindow(hwnd, win32.SW_SHOWNORMAL);
|
||||||
|
var msg: win32.MSG = undefined;
|
||||||
|
while (win32.GetMessageW(&msg, null, 0, 0) != 0) {
|
||||||
|
// No need for TranslateMessage since we don't use WM_*CHAR messages
|
||||||
|
//_ = win32.TranslateMessage(&msg);
|
||||||
|
_ = win32.DispatchMessageW(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const exit_code = std.math.cast(u32, msg.wParam) orelse 0xffffffff;
|
||||||
|
std.log.info("gui thread exit {} ({})", .{ exit_code, msg.wParam });
|
||||||
|
pid.send(.{"quit"}) catch |e| onexit(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns false if there is no hwnd
|
||||||
|
pub fn updateScreen(screen: *const vaxis.Screen) bool {
|
||||||
|
global.mutex.lock();
|
||||||
|
defer global.mutex.unlock();
|
||||||
|
|
||||||
|
const hwnd = global.hwnd orelse return false;
|
||||||
|
const state = stateFromHwnd(hwnd);
|
||||||
|
|
||||||
|
_ = state.shared_screen_arena.reset(.retain_capacity);
|
||||||
|
|
||||||
|
const buf = state.shared_screen_arena.allocator().alloc(vaxis.Cell, screen.buf.len) catch |e| oom(e);
|
||||||
|
@memcpy(buf, screen.buf);
|
||||||
|
for (buf) |*cell| {
|
||||||
|
cell.char.grapheme = state.shared_screen_arena.allocator().dupe(u8, cell.char.grapheme) catch |e| oom(e);
|
||||||
|
}
|
||||||
|
state.shared_screen = .{
|
||||||
|
.width = screen.width,
|
||||||
|
.height = screen.height,
|
||||||
|
.width_pix = screen.width_pix,
|
||||||
|
.height_pix = screen.height_pix,
|
||||||
|
.buf = buf,
|
||||||
|
.cursor_row = screen.cursor_row,
|
||||||
|
.cursor_col = screen.cursor_col,
|
||||||
|
.cursor_vis = screen.cursor_vis,
|
||||||
|
.unicode = undefined,
|
||||||
|
.width_method = undefined,
|
||||||
|
.mouse_shape = screen.mouse_shape,
|
||||||
|
.cursor_shape = undefined,
|
||||||
|
};
|
||||||
|
win32.invalidateHwnd(hwnd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getCellSize(
|
||||||
|
dpi: u32,
|
||||||
|
text_format_editor: *win32.IDWriteTextFormat,
|
||||||
|
) XY(i32) {
|
||||||
|
const metrics = getTextFormatMetrics(text_format_editor);
|
||||||
|
|
||||||
|
// TODO: get the actual font metrics
|
||||||
|
const font_size = text_format_editor.GetFontSize();
|
||||||
|
const pixels_per_design_unit: f32 = font_size / @as(f32, @floatFromInt(metrics.designUnitsPerEm));
|
||||||
|
|
||||||
|
const width: f32 = getTextFormatWidth(text_format_editor);
|
||||||
|
const width_scaled: i32 = @intFromFloat(@ceil(win32.scaleDpi(f32, width, dpi)));
|
||||||
|
|
||||||
|
const ascent = @as(f32, @floatFromInt(metrics.ascent)) * pixels_per_design_unit;
|
||||||
|
const descent = @as(f32, @floatFromInt(metrics.descent)) * pixels_per_design_unit;
|
||||||
|
const height: f32 = ascent + descent;
|
||||||
|
const height_scaled: i32 = @intFromFloat(@ceil(win32.scaleDpi(f32, height, dpi)));
|
||||||
|
// std.log.info(
|
||||||
|
// "CellSize font_size={d} size={d}x{d} scaled-size={}x{}",
|
||||||
|
// .{ font_size, width, height, width_scaled, height_scaled },
|
||||||
|
// );
|
||||||
|
return .{
|
||||||
|
.x = width_scaled,
|
||||||
|
.y = height_scaled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getTextFormatWidth(
|
||||||
|
text_format: *win32.IDWriteTextFormat,
|
||||||
|
) f32 {
|
||||||
|
var text_layout: *win32.IDWriteTextLayout = undefined;
|
||||||
|
{
|
||||||
|
const hr = global.dwrite_factory.CreateTextLayout(
|
||||||
|
win32.L("0"),
|
||||||
|
1,
|
||||||
|
text_format, // Text format
|
||||||
|
std.math.floatMax(f32),
|
||||||
|
std.math.floatMax(f32),
|
||||||
|
&text_layout,
|
||||||
|
);
|
||||||
|
if (hr < 0) fatalHr("CreateTextLayout", hr);
|
||||||
|
}
|
||||||
|
defer _ = text_layout.IUnknown.Release();
|
||||||
|
|
||||||
|
var metrics: win32.DWRITE_TEXT_METRICS = undefined;
|
||||||
|
{
|
||||||
|
const hr = text_layout.GetMetrics(&metrics);
|
||||||
|
if (hr < 0) fatalHr("GetMetrics", hr);
|
||||||
|
}
|
||||||
|
return metrics.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getTextFormatMetrics(
|
||||||
|
text_format: *win32.IDWriteTextFormat,
|
||||||
|
) win32.DWRITE_FONT_METRICS {
|
||||||
|
var collection: *win32.IDWriteFontCollection = undefined;
|
||||||
|
|
||||||
|
{
|
||||||
|
const hr = text_format.GetFontCollection(&collection);
|
||||||
|
if (hr < 0) fatalHr("GetFontCollection", hr);
|
||||||
|
}
|
||||||
|
defer _ = collection.IUnknown.Release();
|
||||||
|
|
||||||
|
const max_family_name_len = 300;
|
||||||
|
var family_name_buf: [max_family_name_len + 1]u16 = undefined;
|
||||||
|
const family_name_len = text_format.GetFontFamilyNameLength();
|
||||||
|
if (family_name_len > max_family_name_len) std.debug.panic(
|
||||||
|
"family name len {} is too big",
|
||||||
|
.{family_name_len},
|
||||||
|
);
|
||||||
|
|
||||||
|
family_name_buf[family_name_len] = 0xff;
|
||||||
|
{
|
||||||
|
const hr = text_format.GetFontFamilyName(@ptrCast(&family_name_buf), max_family_name_len);
|
||||||
|
if (hr < 0) fatalHr("GetFontFamilyName", hr);
|
||||||
|
}
|
||||||
|
std.debug.assert(family_name_buf[family_name_len] == 0);
|
||||||
|
|
||||||
|
var family_index: u32 = undefined;
|
||||||
|
|
||||||
|
{
|
||||||
|
var exists: win32.BOOL = undefined;
|
||||||
|
const hr = collection.FindFamilyName(@ptrCast(&family_name_buf), &family_index, &exists);
|
||||||
|
if (hr < 0) fatalHr("FindFamilyName", hr);
|
||||||
|
if (0 == exists) std.debug.panic(
|
||||||
|
"FontFamily '{}' does not exist?",
|
||||||
|
.{std.unicode.fmtUtf16le(family_name_buf[0..family_name_len])},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var family: *win32.IDWriteFontFamily = undefined;
|
||||||
|
|
||||||
|
{
|
||||||
|
const hr = collection.GetFontFamily(family_index, &family);
|
||||||
|
if (hr < 0) fatalHr("GetFontFamily", hr);
|
||||||
|
}
|
||||||
|
defer _ = family.IUnknown.Release();
|
||||||
|
|
||||||
|
var font: *win32.IDWriteFont = undefined;
|
||||||
|
|
||||||
|
{
|
||||||
|
const hr = family.GetFirstMatchingFont(
|
||||||
|
text_format.GetFontWeight(),
|
||||||
|
text_format.GetFontStretch(),
|
||||||
|
text_format.GetFontStyle(),
|
||||||
|
&font,
|
||||||
|
);
|
||||||
|
if (hr < 0) fatalHr("GetFirstMatchingFont", hr);
|
||||||
|
}
|
||||||
|
defer _ = font.IUnknown.Release();
|
||||||
|
|
||||||
|
var metrics: win32.DWRITE_FONT_METRICS = undefined;
|
||||||
|
font.GetMetrics(&metrics);
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cellFromPos(cell_size: XY(i32), x: i32, y: i32) XY(i32) {
|
||||||
|
return XY(i32){
|
||||||
|
.x = @divTrunc(x, cell_size.x),
|
||||||
|
.y = @divTrunc(y, cell_size.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn cellOffsetFromPos(cell_size: XY(i32), x: i32, y: i32) XY(i32) {
|
||||||
|
return .{
|
||||||
|
.x = @mod(x, cell_size.x),
|
||||||
|
.y = @mod(y, cell_size.y),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sendMouse(
|
||||||
|
hwnd: win32.HWND,
|
||||||
|
kind: enum {
|
||||||
|
move,
|
||||||
|
left_down,
|
||||||
|
left_up,
|
||||||
|
right_down,
|
||||||
|
right_up,
|
||||||
|
},
|
||||||
|
lparam: win32.LPARAM,
|
||||||
|
) void {
|
||||||
|
const point = ddui.pointFromLparam(lparam);
|
||||||
|
const state = stateFromHwnd(hwnd);
|
||||||
|
const dpi = win32.dpiFromHwnd(hwnd);
|
||||||
|
const text_format = state.text_format_editor.getOrCreate(Dpi{ .value = dpi });
|
||||||
|
const cell_size = getCellSize(dpi, text_format);
|
||||||
|
const cell = cellFromPos(cell_size, point.x, point.y);
|
||||||
|
const cell_offset = cellOffsetFromPos(cell_size, point.x, point.y);
|
||||||
|
switch (kind) {
|
||||||
|
.move => state.pid.send(.{
|
||||||
|
"GUI",
|
||||||
|
"M",
|
||||||
|
cell.x,
|
||||||
|
cell.y,
|
||||||
|
cell_offset.x,
|
||||||
|
cell_offset.y,
|
||||||
|
}) catch |e| onexit(e),
|
||||||
|
else => |b| state.pid.send(.{
|
||||||
|
"GUI",
|
||||||
|
"B",
|
||||||
|
switch (b) {
|
||||||
|
.move => unreachable,
|
||||||
|
.left_down, .right_down => input.event.press,
|
||||||
|
.left_up, .right_up => input.event.release,
|
||||||
|
},
|
||||||
|
switch (b) {
|
||||||
|
.move => unreachable,
|
||||||
|
.left_down, .left_up => @intFromEnum(input.mouse.BUTTON1),
|
||||||
|
.right_down, .right_up => @intFromEnum(input.mouse.BUTTON2),
|
||||||
|
},
|
||||||
|
cell.x,
|
||||||
|
cell.y,
|
||||||
|
cell_offset.x,
|
||||||
|
cell_offset.y,
|
||||||
|
}) catch |e| onexit(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sendKey(
|
||||||
|
hwnd: win32.HWND,
|
||||||
|
kind: enum {
|
||||||
|
press,
|
||||||
|
release,
|
||||||
|
},
|
||||||
|
wparam: win32.WPARAM,
|
||||||
|
lparam: win32.LPARAM,
|
||||||
|
) void {
|
||||||
|
const state = stateFromHwnd(hwnd);
|
||||||
|
|
||||||
|
var keyboard_state: [256]u8 = undefined;
|
||||||
|
if (0 == win32.GetKeyboardState(&keyboard_state)) fatalWin32(
|
||||||
|
"GetKeyboardState",
|
||||||
|
win32.GetLastError(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const mods: vaxis.Key.Modifiers = .{
|
||||||
|
.shift = (0 != (keyboard_state[@intFromEnum(win32.VK_SHIFT)] & 0x80)),
|
||||||
|
.alt = (0 != (keyboard_state[@intFromEnum(win32.VK_MENU)] & 0x80)),
|
||||||
|
.ctrl = (0 != (keyboard_state[@intFromEnum(win32.VK_CONTROL)] & 0x80)),
|
||||||
|
.super = false,
|
||||||
|
.hyper = false,
|
||||||
|
.meta = false,
|
||||||
|
.caps_lock = (0 != (keyboard_state[@intFromEnum(win32.VK_CAPITAL)] & 1)),
|
||||||
|
.num_lock = false,
|
||||||
|
};
|
||||||
|
// if ((keyboard_state[VK_LWIN] & 0x80) || (keyboard_state[VK_RWIN] & 0x80)) mod_flags_u32 |= tn::ModifierFlags::Super;
|
||||||
|
// if (m_winkey_down) mod_flags_u32 |= tn::ModifierFlags::Super;
|
||||||
|
// // TODO: Numpad?
|
||||||
|
// // TODO: Help?
|
||||||
|
// // TODO: Fn?
|
||||||
|
// tn::ModifierFlags mod_flags = (tn::ModifierFlags)mod_flags_u32;
|
||||||
|
|
||||||
|
const winkey = WinKey{
|
||||||
|
.vk = @intCast(0xffff & wparam),
|
||||||
|
.extended = 0 != (lparam & 0x1000000),
|
||||||
|
};
|
||||||
|
|
||||||
|
const max_char_count = 20;
|
||||||
|
var char_buf: [max_char_count + 1]u16 = undefined;
|
||||||
|
const unicode_result = win32.ToUnicode(
|
||||||
|
winkey.vk,
|
||||||
|
@intCast((@as(usize, @bitCast(lparam)) >> 16) & 0xff),
|
||||||
|
&keyboard_state,
|
||||||
|
@ptrCast(&char_buf),
|
||||||
|
max_char_count,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if (unicode_result < 0) {
|
||||||
|
// < 0 means this is a dead key
|
||||||
|
// The ToUnicode function should remember this dead key
|
||||||
|
// and apply it to the next call
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (unicode_result > max_char_count) {
|
||||||
|
for (char_buf[0..@intCast(unicode_result)], 0..) |codepoint, i| {
|
||||||
|
std.log.err("UNICODE[{}] 0x{x}", .{ i, codepoint });
|
||||||
|
}
|
||||||
|
std.debug.panic("TODO: unicode result is {}", .{unicode_result});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (char_buf[0..@intCast(unicode_result)]) |codepoint| {
|
||||||
|
state.pid.send(.{
|
||||||
|
"GUI",
|
||||||
|
"I",
|
||||||
|
switch (kind) {
|
||||||
|
.press => input.event.press,
|
||||||
|
.release => input.event.release,
|
||||||
|
},
|
||||||
|
@as(u21, codepoint),
|
||||||
|
// TODO: shifted_codepoint?
|
||||||
|
@as(u21, codepoint),
|
||||||
|
"", // text?
|
||||||
|
@as(u8, @bitCast(mods)),
|
||||||
|
}) catch |e| onexit(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WinKey = struct {
|
||||||
|
vk: u16,
|
||||||
|
extended: bool,
|
||||||
|
pub fn eql(self: WinKey, other: WinKey) bool {
|
||||||
|
return self.vk == other.vk and self.extended == other.extended;
|
||||||
|
}
|
||||||
|
pub fn toFlow(self: WinKey) u32 {
|
||||||
|
_ = self;
|
||||||
|
@panic("todo");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var global_msg_tail: ?*windowmsg.MessageNode = null;
|
||||||
|
|
||||||
|
fn WndProc(
|
||||||
|
hwnd: win32.HWND,
|
||||||
|
msg: u32,
|
||||||
|
wparam: win32.WPARAM,
|
||||||
|
lparam: win32.LPARAM,
|
||||||
|
) callconv(std.os.windows.WINAPI) win32.LRESULT {
|
||||||
|
var msg_node: windowmsg.MessageNode = undefined;
|
||||||
|
msg_node.init(&global_msg_tail, hwnd, msg, wparam, lparam);
|
||||||
|
defer msg_node.deinit();
|
||||||
|
switch (msg) {
|
||||||
|
// win32.WM_NCHITTEST,
|
||||||
|
// win32.WM_SETCURSOR,
|
||||||
|
// win32.WM_GETICON,
|
||||||
|
// win32.WM_MOUSEMOVE,
|
||||||
|
// win32.WM_NCMOUSEMOVE,
|
||||||
|
// => {},
|
||||||
|
else => if (false) std.log.info("{}", .{msg_node.fmtPath()}),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (msg) {
|
||||||
|
win32.WM_MOUSEMOVE => {
|
||||||
|
sendMouse(hwnd, .move, lparam);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_LBUTTONDOWN => {
|
||||||
|
sendMouse(hwnd, .left_down, lparam);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_LBUTTONUP => {
|
||||||
|
sendMouse(hwnd, .left_up, lparam);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_RBUTTONDOWN => {
|
||||||
|
sendMouse(hwnd, .right_down, lparam);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_RBUTTONUP => {
|
||||||
|
sendMouse(hwnd, .right_up, lparam);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_KEYDOWN => {
|
||||||
|
sendKey(hwnd, .press, wparam, lparam);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_KEYUP => {
|
||||||
|
sendKey(hwnd, .release, wparam, lparam);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_PAINT => {
|
||||||
|
const dpi = win32.dpiFromHwnd(hwnd);
|
||||||
|
const client_size = getClientSize(hwnd);
|
||||||
|
const state = stateFromHwnd(hwnd);
|
||||||
|
|
||||||
|
const err: HResultError = blk: {
|
||||||
|
var ps: win32.PAINTSTRUCT = undefined;
|
||||||
|
_ = win32.BeginPaint(hwnd, &ps) orelse return fatalWin32(
|
||||||
|
"BeginPaint",
|
||||||
|
win32.GetLastError(),
|
||||||
|
);
|
||||||
|
defer if (0 == win32.EndPaint(hwnd, &ps)) fatalWin32(
|
||||||
|
"EndPaint",
|
||||||
|
win32.GetLastError(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (state.maybe_d2d == null) {
|
||||||
|
var err: HResultError = undefined;
|
||||||
|
state.maybe_d2d = D2d.init(hwnd, &err) catch break :blk err;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const size: win32.D2D_SIZE_U = .{
|
||||||
|
.width = @intCast(client_size.width),
|
||||||
|
.height = @intCast(client_size.height),
|
||||||
|
};
|
||||||
|
const hr = state.maybe_d2d.?.target.Resize(&size);
|
||||||
|
if (hr < 0) break :blk HResultError{ .context = "D2dResize", .hr = hr };
|
||||||
|
}
|
||||||
|
state.maybe_d2d.?.target.ID2D1RenderTarget.BeginDraw();
|
||||||
|
|
||||||
|
{
|
||||||
|
global.mutex.lock();
|
||||||
|
defer global.mutex.unlock();
|
||||||
|
paint(
|
||||||
|
&state.maybe_d2d.?,
|
||||||
|
dpi,
|
||||||
|
&state.shared_screen,
|
||||||
|
state.text_format_editor.getOrCreate(Dpi{ .value = dpi }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk HResultError{
|
||||||
|
.context = "D2dEndDraw",
|
||||||
|
.hr = state.maybe_d2d.?.target.ID2D1RenderTarget.EndDraw(null, null),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (err.hr == win32.D2DERR_RECREATE_TARGET) {
|
||||||
|
std.log.debug("D2DERR_RECREATE_TARGET", .{});
|
||||||
|
state.maybe_d2d.?.deinit();
|
||||||
|
state.maybe_d2d = null;
|
||||||
|
win32.invalidateHwnd(hwnd);
|
||||||
|
} else if (err.hr < 0) std.debug.panic("paint error: {}", .{err});
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_SIZE => {
|
||||||
|
const client_pixel_size: XY(u16) = .{
|
||||||
|
.x = win32.loword(lparam),
|
||||||
|
.y = win32.hiword(lparam),
|
||||||
|
};
|
||||||
|
|
||||||
|
const dpi = win32.dpiFromHwnd(hwnd);
|
||||||
|
const state = stateFromHwnd(hwnd);
|
||||||
|
if (state.maybe_d2d == null) {
|
||||||
|
var err: HResultError = undefined;
|
||||||
|
state.maybe_d2d = D2d.init(hwnd, &err) catch std.debug.panic(
|
||||||
|
"D2d.init failed with {}",
|
||||||
|
.{err},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const single_cell_size = getCellSize(
|
||||||
|
dpi,
|
||||||
|
state.text_format_editor.getOrCreate(Dpi{ .value = @intCast(dpi) }),
|
||||||
|
);
|
||||||
|
const client_cell_size: XY(u16) = .{
|
||||||
|
.x = @intCast(@divTrunc(client_pixel_size.x, single_cell_size.x)),
|
||||||
|
.y = @intCast(@divTrunc(client_pixel_size.y, single_cell_size.y)),
|
||||||
|
};
|
||||||
|
//std.log.info("new size {}x{} {}x{}", .{ new_size.x, new_size.y, new_cell_size.x, new_cell_size.y });
|
||||||
|
state.pid.send(.{
|
||||||
|
"GUI",
|
||||||
|
"Resize",
|
||||||
|
client_cell_size.x,
|
||||||
|
client_cell_size.y,
|
||||||
|
client_pixel_size.x,
|
||||||
|
client_pixel_size.y,
|
||||||
|
}) catch @panic("pid send failed");
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_DISPLAYCHANGE => {
|
||||||
|
win32.invalidateHwnd(hwnd);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_ERASEBKGND => {
|
||||||
|
const state = stateFromHwnd(hwnd);
|
||||||
|
if (!state.erase_bg_done) {
|
||||||
|
state.erase_bg_done = true;
|
||||||
|
const brush = win32.CreateSolidBrush(toColorRef(.{ .r = 29, .g = 29, .b = 29 })) orelse
|
||||||
|
fatalWin32("CreateSolidBrush", win32.GetLastError());
|
||||||
|
defer deleteObject(brush);
|
||||||
|
const hdc: win32.HDC = @ptrFromInt(wparam);
|
||||||
|
var rect: win32.RECT = undefined;
|
||||||
|
if (0 == win32.GetClientRect(hwnd, &rect)) @panic("");
|
||||||
|
if (0 == win32.FillRect(hdc, &rect, brush)) @panic("");
|
||||||
|
}
|
||||||
|
return 1; // background erased
|
||||||
|
},
|
||||||
|
win32.WM_CLOSE => {
|
||||||
|
win32.PostQuitMessage(0);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_CREATE => {
|
||||||
|
const create_struct: *win32.CREATESTRUCTW = @ptrFromInt(@as(usize, @bitCast(lparam)));
|
||||||
|
const create_args: *CreateWindowArgs = @alignCast(@ptrCast(create_struct.lpCreateParams));
|
||||||
|
const state = create_args.allocator.create(State) catch |e| oom(e);
|
||||||
|
|
||||||
|
state.* = .{
|
||||||
|
.pid = create_args.pid,
|
||||||
|
.shared_screen_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator),
|
||||||
|
};
|
||||||
|
const existing = win32.SetWindowLongPtrW(
|
||||||
|
hwnd,
|
||||||
|
@enumFromInt(0),
|
||||||
|
@as(isize, @bitCast(@intFromPtr(state))),
|
||||||
|
);
|
||||||
|
std.debug.assert(existing == 0);
|
||||||
|
std.debug.assert(state == stateFromHwnd(hwnd));
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
win32.WM_DESTROY => {
|
||||||
|
const state = stateFromHwnd(hwnd);
|
||||||
|
state.deinit();
|
||||||
|
// no need to free, it was allocated via an arena
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
else => return win32.DefWindowProcW(hwnd, msg, wparam, lparam),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Rgb8 = struct { r: u8, g: u8, b: u8 };
|
||||||
|
fn toColorRef(rgb: Rgb8) u32 {
|
||||||
|
return (@as(u32, rgb.r) << 0) | (@as(u32, rgb.g) << 8) | (@as(u32, rgb.b) << 16);
|
||||||
|
}
|
||||||
|
fn fatalWin32(what: []const u8, err: win32.WIN32_ERROR) noreturn {
|
||||||
|
std.debug.panic("{s} failed with {}", .{ what, err.fmt() });
|
||||||
|
}
|
||||||
|
fn fatalHr(what: []const u8, hresult: win32.HRESULT) noreturn {
|
||||||
|
std.debug.panic("{s} failed, hresult=0x{x}", .{ what, @as(u32, @bitCast(hresult)) });
|
||||||
|
}
|
||||||
|
fn deleteObject(obj: ?win32.HGDIOBJ) void {
|
||||||
|
if (0 == win32.DeleteObject(obj)) fatalWin32("DeleteObject", win32.GetLastError());
|
||||||
|
}
|
||||||
|
fn getClientSize(hwnd: win32.HWND) win32.D2D_SIZE_U {
|
||||||
|
var rect: win32.RECT = undefined;
|
||||||
|
if (0 == win32.GetClientRect(hwnd, &rect))
|
||||||
|
fatalWin32("GetClientRect", win32.GetLastError());
|
||||||
|
return .{
|
||||||
|
.width = @intCast(rect.right - rect.left),
|
||||||
|
.height = @intCast(rect.bottom - rect.top),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn XY(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
x: T,
|
||||||
|
y: T,
|
||||||
|
pub fn init(x: T, y: T) @This() {
|
||||||
|
return .{ .x = x, .y = y };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
365
src/win32/windowmsg.zig
Normal file
365
src/win32/windowmsg.zig
Normal file
|
@ -0,0 +1,365 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const win32 = @import("win32").everything;
|
||||||
|
|
||||||
|
pub fn pointFromLparam(lparam: win32.LPARAM) win32.POINT {
|
||||||
|
return .{
|
||||||
|
.x = @as(i16, @bitCast(win32.loword(lparam))),
|
||||||
|
.y = @as(i16, @bitCast(win32.hiword(lparam))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const MessageNode = struct {
|
||||||
|
tail_ref: *?*MessageNode,
|
||||||
|
hwnd: win32.HWND,
|
||||||
|
msg: u32,
|
||||||
|
wparam: win32.WPARAM,
|
||||||
|
lparam: win32.LPARAM,
|
||||||
|
old_tail: ?*MessageNode,
|
||||||
|
pub fn init(
|
||||||
|
self: *MessageNode,
|
||||||
|
tail_ref: *?*MessageNode,
|
||||||
|
hwnd: win32.HWND,
|
||||||
|
msg: u32,
|
||||||
|
wparam: win32.WPARAM,
|
||||||
|
lparam: win32.LPARAM,
|
||||||
|
) void {
|
||||||
|
if (tail_ref.*) |old_tail| {
|
||||||
|
std.debug.assert(old_tail.hwnd == hwnd);
|
||||||
|
}
|
||||||
|
self.* = .{
|
||||||
|
.tail_ref = tail_ref,
|
||||||
|
.hwnd = hwnd,
|
||||||
|
.msg = msg,
|
||||||
|
.wparam = wparam,
|
||||||
|
.lparam = lparam,
|
||||||
|
.old_tail = tail_ref.*,
|
||||||
|
};
|
||||||
|
tail_ref.* = self;
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *MessageNode) void {
|
||||||
|
std.debug.assert(self.tail_ref.* == self);
|
||||||
|
self.tail_ref.* = self.old_tail;
|
||||||
|
}
|
||||||
|
pub fn fmtPath(self: *MessageNode) FmtPath {
|
||||||
|
return .{ .node = self };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn writeMessageNodePath(
|
||||||
|
writer: anytype,
|
||||||
|
node: *MessageNode,
|
||||||
|
) !void {
|
||||||
|
if (node.old_tail) |old_tail| {
|
||||||
|
try writeMessageNodePath(writer, old_tail);
|
||||||
|
try writer.writeAll(" > ");
|
||||||
|
}
|
||||||
|
try writer.print("{s}:{}", .{ msg_name(node.msg) orelse "?", node.msg });
|
||||||
|
switch (node.msg) {
|
||||||
|
win32.WM_NCMOUSEMOVE,
|
||||||
|
win32.WM_NCLBUTTONDOWN,
|
||||||
|
=> {
|
||||||
|
const hit_value = node.wparam;
|
||||||
|
const point = pointFromLparam(node.lparam);
|
||||||
|
try writer.print(
|
||||||
|
"(hit={s}:{},point={},{})",
|
||||||
|
.{ getHitName(@bitCast(hit_value)) orelse "?", hit_value, point.x, point.y },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
win32.WM_NCHITTEST,
|
||||||
|
win32.WM_MOUSEMOVE,
|
||||||
|
=> {
|
||||||
|
const point = pointFromLparam(node.lparam);
|
||||||
|
try writer.print("({},{})", .{ point.x, point.y });
|
||||||
|
},
|
||||||
|
win32.WM_CAPTURECHANGED => {
|
||||||
|
try writer.print("({})", .{node.lparam});
|
||||||
|
},
|
||||||
|
win32.WM_SYSCOMMAND => {
|
||||||
|
try writer.print("(type=0x{x})", .{0xfff0 & node.wparam});
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FmtPath = struct {
|
||||||
|
node: *MessageNode,
|
||||||
|
const Self = @This();
|
||||||
|
pub fn format(
|
||||||
|
self: Self,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) @TypeOf(writer).Error!void {
|
||||||
|
_ = fmt;
|
||||||
|
_ = options;
|
||||||
|
try writeMessageNodePath(writer, self.node);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn msg_name(msg: u32) ?[]const u8 {
|
||||||
|
return switch (msg) {
|
||||||
|
0 => "WM_NULL",
|
||||||
|
1 => "WM_CREATE",
|
||||||
|
2 => "WM_DESTROY",
|
||||||
|
3 => "WM_MOVE",
|
||||||
|
5 => "WM_SIZE",
|
||||||
|
6 => "WM_ACTIVATE",
|
||||||
|
7 => "WM_SETFOCUS",
|
||||||
|
8 => "WM_KILLFOCUS",
|
||||||
|
10 => "WM_ENABLE",
|
||||||
|
11 => "WM_SETREDRAW",
|
||||||
|
12 => "WM_SETTEXT",
|
||||||
|
13 => "WM_GETTEXT",
|
||||||
|
14 => "WM_GETTEXTLENGTH",
|
||||||
|
15 => "WM_PAINT",
|
||||||
|
16 => "WM_CLOSE",
|
||||||
|
17 => "WM_QUERYENDSESSION",
|
||||||
|
18 => "WM_QUIT",
|
||||||
|
19 => "WM_QUERYOPEN",
|
||||||
|
20 => "WM_ERASEBKGND",
|
||||||
|
21 => "WM_SYSCOLORCHANGE",
|
||||||
|
22 => "WM_ENDSESSION",
|
||||||
|
24 => "WM_SHOWWINDOW",
|
||||||
|
25 => "WM_CTLCOLOR",
|
||||||
|
26 => "WM_WININICHANGE",
|
||||||
|
27 => "WM_DEVMODECHANGE",
|
||||||
|
28 => "WM_ACTIVATEAPP",
|
||||||
|
29 => "WM_FONTCHANGE",
|
||||||
|
30 => "WM_TIMECHANGE",
|
||||||
|
31 => "WM_CANCELMODE",
|
||||||
|
32 => "WM_SETCURSOR",
|
||||||
|
33 => "WM_MOUSEACTIVATE",
|
||||||
|
34 => "WM_CHILDACTIVATE",
|
||||||
|
35 => "WM_QUEUESYNC",
|
||||||
|
36 => "WM_GETMINMAXINFO",
|
||||||
|
38 => "WM_PAINTICON",
|
||||||
|
39 => "WM_ICONERASEBKGND",
|
||||||
|
40 => "WM_NEXTDLGCTL",
|
||||||
|
42 => "WM_SPOOLERSTATUS",
|
||||||
|
43 => "WM_DRAWITEM",
|
||||||
|
44 => "WM_MEASUREITEM",
|
||||||
|
45 => "WM_DELETEITEM",
|
||||||
|
46 => "WM_VKEYTOITEM",
|
||||||
|
47 => "WM_CHARTOITEM",
|
||||||
|
48 => "WM_SETFONT",
|
||||||
|
49 => "WM_GETFONT",
|
||||||
|
50 => "WM_SETHOTKEY",
|
||||||
|
51 => "WM_GETHOTKEY",
|
||||||
|
55 => "WM_QUERYDRAGICON",
|
||||||
|
57 => "WM_COMPAREITEM",
|
||||||
|
61 => "WM_GETOBJECT",
|
||||||
|
65 => "WM_COMPACTING",
|
||||||
|
68 => "WM_COMMNOTIFY",
|
||||||
|
70 => "WM_WINDOWPOSCHANGING",
|
||||||
|
71 => "WM_WINDOWPOSCHANGED",
|
||||||
|
72 => "WM_POWER",
|
||||||
|
73 => "WM_COPYGLOBALDATA",
|
||||||
|
74 => "WM_COPYDATA",
|
||||||
|
75 => "WM_CANCELJOURNAL",
|
||||||
|
78 => "WM_NOTIFY",
|
||||||
|
80 => "WM_INPUTLANGCHANGEREQUEST",
|
||||||
|
81 => "WM_INPUTLANGCHANGE",
|
||||||
|
82 => "WM_TCARD",
|
||||||
|
83 => "WM_HELP",
|
||||||
|
84 => "WM_USERCHANGED",
|
||||||
|
85 => "WM_NOTIFYFORMAT",
|
||||||
|
123 => "WM_CONTEXTMENU",
|
||||||
|
124 => "WM_STYLECHANGING",
|
||||||
|
125 => "WM_STYLECHANGED",
|
||||||
|
126 => "WM_DISPLAYCHANGE",
|
||||||
|
127 => "WM_GETICON",
|
||||||
|
128 => "WM_SETICON",
|
||||||
|
129 => "WM_NCCREATE",
|
||||||
|
130 => "WM_NCDESTROY",
|
||||||
|
131 => "WM_NCCALCSIZE",
|
||||||
|
132 => "WM_NCHITTEST",
|
||||||
|
133 => "WM_NCPAINT",
|
||||||
|
134 => "WM_NCACTIVATE",
|
||||||
|
135 => "WM_GETDLGCODE",
|
||||||
|
136 => "WM_SYNCPAINT",
|
||||||
|
160 => "WM_NCMOUSEMOVE",
|
||||||
|
161 => "WM_NCLBUTTONDOWN",
|
||||||
|
162 => "WM_NCLBUTTONUP",
|
||||||
|
163 => "WM_NCLBUTTONDBLCLK",
|
||||||
|
164 => "WM_NCRBUTTONDOWN",
|
||||||
|
165 => "WM_NCRBUTTONUP",
|
||||||
|
166 => "WM_NCRBUTTONDBLCLK",
|
||||||
|
167 => "WM_NCMBUTTONDOWN",
|
||||||
|
168 => "WM_NCMBUTTONUP",
|
||||||
|
169 => "WM_NCMBUTTONDBLCLK",
|
||||||
|
171 => "WM_NCXBUTTONDOWN",
|
||||||
|
172 => "WM_NCXBUTTONUP",
|
||||||
|
173 => "WM_NCXBUTTONDBLCLK",
|
||||||
|
255 => "WM_INPUT",
|
||||||
|
256 => "WM_KEYDOWN",
|
||||||
|
257 => "WM_KEYUP",
|
||||||
|
258 => "WM_CHAR",
|
||||||
|
259 => "WM_DEADCHAR",
|
||||||
|
260 => "WM_SYSKEYDOWN",
|
||||||
|
261 => "WM_SYSKEYUP",
|
||||||
|
262 => "WM_SYSCHAR",
|
||||||
|
263 => "WM_SYSDEADCHAR",
|
||||||
|
265 => "WM_UNICHAR",
|
||||||
|
266 => "WM_CONVERTREQUEST",
|
||||||
|
267 => "WM_CONVERTRESULT",
|
||||||
|
268 => "WM_INTERIM",
|
||||||
|
269 => "WM_IME_STARTCOMPOSITION",
|
||||||
|
270 => "WM_IME_ENDCOMPOSITION",
|
||||||
|
271 => "WM_IME_COMPOSITION",
|
||||||
|
272 => "WM_INITDIALOG",
|
||||||
|
273 => "WM_COMMAND",
|
||||||
|
274 => "WM_SYSCOMMAND",
|
||||||
|
275 => "WM_TIMER",
|
||||||
|
276 => "WM_HSCROLL",
|
||||||
|
277 => "WM_VSCROLL",
|
||||||
|
278 => "WM_INITMENU",
|
||||||
|
279 => "WM_INITMENUPOPUP",
|
||||||
|
280 => "WM_SYSTIMER",
|
||||||
|
287 => "WM_MENUSELECT",
|
||||||
|
288 => "WM_MENUCHAR",
|
||||||
|
289 => "WM_ENTERIDLE",
|
||||||
|
290 => "WM_MENURBUTTONUP",
|
||||||
|
291 => "WM_MENUDRAG",
|
||||||
|
292 => "WM_MENUGETOBJECT",
|
||||||
|
293 => "WM_UNINITMENUPOPUP",
|
||||||
|
294 => "WM_MENUCOMMAND",
|
||||||
|
295 => "WM_CHANGEUISTATE",
|
||||||
|
296 => "WM_UPDATEUISTATE",
|
||||||
|
297 => "WM_QUERYUISTATE",
|
||||||
|
305 => "WM_LBTRACKPOINT",
|
||||||
|
306 => "WM_CTLCOLORMSGBOX",
|
||||||
|
307 => "WM_CTLCOLOREDIT",
|
||||||
|
308 => "WM_CTLCOLORLISTBOX",
|
||||||
|
309 => "WM_CTLCOLORBTN",
|
||||||
|
310 => "WM_CTLCOLORDLG",
|
||||||
|
311 => "WM_CTLCOLORSCROLLBAR",
|
||||||
|
312 => "WM_CTLCOLORSTATIC",
|
||||||
|
512 => "WM_MOUSEMOVE",
|
||||||
|
513 => "WM_LBUTTONDOWN",
|
||||||
|
514 => "WM_LBUTTONUP",
|
||||||
|
515 => "WM_LBUTTONDBLCLK",
|
||||||
|
516 => "WM_RBUTTONDOWN",
|
||||||
|
517 => "WM_RBUTTONUP",
|
||||||
|
518 => "WM_RBUTTONDBLCLK",
|
||||||
|
519 => "WM_MBUTTONDOWN",
|
||||||
|
520 => "WM_MBUTTONUP",
|
||||||
|
521 => "WM_MBUTTONDBLCLK",
|
||||||
|
522 => "WM_MOUSEWHEEL",
|
||||||
|
523 => "WM_XBUTTONDOWN",
|
||||||
|
524 => "WM_XBUTTONUP",
|
||||||
|
525 => "WM_XBUTTONDBLCLK",
|
||||||
|
526 => "WM_MOUSEHWHEEL",
|
||||||
|
528 => "WM_PARENTNOTIFY",
|
||||||
|
529 => "WM_ENTERMENULOOP",
|
||||||
|
530 => "WM_EXITMENULOOP",
|
||||||
|
531 => "WM_NEXTMENU",
|
||||||
|
532 => "WM_SIZING",
|
||||||
|
533 => "WM_CAPTURECHANGED",
|
||||||
|
534 => "WM_MOVING",
|
||||||
|
536 => "WM_POWERBROADCAST",
|
||||||
|
537 => "WM_DEVICECHANGE",
|
||||||
|
544 => "WM_MDICREATE",
|
||||||
|
545 => "WM_MDIDESTROY",
|
||||||
|
546 => "WM_MDIACTIVATE",
|
||||||
|
547 => "WM_MDIRESTORE",
|
||||||
|
548 => "WM_MDINEXT",
|
||||||
|
549 => "WM_MDIMAXIMIZE",
|
||||||
|
550 => "WM_MDITILE",
|
||||||
|
551 => "WM_MDICASCADE",
|
||||||
|
552 => "WM_MDIICONARRANGE",
|
||||||
|
553 => "WM_MDIGETACTIVE",
|
||||||
|
560 => "WM_MDISETMENU",
|
||||||
|
561 => "WM_ENTERSIZEMOVE",
|
||||||
|
562 => "WM_EXITSIZEMOVE",
|
||||||
|
563 => "WM_DROPFILES",
|
||||||
|
564 => "WM_MDIREFRESHMENU",
|
||||||
|
640 => "WM_IME_REPORT",
|
||||||
|
641 => "WM_IME_SETCONTEXT",
|
||||||
|
642 => "WM_IME_NOTIFY",
|
||||||
|
643 => "WM_IME_CONTROL",
|
||||||
|
644 => "WM_IME_COMPOSITIONFULL",
|
||||||
|
645 => "WM_IME_SELECT",
|
||||||
|
646 => "WM_IME_CHAR",
|
||||||
|
648 => "WM_IME_REQUEST",
|
||||||
|
656 => "WM_IME_KEYDOWN",
|
||||||
|
657 => "WM_IME_KEYUP",
|
||||||
|
672 => "WM_NCMOUSEHOVER",
|
||||||
|
673 => "WM_MOUSEHOVER",
|
||||||
|
674 => "WM_NCMOUSELEAVE",
|
||||||
|
675 => "WM_MOUSELEAVE",
|
||||||
|
768 => "WM_CUT",
|
||||||
|
769 => "WM_COPY",
|
||||||
|
770 => "WM_PASTE",
|
||||||
|
771 => "WM_CLEAR",
|
||||||
|
772 => "WM_UNDO",
|
||||||
|
773 => "WM_RENDERFORMAT",
|
||||||
|
774 => "WM_RENDERALLFORMATS",
|
||||||
|
775 => "WM_DESTROYCLIPBOARD",
|
||||||
|
776 => "WM_DRAWCLIPBOARD",
|
||||||
|
777 => "WM_PAINTCLIPBOARD",
|
||||||
|
778 => "WM_VSCROLLCLIPBOARD",
|
||||||
|
779 => "WM_SIZECLIPBOARD",
|
||||||
|
780 => "WM_ASKCBFORMATNAME",
|
||||||
|
781 => "WM_CHANGECBCHAIN",
|
||||||
|
782 => "WM_HSCROLLCLIPBOARD",
|
||||||
|
783 => "WM_QUERYNEWPALETTE",
|
||||||
|
784 => "WM_PALETTEISCHANGING",
|
||||||
|
785 => "WM_PALETTECHANGED",
|
||||||
|
786 => "WM_HOTKEY",
|
||||||
|
791 => "WM_PRINT",
|
||||||
|
792 => "WM_PRINTCLIENT",
|
||||||
|
793 => "WM_APPCOMMAND",
|
||||||
|
799 => "WM_DWMNCRENDERINGCHANGED",
|
||||||
|
856 => "WM_HANDHELDFIRST",
|
||||||
|
863 => "WM_HANDHELDLAST",
|
||||||
|
864 => "WM_AFXFIRST",
|
||||||
|
895 => "WM_AFXLAST",
|
||||||
|
896 => "WM_PENWINFIRST",
|
||||||
|
897 => "WM_RCRESULT",
|
||||||
|
898 => "WM_HOOKRCRESULT",
|
||||||
|
899 => "WM_GLOBALRCCHANGE",
|
||||||
|
900 => "WM_SKB",
|
||||||
|
901 => "WM_PENCTL",
|
||||||
|
902 => "WM_PENMISC",
|
||||||
|
903 => "WM_CTLINIT",
|
||||||
|
904 => "WM_PENEVENT",
|
||||||
|
911 => "WM_PENWINLAST",
|
||||||
|
1024 => "WM_USER+0",
|
||||||
|
1025 => "WM_USER+1",
|
||||||
|
1026 => "WM_USER+2",
|
||||||
|
1027 => "WM_USER+3",
|
||||||
|
1028 => "WM_USER+4",
|
||||||
|
1029 => "WM_USER+5",
|
||||||
|
1030 => "WM_USER+6",
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getHitName(hit: win32.LRESULT) ?[]const u8 {
|
||||||
|
return switch (hit) {
|
||||||
|
win32.HTERROR => "err",
|
||||||
|
win32.HTTRANSPARENT => "transprnt",
|
||||||
|
win32.HTNOWHERE => "nowhere",
|
||||||
|
win32.HTCLIENT => "client",
|
||||||
|
win32.HTCAPTION => "caption",
|
||||||
|
win32.HTSYSMENU => "sysmnu",
|
||||||
|
win32.HTSIZE => "size",
|
||||||
|
win32.HTMENU => "menu",
|
||||||
|
win32.HTHSCROLL => "hscroll",
|
||||||
|
win32.HTVSCROLL => "vscroll",
|
||||||
|
win32.HTMINBUTTON => "minbtn",
|
||||||
|
win32.HTMAXBUTTON => "max",
|
||||||
|
win32.HTLEFT => "left",
|
||||||
|
win32.HTRIGHT => "right",
|
||||||
|
win32.HTTOP => "top",
|
||||||
|
win32.HTTOPLEFT => "topleft",
|
||||||
|
win32.HTTOPRIGHT => "topright",
|
||||||
|
win32.HTBOTTOM => "bottom",
|
||||||
|
win32.HTBOTTOMLEFT => "botmleft",
|
||||||
|
win32.HTBOTTOMRIGHT => "botmright",
|
||||||
|
win32.HTBORDER => "border",
|
||||||
|
win32.HTCLOSE => "close",
|
||||||
|
win32.HTHELP => "help",
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue