From e1b1591167bdafc7fcd16f2821477032b16c975b Mon Sep 17 00:00:00 2001 From: CJ van den Berg Date: Fri, 17 Jan 2025 20:32:20 +0100 Subject: [PATCH] feat(win32 gui): add font selection palette closes #102 --- src/keybind/builtin/emacs.json | 1 + src/keybind/builtin/flow.json | 1 + src/keybind/builtin/helix.json | 1 + src/keybind/builtin/vim.json | 1 + src/renderer/win32/renderer.zig | 5 ++ src/tui/home.zig | 9 ++- src/tui/mode/overlay/fontface_palette.zig | 78 +++++++++++++++++++++++ src/tui/mode/overlay/palette.zig | 20 +++--- src/tui/tui.zig | 29 +++++++++ src/win32/gui.zig | 53 +++++++++++++-- 10 files changed, 184 insertions(+), 14 deletions(-) create mode 100644 src/tui/mode/overlay/fontface_palette.zig diff --git a/src/keybind/builtin/emacs.json b/src/keybind/builtin/emacs.json index be5f851..fe107f7 100644 --- a/src/keybind/builtin/emacs.json +++ b/src/keybind/builtin/emacs.json @@ -81,6 +81,7 @@ ["ctrl+x ctrl+f", "open_file"], ["ctrl+x b", "open_recent"], ["alt+x", "open_command_palette"], + ["f", "change_fontface"], ["ctrl+x ctrl+c", "quit"] ] } diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index f4aa868..4776509 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -207,6 +207,7 @@ ["g", "open_gui_config"], ["k", "open_keybind_config"], ["t", "change_theme"], + ["f", "change_fontface"], ["q", "quit"], ["ctrl+\\", "add_split"], ["ctrl+f ctrl+f ctrl+f ctrl+f ctrl+f", "home_sheeran"], diff --git a/src/keybind/builtin/helix.json b/src/keybind/builtin/helix.json index bcd1b74..7f912a5 100644 --- a/src/keybind/builtin/helix.json +++ b/src/keybind/builtin/helix.json @@ -515,6 +515,7 @@ ["b", "open_keybind_config"], ["j", "home_menu_down"], ["k", "home_menu_up"], + ["f", "change_fontface"], ["space", "home_menu_activate"] ] } diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index c6dff63..01e82b8 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -100,6 +100,7 @@ ["b", "open_keybind_config"], ["j", "home_menu_down"], ["k", "home_menu_up"], + ["f", "change_fontface"], ["", "home_menu_activate"] ] } diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index a455218..ac0f899 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -396,6 +396,11 @@ pub fn reset_fontface(self: *Self) void { gui.reset_fontface(hwnd); } +pub fn get_fontfaces(self: *Self) void { + const hwnd = self.hwnd orelse return; + gui.get_fontfaces(hwnd); +} + pub fn set_terminal_cursor_color(self: *Self, color: Color) void { _ = self; _ = color; diff --git a/src/tui/home.zig b/src/tui/home.zig index bd78972..afba9f0 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -37,6 +37,7 @@ const menu_commands = if (build_options.gui) &[_][]const u8{ "open_keybind_config", "toggle_input_mode", "change_theme", + "change_fontface", "quit", } else &[_][]const u8{ "open_help", @@ -101,9 +102,13 @@ fn add_menu_command(self: *Self, comptime command_name: []const u8, menu: anytyp var buf: [64]u8 = undefined; var fis = std.io.fixedBufferStream(&buf); const writer = fis.writer(); - try writer.print("{s} ..", .{description}); + const leader = if (hint.len > 0) "." else " "; + _ = try writer.write(description); + _ = try writer.write(" "); + _ = try writer.write(leader); + _ = try writer.write(leader); for (0..(self.max_desc_len - label_len - 5)) |_| - try writer.print(".", .{}); + _ = try writer.write(leader); try writer.print(" :{s}", .{hint}); const label = fis.getWritten(); try menu.add_item_with_handler(label, menu_action(command_name)); diff --git a/src/tui/mode/overlay/fontface_palette.zig b/src/tui/mode/overlay/fontface_palette.zig new file mode 100644 index 0000000..dce743a --- /dev/null +++ b/src/tui/mode/overlay/fontface_palette.zig @@ -0,0 +1,78 @@ +const std = @import("std"); +const cbor = @import("cbor"); +const tp = @import("thespian"); + +const Widget = @import("../../Widget.zig"); +const tui = @import("../../tui.zig"); + +pub const Type = @import("palette.zig").Create(@This()); + +pub const label = "Select font face"; +pub const name = " font"; +pub const description = "font"; + +pub const Entry = struct { + label: []const u8, +}; + +pub const Match = struct { + label: []const u8, + score: i32, + matches: []const usize, +}; + +var previous_fontface: ?[]const u8 = null; + +pub fn deinit(palette: *Type) void { + if (previous_fontface) |fontface| + palette.allocator.free(fontface); + previous_fontface = null; + for (palette.entries.items) |entry| + palette.allocator.free(entry.label); +} + +pub fn load_entries(palette: *Type) !usize { + var idx: usize = 0; + previous_fontface = try palette.allocator.dupe(u8, tui.current().fontface); + const fontfaces = tui.current().fontfaces orelse return 0; + tui.current().fontfaces = null; + for (fontfaces.items) |fontface| { + idx += 1; + (try palette.entries.addOne()).* = .{ .label = fontface }; + if (previous_fontface) |previous_fontface_| if (std.mem.eql(u8, fontface, previous_fontface_)) { + palette.initial_selected = idx; + }; + } + return 0; +} + +pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void { + var value = std.ArrayList(u8).init(palette.allocator); + defer value.deinit(); + const writer = value.writer(); + try cbor.writeValue(writer, entry.label); + try cbor.writeValue(writer, matches orelse &[_]usize{}); + try palette.menu.add_item_with_handler(value.items, select); + palette.items += 1; +} + +fn select(menu: **Type.MenuState, button: *Type.ButtonState) void { + var label_: []const u8 = undefined; + var iter = button.opts.label; + if (!(cbor.matchString(&iter, &label_) catch false)) return; + tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("fontface_palette", e); + tp.self_pid().send(.{ "cmd", "set_fontface", .{label_} }) catch |e| menu.*.opts.ctx.logger.err("fontface_palette", e); +} + +pub fn updated(palette: *Type, button_: ?*Type.ButtonState) !void { + const button = button_ orelse return cancel(palette); + var label_: []const u8 = undefined; + var iter = button.opts.label; + if (!(cbor.matchString(&iter, &label_) catch false)) return; + tp.self_pid().send(.{ "cmd", "set_fontface", .{label_} }) catch |e| palette.logger.err("fontface_palette upated", e); +} + +pub fn cancel(palette: *Type) !void { + if (previous_fontface) |prev| + tp.self_pid().send(.{ "cmd", "set_fontface", .{prev} }) catch |e| palette.logger.err("fontface_palette cancel", e); +} diff --git a/src/tui/mode/overlay/palette.zig b/src/tui/mode/overlay/palette.zig index 108f145..239d95f 100644 --- a/src/tui/mode/overlay/palette.zig +++ b/src/tui/mode/overlay/palette.zig @@ -76,7 +76,6 @@ pub fn Create(options: type) type { try self.start_query(0); try mv.floating_views.add(self.modal.widget()); try mv.floating_views.add(self.menu.container_widget); - if (self.initial_selected) |idx| self.select(idx); var mode = try keybind.mode("overlay/palette", allocator, .{ .insert_command = "overlay_insert_bytes", }); @@ -154,7 +153,7 @@ pub fn Create(options: type) type { fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void { self.do_resize(); - self.start_query(0) catch {}; + // self.start_query(0) catch {}; } fn do_resize(self: *Self) void { @@ -231,13 +230,18 @@ pub fn Create(options: type) type { } else { _ = try self.query_entries(self.inputbox.text.items); } - self.menu.select_down(); - var i = n; - while (i > 0) : (i -= 1) + if (self.initial_selected) |idx| { + self.initial_selected = null; + self.select(idx); + } else { self.menu.select_down(); - self.do_resize(); - tui.current().refresh_hover(); - self.selection_updated(); + var i = n; + while (i > 0) : (i -= 1) + self.menu.select_down(); + self.do_resize(); + tui.current().refresh_hover(); + self.selection_updated(); + } } fn query_entries(self: *Self, query: []const u8) error{OutOfMemory}!usize { diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 9939bb5..faa3216 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -54,6 +54,8 @@ render_pending: bool = false, keepalive_timer: ?tp.Cancellable = null, mouse_idle_timer: ?tp.Cancellable = null, default_cursor: keybind.CursorShape = .default, +fontface: []const u8 = "", +fontfaces: ?std.ArrayList([]const u8) = null, const keepalive = std.time.us_per_day * 365; // one year const idle_frames = 0; @@ -381,6 +383,27 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void { return; } + if (try m.match(.{ "fontface", "done" })) { + return self.enter_overlay_mode(@import("mode/overlay/fontface_palette.zig").Type); + } + + var fontface: []const u8 = undefined; + if (try m.match(.{ "fontface", "current", tp.extract(&fontface) })) { + if (self.fontface.len > 0) self.allocator.free(self.fontface); + self.fontface = ""; + self.fontface = try self.allocator.dupe(u8, fontface); + return; + } + + if (try m.match(.{ "fontface", tp.extract(&fontface) })) { + var fontfaces = if (self.fontfaces) |*p| p else blk: { + self.fontfaces = std.ArrayList([]const u8).init(self.allocator); + break :blk &self.fontfaces.?; + }; + try fontfaces.append(try self.allocator.dupe(u8, fontface)); + return; + } + return tp.unexpected(m); } @@ -798,6 +821,12 @@ const cmds = struct { } pub const change_file_type_meta = .{ .description = "Change file type" }; + pub fn change_fontface(self: *Self, _: Ctx) Result { + if (build_options.gui) + self.rdr.get_fontfaces(); + } + pub const change_fontface_meta = .{ .description = "Select font face" }; + pub fn exit_overlay_mode(self: *Self, _: Ctx) Result { self.rdr.cursor_disable(); if (self.input_mode_outer == null) return; diff --git a/src/win32/gui.zig b/src/win32/gui.zig index 0c5d437..7dc5456 100644 --- a/src/win32/gui.zig +++ b/src/win32/gui.zig @@ -31,7 +31,8 @@ const WM_APP_SET_FONTSIZE = win32.WM_APP + 4; const WM_APP_SET_FONTFACE = win32.WM_APP + 5; const WM_APP_RESET_FONTSIZE = win32.WM_APP + 6; const WM_APP_RESET_FONTFACE = win32.WM_APP + 7; -const WM_APP_UPDATE_SCREEN = win32.WM_APP + 8; +const WM_APP_GET_FONTFACES = win32.WM_APP + 8; +const WM_APP_UPDATE_SCREEN = win32.WM_APP + 9; const WM_APP_EXIT_RESULT = 0x45feaa11; const WM_APP_SET_BACKGROUND_RESULT = 0x369a26cd; @@ -40,6 +41,7 @@ const WM_APP_SET_FONTSIZE_RESULT = 0x72fa44bc; const WM_APP_SET_FONTFACE_RESULT = 0x1a49ffa8; const WM_APP_RESET_FONTSIZE_RESULT = 0x082c4c0c; const WM_APP_RESET_FONTFACE_RESULT = 0x0101f996; +const WM_APP_GET_FONTFACES_RESULT = 0x07e228f5; const WM_APP_UPDATE_SCREEN_RESULT = 0x3add213b; pub const DropWriter = struct { @@ -128,7 +130,7 @@ fn getIcons(dpi: XY(u32)) Icons { return .{ .small = @ptrCast(small), .large = @ptrCast(large) }; } -fn getConfig() *const gui_config { +fn getConfig() *gui_config { if (global.conf == null) { global.conf, _ = root.read_config(gui_config, global.arena); root.write_config(global.conf.?, global.arena) catch @@ -171,6 +173,15 @@ fn getFontFace() *const FontFace { return &(global.fontface.?); } +fn setFontFace(fontface: *const FontFace) void { + global.fontface = fontface.*; + const conf = getConfig(); + var buf: [FontFace.max * 2]u8 = undefined; + conf.fontface = buf[0 .. std.unicode.utf16LeToUtf8(&buf, fontface.slice()) catch return]; + root.write_config(conf.*, global.arena) catch + std.log.err("failed to write gui config file", .{}); +} + fn getFontSize() f32 { if (global.fontsize == null) { global.fontsize = @floatFromInt(getConfig().fontsize); @@ -492,6 +503,15 @@ pub fn reset_fontface(hwnd: win32.HWND) void { )); } +pub fn get_fontfaces(hwnd: win32.HWND) void { + std.debug.assert(WM_APP_GET_FONTFACES_RESULT == win32.SendMessageW( + hwnd, + WM_APP_GET_FONTFACES, + 0, + 0, + )); +} + pub fn updateScreen(hwnd: win32.HWND, screen: *const vaxis.Screen) void { std.debug.assert(WM_APP_UPDATE_SCREEN_RESULT == win32.SendMessageW( hwnd, @@ -543,6 +563,27 @@ fn updateWindowSize( setWindowPosRect(hwnd, new_rect); } +fn getFontFaces(state: *State) void { + const fonts = render.Fonts.init(); + defer fonts.deinit(); + var buf: [FontFace.max * 2]u8 = undefined; + + if (global.fontface) |fontface| + state.pid.send(.{ + "fontface", + "current", + buf[0 .. std.unicode.utf16LeToUtf8(&buf, fontface.slice()) catch 0], + }) catch {}; + + for (0..fonts.count()) |font_index| + state.pid.send(.{ + "fontface", + buf[0 .. std.unicode.utf16LeToUtf8(&buf, fonts.getName(font_index).slice()) catch 0], + }) catch {}; + + state.pid.send(.{ "fontface", "done" }) catch {}; +} + const CellPos = struct { cell: XY(i32), offset: XY(i32), @@ -1138,8 +1179,7 @@ fn WndProc( }, WM_APP_SET_FONTFACE => { const state = stateFromHwnd(hwnd); - const fontface: *FontFace = @ptrFromInt(wparam); - global.fontface = fontface.*; + setFontFace(@ptrFromInt(wparam)); updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds); win32.invalidateHwnd(hwnd); return WM_APP_SET_FONTFACE_RESULT; @@ -1151,6 +1191,11 @@ fn WndProc( win32.invalidateHwnd(hwnd); return WM_APP_SET_FONTFACE_RESULT; }, + WM_APP_GET_FONTFACES => { + const state = stateFromHwnd(hwnd); + getFontFaces(state); + return WM_APP_GET_FONTFACES_RESULT; + }, WM_APP_UPDATE_SCREEN => { const screen: *const vaxis.Screen = @ptrFromInt(wparam); _ = global.screen_arena.reset(.retain_capacity);