diff --git a/build.zig b/build.zig index 803c559..dab6d5e 100644 --- a/build.zig +++ b/build.zig @@ -452,6 +452,7 @@ pub fn build_exe( .{ .name = "thespian", .module = thespian_mod }, .{ .name = "cbor", .module = cbor_mod }, .{ .name = "config", .module = config_mod }, + .{ .name = "gui_config", .module = gui_config_mod }, .{ .name = "log", .module = log_mod }, .{ .name = "command", .module = command_mod }, .{ .name = "EventHandler", .module = EventHandler_mod }, diff --git a/src/gui_config.zig b/src/gui_config.zig index ea65ca3..c981dc7 100644 --- a/src/gui_config.zig +++ b/src/gui_config.zig @@ -1,4 +1,4 @@ fontface: [] const u8 = "Cascadia Code", -fontsize: f32 = 17.5, +fontsize: u8 = 17, include_files: []const u8 = "", diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 1a83cb5..168aebe 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -199,6 +199,7 @@ ["r", "open_recent_project"], ["p", "open_command_palette"], ["c", "open_config"], + ["g", "open_gui_config"], ["k", "open_keybind_config"], ["t", "change_theme"], ["q", "quit"], diff --git a/src/main.zig b/src/main.zig index 83469bd..1491189 100644 --- a/src/main.zig +++ b/src/main.zig @@ -393,7 +393,11 @@ pub fn free_config(allocator: std.mem.Allocator, bufs: [][]const u8) void { for (bufs) |buf| allocator.free(buf); } +var config_mutex: std.Thread.Mutex = .{}; + pub fn read_config(T: type, allocator: std.mem.Allocator) struct { T, [][]const u8 } { + config_mutex.lock(); + defer config_mutex.unlock(); var bufs: [][]const u8 = &[_][]const u8{}; const file_name = get_app_config_file_name(application_name, @typeName(T)) catch return .{ .{}, bufs }; var conf: T = .{}; @@ -404,7 +408,7 @@ pub fn read_config(T: type, allocator: std.mem.Allocator) struct { T, [][]const fn read_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs: *[][]const u8, file_name: []const u8) void { read_json_config_file(T, allocator, conf, bufs, file_name) catch |e| - log.logger("config").print_err("read_config", "error reading config file: {any}", .{e}); + std.log.err("error reading config file '{s}' {any}", .{ file_name, e }); return; } @@ -453,6 +457,8 @@ fn read_nested_include_files(T: type, allocator: std.mem.Allocator, conf: *T, bu } pub fn write_config(conf: anytype, allocator: std.mem.Allocator) !void { + config_mutex.lock(); + defer config_mutex.unlock(); return write_json_file(@TypeOf(conf), conf, allocator, try get_app_config_file_name(application_name, @typeName(@TypeOf(conf)))); } @@ -643,14 +649,8 @@ fn get_app_config_file_name(appname: []const u8, comptime base_name: []const u8) fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name: []const u8) ![]const u8 { const local = struct { var config_file_buffer: [std.posix.PATH_MAX]u8 = undefined; - var config_file: ?[]const u8 = null; }; - const config_file = if (local.config_file) |file| - file - else - try std.fmt.bufPrint(&local.config_file_buffer, "{s}/{s}", .{ try get_app_config_dir(appname), config_file_name }); - local.config_file = config_file; - return config_file; + return std.fmt.bufPrint(&local.config_file_buffer, "{s}/{s}", .{ try get_app_config_dir(appname), config_file_name }); } pub fn get_config_file_name(T: type) ![]const u8 { diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index 90ded6a..171e5e9 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -134,7 +134,10 @@ pub fn stop(self: *Self) void { // the window is created and we call dispatch_initialized const hwnd = self.hwnd orelse unreachable; gui.stop(hwnd); - if (self.thread) |thread| thread.join(); + if (self.thread) |thread| { + thread.join(); + self.thread = null; + } } pub fn stdplane(self: *Self) Plane { diff --git a/src/tui/home.zig b/src/tui/home.zig index cbd5c24..bd78972 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const build_options = @import("build_options"); const tp = @import("thespian"); const Plane = @import("renderer").Plane; @@ -25,7 +26,19 @@ input_namespace: []const u8, const Self = @This(); -const menu_commands = &[_][]const u8{ +const menu_commands = if (build_options.gui) &[_][]const u8{ + "open_help", + "open_file", + "open_recent", + "open_recent_project", + "open_command_palette", + "open_config", + "open_gui_config", + "open_keybind_config", + "toggle_input_mode", + "change_theme", + "quit", +} else &[_][]const u8{ "open_help", "open_file", "open_recent", diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index e6630b5..bdb26b2 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -378,11 +378,17 @@ const cmds = struct { pub const open_help_meta = .{ .description = "Open help" }; pub fn open_config(_: *Self, _: Ctx) Result { - const file_name = try root.get_config_file_name(@TypeOf(tui.current().config)); + const file_name = try root.get_config_file_name(@import("config")); try tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_name } }); } pub const open_config_meta = .{ .description = "Edit configuration file" }; + pub fn open_gui_config(_: *Self, _: Ctx) Result { + const file_name = try root.get_config_file_name(@import("gui_config")); + try tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_name } }); + } + pub const open_gui_config_meta = .{ .description = "Edit gui configuration file" }; + pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result { try self.check_all_not_dirty(); tui.reset_drag_context(); diff --git a/src/win32/gui.zig b/src/win32/gui.zig index 1e19e2b..7897a9c 100644 --- a/src/win32/gui.zig +++ b/src/win32/gui.zig @@ -52,6 +52,7 @@ const global = struct { var icons: Icons = undefined; var dwrite_factory: *win32.IDWriteFactory = undefined; var d2d_factory: *win32.ID2D1Factory = undefined; + var conf: ?*gui_config = null; const shared_screen = struct { var mutex: std.Thread.Mutex = .{}; @@ -149,10 +150,26 @@ const Dpi = struct { }; fn createTextFormatEditor(dpi: Dpi) *win32.IDWriteTextFormat { + const default_config = gui_config{}; + const fontface_utf8 = if (global.conf) |conf| conf.fontface else blk: { + std.log.err("global gui config not found", .{}); + break :blk default_config.fontface; + }; + const fontsize = if (global.conf) |conf| conf.fontsize else default_config.fontsize; + + var buf: [4096]u8 = undefined; + var fba = std.heap.FixedBufferAllocator.init(&buf); + var fontface = std.ArrayList(u16).init(fba.allocator()); + std.unicode.utf8ToUtf16LeArrayList(&fontface, fontface_utf8) catch { + std.log.err("fontface contains invalid UTF-8", .{}); + fontface.clearRetainingCapacity(); + std.unicode.utf8ToUtf16LeArrayList(&fontface, default_config.fontface) catch {}; + }; + var err: HResultError = undefined; return ddui.createTextFormat(global.dwrite_factory, &err, .{ - .size = win32.scaleDpi(f32, 14, dpi.value), - .family_name = win32.L("Cascadia Code"), + .size = win32.scaleDpi(f32, @as(f32, @floatFromInt(fontsize)), dpi.value), + .family_name = fontface.toOwnedSliceSentinel(0) catch @panic("OOM:createTextFormatEditor"), }) catch std.debug.panic("{s} failed, hresult=0x{x}", .{ err.context, err.hr }); } @@ -230,6 +247,7 @@ const State = struct { scroll_delta: isize = 0, currently_rendered_cell_size: ?XY(i32) = null, background: ?u32 = null, + conf: gui_config, }; fn stateFromHwnd(hwnd: win32.HWND) *State { const addr: usize = @bitCast(win32.GetWindowLongPtrW(hwnd, @enumFromInt(0))); @@ -422,6 +440,8 @@ fn entry(pid: thespian.pid) !void { ); const conf, _ = root.read_config(gui_config, arena_instance.allocator()); + root.write_config(conf, arena_instance.allocator()) catch + std.log.err("failed to write gui config file", .{}); var create_args = CreateWindowArgs{ .allocator = arena_instance.allocator(), @@ -1132,7 +1152,10 @@ fn WndProc( state.* = .{ .pid = create_args.pid, + .conf = create_args.conf, }; + global.conf = &state.conf; + const existing = win32.SetWindowLongPtrW( hwnd, @enumFromInt(0),