From 257b97e9ca8c205fec9b9372b19ee41b3987bf79 Mon Sep 17 00:00:00 2001
From: Dorota Czaplejewicz <dorota.czaplejewicz@puri.sm>
Date: Sun, 15 Dec 2019 18:39:49 +0000
Subject: [PATCH] layout: Respect margins

---
 src/data.rs                |  17 ++++++
 src/layout.rs              | 119 +++++++++++++++++++++++++++++++++----
 tests/layout_margins.yaml  |   9 +++
 tests/layout_position.yaml |   9 +++
 4 files changed, 141 insertions(+), 13 deletions(-)
 create mode 100644 tests/layout_margins.yaml
 create mode 100644 tests/layout_position.yaml

diff --git a/src/data.rs b/src/data.rs
index baa62e26..9aa1fc4b 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -850,4 +850,21 @@ mod tests {
             },
         );
     }
+
+    #[test]
+    fn test_layout_margins() {
+        let out = Layout::from_file(PathBuf::from("tests/layout_margins.yaml"))
+            .unwrap()
+            .build(PanicWarn).0
+            .unwrap();
+        assert_eq!(
+            out.margins,
+            layout::Margins {
+                top: 1.0,
+                bottom: 3.0,
+                left: 2.0,
+                right: 2.0,
+            }
+        );
+    }
 }
diff --git a/src/layout.rs b/src/layout.rs
index b1d654fd..950b2349 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -99,7 +99,7 @@ pub mod c {
         }
     }
 
-    /// Scale + translate
+    /// Translate and then scale
     #[repr(C)]
     pub struct Transformation {
         pub origin_x: f64,
@@ -108,6 +108,14 @@ pub mod c {
     }
 
     impl Transformation {
+        /// Applies the new transformation after this one
+        pub fn chain(self, next: Transformation) -> Transformation {
+            Transformation {
+                origin_x: self.origin_x + self.scale * next.origin_x,
+                origin_y: self.origin_y + self.scale * next.origin_y,
+                scale: self.scale * next.scale,
+            }
+        }
         fn forward(&self, p: Point) -> Point {
             Point {
                 x: (p.x - self.origin_x) / self.scale,
@@ -195,7 +203,8 @@ pub mod c {
         println!("{:?}", button);
     }
     
-    /// Positions the layout within the available space
+    /// Positions the layout contents within the available space.
+    /// The origin of the transformation is the point inside the margins.
     #[no_mangle]
     pub extern "C"
     fn squeek_layout_calculate_transformation(
@@ -204,15 +213,10 @@ pub mod c {
         allocation_height: f64,
     ) -> Transformation {
         let layout = unsafe { &*layout };
-        let size = layout.calculate_size();
-        let h_scale = allocation_width / size.width;
-        let v_scale = allocation_height / size.height;
-        let scale = if h_scale < v_scale { h_scale } else { v_scale };
-        Transformation {
-            origin_x: (allocation_width - (scale * size.width)) / 2.0,
-            origin_y: (allocation_height - (scale * size.height)) / 2.0,
-            scale: scale,
-        }
+        layout.calculate_transformation(Size {
+            width: allocation_width,
+            height: allocation_height,
+        })
     }
     
     #[no_mangle]
@@ -427,7 +431,7 @@ pub struct ButtonPlace<'a> {
     offset: c::Point,
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
 pub struct Size {
     pub width: f64,
     pub height: f64,
@@ -560,6 +564,7 @@ pub enum ArrangementKind {
     Wide = 1,
 }
 
+#[derive(Debug, PartialEq)]
 pub struct Margins {
     pub top: f64,
     pub bottom: f64,
@@ -713,7 +718,8 @@ impl Layout {
         };
     }
 
-    fn calculate_size(&self) -> Size {
+    /// Calculates size without margins
+    fn calculate_inner_size(&self) -> Size {
         Size {
             height: find_max_double(
                 self.views.iter(),
@@ -725,6 +731,39 @@ impl Layout {
             ),
         }
     }
+
+    /// Size including margins
+    fn calculate_size(&self) -> Size {
+        let inner_size = self.calculate_inner_size();
+        Size {
+            width: self.margins.left + inner_size.width + self.margins.right,
+            height: (
+                self.margins.top
+                + inner_size.height
+                + self.margins.bottom
+            ),
+        }
+    }
+    
+    pub fn calculate_transformation(
+        &self,
+        available: Size,
+    ) -> c::Transformation {
+        let size = self.calculate_size();
+        let h_scale = available.width / size.width;
+        let v_scale = available.height / size.height;
+        let scale = if h_scale < v_scale { h_scale } else { v_scale };
+        let outside_margins = c::Transformation {
+            origin_x: (available.width - (scale * size.width)) / 2.0,
+            origin_y: (available.height - (scale * size.height)) / 2.0,
+            scale: scale,
+        };
+        outside_margins.chain(c::Transformation {
+            origin_x: self.margins.left,
+            origin_y: self.margins.top,
+            scale: 1.0,
+        })
+    }
 }
 
 mod procedures {
@@ -907,4 +946,58 @@ mod test {
                 .is_none()
         );
     }
+
+    #[test]
+    fn check_bottom_margin() {
+        // just one button
+        let view = View::new(vec![
+            (
+                0.0,
+                Row {
+                    angle: 0,
+                    buttons: vec![(
+                        0.0,
+                        Box::new(Button {
+                            size: Size { width: 1.0, height: 1.0 },
+                            ..*make_button_with_state("foo".into(), make_state())
+                        }),
+                    )]
+                },
+            ),
+        ]);
+        let layout = Layout {
+            current_view: String::new(),
+            keymap_str: CString::new("").unwrap(),
+            kind: ArrangementKind::Base,
+            locked_keys: HashSet::new(),
+            pressed_keys: HashSet::new(),
+            // Lots of bottom margin
+            margins: Margins {
+                top: 0.0,
+                left: 0.0,
+                right: 0.0,
+                bottom: 1.0,
+            },
+            views: hashmap! {
+                String::new() => view,
+            },
+        };
+        assert_eq!(
+            layout.calculate_inner_size(),
+            Size { width: 1.0, height: 1.0 }
+        );
+        assert_eq!(
+            layout.calculate_size(),
+            Size { width: 1.0, height: 2.0 }
+        );
+        // Don't change those values randomly!
+        // They take advantage of incidental precise float representation
+        // to even be comparable.
+        let transformation = layout.calculate_transformation(
+            Size { width: 2.0, height: 2.0 }
+        );
+        assert_eq!(transformation.scale, 1.0);
+        assert_eq!(transformation.origin_x, 0.5);
+        assert_eq!(transformation.origin_y, 0.0);
+    }
 }
diff --git a/tests/layout_margins.yaml b/tests/layout_margins.yaml
new file mode 100644
index 00000000..3a704da7
--- /dev/null
+++ b/tests/layout_margins.yaml
@@ -0,0 +1,9 @@
+---
+# Margins present
+margins: { top: 1, side: 2, bottom: 3 }
+views:
+    base:
+        - "test"
+outlines:
+    default: { width: 1, height: 1 }
+
diff --git a/tests/layout_position.yaml b/tests/layout_position.yaml
new file mode 100644
index 00000000..3a704da7
--- /dev/null
+++ b/tests/layout_position.yaml
@@ -0,0 +1,9 @@
+---
+# Margins present
+margins: { top: 1, side: 2, bottom: 3 }
+views:
+    base:
+        - "test"
+outlines:
+    default: { width: 1, height: 1 }
+
-- 
GitLab