]> git.mdlowis.com Git - proto/labwc.git/commitdiff
feat: add Shade/Unshade/ToggleShade actions
authorConsolatis <35009135+Consolatis@users.noreply.github.com>
Tue, 8 Aug 2023 01:39:35 +0000 (03:39 +0200)
committerJohan Malm <johanmalm@users.noreply.github.com>
Mon, 15 Jan 2024 21:37:36 +0000 (21:37 +0000)
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>
19 files changed:
docs/labwc-actions.5.scd
docs/menu.xml
docs/rc.xml.all
include/ssd.h
include/view.h
po/labwc.pot
src/action.c
src/config/rcxml.c
src/input/cursor.c
src/interactive.c
src/menu/menu.c
src/placement.c
src/resistance.c
src/snap.c
src/ssd/resize_indicator.c
src/ssd/ssd.c
src/ssd/ssd_border.c
src/ssd/ssd_extents.c
src/view.c

index 38bad8a2d9996b19ae6ad1c51c17c227a31b1cad..52beef4228957d7f99e88e13aeb271998f5f6371 100644 (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.
index d03f8e711bf02c2c7f465fc6c91f8f5436f2621b..521d80a94d32cc1c8d86998ec03e29b16834e517 100644 (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>
index 012818c23052c39b1fd3e93e1f848892335f1aef..350a5be16967e647319b571266852e9b5bcf8aca 100644 (file)
         <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">
index f1b275168bddd67f525f084949bca7293f8013ea..5d0a32eee0b6ca097607eaf931751861525b5bda 100644 (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,
index c446df8bd1d206171d9a232ee4824567045bc153..f18904fc144ac1f3820d8a3ef47bc1b91656aff3 100644 (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);
 
index eff1d14e39e23f6bb87c52fc2b3fa3b02b35ccdb..51508ea59efb29ce5a53306ae57feb72b72f69ba 100644 (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 ""
index 46bc8c9ee284395bc7ddae1f42327dac1bf36259..d9b46866ec36cfae489a1c1bcbdad81f9f265eb3 100644 (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;
index 4547422a7537920c39295e59d533ef13ea7bbdbe..bc21a458d8f7d588206180e47022fd78392bbbfe 100644 (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},
index 309bb6cd05e3d9d750235a2aadfb8e7e268a15a4..38bf250c622e4033c90b8323f64b0c3d071564e0 100644 (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);
                }
        }
 }
index f92ab8c155b9cd56b26d15fcddbfd39da0cc7243..0a4e1818c2f8ac8677517e1db2cd2846e6e0ab58 100644 (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;
                }
index 236ab26df6d8592777bb63db7a3fd4189ee7ca1b..5790caf5d383b6817a5527a64ed26d8342bfe09e 100644 (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);
index c2a724af079a542bec44eeff3e91c872c414192f..560b5e5455dd08a0e7bd937ca26f9fe8b519643e 100644 (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
index 26ee84e6b4c40cfd92984a17ce7db3e54e7aab0e..a03080a9e323fc9d71270045e1e5ab461d0c10d0 100644 (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 */
index 7128f16788bcea94c79a5aa23467cbee82607207..fd60cad0040a54272f7725666a2c77b490d435bf 100644 (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 {
index 5360f1c0debfcd75195a6f0cb7e729a21814d68f..e140886d84aa1d1a30455a91a196dfe46bb21c46 100644 (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 */);
index df18f1e5f9b4dbce7c87dc3f45fa5eba84bf51c2..dd7f1b60d4fa2cd6a87090e3429116c94aa4e5ec 100644 (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)
 {
index c12b07b26d404dfa38d89605bc6e58ba27cbd1a0..74f29ffa22b0078ed3d50fbfb621abf7880aa2cc 100644 (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;
index 22bfa579d863bd4bc7f500534b77e823148d270c..9a926a2ba14dee691e18e187b43e53883a0f13f0 100644 (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;
index 7ab09e2ddf46e594505b1135f494ece3fd0f745d..eac7893d32cbd355aaebb01893293f7f50899d26 100644 (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)
 {