git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commite2e9fb63c0f6c4d366d3437c5ec1def29014b8b9
parent2d96db7833
authorLucas Galante <[email protected]>
date2026-06-06 00:07
Update clear-computing-environment-client to adapt to Element trait refactoring (modified: src/config.rs, src/decorations.rs, src/inspector_cli.rs and 4 others)

 src/config.rs        |  18 ++++++
 src/decorations.rs   | 157 +++++++++++++++++++++++++++++++++++++++++++++++++--
 src/inspector_cli.rs |   4 +-
 src/ipc.rs           |  14 +++++
 src/types.rs         |   4 ++
 src/wayland.rs       | 102 ++++++++++++++++++++++++++++-----
 src/wm.rs            |   8 ++-
 7 files changed, 281 insertions(+), 26 deletions(-)

diff --git a/src/config.rs b/src/config.rs
index b3abb45..dcd933b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -84,6 +84,10 @@ pub struct LayoutConfig {
     pub border_blur: bool,
     #[serde(default = "default_window_blur")]
     pub window_blur: bool,
+    #[serde(default = "default_side_panel_behavior")]
+    pub side_panel_behavior: String,
+    #[serde(default = "default_side_panel_width")]
+    pub side_panel_width: i64,
 }
 
 impl Default for LayoutConfig {
@@ -108,6 +112,8 @@ impl Default for LayoutConfig {
             grid_gap: default_grid_gap(),
             border_blur: default_border_blur(),
             window_blur: default_window_blur(),
+            side_panel_behavior: default_side_panel_behavior(),
+            side_panel_width: default_side_panel_width(),
         }
     }
 }
@@ -124,6 +130,14 @@ fn default_window_blur() -> bool {
     false
 }
 
+fn default_side_panel_behavior() -> String {
+    "inline".to_string()
+}
+
+fn default_side_panel_width() -> i64 {
+    360
+}
+
 fn default_gap() -> i64 {
     48
 }
@@ -325,6 +339,8 @@ pub fn parse_config(path: &str, cold_start: bool, state: &mut WindowManager) ->
     state.layout.grid_gap = config.layout.grid_gap as i32;
     state.layout.border_blur = config.layout.border_blur;
     state.layout.window_blur = config.layout.window_blur;
+    state.layout.side_panel_behavior = config.layout.side_panel_behavior.clone();
+    state.layout.side_panel_width = config.layout.side_panel_width as i32;
     if let Some((r, g, b, a)) = parse_hex_color(&config.layout.border_color) {
         state.layout.border_r = r;
         state.layout.border_g = g;
@@ -698,6 +714,8 @@ mod tests {
         assert_eq!(lc.border_color, "#3e3e3e");
         assert_eq!(lc.border_font_size, 11);
         assert_eq!(lc.grid_gap, 18);
+        assert_eq!(lc.side_panel_width, 360);
+        assert_eq!(lc.side_panel_behavior, "inline");
     }
 
     #[test]
diff --git a/src/decorations.rs b/src/decorations.rs
index d77ac08..bcb08d8 100644
--- a/src/decorations.rs
+++ b/src/decorations.rs
@@ -283,6 +283,8 @@ fn update_border_decoration(
     rect_h: i32,
     border_width: i32,
     round_bottom: bool,
+    side: &str,
+    hover_info: Option<(f64, f64)>,
 ) {
     let dec_width = logical_width * scale;
     let dec_height = logical_height * scale;
@@ -378,6 +380,50 @@ fn update_border_decoration(
                 *pixel = 0x00000000;
             }
 
+            let mut highlight_region: Option<HighlightRegion> = None;
+            if let Some((hx, hy)) = hover_info {
+                const CORNER_THRESHOLD: f64 = 16.0;
+                match side {
+                    "left" => {
+                        let mid_x = (logical_width as f64) / 2.0;
+                        if hx < mid_x {
+                            if hy < CORNER_THRESHOLD {
+                                highlight_region = Some(HighlightRegion::TopLeft);
+                            } else if hy > (logical_height as f64 - CORNER_THRESHOLD) {
+                                highlight_region = Some(HighlightRegion::BottomLeft);
+                            } else {
+                                highlight_region = Some(HighlightRegion::Left);
+                            }
+                        }
+                    }
+                    "right" => {
+                        let mid_x = (logical_width as f64) / 2.0;
+                        if hx >= mid_x {
+                            if hy < CORNER_THRESHOLD {
+                                highlight_region = Some(HighlightRegion::TopRight);
+                            } else if hy > (logical_height as f64 - CORNER_THRESHOLD) {
+                                highlight_region = Some(HighlightRegion::BottomRight);
+                            } else {
+                                highlight_region = Some(HighlightRegion::Right);
+                            }
+                        }
+                    }
+                    "bottom" => {
+                        let mid_y = (logical_height as f64) / 2.0;
+                        if hy >= mid_y {
+                            if hx < CORNER_THRESHOLD {
+                                highlight_region = Some(HighlightRegion::BottomLeft);
+                            } else if hx > (logical_width as f64 - CORNER_THRESHOLD) {
+                                highlight_region = Some(HighlightRegion::BottomRight);
+                            } else {
+                                highlight_region = Some(HighlightRegion::Bottom);
+                            }
+                        }
+                    }
+                    _ => {}
+                }
+            }
+
             // Draw visual border color with 3D cylindrical shading and mitred joints
             let rx_start = rect_x * scale;
             let ry_start = rect_y * scale;
@@ -444,9 +490,17 @@ fn update_border_decoration(
                             };
 
                             let a = ((bg_color >> 24) & 0xFF) as f32;
-                            let r_val = (((bg_color >> 16) & 0xFF) as f32 * s).round().min(255.0) as u32;
-                            let g = (((bg_color >> 8) & 0xFF) as f32 * s).round().min(255.0) as u32;
-                            let b = ((bg_color & 0xFF) as f32 * s).round().min(255.0) as u32;
+                            let mut r_val = (((bg_color >> 16) & 0xFF) as f32 * s).round().min(255.0) as u32;
+                            let mut g = (((bg_color >> 8) & 0xFF) as f32 * s).round().min(255.0) as u32;
+                            let mut b = ((bg_color & 0xFF) as f32 * s).round().min(255.0) as u32;
+
+                            if let Some(region) = highlight_region {
+                                if is_pixel_in_highlight_region(side, region, px, py, logical_width, logical_height, scale) {
+                                    r_val = (r_val as f32 * 0.6 + 255.0 * 0.4).round() as u32;
+                                    g = (g as f32 * 0.6 + 255.0 * 0.4).round() as u32;
+                                    b = (b as f32 * 0.6 + 255.0 * 0.4).round() as u32;
+                                }
+                            }
 
                             buffer_slice[row_offset + px as usize] = ((a as u32) << 24)
                                 | (r_val << 16)
@@ -699,6 +753,10 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
                 border_width.max(1)
             };
 
+            let hover_left = state.pointer_hovered_surface.as_ref()
+                .filter(|surf| wp.dec_left.as_ref().map_or(false, |d| &d.surface == *surf))
+                .map(|_| (state.last_pointer_surface_x, state.last_pointer_surface_y));
+
             update_border_decoration(
                 wid,
                 &mut wp.dec_left,
@@ -719,8 +777,14 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
                 win_height,
                 border_width,
                 false,
+                "left",
+                hover_left,
             );
 
+            let hover_right = state.pointer_hovered_surface.as_ref()
+                .filter(|surf| wp.dec_right.as_ref().map_or(false, |d| &d.surface == *surf))
+                .map(|_| (state.last_pointer_surface_x, state.last_pointer_surface_y));
+
             update_border_decoration(
                 wid,
                 &mut wp.dec_right,
@@ -741,8 +805,14 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
                 win_height,
                 border_width,
                 false,
+                "right",
+                hover_right,
             );
 
+            let hover_bottom = state.pointer_hovered_surface.as_ref()
+                .filter(|surf| wp.dec_bottom.as_ref().map_or(false, |d| &d.surface == *surf))
+                .map(|_| (state.last_pointer_surface_x, state.last_pointer_surface_y));
+
             update_border_decoration(
                 wid,
                 &mut wp.dec_bottom,
@@ -763,6 +833,8 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
                 border_width,
                 border_width,
                 true,
+                "bottom",
+                hover_bottom,
             );
         }
 
@@ -799,6 +871,10 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
             });
         }
 
+        let hover_top = state.pointer_hovered_surface.as_ref()
+            .filter(|surf| wp.decoration.as_ref().map_or(false, |d| &d.surface == *surf))
+            .map(|_| (state.last_pointer_surface_x, state.last_pointer_surface_y));
+
         let dec = wp.decoration.as_mut().unwrap();
 
         // Position decoration on top of the window top border
@@ -888,6 +964,21 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
             let titlebar_h_scaled = logical_height * scale;
             let win_w_scaled = (if is_minimized { 160 - 2 * border_width } else { win_width }) * scale;
 
+            let mut highlight_region: Option<HighlightRegion> = None;
+            if let Some((hx, hy)) = hover_top {
+                let mid_y = (logical_height as f64) / 2.0;
+                if hy < mid_y {
+                    const CORNER_THRESHOLD: f64 = 16.0;
+                    if hx < CORNER_THRESHOLD {
+                        highlight_region = Some(HighlightRegion::TopLeft);
+                    } else if hx > (logical_width as f64 - CORNER_THRESHOLD) {
+                        highlight_region = Some(HighlightRegion::TopRight);
+                    } else {
+                        highlight_region = Some(HighlightRegion::Top);
+                    }
+                }
+            }
+
             for py in 0..dec_height {
                 let row_offset = (py * dec_width) as usize;
                 for px in 0..dec_width {
@@ -924,9 +1015,17 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
                     };
 
                     let a = ((bg_color >> 24) & 0xFF) as f32;
-                    let r_val = (((bg_color >> 16) & 0xFF) as f32 * s).round().min(255.0) as u32;
-                    let g = (((bg_color >> 8) & 0xFF) as f32 * s).round().min(255.0) as u32;
-                    let b = ((bg_color & 0xFF) as f32 * s).round().min(255.0) as u32;
+                    let mut r_val = (((bg_color >> 16) & 0xFF) as f32 * s).round().min(255.0) as u32;
+                    let mut g = (((bg_color >> 8) & 0xFF) as f32 * s).round().min(255.0) as u32;
+                    let mut b = ((bg_color & 0xFF) as f32 * s).round().min(255.0) as u32;
+
+                    if let Some(region) = highlight_region {
+                        if is_pixel_in_highlight_region("top", region, px, py, logical_width, logical_height, scale) {
+                            r_val = (r_val as f32 * 0.6 + 255.0 * 0.4).round() as u32;
+                            g = (g as f32 * 0.6 + 255.0 * 0.4).round() as u32;
+                            b = (b as f32 * 0.6 + 255.0 * 0.4).round() as u32;
+                        }
+                    }
 
                     buffer_slice[row_offset + px as usize] = ((a as u32) << 24) | (r_val << 16) | (g << 8) | b;
                 }
@@ -1035,3 +1134,49 @@ pub fn update_decorations(state: &mut AppState, qhandle: &QueueHandle<AppState>)
         }
     }
 }
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum HighlightRegion {
+    TopLeft,
+    Top,
+    TopRight,
+    Left,
+    Right,
+    BottomLeft,
+    Bottom,
+    BottomRight,
+}
+
+fn is_pixel_in_highlight_region(
+    side: &str,
+    region: HighlightRegion,
+    px: i32,
+    py: i32,
+    logical_width: i32,
+    logical_height: i32,
+    scale: i32,
+) -> bool {
+    let px_logical = px as f32 / scale as f32;
+    let py_logical = py as f32 / scale as f32;
+    const CORNER_THRESHOLD: f32 = 16.0;
+
+    match (side, region) {
+        ("left", HighlightRegion::TopLeft) => py_logical < CORNER_THRESHOLD,
+        ("left", HighlightRegion::BottomLeft) => py_logical > (logical_height as f32 - CORNER_THRESHOLD),
+        ("left", HighlightRegion::Left) => py_logical >= CORNER_THRESHOLD && py_logical <= (logical_height as f32 - CORNER_THRESHOLD),
+
+        ("right", HighlightRegion::TopRight) => py_logical < CORNER_THRESHOLD,
+        ("right", HighlightRegion::BottomRight) => py_logical > (logical_height as f32 - CORNER_THRESHOLD),
+        ("right", HighlightRegion::Right) => py_logical >= CORNER_THRESHOLD && py_logical <= (logical_height as f32 - CORNER_THRESHOLD),
+
+        ("bottom", HighlightRegion::BottomLeft) => px_logical < CORNER_THRESHOLD,
+        ("bottom", HighlightRegion::BottomRight) => px_logical > (logical_width as f32 - CORNER_THRESHOLD),
+        ("bottom", HighlightRegion::Bottom) => px_logical >= CORNER_THRESHOLD && px_logical <= (logical_width as f32 - CORNER_THRESHOLD),
+
+        ("top", HighlightRegion::TopLeft) => px_logical < CORNER_THRESHOLD,
+        ("top", HighlightRegion::TopRight) => px_logical > (logical_width as f32 - CORNER_THRESHOLD),
+        ("top", HighlightRegion::Top) => px_logical >= CORNER_THRESHOLD && px_logical <= (logical_width as f32 - CORNER_THRESHOLD),
+
+        _ => false,
+    }
+}
diff --git a/src/inspector_cli.rs b/src/inspector_cli.rs
index 8c62ae6..da24454 100644
--- a/src/inspector_cli.rs
+++ b/src/inspector_cli.rs
@@ -321,7 +321,7 @@ fn main() {
                         }
                     }
                 } else {
-                    eprintln!("Error: Widget of type '{}'{} not found in surface '{}'", 
+                    eprintln!("Error: Element of type '{}'{} not found in surface '{}'", 
                         target_type, 
                         widget_label.as_ref().map(|l| format!(" with label '{}'", l)).unwrap_or_default(),
                         target_app_id
@@ -351,7 +351,7 @@ fn main() {
         println!("  Dimensions:  {}x{} px", surface.width, surface.height);
         
         // Pretty print widget state if it's JSON
-        print!("  Widget Tree: ");
+        print!("  Element Tree: ");
         if let Ok(json_val) = serde_json::from_str::<Value>(&surface.state) {
             if let Ok(pretty_json) = serde_json::to_string_pretty(&json_val) {
                 // Indent pretty JSON for clean display
diff --git a/src/ipc.rs b/src/ipc.rs
index 442dc51..274d695 100644
--- a/src/ipc.rs
+++ b/src/ipc.rs
@@ -706,6 +706,20 @@ fn handle_layout_command(rest: &str, state: &mut WindowManager) {
                 }
             }
         }
+        "side_panel_behavior" | "side-panel-behavior" => {
+            state.layout.side_panel_behavior = value_str.to_string();
+            if state.notifications_enable {
+                crate::config::show_notification("ccec", &format!("Side panel behavior set to {}", value_str));
+            }
+        }
+        "side_panel_width" | "side-panel-width" => {
+            if let Ok(value) = value_str.parse::<i32>() {
+                state.layout.side_panel_width = value;
+                if state.notifications_enable {
+                    crate::config::show_notification("ccec", &format!("Side panel width set to {}px", value));
+                }
+            }
+        }
         _ => {}
     }
     state.needs_render = true;
diff --git a/src/types.rs b/src/types.rs
index 3a70e00..4e1dcda 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -90,6 +90,8 @@ pub struct Layout {
     pub grid_gap: i32,
     pub border_blur: bool,
     pub window_blur: bool,
+    pub side_panel_behavior: String,
+    pub side_panel_width: i32,
 }
 
 impl Default for Layout {
@@ -120,6 +122,8 @@ impl Default for Layout {
             grid_gap: 18,
             border_blur: false,
             window_blur: false,
+            side_panel_behavior: "inline".to_string(),
+            side_panel_width: 360,
         }
     }
 }
diff --git a/src/wayland.rs b/src/wayland.rs
index 360cb81..274a145 100644
--- a/src/wayland.rs
+++ b/src/wayland.rs
@@ -1076,6 +1076,20 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
                             0
                         } else if win.tiling_mode == crate::types::TilingMode::Popup {
                             5
+                        } else if win.tiling_mode == crate::types::TilingMode::SidePanel {
+                            if state.wm.layout.side_panel_behavior == "above" {
+                                if Some(win.id) == focused_id {
+                                    4
+                                } else {
+                                    3
+                                }
+                            } else {
+                                if Some(win.id) == focused_id {
+                                    2
+                                } else {
+                                    1
+                                }
+                            }
                         } else if win.tiling_mode != crate::types::TilingMode::Floating {
                             if Some(win.id) == focused_id {
                                 2
@@ -1616,11 +1630,32 @@ impl Dispatch<RiverWindowV1, ()> for AppState {
 
             river_window_v1::Event::PointerResizeRequested { seat, edges } => {
                 if let Some(_sid) = state.seat_id_for_proxy(&seat) {
+                    let edges_u32: u32 = edges.into();
+                    let op_type = match edges_u32 {
+                        1 => PointerOpType::ResizeTop,
+                        2 => PointerOpType::ResizeBottom,
+                        4 => PointerOpType::ResizeLeft,
+                        8 => PointerOpType::ResizeRight,
+                        5 => PointerOpType::ResizeTopLeft,
+                        9 => PointerOpType::ResizeTopRight,
+                        6 => PointerOpType::ResizeBottomLeft,
+                        10 => PointerOpType::ResizeBottomRight,
+                        _ => PointerOpType::Resize,
+                    };
+
                     let mut window_found = false;
                     let mut needs_render = false;
                     if let Some(win) = state.wm.get_window_mut(wid) {
                         if win.tiling_mode != TilingMode::Fullscreen && win.tiling_mode != TilingMode::Popup {
-                            if win.tiling_mode != TilingMode::Floating {
+                            let is_right_resize_on_side_panel = win.tiling_mode == TilingMode::SidePanel
+                                && matches!(
+                                    op_type,
+                                    PointerOpType::Resize
+                                        | PointerOpType::ResizeRight
+                                        | PointerOpType::ResizeTopRight
+                                        | PointerOpType::ResizeBottomRight
+                                );
+                            if !is_right_resize_on_side_panel && win.tiling_mode != TilingMode::Floating {
                                 win.tiling_mode = TilingMode::Floating;
                                 needs_render = true;
                             }
@@ -1642,18 +1677,6 @@ impl Dispatch<RiverWindowV1, ()> for AppState {
                             win.anim_h = None;
                             win.anim_opacity = None;
                             
-                            let edges_u32: u32 = edges.into();
-                            let op_type = match edges_u32 {
-                                1 => PointerOpType::ResizeTop,
-                                2 => PointerOpType::ResizeBottom,
-                                4 => PointerOpType::ResizeLeft,
-                                8 => PointerOpType::ResizeRight,
-                                5 => PointerOpType::ResizeTopLeft,
-                                9 => PointerOpType::ResizeTopRight,
-                                6 => PointerOpType::ResizeBottomLeft,
-                                10 => PointerOpType::ResizeBottomRight,
-                                _ => PointerOpType::Resize,
-                            };
                             state.active_pointer_op = Some(PointerOp {
                                 window_id: wid,
                                 op_type,
@@ -1782,6 +1805,7 @@ impl Dispatch<RiverSeatV1, ()> for AppState {
             river_seat_v1::Event::OpDelta { dx, dy } => {
                 if let Some(ref op) = state.active_pointer_op {
                     let wid = op.window_id;
+                    let bw = state.wm.layout.cascade_border_width;
                     if let Some(win) = state.wm.get_window_mut(wid) {
                         match op.op_type {
                             PointerOpType::Move => {
@@ -1790,6 +1814,9 @@ impl Dispatch<RiverSeatV1, ()> for AppState {
                             }
                             PointerOpType::Resize | PointerOpType::ResizeRight => {
                                 win.width = std::cmp::max(50, op.start_width + dx);
+                                if win.tiling_mode == TilingMode::SidePanel {
+                                    win.hint_min_width = win.width + bw * 2;
+                                }
                             }
                             PointerOpType::ResizeLeft => {
                                 let target_width = std::cmp::max(50, op.start_width - dx);
@@ -1809,6 +1836,9 @@ impl Dispatch<RiverSeatV1, ()> for AppState {
                             PointerOpType::ResizeBottomRight => {
                                 win.width = std::cmp::max(50, op.start_width + dx);
                                 win.height = std::cmp::max(50, op.start_height + dy);
+                                if win.tiling_mode == TilingMode::SidePanel {
+                                    win.hint_min_width = win.width + bw * 2;
+                                }
                             }
                             PointerOpType::ResizeBottomLeft => {
                                 let target_width = std::cmp::max(50, op.start_width - dx);
@@ -1835,6 +1865,9 @@ impl Dispatch<RiverSeatV1, ()> for AppState {
                                 let actual_dy = op.start_height - target_height;
                                 win.y = op.start_y + actual_dy;
                                 win.height = target_height;
+                                if win.tiling_mode == TilingMode::SidePanel {
+                                    win.hint_min_width = win.width + bw * 2;
+                                }
                             }
                         }
                     }
@@ -2490,7 +2523,15 @@ fn execute_action(state: &mut AppState, seat_id: u64, action: &crate::types::Act
                 let mut needs_render = false;
                 if let Some(win) = state.wm.get_window_mut(wid) {
                     if win.tiling_mode != TilingMode::Fullscreen && win.tiling_mode != TilingMode::Popup {
-                        if win.tiling_mode != TilingMode::Floating {
+                        let is_right_resize_on_side_panel = win.tiling_mode == TilingMode::SidePanel
+                            && matches!(
+                                op_type,
+                                PointerOpType::Resize
+                                    | PointerOpType::ResizeRight
+                                    | PointerOpType::ResizeTopRight
+                                    | PointerOpType::ResizeBottomRight
+                            );
+                        if !is_right_resize_on_side_panel && win.tiling_mode != TilingMode::Floating {
                             win.tiling_mode = TilingMode::Floating;
                             needs_render = true;
                         }
@@ -3730,6 +3771,16 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
 
                 Self::update_cursor_shape_for_surface(state, proxy);
 
+                let is_decoration = state.window_proxies.iter().any(|(_, wp)| {
+                    wp.decoration.as_ref().map_or(false, |d| &d.surface == &surface)
+                        || wp.dec_left.as_ref().map_or(false, |d| &d.surface == &surface)
+                        || wp.dec_right.as_ref().map_or(false, |d| &d.surface == &surface)
+                        || wp.dec_bottom.as_ref().map_or(false, |d| &d.surface == &surface)
+                });
+                if is_decoration {
+                    state.wm.needs_render = true;
+                }
+
                 if state.wm.expose_active {
                     let current_surface = &surface;
                     let matched_window = state.window_proxies.iter().find_map(|(id, proxy)| {
@@ -3787,6 +3838,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
                 eprintln!("[pointer] leave");
                 state.pointer_hovered_surface = None;
                 Self::update_cursor_shape_for_surface(state, proxy);
+                state.wm.needs_render = true;
             }
             wl_pointer::Event::Motion { surface_x, surface_y, .. } => {
                 state.last_pointer_surface_x = surface_x;
@@ -3794,6 +3846,18 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
 
                 Self::update_cursor_shape_for_surface(state, proxy);
 
+                if let Some(ref current_surface) = state.pointer_hovered_surface {
+                    let is_decoration = state.window_proxies.iter().any(|(_, wp)| {
+                        wp.decoration.as_ref().map_or(false, |d| &d.surface == current_surface)
+                            || wp.dec_left.as_ref().map_or(false, |d| &d.surface == current_surface)
+                            || wp.dec_right.as_ref().map_or(false, |d| &d.surface == current_surface)
+                            || wp.dec_bottom.as_ref().map_or(false, |d| &d.surface == current_surface)
+                    });
+                    if is_decoration {
+                        state.wm.needs_render = true;
+                    }
+                }
+
                 if let Some(pending) = state.pending_border_drag.clone() {
                     let dx = surface_x - pending.start_surface_x;
                     let dy = surface_y - pending.start_surface_y;
@@ -3806,7 +3870,15 @@ impl Dispatch<wl_pointer::WlPointer, ()> for AppState {
                         let mut needs_render = false;
                         if let Some(win) = state.wm.get_window_mut(pending.window_id) {
                             if win.tiling_mode != TilingMode::Fullscreen && win.tiling_mode != TilingMode::Popup {
-                                if win.tiling_mode != TilingMode::Floating {
+                                let is_right_resize_on_side_panel = win.tiling_mode == TilingMode::SidePanel
+                                    && matches!(
+                                        pending.op_type,
+                                        PointerOpType::Resize
+                                            | PointerOpType::ResizeRight
+                                            | PointerOpType::ResizeTopRight
+                                            | PointerOpType::ResizeBottomRight
+                                    );
+                                if !is_right_resize_on_side_panel && win.tiling_mode != TilingMode::Floating {
                                     win.tiling_mode = TilingMode::Floating;
                                     needs_render = true;
                                 }
diff --git a/src/wm.rs b/src/wm.rs
index 1908264..3b1fa97 100644
--- a/src/wm.rs
+++ b/src/wm.rs
@@ -491,10 +491,12 @@ fn compute_tiling(
     });
 
     let shift_x = if let Some(panel_win) = side_panel_win {
-        if panel_win.hint_min_width > 32 {
+        if wm.layout.side_panel_behavior == "above" {
+            0
+        } else if panel_win.hint_min_width > 32 {
             panel_win.hint_min_width
         } else {
-            360
+            wm.layout.side_panel_width
         }
     } else {
         0
@@ -517,7 +519,7 @@ fn compute_tiling(
                 let target_w = if win.hint_min_width > 32 {
                     win.hint_min_width
                 } else {
-                    360
+                    wm.layout.side_panel_width
                 };
                 let bw = wm.layout.cascade_border_width;
                 let dec_h = std::cmp::max(bw, 16);