feat: add Shade/Unshade/ToggleShade actions

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>
This commit is contained in:
Consolatis 2023-08-08 03:39:35 +02:00 committed by Johan Malm
parent 722a802de0
commit e05bedb140
19 changed files with 218 additions and 47 deletions

View file

@ -229,6 +229,15 @@ Actions are used in menus and keyboard/mouse bindings.
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.

View file

@ -12,6 +12,9 @@
<item label="Fullscreen">
<action name="ToggleFullscreen" />
</item>
<item label="Roll up/down">
<action name="ToggleShade" />
</item>
<item label="Decorations">
<action name="ToggleDecorations" />
</item>

View file

@ -331,6 +331,14 @@
<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">

View file

@ -68,6 +68,7 @@ void ssd_destroy(struct ssd *ssd);
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,

View file

@ -148,6 +148,7 @@ struct view {
bool ssd_enabled;
bool ssd_titlebar_hidden;
enum ssd_preference ssd_preference;
bool shaded;
bool minimized;
enum view_axis maximized;
bool fullscreen;
@ -394,6 +395,13 @@ bool view_compute_centered_position(struct view *view,
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
@ -463,6 +471,8 @@ void view_update_title(struct view *view);
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);

View file

@ -1,5 +1,5 @@
# 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.
#
@ -8,7 +8,7 @@ msgid ""
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"
@ -17,50 +17,54 @@ msgstr ""
"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 ""

View file

@ -103,6 +103,9 @@ enum action_type {
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[] = {
@ -151,6 +154,9 @@ const char *action_names[] = {
"VirtualOutputRemove",
"AutoPlace",
"ToggleTearing",
"Shade",
"Unshade",
"ToggleShade",
NULL
};
@ -841,6 +847,7 @@ actions_run(struct view *activator, struct server *server,
.width = width ? : view->pending.width,
.height = height ? : view->pending.height,
};
view_set_shade(view, false);
view_move_resize(view, box);
}
break;
@ -960,6 +967,21 @@ actions_run(struct view *activator, struct server *server,
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;

View file

@ -1166,6 +1166,10 @@ static struct mouse_combos {
{ "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},

View file

@ -471,7 +471,12 @@ cursor_update_common(struct server *server, struct cursor_context *ctx,
*/
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);
}
}
}

View file

@ -55,7 +55,8 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
}
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).
*/
@ -66,6 +67,8 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
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 */
@ -80,10 +83,11 @@ interactive_begin(struct view *view, enum input_mode mode, uint32_t edges)
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;
}

View file

@ -718,6 +718,8 @@ init_windowmenu(struct server *server)
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);

View file

@ -168,8 +168,9 @@ build_grid(struct overlap_bitmap *bmp, struct view *view)
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) {
@ -259,8 +260,9 @@ build_overlap(struct overlap_bitmap *bmp, struct view *view)
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

View file

@ -47,15 +47,18 @@ resistance_move_apply(struct view *view, double *x, double *y)
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;
@ -91,7 +94,7 @@ resistance_move_apply(struct view *view, double *x, double *y)
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 */

View file

@ -37,8 +37,9 @@ snap_get_view_edge(struct view *view)
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;
}
@ -52,9 +53,10 @@ snap_get_max_distance(struct view *view)
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;
@ -115,7 +117,8 @@ _snap_next_edge(struct view *view, int start_pos, const struct snap_search def,
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) {
@ -131,12 +134,15 @@ _snap_move_resize_to_edge(struct view *view, enum view_edge direction, enum snap
{
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 {

View file

@ -159,14 +159,17 @@ resize_indicator_update(struct view *view)
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:
@ -192,8 +195,8 @@ resize_indicator_update(struct view *view)
/* 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 */);

View file

@ -61,11 +61,15 @@ ssd_max_extents(struct view *view)
{
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,
};
}
@ -220,7 +224,11 @@ ssd_update_geometry(struct ssd *ssd)
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);
@ -333,6 +341,16 @@ ssd_set_active(struct ssd *ssd, bool active)
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)
{

View file

@ -20,7 +20,7 @@ ssd_border_create(struct ssd *ssd)
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;
@ -83,7 +83,7 @@ ssd_border_update(struct ssd *ssd)
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;

View file

@ -109,7 +109,7 @@ ssd_extents_update(struct ssd *ssd)
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;

View file

@ -384,6 +384,7 @@ view_resize_relative(struct view *view, int left, int right, int top, int bottom
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;
@ -703,6 +704,18 @@ view_store_natural_geometry(struct view *view)
}
}
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)
{
@ -1032,12 +1045,17 @@ view_maximize(struct view *view, enum view_axis axis,
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
@ -1087,6 +1105,12 @@ void
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) {
/*
@ -1209,6 +1233,7 @@ void
view_toggle_fullscreen(struct view *view)
{
assert(view);
view_set_fullscreen(view, !view->fullscreen);
}
@ -1216,6 +1241,11 @@ view_toggle_fullscreen(struct view *view)
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);
@ -1561,7 +1591,8 @@ view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_wind
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;
@ -1578,9 +1609,11 @@ view_move_to_edge(struct view *view, enum view_edge direction, bool snap_to_wind
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 */
@ -1599,11 +1632,14 @@ view_grow_to_edge(struct view *view, enum view_edge direction)
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);
@ -1613,15 +1649,19 @@ void
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);
@ -1670,15 +1710,19 @@ view_snap_to_edge(struct view *view, enum view_edge edge,
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);
@ -1721,15 +1765,19 @@ view_snap_to_region(struct view *view, struct region *region,
{
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,
@ -1974,6 +2022,25 @@ view_connect_map(struct view *view, struct wlr_surface *surface)
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)
{