From 5dbfa6fbcfb29524ad91a68a176bc3db74569ddc Mon Sep 17 00:00:00 2001
From: Janis <janis@nirgendwo.xyz>
Date: Thu, 2 Dec 2021 20:22:21 +0100
Subject: [PATCH] added fullscreen mechanics

---
 src/clients.rs | 173 ++++++++++++++++++++++++++++++++++++++-----------
 src/state.rs   |  26 +++++++-
 2 files changed, 159 insertions(+), 40 deletions(-)

diff --git a/src/clients.rs b/src/clients.rs
index a01f22e..e806c08 100644
--- a/src/clients.rs
+++ b/src/clients.rs
@@ -1,4 +1,3 @@
-use std::num::NonZeroI32;
 use std::{ops::Rem, usize};
 
 use indexmap::IndexMap;
@@ -20,6 +19,7 @@ mod client {
         pub(crate) size: Point<i32>,
         pub(crate) position: Point<i32>,
         pub(crate) transient_for: Option<Window>,
+        pub(crate) fullscreen: bool,
     }
 
     impl Default for Client {
@@ -29,6 +29,7 @@ mod client {
                 size: (100, 100).into(),
                 position: (0, 0).into(),
                 transient_for: None,
+                fullscreen: false,
             }
         }
     }
@@ -44,7 +45,7 @@ mod client {
                 window,
                 size,
                 position,
-                transient_for: None,
+                ..Self::default()
             }
         }
 
@@ -68,6 +69,27 @@ mod client {
             }
         }
 
+        /// toggles the clients fullscreen flag.
+        /// returns `true` if the client is now fullscreen.
+        pub fn toggle_fullscreen(&mut self) -> bool {
+            self.fullscreen = !self.fullscreen;
+
+            self.is_fullscreen()
+        }
+
+        pub fn set_fullscreen(&mut self, fullscreen: bool) -> bool {
+            if self.fullscreen == fullscreen {
+                false
+            } else {
+                self.fullscreen = fullscreen;
+                true
+            }
+        }
+
+        pub fn is_fullscreen(&self) -> bool {
+            self.fullscreen
+        }
+
         pub fn is_transient(&self) -> bool {
             self.transient_for.is_some()
         }
@@ -408,6 +430,43 @@ impl ClientState {
         self.arrange_virtual_screen();
     }
 
+    pub fn set_fullscreen<K>(&mut self, key: &K, fullscreen: bool) -> bool
+    where
+        K: ClientKey,
+    {
+        self.get(key)
+            .into_option()
+            .map(|client| client.is_fullscreen() != fullscreen)
+            .unwrap_or(false)
+            .then(|| self.toggle_fullscreen(key))
+            .unwrap_or(false)
+    }
+
+    pub fn toggle_fullscreen<K>(&mut self, key: &K) -> bool
+    where
+        K: ClientKey,
+    {
+        if self.inner_toggle_fullscreen(key) {
+            self.arrange_virtual_screen();
+            true
+        } else {
+            false
+        }
+    }
+
+    fn inner_toggle_fullscreen<K>(&mut self, key: &K) -> bool
+    where
+        K: ClientKey,
+    {
+        self.get_mut(key)
+            .into_option()
+            .map(|client| {
+                client.toggle_fullscreen();
+                true
+            })
+            .unwrap_or(false)
+    }
+
     /**
     Sets a tiled client to floating and returns true, does nothing for a floating client and
     returns false. If this function returns `true` you have to call `arrange_clients` after.
@@ -635,49 +694,87 @@ impl ClientState {
         let vs = self.virtual_screens.get_mut_current();
         // if aux is empty -> width : width / 2
 
-        let (master_width, aux_width) = {
-            let effective_width = width - gap * 2;
+        let vs_width = width - gap * 2;
 
-            let master_size = if vs.aux.is_empty() {
+        let master_position = Point::new(0, 0);
+        let master_window_size = {
+            let factor = if vs.aux.is_empty() {
                 1.0
             } else {
                 self.master_size / 2.0
             };
 
-            let master_width = (effective_width as f32 * master_size) as i32;
-            let aux_width = effective_width - master_width;
+            let width = (vs_width as f32 * factor) as i32;
 
-            (master_width, aux_width)
+            // make sure we dont devide by 0
+            // height is max height / number of clients in the stack
+            let height = match vs.master.len() as i32 {
+                0 => 1,
+                n => (height - gap * 2) / n,
+            };
+
+            Size::new(width, height)
         };
 
-        // make sure we dont devide by 0
-        // height is max height / number of clients in the stack
-        let master_height = (height - gap * 2)
-            / match NonZeroI32::new(vs.master.len() as i32) {
-                Some(i) => i.get(),
-                None => 1,
+        let aux_position = Point::new(master_window_size.width, 0);
+        let aux_window_size = {
+            let width = vs_width - master_window_size.width;
+
+            // make sure we dont devide by 0
+            // height is max height / number of clients in the stack
+            let height = match vs.aux.len() as i32 {
+                0 => 1,
+                n => (height - gap * 2) / n,
             };
 
-        // height is max height / number of clients in the stack
-        let aux_height = (height - gap * 2)
-            / match NonZeroI32::new(vs.aux.len() as i32) {
-                Some(i) => i.get(),
-                None => 1,
-            };
+            Size::new(width, height)
+        };
+
+        fn calculate_window_dimensions(
+            screen_size: Size<i32>,
+            stack_size: Size<i32>,
+            stack_position: Point<i32>,
+            fullscreen: bool,
+            nth: i32,
+            gap: i32,
+            border: i32,
+        ) -> (Size<i32>, Point<i32>) {
+            if fullscreen {
+                let size = Size::new(
+                    screen_size.width - border * 2,
+                    screen_size.height - border * 2,
+                );
+                let pos = Point::new(0, 0);
+                (size, pos)
+            } else {
+                let size = Size::new(
+                    stack_size.width - gap * 2 - border * 2,
+                    stack_size.height - gap * 2 - border * 2,
+                );
+                let pos = Point::new(
+                    stack_position.x + gap * 2,
+                    stack_position.y + stack_size.height * nth + gap * 2,
+                );
+                (size, pos)
+            }
+        }
 
         // Master
         for (i, key) in vs.master.iter().enumerate() {
-            let size = (
-                master_width - gap * 2 - self.border_size * 2,
-                master_height - gap * 2 - self.border_size * 2,
-            );
-
-            let position = (gap * 2, master_height * i as i32 + gap * 2);
-
             if let Some(client) = self.clients.get_mut(key) {
+                let (size, position) = calculate_window_dimensions(
+                    self.screen_size.into(),
+                    master_window_size,
+                    master_position,
+                    client.is_fullscreen(),
+                    i as i32,
+                    gap,
+                    self.border_size,
+                );
+
                 *client = Client {
                     size: size.into(),
-                    position: position.into(),
+                    position,
                     ..*client
                 };
             }
@@ -685,18 +782,20 @@ impl ClientState {
 
         // Aux
         for (i, key) in vs.aux.iter().enumerate() {
-            let size = (
-                aux_width - gap * 2 - self.border_size * 2,
-                aux_height - gap * 2 - self.border_size * 2,
-            );
-
-            let position =
-                (master_width + gap * 2, aux_height * i as i32 + gap * 2);
-
             if let Some(client) = self.clients.get_mut(key) {
+                let (size, position) = calculate_window_dimensions(
+                    self.screen_size.into(),
+                    aux_window_size,
+                    aux_position,
+                    client.is_fullscreen(),
+                    i as i32,
+                    gap,
+                    self.border_size,
+                );
+
                 *client = Client {
                     size: size.into(),
-                    position: position.into(),
+                    position,
                     ..*client
                 };
             }
diff --git a/src/state.rs b/src/state.rs
index 4e1bdd0..993d0fb 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -8,9 +8,9 @@ use crate::{
     backends::{
         keycodes::{MouseButton, VirtualKeyCode},
         window_event::{
-            ButtonEvent, ConfigureEvent, FullscreenEvent, KeyBind, KeyEvent,
-            KeyState, MapEvent, ModifierKey, ModifierState, MotionEvent,
-            MouseBind, Point, WindowEvent,
+            ButtonEvent, ConfigureEvent, FullscreenEvent, FullscreenState,
+            KeyBind, KeyEvent, KeyState, MapEvent, ModifierKey, ModifierState,
+            MotionEvent, MouseBind, Point, WindowEvent,
         },
         xlib::XLib,
         WindowServerBackend,
@@ -469,6 +469,20 @@ where
                     state,
                 }) => {
                     info!("FullscreenEvent for window {}: {:?}", window, state);
+
+                    if match state {
+                        FullscreenState::On => {
+                            self.clients.set_fullscreen(&window, true)
+                        }
+                        FullscreenState::Off => {
+                            self.clients.set_fullscreen(&window, false)
+                        }
+                        FullscreenState::Toggle => {
+                            self.clients.toggle_fullscreen(&window)
+                        }
+                    } {
+                        self.arrange_clients();
+                    }
                 }
 
                 // i dont think i actually have to handle destroy notify events.
@@ -658,6 +672,12 @@ where
         self.clients
             .iter_transient()
             .for_each(|(_, c)| self.backend.raise_window(c.window));
+
+        //raise fullscreen windows
+        self.clients
+            .iter_current_screen()
+            .filter(|(_, c)| c.is_fullscreen())
+            .for_each(|(_, c)| self.backend.raise_window(c.window));
     }
 
     fn arrange_clients(&mut self) {