Browse Source

First steps towards supporting node alignment.

Kestrel 4 months ago
parent
commit
5fe6e9d99a
7 changed files with 114 additions and 36 deletions
  1. 14 9
      examples/table_layout.rs
  2. 17 3
      src/layout.rs
  3. 42 2
      src/layout/arr.rs
  4. 3 0
      src/layout/arr/line.rs
  5. 12 1
      src/layout/arr/table.rs
  6. 17 1
      src/widget.rs
  7. 9 20
      src/widget/label.rs

+ 14 - 9
examples/table_layout.rs

@@ -1,5 +1,5 @@
 use patina::{
-    layout::TableCell,
+    layout::{TableCell, VerticalAlignment},
     prelude::*,
     ui::{UIControlMsg, UIHandle},
     widget,
@@ -14,24 +14,29 @@ impl TableWindow {
         let mut group = widget::PlainGroup::new_table(uih);
         group.extend(
             vec![
-                widget::Label::new_with_text(uih, "r1c1")
-                    .framed()
+                widget::Label::new_with_text(uih, "x0y0")
+                    .with_valign(VerticalAlignment::Centre)
+                    // .framed()
                     .with_table_cell(TableCell::new(0, 0))
                     .boxed(),
-                widget::Label::new_with_text(uih, "r2c1")
+                widget::Label::new_with_text(uih, "x0y1")
+                    .with_valign(VerticalAlignment::Centre)
                     .framed()
                     .with_table_cell(TableCell::new(0, 1))
                     .boxed(),
-                widget::Label::new_with_text(uih, "r2c2")
-                    .framed()
+                widget::Label::new_with_text(uih, "x1y1")
+                    .with_valign(VerticalAlignment::Centre)
+                    // .framed()
                     .with_table_cell(TableCell::new(1, 1))
                     .boxed(),
-                widget::Label::new_with_text(uih, "r2c2")
+                widget::Label::new_with_text(uih, "x1y2")
+                    .with_valign(VerticalAlignment::Centre)
                     .framed()
                     .with_table_cell(TableCell::new(1, 2))
                     .boxed(),
-                widget::Label::new_with_text(uih, "r3c2")
-                    .framed()
+                widget::Label::new_with_text(uih, "x3y2")
+                    .with_valign(VerticalAlignment::Centre)
+                    // .framed()
                     .with_table_cell(TableCell::new(3, 2))
                     .boxed(),
             ]

+ 17 - 3
src/layout.rs

@@ -71,6 +71,20 @@ impl SizePolicy {
         }
     }
 
+    pub fn with_minimum(mut self, new_min: usize) -> Self {
+        self.minimum = new_min;
+        self.desired = self.desired.max(new_min);
+        self
+    }
+    pub fn with_desired(mut self, new_desire: usize) -> Self {
+        self.desired = new_desire;
+        self
+    }
+    pub fn with_slack(mut self, new_slack: usize) -> Self {
+        self.slack_weight = new_slack;
+        self
+    }
+
     pub fn max(self, rhs: SizePolicy) -> SizePolicy {
         Self {
             minimum: self.minimum.max(rhs.minimum),
@@ -184,13 +198,13 @@ pub struct LayoutNode {
     width_policy: SizePolicy,
     /// Height policy: how does this widget take up vertical space?
     height_policy: SizePolicy,
-    /// Horizontal alignment of children inside this node.
+    /// Horizontal alignment of this node inside its parent.
     halign: HorizontalAlignment,
-    /// Vertical alignment of children inside this node.
+    /// Vertical alignment of this node inside its parent.
     valign: VerticalAlignment,
     /// User-exposed margins, or spacing between the parent and the render area of this node.
     margin: PixelSideOffsets,
-    /// Table row/column assignment for child nodes, coordinates stored as (row, column).
+    /// Table row/column assignment for this node insite its parent.
     table_cell: Option<TableCell>,
 }
 

+ 42 - 2
src/layout/arr.rs

@@ -1,7 +1,7 @@
-use kahlo::math::PixelBox;
+use kahlo::math::{PixelBox, PixelSideOffsets};
 use std::ops::Add;
 
-use super::{LayoutNodeAccess, SizePolicy};
+use super::{LayoutNodeAccess, SizePolicy, HorizontalAlignment, VerticalAlignment};
 
 /// Child arrangement calculator trait.
 ///
@@ -19,6 +19,46 @@ pub use line::LineArrangement;
 mod table;
 pub use table::TableArrangement;
 
+fn do_align(node: &LayoutNodeAccess, mut inside: PixelBox) -> PixelBox {
+    let net_policy = node.cache.with_state(node.cache_key, |ns| ns.net_policy).expect("do_align invoked with node that has no net_policy");
+    println!("do_align | net: {net_policy:?}");
+    println!("         | alignments: {:?} {:?}", node.halign(), node.valign());
+    println!("         | inside before: {inside:?}");
+    if net_policy.0.slack_weight == 0 {
+        let slack = (inside.width() - net_policy.0.desired as i32).max(0);
+        let hshrink = match node.halign() {
+            HorizontalAlignment::Left => {
+                PixelSideOffsets::new(0, slack, 0, 0)
+            },
+            HorizontalAlignment::Centre => {
+                PixelSideOffsets::new(0, slack/2, 0, slack - (slack/2))
+            },
+            HorizontalAlignment::Right => {
+                PixelSideOffsets::new(0, 0, 0, slack)
+            },
+        };
+        inside = inside.inner_box(hshrink);
+    }
+    if net_policy.1.slack_weight == 0 {
+        let slack = (inside.height() - net_policy.1.desired as i32).max(0);
+        let vshrink = match node.valign() {
+            VerticalAlignment::Top => {
+                PixelSideOffsets::new(0, 0, slack, 0)
+            },
+            VerticalAlignment::Centre => {
+                PixelSideOffsets::new(slack/2, 0, slack-(slack/2), 0)
+            },
+            VerticalAlignment::Bottom => {
+                PixelSideOffsets::new(slack, 0, 0, 0)
+            },
+        };
+        inside = inside.inner_box(vshrink);
+        
+    }
+    println!("         | inside after:  {inside:?}");
+    inside
+}
+
 /// Calculate the fit of a number of size policies inside a given length.
 ///
 /// Returns the endpoints of each item.

+ 3 - 0
src/layout/arr/line.rs

@@ -63,6 +63,7 @@ impl ArrangementCalculator for LineArrangement {
     }
 
     fn layout_step(&self, node: LayoutNodeAccess, inside: PixelBox) {
+        let inside = super::do_align(&node, inside);
         // do the final children layouts
         node.cache.with_state(node.cache_key, |ns| {
             if Some(inside) != ns.area {
@@ -95,6 +96,8 @@ impl ArrangementCalculator for LineArrangement {
             policies,
         );
 
+        // XXX: do 'alignment' of child stack here via an offset?
+
         for (offset, child) in fit.zip(node.child_iter()) {
             let cbox = if self.is_column() {
                 PixelBox::from_origin_and_size(

+ 12 - 1
src/layout/arr/table.rs

@@ -66,7 +66,6 @@ impl ArrangementCalculator for TableArrangement {
 
         // if there are no table cells...
         if tstate.row_policies.len() == 0 || tstate.col_policies.len() == 0 {
-            println!("returning early");
             return (node.width_policy(), node.height_policy());
         }
 
@@ -88,6 +87,15 @@ impl ArrangementCalculator for TableArrangement {
     }
 
     fn layout_step(&self, node: LayoutNodeAccess, inside: PixelBox) {
+        let inside = super::do_align(&node, inside);
+
+        node.cache.with_state(node.cache_key, |ns| {
+            if Some(inside) != ns.area {
+                ns.area = Some(inside);
+                ns.needs_render = true;
+            }
+        });
+
         let tstate = self.build_table_state(&node);
 
         let mut col_offsets = vec![0];
@@ -95,6 +103,9 @@ impl ArrangementCalculator for TableArrangement {
         let mut row_offsets = vec![0];
         row_offsets.extend(do_fit(inside.height(), tstate.row_policies.into_iter()));
 
+        // XXX: do 'alignment' of child stack here via an offset?
+        // let x_off = 
+
         for ch in node.child_iter() {
             let Some(cell) = ch.table_cell() else {
                 continue;

+ 17 - 1
src/widget.rs

@@ -1,7 +1,7 @@
 use crate::{
     component::Component,
     input::InputState,
-    layout::{LayoutNode, LayoutNodeAccess, TableCell},
+    layout::{LayoutNode, LayoutNodeAccess, TableCell, HorizontalAlignment, VerticalAlignment},
     theme::Theme,
     ui::UIHandle,
     window::RenderFormat,
@@ -41,6 +41,22 @@ pub trait WidgetExt<C: Component>: Widget<C> {
         Frame::wrap_widget(self)
     }
 
+    fn set_halign(&mut self, halign: HorizontalAlignment) {
+        self.layout_node_mut().set_halign(halign);
+    }
+    fn with_halign(mut self, halign: HorizontalAlignment) -> Self where Self: Sized {
+        self.set_halign(halign);
+        self
+    }
+
+    fn set_valign(&mut self, valign: VerticalAlignment) {
+        self.layout_node_mut().set_valign(valign);
+    }
+    fn with_valign(mut self, valign: VerticalAlignment) -> Self where Self: Sized {
+        self.set_valign(valign);
+        self
+    }
+
     fn clear_table_cell(&mut self) {
         self.layout_node_mut().set_table_cell(None);
     }

+ 9 - 20
src/widget/label.rs

@@ -1,13 +1,13 @@
 use std::cell::RefCell;
 
 use kahlo::{
-    math::{PixelBox, PixelSideOffsets},
+    math::PixelBox,
     prelude::*,
 };
 
 use crate::{
     component::Component,
-    layout::{HorizontalAlignment, LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy},
+    layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy},
     theme::Theme,
     ui::UIHandle,
     window::RenderFormat,
@@ -25,7 +25,7 @@ pub struct Label<C: Component> {
 impl<C: Component> Label<C> {
     pub fn new(uih: &UIHandle) -> Self {
         let mut node = uih.new_layout_node();
-        node.set_width_policy(SizePolicy::expanding(1))
+        node.set_width_policy(SizePolicy::expanding(0))
             .set_height_policy(SizePolicy::fixed(uih.theme().ui_font_size.ceil() as usize));
         Self {
             lnode: LeafLayoutNode::new(node),
@@ -56,8 +56,14 @@ impl<C: Component> Label<C> {
         };
         let line = uih.theme().make_line(text.as_str());
         let rendered = line.render_line();
+        let sz = rendered.size();
         *self.rendered.borrow_mut() = Some(rendered);
         self.lnode.render_needed();
+
+        let wp = self.lnode.width_policy().with_minimum(sz.width as usize);
+        self.lnode.set_width_policy(wp);
+        let hp = self.lnode.height_policy().with_minimum(sz.height as usize);
+        self.lnode.set_height_policy(hp);
     }
 }
 
@@ -91,23 +97,6 @@ impl<C: Component> Widget<C> for Label<C> {
         };
         let render_location = self.layout_node().render_area().unwrap();
 
-        // perform horizontal alignment
-        let alignment_slack = (render_location.width() - rendered.size().width).max(0);
-        let render_location = match self.lnode.halign() {
-            HorizontalAlignment::Left => {
-                render_location.inner_box(PixelSideOffsets::new(0, alignment_slack, 0, 0))
-            }
-            HorizontalAlignment::Centre => render_location.inner_box(PixelSideOffsets::new(
-                0,
-                alignment_slack / 2,
-                0,
-                alignment_slack / 2,
-            )),
-            HorizontalAlignment::Right => {
-                render_location.inner_box(PixelSideOffsets::new(0, 0, 0, alignment_slack))
-            }
-        };
-
         surface.fill_region_masked(
             &rendered,
             PixelBox::from_size(rendered.size()),