win32 gui: track and use window bounds to prevent infinite shrinkage

This commit is contained in:
Jonathan Marler 2025-01-09 13:55:43 -07:00 committed by CJ van den Berg
parent 7d138a742c
commit e542ccb8f1
2 changed files with 59 additions and 34 deletions

View file

@ -1,4 +1,10 @@
{ {
"project": {
"press": [
["ctrl+=", "adjust_fontsize", 1.0],
["ctrl+-", "adjust_fontsize", -1.0]
]
},
"normal": { "normal": {
"press": [ "press": [
["ctrl+g", "cancel"], ["ctrl+g", "cancel"],

View file

@ -282,6 +282,7 @@ const State = struct {
currently_rendered_cell_size: ?XY(i32) = null, currently_rendered_cell_size: ?XY(i32) = null,
background: ?u32 = null, background: ?u32 = null,
last_sizing_edge: ?win32.WPARAM = null, last_sizing_edge: ?win32.WPARAM = null,
bounds: ?WindowBounds = null,
}; };
fn stateFromHwnd(hwnd: win32.HWND) *State { fn stateFromHwnd(hwnd: win32.HWND) *State {
std.debug.assert(hwnd == global.state.?.hwnd); std.debug.assert(hwnd == global.state.?.hwnd);
@ -403,19 +404,19 @@ fn calcWindowPlacement(
.x = win32.scaleDpi(i32, @intCast(initial_window_x), result.dpi.x), .x = win32.scaleDpi(i32, @intCast(initial_window_x), result.dpi.x),
.y = win32.scaleDpi(i32, @intCast(initial_window_y), result.dpi.y), .y = win32.scaleDpi(i32, @intCast(initial_window_y), result.dpi.y),
}; };
const unadjusted_size: XY(i32) = .{ const bounding_size: XY(i32) = .{
.x = @min(wanted_size.x, work_size.x), .x = @min(wanted_size.x, work_size.x),
.y = @min(wanted_size.y, work_size.y), .y = @min(wanted_size.y, work_size.y),
}; };
const unadjusted_rect: win32.RECT = ddui.rectIntFromSize(.{ const bouding_rect: win32.RECT = ddui.rectIntFromSize(.{
.left = work_rect.left + @divTrunc(work_size.x - unadjusted_size.x, 2), .left = work_rect.left + @divTrunc(work_size.x - bounding_size.x, 2),
.top = work_rect.top + @divTrunc(work_size.y - unadjusted_size.y, 2), .top = work_rect.top + @divTrunc(work_size.y - bounding_size.y, 2),
.width = unadjusted_size.x, .width = bounding_size.x,
.height = unadjusted_size.y, .height = bounding_size.y,
}); });
const adjusted_rect: win32.RECT = shrinkWindowRectForCells( const adjusted_rect: win32.RECT = calcWindowRect(
dpi, dpi,
unadjusted_rect, bouding_rect,
null, null,
cell_size, cell_size,
); );
@ -637,7 +638,16 @@ pub fn updateScreen(screen: *const vaxis.Screen) void {
}; };
} }
fn updateWindowSize(hwnd: win32.HWND, edge: ?win32.WPARAM) void { const WindowBounds = struct {
token: win32.RECT,
rect: win32.RECT,
};
fn updateWindowSize(
hwnd: win32.HWND,
edge: ?win32.WPARAM,
bounds_ref: *?WindowBounds,
) void {
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
const text_format_editor = global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = dpi, .fontsize = getFontSize() }); const text_format_editor = global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = dpi, .fontsize = getFontSize() });
const cell_size = getCellSize(text_format_editor); const cell_size = getCellSize(text_format_editor);
@ -647,12 +657,26 @@ fn updateWindowSize(hwnd: win32.HWND, edge: ?win32.WPARAM) void {
"GetWindowRect", "GetWindowRect",
win32.GetLastError(), win32.GetLastError(),
); );
const new_rect = shrinkWindowRectForCells(
const restored_bounds: ?win32.RECT = blk: {
if (bounds_ref.*) |b| {
if (std.meta.eql(b.token, window_rect)) {
break :blk b.rect;
}
}
break :blk null;
};
const bounds = if (restored_bounds) |b| b else window_rect;
const new_rect = calcWindowRect(
dpi, dpi,
window_rect, bounds,
edge, edge,
cell_size, cell_size,
); );
bounds_ref.* = .{
.token = new_rect,
.rect = if (restored_bounds) |b| b else new_rect,
};
setWindowPosRect(hwnd, new_rect); setWindowPosRect(hwnd, new_rect);
} }
@ -680,8 +704,8 @@ fn getCellSize(text_format: *win32.IDWriteTextFormat) XY(i32) {
if (hr < 0) fatalHr("GetMetrics", hr); if (hr < 0) fatalHr("GetMetrics", hr);
} }
return .{ return .{
.x = @as(i32, @intFromFloat(@floor(metrics.width))), .x = @max(1, @as(i32, @intFromFloat(@floor(metrics.width)))),
.y = @as(i32, @intFromFloat(@floor(metrics.height))), .y = @max(1, @as(i32, @intFromFloat(@floor(metrics.height)))),
}; };
} }
@ -1198,7 +1222,7 @@ fn WndProc(
const new_dpi: u32 = @intCast(0xffffffff & wparam); const new_dpi: u32 = @intCast(0xffffffff & wparam);
const text_format_editor = global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = new_dpi, .fontsize = getFontSize() }); const text_format_editor = global.text_format_editor.getOrCreate(FontCacheParams{ .dpi = new_dpi, .fontsize = getFontSize() });
const cell_size = getCellSize(text_format_editor); const cell_size = getCellSize(text_format_editor);
const new_rect = shrinkWindowRectForCells( const new_rect = calcWindowRect(
new_dpi, new_dpi,
.{ .{
.left = 0, .left = 0,
@ -1216,20 +1240,13 @@ fn WndProc(
return 1; return 1;
}, },
win32.WM_DPICHANGED => { win32.WM_DPICHANGED => {
const state = stateFromHwnd(hwnd);
const dpi = win32.dpiFromHwnd(hwnd); const dpi = win32.dpiFromHwnd(hwnd);
if (dpi != win32.hiword(wparam)) @panic("unexpected hiword dpi"); if (dpi != win32.hiword(wparam)) @panic("unexpected hiword dpi");
if (dpi != win32.loword(wparam)) @panic("unexpected loword dpi"); if (dpi != win32.loword(wparam)) @panic("unexpected loword dpi");
const rect: *win32.RECT = @ptrFromInt(@as(usize, @bitCast(lparam))); const rect: *win32.RECT = @ptrFromInt(@as(usize, @bitCast(lparam)));
if (0 == win32.SetWindowPos( setWindowPosRect(hwnd, rect.*);
hwnd, state.bounds = null;
null, // ignored via NOZORDER
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
.{ .NOZORDER = 1 },
)) fatalWin32("SetWindowPos", win32.GetLastError());
sendResize(hwnd);
return 0; return 0;
}, },
win32.WM_SIZE, win32.WM_SIZE,
@ -1254,7 +1271,8 @@ fn WndProc(
}, },
win32.WM_EXITSIZEMOVE => { win32.WM_EXITSIZEMOVE => {
const state = stateFromHwnd(hwnd); const state = stateFromHwnd(hwnd);
updateWindowSize(hwnd, state.last_sizing_edge); state.bounds = null;
updateWindowSize(hwnd, state.last_sizing_edge, &state.bounds);
state.last_sizing_edge = null; state.last_sizing_edge = null;
return 0; return 0;
}, },
@ -1292,9 +1310,10 @@ fn WndProc(
return WM_APP_SET_BACKGROUND_RESULT; return WM_APP_SET_BACKGROUND_RESULT;
}, },
WM_APP_ADJUST_FONTSIZE => { WM_APP_ADJUST_FONTSIZE => {
const state = stateFromHwnd(hwnd);
const amount: f32 = @bitCast(@as(u32, @intCast(0xFFFFFFFFF & wparam))); const amount: f32 = @bitCast(@as(u32, @intCast(0xFFFFFFFFF & wparam)));
global.fontsize = @max(getFontSize() + amount, 1.0); global.fontsize = @max(getFontSize() + amount, 1.0);
updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT); updateWindowSize(hwnd, win32.WMSZ_BOTTOMRIGHT, &state.bounds);
win32.invalidateHwnd(hwnd); win32.invalidateHwnd(hwnd);
return WM_APP_ADJUST_FONTSIZE_RESULT; return WM_APP_ADJUST_FONTSIZE_RESULT;
}, },
@ -1392,15 +1411,15 @@ fn getClientSize(hwnd: win32.HWND) XY(i32) {
return .{ .x = rect.right, .y = rect.bottom }; return .{ .x = rect.right, .y = rect.bottom };
} }
fn shrinkWindowRectForCells( fn calcWindowRect(
dpi: u32, dpi: u32,
rect: win32.RECT, bouding_rect: win32.RECT,
maybe_edge: ?win32.WPARAM, maybe_edge: ?win32.WPARAM,
cell_size: XY(i32), cell_size: XY(i32),
) win32.RECT { ) win32.RECT {
const window_size: XY(i32) = .{ const window_size: XY(i32) = .{
.x = rect.right - rect.left, .x = bouding_rect.right - bouding_rect.left,
.y = rect.bottom - rect.top, .y = bouding_rect.bottom - bouding_rect.top,
}; };
const client_inset = getClientInset(dpi); const client_inset = getClientInset(dpi);
const client_size: XY(i32) = .{ const client_size: XY(i32) = .{
@ -1427,22 +1446,22 @@ fn shrinkWindowRectForCells(
} else .{ .x = .both, .y = .both }; } else .{ .x = .both, .y = .both };
return .{ return .{
.left = rect.left - switch (adjustments.x) { .left = bouding_rect.left - switch (adjustments.x) {
.low => diff.x, .low => diff.x,
.high => 0, .high => 0,
.both => @divTrunc(diff.x, 2), .both => @divTrunc(diff.x, 2),
}, },
.top = rect.top - switch (adjustments.y) { .top = bouding_rect.top - switch (adjustments.y) {
.low => diff.y, .low => diff.y,
.high => 0, .high => 0,
.both => @divTrunc(diff.y, 2), .both => @divTrunc(diff.y, 2),
}, },
.right = rect.right + switch (adjustments.x) { .right = bouding_rect.right + switch (adjustments.x) {
.low => 0, .low => 0,
.high => diff.x, .high => diff.x,
.both => @divTrunc(diff.x + 1, 2), .both => @divTrunc(diff.x + 1, 2),
}, },
.bottom = rect.bottom + switch (adjustments.y) { .bottom = bouding_rect.bottom + switch (adjustments.y) {
.low => 0, .low => 0,
.high => diff.y, .high => diff.y,
.both => @divTrunc(diff.y + 1, 2), .both => @divTrunc(diff.y + 1, 2),