diff --git a/CHANGELOG.md b/CHANGELOG.md index cb62594e..14e95557 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,8 @@ * Unicode input mode now accepts input from the numpad as well, numlock is ignored. +* A new `resize-by-cells` option, enabled by default, allows the size + of floating windows to be constrained to multiples of the cell size. ### Changed @@ -68,6 +70,8 @@ * Kitty keyboard protocol: updated behavior of modifiers bits during modifier key events, to match the (new [#6913][kitty-6913]) behavior in kitty >= 0.32.0 ([#1561][1561]). +* When changing font sizes or display scales in floating windows, the + window will be resized as needed to preserve the same grid size. [1526]: https://codeberg.org/dnkl/foot/issues/1526 [1528]: https://codeberg.org/dnkl/foot/issues/1528 diff --git a/config.c b/config.c index f964f7c1..df250e57 100644 --- a/config.c +++ b/config.c @@ -921,6 +921,9 @@ parse_section_main(struct context *ctx) else if (strcmp(key, "resize-delay-ms") == 0) return value_to_uint16(ctx, 10, &conf->resize_delay_ms); + else if (strcmp(key, "resize-by-cells") == 0) + return value_to_bool(ctx, &conf->resize_by_cells); + else if (strcmp(key, "bold-text-in-bright") == 0) { if (strcmp(value, "palette-based") == 0) { conf->bold_in_bright.enabled = true; @@ -2990,6 +2993,7 @@ config_load(struct config *conf, const char *conf_path, }, .pad_x = 0, .pad_y = 0, + .resize_by_cells = true, .resize_delay_ms = 100, .bold_in_bright = { .enabled = false, diff --git a/config.h b/config.h index 3c5b3df7..cc4094c4 100644 --- a/config.h +++ b/config.h @@ -128,6 +128,9 @@ struct config { unsigned pad_x; unsigned pad_y; bool center; + + bool resize_by_cells; + uint16_t resize_delay_ms; struct { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index e276d186..1c87db53 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -252,6 +252,21 @@ empty string to be set, but it must be quoted: *KEY=""*) Default: _100_. +*resize-by-cells* + Boolean. + + When set to *yes*, the window size will be constrained to multiples + of the cell size (plus any configured padding). When set to *no*, + the window size will be unconstrained, and padding may be adjusted + as necessary to accommodate window sizes that are not multiples of + the cell size. + + This option only applies to floating windows. Sizes of maxmized, tiled + or fullscreen windows will not be constrained to multiples of the cell + size. + + Default: _yes_ + *initial-window-size-pixels* Initial window width and height in _pixels_ (subject to output scaling), in the form _WIDTHxHEIGHT_. The height _includes_ the diff --git a/render.c b/render.c index a522c24c..38c828c6 100644 --- a/render.c +++ b/render.c @@ -3912,9 +3912,25 @@ send_dimensions_to_client(struct terminal *term) } } +static void +set_size_from_grid(struct terminal *term, int *width, int *height, int cols, int rows) +{ + /* Nominal grid dimensions */ + *width = cols * term->cell_width; + *height = rows * term->cell_height; + + /* Include any configured padding */ + *width += 2 * term->conf->pad_x * term->scale; + *height += 2 * term->conf->pad_y * term->scale; + + /* Round to multiples of scale */ + *width = round(term->scale * round(*width / term->scale)); + *height = round(term->scale * round(*height / term->scale)); +} + /* Move to terminal.c? */ -static bool -maybe_resize(struct terminal *term, int width, int height, bool force) +bool +render_resize(struct terminal *term, int width, int height, uint8_t opts) { if (term->shutdown.in_progress) return false; @@ -3925,21 +3941,29 @@ maybe_resize(struct terminal *term, int width, int height, bool force) if (term->cell_width == 0 && term->cell_height == 0) return false; + const bool is_floating = + !term->window->is_maximized && + !term->window->is_fullscreen && + !term->window->is_tiled; + + /* Convert logical size to physical size */ const float scale = term->scale; width = round(width * scale); height = round(height * scale); + /* If the grid should be kept, the size should be overridden */ + if (is_floating && (opts & RESIZE_KEEP_GRID)) { + set_size_from_grid(term, &width, &height, term->cols, term->rows); + } + if (width == 0 && height == 0) { - /* - * The compositor is letting us choose the size - * - * If we have a "last" used size - use that. Otherwise, use - * the size from the user configuration. - */ + /* The compositor is letting us choose the size */ if (term->stashed_width != 0 && term->stashed_height != 0) { + /* If a default size is requested, prefer the "last used" size */ width = term->stashed_width; height = term->stashed_height; } else { + /* Otherwise, use a user-configured size */ switch (term->conf->size.type) { case CONF_SIZE_PX: width = term->conf->size.width; @@ -3959,15 +3983,8 @@ maybe_resize(struct terminal *term, int width, int height, bool force) break; case CONF_SIZE_CELLS: - width = term->conf->size.width * term->cell_width; - height = term->conf->size.height * term->cell_height; - - width += 2 * term->conf->pad_x * scale; - height += 2 * term->conf->pad_y * scale; - - /* Ensure width/height is a valid multiple of scale */ - width = roundf(scale * roundf(width / scale)); - height = roundf(scale * roundf(height / scale)); + set_size_from_grid(term, &width, &height, + term->conf->size.width, term->conf->size.height); break; } } @@ -3990,8 +4007,25 @@ maybe_resize(struct terminal *term, int width, int height, bool force) const int pad_x = min(max_pad_x, scale * term->conf->pad_x); const int pad_y = min(max_pad_y, scale * term->conf->pad_y); - if (!force && width == term->width && height == term->height && scale == term->scale) + if (is_floating && + (opts & RESIZE_BY_CELLS) && + term->conf->resize_by_cells) + { + /* If resizing in cell increments, restrict the width and height */ + width = ((width - 2 * pad_x) / term->cell_width) * term->cell_width + 2 * pad_x; + width = max(min_width, roundf(scale * roundf(width / scale))); + + height = ((height - 2 * pad_y) / term->cell_height) * term->cell_height + 2 * pad_y; + height = max(min_height, roundf(scale * roundf(height / scale))); + } + + if (!(opts & RESIZE_FORCE) && + width == term->width && + height == term->height && + scale == term->scale) + { return false; + } /* Cancel an application initiated "Synchronized Update" */ term_disable_app_sync_updates(term); @@ -4225,10 +4259,7 @@ damage_view: /* Signal TIOCSWINSZ */ send_dimensions_to_client(term); - if (!term->window->is_maximized && - !term->window->is_fullscreen && - !term->window->is_tiled) - { + if (is_floating) { /* Stash current size, to enable us to restore it when we're * being un-maximized/fullscreened/tiled */ term->stashed_width = term->width; @@ -4291,18 +4322,6 @@ damage_view: return true; } -bool -render_resize(struct terminal *term, int width, int height) -{ - return maybe_resize(term, width, height, false); -} - -bool -render_resize_force(struct terminal *term, int width, int height) -{ - return maybe_resize(term, width, height, true); -} - static void xcursor_callback( void *data, struct wl_callback *wl_callback, uint32_t callback_data); static const struct wl_callback_listener xcursor_listener = { diff --git a/render.h b/render.h index f038ffb0..78ebae40 100644 --- a/render.h +++ b/render.h @@ -10,8 +10,15 @@ struct renderer; struct renderer *render_init(struct fdm *fdm, struct wayland *wayl); void render_destroy(struct renderer *renderer); -bool render_resize(struct terminal *term, int width, int height); -bool render_resize_force(struct terminal *term, int width, int height); +enum resize_options { + RESIZE_NORMAL = 0, + RESIZE_FORCE = 1 << 0, + RESIZE_BY_CELLS = 1 << 1, + RESIZE_KEEP_GRID = 1 << 2, +}; + +bool render_resize( + struct terminal *term, int width, int height, uint8_t resize_options); void render_refresh(struct terminal *term); void render_refresh_csd(struct terminal *term); diff --git a/terminal.c b/terminal.c index 7a51257d..e4ea31b2 100644 --- a/terminal.c +++ b/terminal.c @@ -784,10 +784,11 @@ term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4], * render_resize() after this function */ if (resize_grid) { /* Use force, since cell-width/height may have changed */ - render_resize_force( + render_resize( term, (int)roundf(term->width / term->scale), - (int)roundf(term->height / term->scale)); + (int)roundf(term->height / term->scale), + RESIZE_FORCE | RESIZE_KEEP_GRID); } return true; } diff --git a/wayland.c b/wayland.c index 25037c8a..7d4150b6 100644 --- a/wayland.c +++ b/wayland.c @@ -392,8 +392,6 @@ static void update_term_for_output_change(struct terminal *term) { const float old_scale = term->scale; - const float logical_width = term->width / term->scale; - const float logical_height = term->height / term->scale; /* Note: order matters! term_update_scale() must come first */ bool scale_updated = term_update_scale(term); @@ -402,24 +400,37 @@ update_term_for_output_change(struct terminal *term) csd_reload_font(term->window, old_scale); + uint8_t resize_opts = RESIZE_KEEP_GRID; + if (fonts_updated) { /* * If the fonts have been updated, the cell dimensions have * changed. This requires a “forced” resize, since the surface * buffer dimensions may not have been updated (in which case - * render_size() normally shortcuts and returns early). + * render_resize() normally shortcuts and returns early). */ - render_resize_force(term, (int)roundf(logical_width), (int)roundf(logical_height)); + resize_opts |= RESIZE_FORCE; + } else if (!scale_updated) { + /* No need to resize if neither scale nor fonts have changed */ + return; + } else if (term->conf->dpi_aware) { + /* + * If fonts are sized according to DPI, it is possible for the cell + * size to remain the same when display scale changes. This will not + * change the surface buffer dimensions, but will change the logical + * size of the window. To ensure that the compositor is made aware of + * the proper logical size, force a resize rather than allowing + * render_resize() to shortcut the notification if the buffer + * dimensions remain the same. + */ + resize_opts |= RESIZE_FORCE; } - else if (scale_updated) { - /* - * A scale update means the surface buffer dimensions have - * been updated, even though the window logical dimensions - * haven’t changed. - */ - render_resize(term, (int)roundf(logical_width), (int)roundf(logical_height)); - } + render_resize( + term, + (int)roundf(term->width / term->scale), + (int)roundf(term->height / term->scale), + resize_opts); } static void @@ -976,6 +987,8 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, xdg_surface_ack_configure(xdg_surface, serial); + enum resize_options opts = RESIZE_BY_CELLS; + #if 1 /* * TODO: decide if we should do the last “forced” call when ending @@ -989,13 +1002,12 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, * Note: if we also disable content centering while resizing, then * the last, forced, resize *is* necessary. */ - bool resized = was_resizing && !win->is_resizing - ? render_resize_force(term, new_width, new_height) - : render_resize(term, new_width, new_height); -#else - bool resized = render_resize(term, new_width, new_height); + if (was_resizing && !win->is_resizing) + opts |= RESIZE_FORCE; #endif + bool resized = render_resize(term, new_width, new_height, opts); + if (win->configure.is_activated) term_visual_focus_in(term); else