This builds on the work of @Consolatis in #1018.
Co-authored-by: Consolatis <35009135+Consolatis@users.noreply.github.com>
Co-authored-by: Andrew J. Hesford <ajh@sideband.org>
Use the automatic placement policy to move the active window to a
position on its output that will minimize overlap with other windows.
+*<action name="Shade" />*++
+*<action name="Unshade" />*++
+*<action name="ToggleShade" />*
+ Set, unset, or toggle, respectively, the "shaded" state of the active
+ window. When shaded, window contents are hidden, leaving only the
+ titlebar visible. Full-screen windows or those without server-side
+ decorations (including those for which the server-side titlebar has been
+ hidden) are not eligible for shading.
+
*<action name="None" />*
If used as the only action for a binding: clear an earlier defined
binding.
<item label="Fullscreen">
<action name="ToggleFullscreen" />
</item>
+ <item label="Roll up/down">
+ <action name="ToggleShade" />
+ </item>
<item label="Decorations">
<action name="ToggleDecorations" />
</item>
<action name="Focus" />
<action name="Raise" />
</mousebind>
+ <mousebind direction="Up" action="Scroll">
+ <action name="Unshade" />
+ <action name="Focus" />
+ </mousebind>
+ <mousebind direction="Down" action="Scroll">
+ <action name="Unfocus" />
+ <action name="Shade" />
+ </mousebind>
</context>
<context name="Title">
void ssd_titlebar_hide(struct ssd *ssd);
void ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable);
+void ssd_enable_shade(struct ssd *ssd, bool enable);
struct ssd_hover_state *ssd_hover_state_new(void);
void ssd_update_button_hover(struct wlr_scene_node *node,
bool ssd_enabled;
bool ssd_titlebar_hidden;
enum ssd_preference ssd_preference;
+ bool shaded;
bool minimized;
enum view_axis maximized;
bool fullscreen;
bool view_adjust_floating_geometry(struct view *view, struct wlr_box *geometry);
void view_store_natural_geometry(struct view *view);
+/**
+ * view_effective_height - effective height of view, with respect to shaded state
+ * @view: view for which effective height is desired
+ * @use_pending: if false, report current height; otherwise, report pending height
+ */
+int view_effective_height(struct view *view, bool use_pending);
+
/**
* view_center - center view within some region
* @view: view to be centered
void view_update_app_id(struct view *view);
void view_reload_ssd(struct view *view);
+void view_set_shade(struct view *view, bool shaded);
+
struct view_size_hints view_get_size_hints(struct view *view);
void view_adjust_size(struct view *view, int *w, int *h);
# Labwc pot file
-# Copyright (C) 2023
+# Copyright (C) 2024
# This file is distributed under the same license as the labwc package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgstr ""
"Project-Id-Version: labwc\n"
"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n"
-"POT-Creation-Date: 2023-01-02 11:22+1000\n"
+"POT-Creation-Date: 2024-01-15 16:00-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-#: src/menu/menu.c:698
+#: src/menu/menu.c:697
msgid "Reconfigure"
msgstr ""
-#: src/menu/menu.c:700
+#: src/menu/menu.c:699
msgid "Exit"
msgstr ""
-#: src/menu/menu.c:716
+#: src/menu/menu.c:715
msgid "Minimize"
msgstr ""
-#: src/menu/menu.c:718
+#: src/menu/menu.c:717
msgid "Maximize"
msgstr ""
-#: src/menu/menu.c:720
+#: src/menu/menu.c:719
msgid "Fullscreen"
msgstr ""
-#: src/menu/menu.c:722
+#: src/menu/menu.c:721
+msgid "Roll up/down"
+msgstr ""
+
+#: src/menu/menu.c:723
msgid "Decorations"
msgstr ""
-#: src/menu/menu.c:724
+#: src/menu/menu.c:725
msgid "Always on Top"
msgstr ""
-#: src/menu/menu.c:729
+#: src/menu/menu.c:730
msgid "Move left"
msgstr ""
-#: src/menu/menu.c:736
+#: src/menu/menu.c:737
msgid "Move right"
msgstr ""
-#: src/menu/menu.c:741
+#: src/menu/menu.c:742
msgid "Always on Visible Workspace"
msgstr ""
-#: src/menu/menu.c:744
+#: src/menu/menu.c:745
msgid "Workspace"
msgstr ""
-#: src/menu/menu.c:747
+#: src/menu/menu.c:748
msgid "Close"
msgstr ""
ACTION_TYPE_VIRTUAL_OUTPUT_REMOVE,
ACTION_TYPE_AUTO_PLACE,
ACTION_TYPE_TOGGLE_TEARING,
+ ACTION_TYPE_SHADE,
+ ACTION_TYPE_UNSHADE,
+ ACTION_TYPE_TOGGLE_SHADE,
};
const char *action_names[] = {
"VirtualOutputRemove",
"AutoPlace",
"ToggleTearing",
+ "Shade",
+ "Unshade",
+ "ToggleShade",
NULL
};
.width = width ? : view->pending.width,
.height = height ? : view->pending.height,
};
+ view_set_shade(view, false);
view_move_resize(view, box);
}
break;
view->tearing_hint ? "en" : "dis");
}
break;
+ case ACTION_TYPE_TOGGLE_SHADE:
+ if (view) {
+ view_set_shade(view, !view->shaded);
+ }
+ break;
+ case ACTION_TYPE_SHADE:
+ if (view) {
+ view_set_shade(view, true);
+ }
+ break;
+ case ACTION_TYPE_UNSHADE:
+ if (view) {
+ view_set_shade(view, false);
+ }
+ break;
case ACTION_TYPE_INVALID:
wlr_log(WLR_ERROR, "Not executing unknown action");
break;
{ "Frame", "A-Right", "Drag", "Resize", NULL, NULL},
{ "Titlebar", "Left", "Press", "Focus", NULL, NULL},
{ "Titlebar", "Left", "Press", "Raise", NULL, NULL},
+ { "Titlebar", "Up", "Scroll", "Unfocus", NULL, NULL},
+ { "Titlebar", "Up", "Scroll", "Shade", NULL, NULL},
+ { "Titlebar", "Down", "Scroll", "Unshade", NULL, NULL},
+ { "Titlebar", "Down", "Scroll", "Focus", NULL, NULL},
{ "Title", "Left", "Drag", "Move", NULL, NULL },
{ "Title", "Left", "DoubleClick", "ToggleMaximize", NULL, NULL },
{ "TitleBar", "Right", "Click", "Focus", NULL, NULL},
*/
wlr_seat_pointer_notify_clear_focus(wlr_seat);
if (!seat->drag.active) {
- cursor_set(seat, cursor_get_from_ssd(ctx->type));
+ enum lab_cursors cursor = cursor_get_from_ssd(ctx->type);
+ if (ctx->view && ctx->view->shaded && cursor > LAB_CURSOR_GRAB) {
+ /* Prevent resize cursor on borders for shaded SSD */
+ cursor = LAB_CURSOR_DEFAULT;
+ }
+ cursor_set(seat, cursor);
}
}
}
}
if (!view_is_floating(view)) {
/*
- * Un-maximize and restore natural width/height.
+ * Un-maximize, unshade and restore natural
+ * width/height.
* Don't reset tiled state yet since we may want
* to keep it (in the snap-to-maximize case).
*/
geometry.y = max_move_scale(seat->cursor->y,
view->current.y, view->current.height,
geometry.height);
+
+ view_set_shade(view, false);
view_restore_to(view, geometry);
} else {
/* Store natural geometry at start of move */
cursor_set(seat, LAB_CURSOR_GRAB);
break;
case LAB_INPUT_STATE_RESIZE:
- if (view->fullscreen || view->maximized == VIEW_AXIS_BOTH) {
+ if (view->shaded || view->fullscreen ||
+ view->maximized == VIEW_AXIS_BOTH) {
/*
- * We don't allow resizing while fullscreen or
- * maximized in both directions.
+ * We don't allow resizing while shaded,
+ * fullscreen or maximized in both directions.
*/
return;
}
fill_item("name.action", "ToggleMaximize");
current_item = item_create(menu, _("Fullscreen"), false);
fill_item("name.action", "ToggleFullscreen");
+ current_item = item_create(menu, _("Roll up/down"), false);
+ fill_item("name.action", "ToggleShade");
current_item = item_create(menu, _("Decorations"), false);
fill_item("name.action", "ToggleDecorations");
current_item = item_create(menu, _("Always on Top"), false);
bmp->rows[nr_rows++] = y;
}
- x = v->pending.x + v->pending.width + margin.right;
- y = v->pending.y + v->pending.height + margin.bottom;
+ x = v->pending.x + margin.right + v->pending.width;
+ y = v->pending.y + margin.bottom
+ + view_effective_height(v, /* use_pending */ true);
/* Add a column if the right view edge is in the usable region */
if (x > usable.x && x < usable_right) {
struct border margin = ssd_get_margin(v->ssd);
int lx = v->pending.x - margin.left;
int ly = v->pending.y - margin.top;
- int hx = v->pending.x + v->pending.width + margin.right;
- int hy = v->pending.y + v->pending.height + margin.bottom;
+ int hx = v->pending.x + margin.right + v->pending.width;
+ int hy = v->pending.y + margin.bottom
+ + view_effective_height(v, /* use_pending */ true);
/*
* Find the first and last row and column intervals spanned by
struct edges other_edges; /* The edges of the monitor/other view */
struct edges flags = { 0 };
+ /* Use the effective height to properly snap shaded views */
+ int eff_height = view_effective_height(view, /* use_pending */ false);
+
view_edges.left = vgeom.x - border.left + 1;
view_edges.top = vgeom.y - border.top + 1;
view_edges.right = vgeom.x + vgeom.width + border.right;
- view_edges.bottom = vgeom.y + vgeom.height + border.bottom;
+ view_edges.bottom = vgeom.y + eff_height + border.bottom;
target_edges.left = *x - border.left;
target_edges.top = *y - border.top;
target_edges.right = *x + vgeom.width + border.right;
- target_edges.bottom = *y + vgeom.height + border.bottom;
+ target_edges.bottom = *y + eff_height + border.bottom;
if (!rc.screen_edge_strength) {
return;
if (flags.top == 1) {
*y = other_edges.top + border.top;
} else if (flags.bottom == 1) {
- *y = other_edges.bottom - vgeom.height - border.bottom;
+ *y = other_edges.bottom - eff_height - border.bottom;
}
/* reset the flags */
struct border edge = {
.left = view->pending.x - margin.left,
.top = view->pending.y - margin.top,
- .right = view->pending.x + view->pending.width + margin.right,
- .bottom = view->pending.y + view->pending.height + margin.bottom
+ .right = view->pending.x + margin.right + view->pending.width,
+ .bottom = view->pending.y + margin.bottom
+ + view_effective_height(view, /* use_pending */ true)
};
return edge;
}
struct border distance = {
.left = usable.x + margin.left + rc.gap - view->pending.x,
.top = usable.y + margin.top + rc.gap - view->pending.y,
- .right = usable.x + usable.width - view->pending.width
+ .right = usable.x + usable.width - view->pending.width
- margin.right - rc.gap - view->pending.x,
- .bottom = usable.y + usable.height - view->pending.height
+ .bottom = usable.y + usable.height
+ - view_effective_height(view, /* use_pending */ true)
- margin.bottom - rc.gap - view->pending.y
};
return distance;
vp += def.add_view_x * v->pending.x;
vp += def.add_view_y * v->pending.y;
vp += def.add_view_width * v->pending.width;
- vp += def.add_view_height * v->pending.height;
+ vp += def.add_view_height
+ * view_effective_height(v, /* use_pending */ true);
vp += gap;
if (def.search_dir * vp > 0 && def.search_dir * (vp - p) < 0) {
{
struct border edge = snap_get_view_edge(view);
struct border dmax;
+
if (mode == SNAP_MODE_SHRINK) {
/* limit to half of current size */
- int width_max_dx = max(view->pending.width - LAB_MIN_VIEW_WIDTH, 0);
- int height_max_dy = max(view->pending.height - LAB_MIN_VIEW_HEIGHT, 0);
+ int eff_height =
+ view_effective_height(view, /* use_pending */ true);
+ int width_max_dx = max(view->pending.width - LAB_MIN_VIEW_WIDTH, 0);
+ int height_max_dy = max(eff_height - LAB_MIN_VIEW_HEIGHT, 0);
dmax.right = min(width_max_dx, view->pending.width / 2);
- dmax.bottom = min(height_max_dy, view->pending.height / 2);
+ dmax.bottom = min(height_max_dy, eff_height / 2);
dmax.left = -dmax.right;
dmax.top = -dmax.bottom;
} else {
char text[32]; /* 12345 x 12345 would be 13 chars + 1 null byte */
+ int eff_height = view_effective_height(view, /* use_pending */ false);
+ int eff_width = view->current.width;
+
switch (view->server->input_mode) {
case LAB_INPUT_STATE_RESIZE:
; /* works around "a label can only be part of a statement" */
struct view_size_hints hints = view_get_size_hints(view);
snprintf(text, sizeof(text), "%d x %d",
- MAX(0, view->current.width - hints.base_width)
+ MAX(0, eff_width - hints.base_width)
/ MAX(1, hints.width_inc),
- MAX(0, view->current.height - hints.base_height)
+ MAX(0, eff_height - hints.base_height)
/ MAX(1, hints.height_inc));
break;
case LAB_INPUT_STATE_MOVE:
/* Center the indicator in the window */
wlr_scene_node_set_position(&indicator->tree->node,
- (view->current.width - indicator->width) / 2,
- (view->current.height - indicator->height) / 2);
+ (eff_width - indicator->width) / 2,
+ (eff_height - indicator->height) / 2);
scaled_font_buffer_update(indicator->text, text, width, &rc.font_osd,
rc.theme->osd_label_text_color, NULL /* const char *arrow */);
{
assert(view);
struct border border = ssd_thickness(view);
+
+ int eff_width = view->current.width;
+ int eff_height = view_effective_height(view, /* use_pending */ false);
+
return (struct wlr_box){
.x = view->current.x - border.left,
.y = view->current.y - border.top,
- .width = view->current.width + border.left + border.right,
- .height = view->current.height + border.top + border.bottom,
+ .width = eff_width + border.left + border.right,
+ .height = eff_height + border.top + border.bottom,
};
}
struct wlr_box cached = ssd->state.geometry;
struct wlr_box current = ssd->view->current;
- if (current.width == cached.width && current.height == cached.height) {
+
+ int eff_width = current.width;
+ int eff_height = view_effective_height(ssd->view, /* use_pending */ false);
+
+ if (eff_width == cached.width && eff_height == cached.height) {
if (current.x != cached.x || current.y != cached.y) {
/* Dynamically resize extents based on position and usable_area */
ssd_extents_update(ssd);
wlr_scene_node_set_enabled(&ssd->titlebar.inactive.tree->node, !active);
}
+void
+ssd_enable_shade(struct ssd *ssd, bool enable)
+{
+ if (!ssd) {
+ return;
+ }
+ ssd_border_update(ssd);
+ wlr_scene_node_set_enabled(&ssd->extents.tree->node, !enable);
+}
+
void
ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable)
{
struct view *view = ssd->view;
struct theme *theme = view->server->theme;
int width = view->current.width;
- int height = view->current.height;
+ int height = view_effective_height(view, /* use_pending */ false);
int full_width = width + 2 * theme->border_width;
float *color;
struct theme *theme = view->server->theme;
int width = view->current.width;
- int height = view->current.height;
+ int height = view_effective_height(view, /* use_pending */ false);
int full_width = width + 2 * theme->border_width;
struct ssd_part *part;
struct theme *theme = view->server->theme;
int width = view->current.width;
- int height = view->current.height;
+ int height = view_effective_height(view, /* use_pending */ false);
int full_height = height + theme->border_width * 2 + ssd->titlebar.height;
int full_width = width + 2 * theme->border_width;
int extended_area = SSD_EXTENDED_AREA;
if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
+ view_set_shade(view, false);
struct wlr_box newgeo = view->pending;
newgeo.x -= left;
newgeo.width += left + right;
}
}
+int
+view_effective_height(struct view *view, bool use_pending)
+{
+ assert(view);
+
+ if (view->shaded) {
+ return 0;
+ }
+
+ return use_pending ? view->pending.height : view->current.height;
+}
+
void
view_center(struct view *view, const struct wlr_box *ref)
{
bool store_natural_geometry)
{
assert(view);
+
if (view->maximized == axis) {
return;
}
+
if (view->fullscreen) {
return;
}
+
+ view_set_shade(view, false);
+
if (axis != VIEW_AXIS_NONE) {
/*
* Maximize via keybind or client request cancels
view_toggle_decorations(struct view *view)
{
assert(view);
+
+ /* Reject decoration toggles when shaded */
+ if (view->shaded) {
+ return;
+ }
+
if (rc.ssd_keep_border && view->ssd_enabled && view->ssd
&& !view->ssd_titlebar_hidden) {
/*
view_toggle_fullscreen(struct view *view)
{
assert(view);
+
view_set_fullscreen(view, !view->fullscreen);
}
static void
set_fullscreen(struct view *view, bool fullscreen)
{
+ /* When going fullscreen, unshade the window */
+ if (fullscreen) {
+ view_set_shade(view, false);
+ }
+
/* Hide decorations when going fullscreen */
if (fullscreen && view->ssd_enabled) {
undecorate(view);
destination_y = top;
break;
case VIEW_EDGE_DOWN:
- destination_y = bottom - view->pending.height;
+ destination_y = bottom
+ - view_effective_height(view, /* use_pending */ true);
break;
default:
return;
destination_x = MAX(destination_x, left);
/* If more than half the view is below usable region, align to bottom */
- midpoint = destination_y + view->pending.height / 2;
+ midpoint = destination_y
+ + view_effective_height(view, /* use_pending */ true) / 2;
if (destination_y >= top && midpoint > usable.y + usable.height) {
- destination_y = bottom - view->pending.height;
+ destination_y = bottom
+ - view_effective_height(view, /* use_pending */ true);
}
/* Never allow the window to start above the usable edge */
if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
+
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not growing view");
return;
}
+ view_set_shade(view, false);
+
struct wlr_box geo = view->pending;
snap_grow_to_next_edge(view, direction, &geo);
view_move_resize(view, geo);
view_shrink_to_edge(struct view *view, enum view_edge direction)
{
assert(view);
+
/* TODO: allow shrink to edge if maximized along the other axis */
if (view->fullscreen || view->maximized != VIEW_AXIS_NONE) {
return;
}
+
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not shrinking view");
return;
}
+ view_set_shade(view, false);
+
struct wlr_box geo = view->pending;
snap_shrink_to_next_edge(view, direction, &geo);
view_move_resize(view, geo);
bool across_outputs, bool store_natural_geometry)
{
assert(view);
+
if (view->fullscreen) {
return;
}
+
struct output *output = view->output;
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "view has no output, not snapping to edge");
return;
}
+ view_set_shade(view, false);
+
if (across_outputs && view->tiled == edge && view->maximized == VIEW_AXIS_NONE) {
/* We are already tiled for this edge; try to switch outputs */
output = view_get_adjacent_output(view, edge);
{
assert(view);
assert(region);
+
if (view->fullscreen) {
return;
}
+
/* view_apply_region_geometry() needs a usable output */
if (!output_is_usable(view->output)) {
wlr_log(WLR_ERROR, "view has no output, not snapping to region");
return;
}
+ view_set_shade(view, false);
+
if (view->maximized != VIEW_AXIS_NONE) {
/* Unmaximize + keep using existing natural_geometry */
view_maximize(view, VIEW_AXIS_NONE,
mappable_connect(&view->mappable, surface, handle_map, handle_unmap);
}
+void
+view_set_shade(struct view *view, bool shaded)
+{
+ assert(view);
+
+ if (view->shaded == shaded) {
+ return;
+ }
+
+ /* Views without a title-bar or SSD cannot be shaded */
+ if (shaded && (!view->ssd || view->ssd_titlebar_hidden)) {
+ return;
+ }
+
+ view->shaded = shaded;
+ ssd_enable_shade(view->ssd, view->shaded);
+ wlr_scene_node_set_enabled(view->scene_node, !view->shaded);
+}
+
void
view_destroy(struct view *view)
{