bool was_omnipresent;
/*
- * Corners need to be (un)rounded when toggling tiling or
- * maximization, and the button needs to be swapped on
+ * Corners need to be (un)rounded and borders need be shown/hidden
+ * when toggling maximization, and the button needs to be swapped on
* maximization toggles.
*/
bool was_maximized;
- bool was_tiled_not_maximized;
+
+ /*
+ * Corners need to be (un)rounded but borders should be kept shown when
+ * the window is (un)tiled and notified about it or when the window may
+ * become so small that only a squared scene-rect can be used to render
+ * such a small titlebar.
+ */
+ bool was_squared;
struct wlr_box geometry;
struct ssd_state_title {
/* This part represented in scene graph */
struct wlr_scene_node *node;
- /* Targeted geometry. May be NULL */
- struct wlr_box *geometry;
-
struct wl_list link;
};
void ssd_titlebar_create(struct ssd *ssd);
void ssd_titlebar_update(struct ssd *ssd);
void ssd_titlebar_destroy(struct ssd *ssd);
+bool ssd_should_be_squared(struct ssd *ssd);
void ssd_border_create(struct ssd *ssd);
void ssd_border_update(struct ssd *ssd);
* |_______________|
*/
- int side_height = ssd->state.was_tiled_not_maximized
+ int side_height = ssd->state.was_squared
? height + ssd->titlebar.height
: height;
- int side_y = ssd->state.was_tiled_not_maximized
+ int side_y = ssd->state.was_squared
? -ssd->titlebar.height
: 0;
- int top_width = ssd->titlebar.height <= 0 || ssd->state.was_tiled_not_maximized
+ int top_width = ssd->titlebar.height <= 0 || ssd->state.was_squared
? full_width
: width - 2 * theme->window_button_width;
- int top_x = ssd->titlebar.height <= 0 || ssd->state.was_tiled_not_maximized
+ int top_x = ssd->titlebar.height <= 0 || ssd->state.was_squared
? 0
: theme->border_width + theme->window_button_width;
// SPDX-License-Identifier: GPL-2.0-only
+#include <assert.h>
#include <pixman.h>
#include "common/mem.h"
#include "common/scene-helpers.h"
{
float invisible[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
struct ssd_part *part = add_scene_part(part_list, type);
- /*
- * Extents need additional geometry to enable dynamic
- * resize based on position and output->usable_area.
- *
- * part->geometry will get free'd automatically in ssd_destroy_parts().
- */
part->node = &wlr_scene_rect_create(parent, 0, 0, invisible)->node;
- part->geometry = znew(struct wlr_box);
return part;
}
struct theme *theme = view->server->theme;
struct wl_list *part_list = &ssd->extents.parts;
int extended_area = SSD_EXTENDED_AREA;
- int corner_size = extended_area + theme->border_width +
- theme->window_button_width / 2;
ssd->extents.tree = wlr_scene_tree_create(ssd->tree);
struct wlr_scene_tree *parent = ssd->extents.tree;
-(theme->border_width + extended_area),
-(ssd->titlebar.height + theme->border_width + extended_area));
- /* Initialize parts and set constant values for targeted geometry */
- struct ssd_part *p;
-
/* Top */
- p = add_extent(part_list, LAB_SSD_PART_CORNER_TOP_LEFT, parent);
- p->geometry->width = corner_size;
- p->geometry->height = corner_size;
-
- p = add_extent(part_list, LAB_SSD_PART_TOP, parent);
- p->geometry->x = corner_size;
- p->geometry->height = extended_area;
-
- p = add_extent(part_list, LAB_SSD_PART_CORNER_TOP_RIGHT, parent);
- p->geometry->width = corner_size;
- p->geometry->height = corner_size;
-
+ add_extent(part_list, LAB_SSD_PART_CORNER_TOP_LEFT, parent);
+ add_extent(part_list, LAB_SSD_PART_TOP, parent);
+ add_extent(part_list, LAB_SSD_PART_CORNER_TOP_RIGHT, parent);
/* Sides */
- p = add_extent(part_list, LAB_SSD_PART_LEFT, parent);
- p->geometry->y = corner_size;
- p->geometry->width = extended_area;
-
- p = add_extent(part_list, LAB_SSD_PART_RIGHT, parent);
- p->geometry->y = corner_size;
- p->geometry->width = extended_area;
-
+ add_extent(part_list, LAB_SSD_PART_LEFT, parent);
+ add_extent(part_list, LAB_SSD_PART_RIGHT, parent);
/* Bottom */
- p = add_extent(part_list, LAB_SSD_PART_CORNER_BOTTOM_LEFT, parent);
- p->geometry->width = corner_size;
- p->geometry->height = corner_size;
-
- p = add_extent(part_list, LAB_SSD_PART_BOTTOM, parent);
- p->geometry->x = corner_size;
- p->geometry->height = extended_area;
-
- p = add_extent(part_list, LAB_SSD_PART_CORNER_BOTTOM_RIGHT, parent);
- p->geometry->width = corner_size;
- p->geometry->height = corner_size;
+ add_extent(part_list, LAB_SSD_PART_CORNER_BOTTOM_LEFT, parent);
+ add_extent(part_list, LAB_SSD_PART_BOTTOM, parent);
+ add_extent(part_list, LAB_SSD_PART_CORNER_BOTTOM_RIGHT, parent);
/* Initial manual update to keep X11 applications happy */
ssd_extents_update(ssd);
int full_width = width + 2 * theme->border_width;
int extended_area = SSD_EXTENDED_AREA;
int corner_size = extended_area + theme->border_width +
- theme->window_button_width / 2;
+ MIN(theme->window_button_width, width) / 2;
int side_width = full_width + extended_area * 2 - corner_size * 2;
int side_height = full_height + extended_area * 2 - corner_size * 2;
struct wlr_box result_box;
struct ssd_part *part;
struct wlr_scene_rect *rect;
+ struct wlr_box target;
/* Make sure we update the y offset based on titlebar shown / hidden */
wlr_scene_node_set_position(&ssd->extents.tree->node,
int base_x, base_y;
wlr_scene_node_coords(&ssd->extents.tree->node, &base_x, &base_y);
- struct wlr_box *target;
wl_list_for_each(part, &ssd->extents.parts, link) {
rect = wlr_scene_rect_from_node(part->node);
- target = part->geometry;
switch (part->type) {
+ case LAB_SSD_PART_CORNER_TOP_LEFT:
+ target.x = 0;
+ target.y = 0;
+ target.width = corner_size;
+ target.height = corner_size;
+ break;
case LAB_SSD_PART_TOP:
- target->width = side_width;
+ target.x = corner_size;
+ target.y = 0;
+ target.width = side_width;
+ target.height = extended_area;
break;
case LAB_SSD_PART_CORNER_TOP_RIGHT:
- target->x = corner_size + side_width;
+ target.x = corner_size + side_width;
+ target.y = 0;
+ target.width = corner_size;
+ target.height = corner_size;
break;
case LAB_SSD_PART_LEFT:
- target->height = side_height;
+ target.x = 0;
+ target.y = corner_size;
+ target.width = extended_area;
+ target.height = side_height;
break;
case LAB_SSD_PART_RIGHT:
- target->x = extended_area + full_width;
- target->height = side_height;
+ target.x = extended_area + full_width;
+ target.y = corner_size;
+ target.width = extended_area;
+ target.height = side_height;
break;
case LAB_SSD_PART_CORNER_BOTTOM_LEFT:
- target->y = corner_size + side_height;
+ target.x = 0;
+ target.y = corner_size + side_height;
+ target.width = corner_size;
+ target.height = corner_size;
break;
case LAB_SSD_PART_BOTTOM:
- target->width = side_width;
- target->y = extended_area + full_height;
+ target.x = corner_size;
+ target.y = extended_area + full_height;
+ target.width = side_width;
+ target.height = extended_area;
break;
case LAB_SSD_PART_CORNER_BOTTOM_RIGHT:
- target->x = corner_size + side_width;
- target->y = corner_size + side_height;
+ target.x = corner_size + side_width;
+ target.y = corner_size + side_height;
+ target.width = corner_size;
+ target.height = corner_size;
break;
default:
- break;
+ /* not reached */
+ assert(false);
+ /* suppress warnings with NDEBUG */
+ target = (struct wlr_box){0};
}
/* Get layout geometry of what the part *should* be */
- part_box.x = base_x + target->x;
- part_box.y = base_y + target->y;
- part_box.width = target->width;
- part_box.height = target->height;
+ part_box.x = base_x + target.x;
+ part_box.y = base_y + target.y;
+ part_box.width = target.width;
+ part_box.height = target.height;
/* Constrain part to output->usable_area */
pixman_region32_clear(&intersection);
wlr_scene_rect_set_size(rect, result_box.width,
result_box.height);
wlr_scene_node_set_position(part->node,
- target->x + (result_box.x - part_box.x),
- target->y + (result_box.y - part_box.y));
- continue;
- }
-
- /* Fully visible */
- if (target->x != part->node->x
- || target->y != part->node->y) {
- wlr_scene_node_set_position(part->node, target->x, target->y);
- }
- if (target->width != rect->width || target->height != rect->height) {
- wlr_scene_rect_set_size(rect, target->width, target->height);
+ target.x + (result_box.x - part_box.x),
+ target.y + (result_box.y - part_box.y));
+ } else {
+ /* Fully visible */
+ wlr_scene_node_set_position(part->node, target.x, target.y);
+ wlr_scene_rect_set_size(rect, target.width, target.height);
}
}
pixman_region32_fini(&intersection);
}
/* part->buffer will free itself along the scene_buffer node */
part->buffer = NULL;
- if (part->geometry) {
- free(part->geometry);
- part->geometry = NULL;
- }
wl_list_remove(&part->link);
free(part);
}
static void set_squared_corners(struct ssd *ssd, bool enable);
static void set_alt_button_icon(struct ssd *ssd, enum ssd_part_type type, bool enable);
+static void update_visible_buttons(struct ssd *ssd);
static void
add_button(struct ssd *ssd, struct ssd_sub_tree *subtree, enum ssd_part_type type, int x)
}
} FOR_EACH_END
+ update_visible_buttons(ssd);
+
ssd_update_title(ssd);
bool maximized = view->maximized == VIEW_AXIS_BOTH;
set_alt_button_icon(ssd, LAB_SSD_BUTTON_OMNIPRESENT, true);
}
- if (view_is_tiled_and_notify_tiled(view) && !maximized) {
+ if (ssd_should_be_squared(ssd)) {
set_squared_corners(ssd, true);
- ssd->state.was_tiled_not_maximized = true;
+ ssd->state.was_squared = true;
}
}
} FOR_EACH_END
}
+/*
+ * Usually this function just enables all the nodes for buttons, but some
+ * buttons can be hidden for small windows (e.g. xterm -geometry 1x1).
+ */
+static void
+update_visible_buttons(struct ssd *ssd)
+{
+ struct view *view = ssd->view;
+ int width = view->current.width;
+ int button_width = view->server->theme->window_button_width;
+ int button_count_left = wl_list_length(&rc.title_buttons_left);
+ int button_count_right = wl_list_length(&rc.title_buttons_right);
+
+ /* Make sure infinite loop never occurs */
+ assert(button_width > 0);
+ /*
+ * The corner-left button is lastly removed as it's usually a window
+ * menu button (or an app icon button in the future).
+ */
+ while (width < button_width * (button_count_left + button_count_right)) {
+ if (button_count_left > button_count_right) {
+ button_count_left--;
+ } else {
+ button_count_right--;
+ }
+ }
+
+ int button_count;
+ struct ssd_part *part;
+ struct ssd_sub_tree *subtree;
+ struct title_button *b;
+ FOR_EACH_STATE(ssd, subtree) {
+ button_count = 0;
+ wl_list_for_each(b, &rc.title_buttons_left, link) {
+ part = ssd_get_part(&subtree->parts, b->type);
+ wlr_scene_node_set_enabled(part->node,
+ button_count < button_count_left);
+ button_count++;
+ }
+
+ button_count = 0;
+ wl_list_for_each_reverse(b, &rc.title_buttons_right, link) {
+ part = ssd_get_part(&subtree->parts, b->type);
+ wlr_scene_node_set_enabled(part->node,
+ button_count < button_count_right);
+ button_count++;
+ }
+ } FOR_EACH_END
+}
+
void
ssd_titlebar_update(struct ssd *ssd)
{
struct theme *theme = view->server->theme;
bool maximized = view->maximized == VIEW_AXIS_BOTH;
- bool tiled_not_maximized =
- view_is_tiled_and_notify_tiled(ssd->view) && !maximized;
+ bool squared = ssd_should_be_squared(ssd);
if (ssd->state.was_maximized != maximized
- || ssd->state.was_tiled_not_maximized != tiled_not_maximized) {
- set_squared_corners(ssd, maximized || tiled_not_maximized);
+ || ssd->state.was_squared != squared) {
+ set_squared_corners(ssd, maximized || squared);
if (ssd->state.was_maximized != maximized) {
set_alt_button_icon(ssd, LAB_SSD_BUTTON_MAXIMIZE, maximized);
}
ssd->state.was_maximized = maximized;
- ssd->state.was_tiled_not_maximized = tiled_not_maximized;
+ ssd->state.was_squared = squared;
}
if (ssd->state.was_shaded != view->shaded) {
return;
}
+ update_visible_buttons(ssd);
+
int x;
struct ssd_part *part;
struct ssd_sub_tree *subtree;
struct title_button *b;
- int bg_offset = maximized || tiled_not_maximized ? 0 : theme->window_button_width;
+ int bg_offset = maximized || squared ? 0 : theme->window_button_width;
FOR_EACH_STATE(ssd, subtree) {
part = ssd_get_part(&subtree->parts, LAB_SSD_PART_TITLEBAR);
wlr_scene_rect_set_size(
*/
static void
-ssd_update_title_positions(struct ssd *ssd)
+ssd_update_title_positions(struct ssd *ssd, int offset_left, int offset_right)
{
struct view *view = ssd->view;
struct theme *theme = view->server->theme;
int width = view->current.width;
- int offset_left = theme->window_button_width * wl_list_length(&rc.title_buttons_left);
- int offset_right = theme->window_button_width * wl_list_length(&rc.title_buttons_right);
int title_bg_width = width - offset_left - offset_right;
int x, y;
} FOR_EACH_END
}
+/*
+ * Get left/right offsets of the title area based on visible/hidden states of
+ * buttons set in update_visible_buttons().
+ */
+static void
+get_title_offsets(struct ssd *ssd, int *offset_left, int *offset_right)
+{
+ struct ssd_sub_tree *subtree = &ssd->titlebar.active;
+ int button_width = ssd->view->server->theme->window_button_width;
+ *offset_left = 0;
+ *offset_right = 0;
+
+ struct title_button *b;
+ wl_list_for_each(b, &rc.title_buttons_left, link) {
+ struct ssd_part *part = ssd_get_part(&subtree->parts, b->type);
+ if (part->node->enabled) {
+ *offset_left += button_width;
+ }
+ }
+ wl_list_for_each_reverse(b, &rc.title_buttons_right, link) {
+ struct ssd_part *part = ssd_get_part(&subtree->parts, b->type);
+ if (part->node->enabled) {
+ *offset_right += button_width;
+ }
+ }
+}
+
void
ssd_update_title(struct ssd *ssd)
{
struct ssd_part *part;
struct ssd_sub_tree *subtree;
struct ssd_state_title_width *dstate;
- int offset_left = theme->window_button_width * wl_list_length(&rc.title_buttons_left);
- int offset_right = theme->window_button_width * wl_list_length(&rc.title_buttons_right);
+
+ int offset_left, offset_right;
+ get_title_offsets(ssd, &offset_left, &offset_right);
int title_bg_width = view->current.width - offset_left - offset_right;
FOR_EACH_STATE(ssd, subtree) {
}
state->text = xstrdup(title);
}
- ssd_update_title_positions(ssd);
+ ssd_update_title_positions(ssd, offset_left, offset_right);
}
static void
}
}
+bool
+ssd_should_be_squared(struct ssd *ssd)
+{
+ struct view *view = ssd->view;
+ int button_width = view->server->theme->window_button_width;
+
+ return (view_is_tiled_and_notify_tiled(view)
+ || view->current.width < button_width * 2)
+ && view->maximized != VIEW_AXIS_BOTH;
+}
+
#undef FOR_EACH_STATE
ssd_extents_create(ssd);
/*
* We need to create the borders after the titlebar because it sets
- * ssd->state.was_tiled_not_maximized which ssd_border_create()
- * reacts to. TODO: Set the state here instead so the order does
- * not matter anymore.
+ * ssd->state.squared which ssd_border_create() reacts to.
+ * TODO: Set the state here instead so the order does not matter
+ * anymore.
*/
ssd_titlebar_create(ssd);
ssd_border_create(ssd);
struct wlr_box cached = ssd->state.geometry;
struct wlr_box current = view->current;
- int min_view_width = rc.theme->window_button_width * (
- wl_list_length(&rc.title_buttons_left) + wl_list_length(&rc.title_buttons_right));
int eff_width = current.width;
int eff_height = view_effective_height(view, /* use_pending */ false);
- if (eff_width > 0 && eff_width < min_view_width) {
- /*
- * Prevent negative values in calculations like
- * `width - theme->window_button_width
- * * (wl_list_length(&rc.title_buttons_left)
- * + wl_list_length(&rc.title_buttons_right))`
- */
- wlr_log(WLR_ERROR,
- "view width is smaller than its minimal value");
- return;
- }
-
bool update_area = eff_width != cached.width || eff_height != cached.height;
bool update_extents = update_area
|| current.x != cached.x || current.y != cached.y;
bool maximized = view->maximized == VIEW_AXIS_BOTH;
- bool tiled_not_maximized = view_is_tiled(view) && !maximized;
+ bool squared = ssd_should_be_squared(ssd);
bool state_changed = ssd->state.was_maximized != maximized
|| ssd->state.was_shaded != view->shaded
- || ssd->state.was_tiled_not_maximized != tiled_not_maximized
+ || ssd->state.was_squared != squared
|| ssd->state.was_omnipresent != view->visible_on_all_workspaces;
if (update_extents) {