From f1508350e37f7d4d3e048ffa6e5936882c245bf3 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Wed, 12 Jul 2023 18:50:41 -0500 Subject: [PATCH] Add spiral layout --- api/lua/example_config.lua | 13 +++++ api/lua/msg.lua | 1 + api/lua/tag.lua | 31 +++++++++++ src/api/msg.rs | 10 +++- src/handlers.rs | 8 ++- src/layout.rs | 105 ++++++++++++++++++++++++++++++++++++- src/state.rs | 12 +++++ src/tag.rs | 8 ++- src/window.rs | 22 ++++++-- 9 files changed, 202 insertions(+), 8 deletions(-) diff --git a/api/lua/example_config.lua b/api/lua/example_config.lua index 08131b9..95d7321 100644 --- a/api/lua/example_config.lua +++ b/api/lua/example_config.lua @@ -65,6 +65,19 @@ require("pinnacle").setup(function(pinnacle) tag.toggle("1", op) end) + ---@type Layout[] + local layouts = { "MasterStack", "Dwindle", "Spiral" } + local index = 1 + + input.keybind({ mod_key }, keys.space, function() + tag.set_layout("1", layouts[index]) + if index + 1 > #layouts then + index = 1 + else + index = index + 1 + end + end) + input.keybind({ mod_key }, keys.KEY_1, function() tag.switch_to("1") end) diff --git a/api/lua/msg.lua b/api/lua/msg.lua index b03b1a7..b889a3d 100644 --- a/api/lua/msg.lua +++ b/api/lua/msg.lua @@ -23,6 +23,7 @@ ---@field SwitchToTag { output_name: string, tag_name: string } ---@field AddTags { output_name: string, tags: string[] } ---@field RemoveTags { output_name: string, tags: string[] } +---@field SetLayout { output_name: string, tag_name: string, layout: Layout } --Outputs ---@field ConnectForAllOutputs { callback_id: integer } diff --git a/api/lua/tag.lua b/api/lua/tag.lua index 9e648f8..ce62b1d 100644 --- a/api/lua/tag.lua +++ b/api/lua/tag.lua @@ -4,6 +4,11 @@ -- -- SPDX-License-Identifier: MPL-2.0 +---@alias Layout +---| "MasterStack" # One master window on the left with all other windows stacked to the right. +---| "Dwindle" # Windows split in half towards the bottom right corner. +---| "Spiral" # Windows split in half in a spiral. + local tag = {} ---Add tags. @@ -121,4 +126,30 @@ function tag.switch_to(name, output) end end +---Set a layout for the tag on the specified output. If there is none, set it for the tag on the currently focused one. +---@param name string The name of the tag. +---@param layout Layout The layout. +---@param output Output? The output. +function tag.set_layout(name, layout, output) + if output ~= nil then + SendMsg({ + SetLayout = { + output_name = output.name, + tag_name = name, + layout = layout, + }, + }) + else + local op = require("output").get_focused() + if op ~= nil then + SendMsg({ + SetLayout = { + output_name = op.name, + tag_name = name, + layout = layout, + }, + }) + end + end +end return tag diff --git a/src/api/msg.rs b/src/api/msg.rs index 69cb516..3c1b820 100644 --- a/src/api/msg.rs +++ b/src/api/msg.rs @@ -7,7 +7,10 @@ // The MessagePack format for these is a one-element map where the element's key is the enum name and its // value is a map of the enum's values -use crate::window::{window_state::WindowId, WindowProperties}; +use crate::{ + layout::Layout, + window::{window_state::WindowId, WindowProperties}, +}; #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)] pub struct CallbackId(pub u32); @@ -65,6 +68,11 @@ pub enum Msg { output_name: String, tags: Vec, }, + SetLayout { + output_name: String, + tag_name: String, + layout: Layout, + }, // Output management ConnectForAllOutputs { diff --git a/src/handlers.rs b/src/handlers.rs index 11145bd..660c96e 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -19,7 +19,7 @@ use smithay::{ }, reexports::{ calloop::Interest, - wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge, + wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, wayland_server::{ protocol::{wl_buffer::WlBuffer, wl_seat::WlSeat, wl_surface::WlSurface}, Client, Resource, @@ -223,6 +223,12 @@ impl XdgShellHandler for State { fn new_toplevel(&mut self, surface: ToplevelSurface) { let window = Window::new(surface); + window.toplevel().with_pending_state(|tl_state| { + tl_state.states.set(xdg_toplevel::State::TiledTop); + tl_state.states.set(xdg_toplevel::State::TiledBottom); + tl_state.states.set(xdg_toplevel::State::TiledLeft); + tl_state.states.set(xdg_toplevel::State::TiledRight); + }); window.with_state(|state| { state.tags = match ( &self.focus_state.focused_output, diff --git a/src/layout.rs b/src/layout.rs index 9b1bfa4..8e594d5 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -25,10 +25,11 @@ pub enum Direction { } // TODO: couple this with the layouts -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum Layout { MasterStack, Dwindle, + Spiral, } impl Layout { @@ -176,6 +177,108 @@ impl Layout { y_factor_2 += (1.0 - y_factor_2) / 2.0; } + win1.with_state(|state| { + let new_loc = ( + (output_geo.size.w as f32 * x_factor_1 + output_loc.x as f32) + as i32, + (output_geo.size.h as f32 * y_factor_1 + output_loc.y as f32) + as i32, + ) + .into(); + state.resize_state = WindowResizeState::WaitingForAck( + win1.toplevel().send_configure(), + new_loc, + ); + }); + win2.with_state(|state| { + let new_loc = ( + (output_geo.size.w as f32 * x_factor_2 + output_loc.x as f32) + as i32, + (output_geo.size.h as f32 * y_factor_2 + output_loc.y as f32) + as i32, + ) + .into(); + state.resize_state = WindowResizeState::WaitingForAck( + win2.toplevel().send_configure(), + new_loc, + ); + }); + } + } + } + Layout::Spiral => { + let mut iter = windows.windows(2).peekable(); + let Some(output_geo) = space.output_geometry(output) else { + tracing::error!("could not get output geometry"); + return; + }; + + let output_loc = output.current_location(); + + if iter.peek().is_none() { + if let Some(window) = windows.first() { + window.toplevel().with_pending_state(|state| { + state.size = Some(output_geo.size); + }); + + window.with_state(|state| { + state.resize_state = WindowResizeState::WaitingForAck( + window.toplevel().send_configure(), + (output_loc.x, output_loc.y).into(), + ); + }); + } + } else { + let mut div_factor_w = 1; + let mut div_factor_h = 1; + let mut x_factor_1: f32 = 0.0; + let mut y_factor_1: f32; + let mut x_factor_2: f32 = 0.0; + let mut y_factor_2: f32; + + fn series(n: u32) -> f32 { + (0..n) + .map(|n| (-1i32).pow(n) as f32 * (1.0 / 2.0_f32.powi(n as i32))) + .sum() + } + + for (i, wins) in iter.enumerate() { + let win1 = &wins[0]; + let win2 = &wins[1]; + + if i % 2 == 0 { + div_factor_w *= 2; + } else { + div_factor_h *= 2; + } + + win1.toplevel().with_pending_state(|state| { + let new_size = ( + output_geo.size.w / div_factor_w, + output_geo.size.h / div_factor_h, + ) + .into(); + state.size = Some(new_size); + }); + win2.toplevel().with_pending_state(|state| { + let new_size = ( + output_geo.size.w / div_factor_w, + output_geo.size.h / div_factor_h, + ) + .into(); + state.size = Some(new_size); + }); + + y_factor_1 = x_factor_1; + y_factor_2 = x_factor_2; + + x_factor_1 = { + let first = (i / 4) * 2; + let indices = [first, first + 2, first + 3, first + 2]; + series(indices[i % 4] as u32) + }; + x_factor_2 = series((i as u32 / 4 + 1) * 2); + win1.with_state(|state| { let new_loc = ( (output_geo.size.w as f32 * x_factor_1 + output_loc.x as f32) diff --git a/src/state.rs b/src/state.rs index 0627399..8405cff 100644 --- a/src/state.rs +++ b/src/state.rs @@ -271,6 +271,18 @@ impl State { }); } } + Msg::SetLayout { output_name, tag_name, layout } => { + let output = self.space.outputs().find(|op| op.name() == output_name).cloned(); + if let Some(output) = output { + + output.with_state(|state| { + if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) { + tag.set_layout(layout); + } + }); + self.re_layout(&output); + } + } Msg::ConnectForAllOutputs { callback_id } => { let stream = self diff --git a/src/tag.rs b/src/tag.rs index 7769fcb..9f866db 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -66,13 +66,17 @@ impl Tag { self.0.borrow().active } - pub fn set_active(&mut self, active: bool) { + pub fn set_active(&self, active: bool) { self.0.borrow_mut().active = active; } pub fn layout(&self) -> Layout { self.0.borrow().layout } + + pub fn set_layout(&self, layout: Layout) { + self.0.borrow_mut().layout = layout; + } } impl Tag { @@ -81,7 +85,7 @@ impl Tag { id: TagId::next(), name, active: false, - layout: Layout::Dwindle, // TODO: get from config + layout: Layout::MasterStack, // TODO: get from config }))) } } diff --git a/src/window.rs b/src/window.rs index 6dda1bc..6a47320 100644 --- a/src/window.rs +++ b/src/window.rs @@ -5,7 +5,11 @@ // SPDX-License-Identifier: MPL-2.0 use smithay::{ - desktop::Window, reexports::wayland_server::protocol::wl_surface::WlSurface, + desktop::Window, + reexports::{ + wayland_protocols::xdg::shell::server::xdg_toplevel, + wayland_server::protocol::wl_surface::WlSurface, + }, wayland::seat::WaylandFocus, }; @@ -46,10 +50,17 @@ pub fn toggle_floating(state: &mut State, window: &Window) { window.toplevel().send_pending_configure(); - state.space.map_element(window.clone(), prev_loc, false); // TODO: should it activate? + state.space.map_element(window.clone(), prev_loc, false); + // TODO: should it activate? } window_state.floating = Float::Floating; + window.toplevel().with_pending_state(|tl_state| { + tl_state.states.unset(xdg_toplevel::State::TiledTop); + tl_state.states.unset(xdg_toplevel::State::TiledBottom); + tl_state.states.unset(xdg_toplevel::State::TiledLeft); + tl_state.states.unset(xdg_toplevel::State::TiledRight); + }); } Float::Floating => { window_state.floating = Float::Tiled(Some(( @@ -58,6 +69,12 @@ pub fn toggle_floating(state: &mut State, window: &Window) { state.space.element_location(window).unwrap(), window.geometry().size, ))); + window.toplevel().with_pending_state(|tl_state| { + tl_state.states.set(xdg_toplevel::State::TiledTop); + tl_state.states.set(xdg_toplevel::State::TiledBottom); + tl_state.states.set(xdg_toplevel::State::TiledLeft); + tl_state.states.set(xdg_toplevel::State::TiledRight); + }); } } }); @@ -65,7 +82,6 @@ pub fn toggle_floating(state: &mut State, window: &Window) { let output = state.focus_state.focused_output.clone().unwrap(); state.re_layout(&output); - let output = state.focus_state.focused_output.as_ref().unwrap(); let render = output.with_state(|op_state| { state .windows