Browse Source

Made rendering significantly more on-demand.

Kestrel 4 months ago
parent
commit
fd8deb97f3
13 changed files with 373 additions and 89 deletions
  1. 2 2
      examples/button.rs
  2. 46 6
      src/layout.rs
  3. 97 21
      src/layout/arr.rs
  4. 3 4
      src/layout/cache.rs
  5. 155 1
      src/layout/calc.rs
  6. 7 3
      src/theme.rs
  7. 6 3
      src/ui.rs
  8. 5 5
      src/widget.rs
  9. 10 7
      src/widget/button.rs
  10. 4 4
      src/widget/frame.rs
  11. 2 2
      src/widget/group.rs
  12. 14 10
      src/widget/label.rs
  13. 22 21
      src/window.rs

+ 2 - 2
examples/button.rs

@@ -19,8 +19,8 @@ impl ButtonWindow {
         group
             .layout_node_mut()
             .set_arrangement(patina::layout::ChildArrangement::Column)
-            .set_width_policy(SizePolicy::expands(1))
-            .set_height_policy(SizePolicy::expands(1));
+            .set_width_policy(SizePolicy::expanding(1))
+            .set_height_policy(SizePolicy::expanding(1));
         group.append(Box::new(widget::Spacer::new(uih)));
         group.append(Box::new({
             let mut button = widget::Button::new(uih);

+ 46 - 6
src/layout.rs

@@ -1,3 +1,11 @@
+//! Layout calculations for the widget tree.
+//!
+//! Layout calculations are done in two passes:
+//! - **Arrangement**: computes minimum sizes, net size policies, and records parent/child relationships between
+//! nodes.
+//! - **Layout**: given minimum sizes and net size policies from the arrangement step, computes
+//! exact pixel values for positions and sizes.
+
 use std::{ops::Deref, rc::Rc};
 
 use cache::{Layer, LayoutCacheKey, NodeState};
@@ -35,7 +43,7 @@ impl Default for SizePolicy {
 }
 
 impl SizePolicy {
-    pub fn expands(weight: usize) -> Self {
+    pub fn expanding(weight: usize) -> Self {
         Self {
             minimum: 0,
             desired: 0,
@@ -43,6 +51,14 @@ impl SizePolicy {
         }
     }
 
+    pub fn fixed(size: usize) -> Self {
+        Self {
+            minimum: size,
+            desired: size,
+            slack_weight: 0,
+        }
+    }
+
     pub fn max(self, rhs: SizePolicy) -> SizePolicy {
         Self {
             minimum: self.minimum.max(rhs.minimum),
@@ -142,16 +158,25 @@ impl Deref for ChildArrangement {
 }
 
 pub struct LayoutNode {
+    /// Human-readable label for making layout tree dumps easier to read.
     label: Option<String>,
+    /// Unique identifier for this LayoutNode
     cache_key: LayoutCacheKey,
+    /// Reference to the global layout item cache.
     cache: Rc<LayoutCache>,
+    /// Layout behaviour, or how this node behaves with respect to its parent.
     behaviour: NodeBehaviour,
+    /// Child arrangement, or how this node arranges its children.
     child_arrangement: ChildArrangement,
+    /// Width policy: how does this widget take up horizontal space?
     width_policy: SizePolicy,
+    /// Height policy: how does this widget take up vertical space?
     height_policy: SizePolicy,
+    /// Horizontal alignment of children inside this node.
     halign: HorizontalAlignment,
+    /// Vertical alignment of children inside this node.
     valign: VerticalAlignment,
-
+    /// User-exposed margins, or spacing between the parent and the render area of this node.
     margin: PixelSideOffsets,
 }
 
@@ -191,6 +216,16 @@ impl LayoutNode {
         self.cache.update_queue.borrow_mut().push(self.cache_key);
     }
 
+    pub fn relayout_tree(&self) {
+        let mut to_mark : Vec<LayoutCacheKey> = vec![self.cache_key];
+        while let Some(next) = to_mark.pop() {
+            self.cache.with_state(next, |ns| {
+                ns.needs_update = true;
+                to_mark.extend(ns.children.iter());
+            });
+        }
+    }
+
     pub fn render_area(&self) -> Option<PixelBox> {
         self.cache
             .with_state(self.cache_key, |ns| ns.area)
@@ -198,6 +233,7 @@ impl LayoutNode {
             .map(|pb| pb.inner_box(self.margin))
     }
 
+    /// Checks if node needs to be rerendered, clearing the flag if so.
     pub fn render_check(&self) -> bool {
         self.cache.with_state(self.cache_key, |ns| {
             let ret = ns.needs_render;
@@ -224,8 +260,10 @@ impl LayoutNode {
         self.behaviour
     }
     pub fn set_behaviour(&mut self, mode: NodeBehaviour) -> &mut Self {
-        self.behaviour = mode;
-        self.relayout();
+        if self.behaviour != mode {
+            self.behaviour = mode;
+            self.relayout();
+        }
         self
     }
 
@@ -311,14 +349,16 @@ fn dump_node_tree_helper(lna: LayoutNodeAccess, indent: usize, out: &mut String)
     });
     out.push_str(
         format!(
-            ") [wpol: {}/{}/{} hpol: {}/{}/{} behaviour: {:?}]\n",
+            ") [wpol: {}/{}/{} hpol: {}/{}/{} behaviour: {:?} upd: {:?} rend: {:?}]\n",
             lna.width_policy.minimum,
             lna.width_policy.desired,
             lna.width_policy.slack_weight,
             lna.height_policy.minimum,
             lna.height_policy.desired,
             lna.height_policy.slack_weight,
-            lna.behaviour
+            lna.behaviour,
+            lna.cache.with_state(lna.cache_key, |v| v.needs_update),
+            lna.cache.with_state(lna.cache_key, |v| v.needs_render),
         )
         .as_str(),
     );

+ 97 - 21
src/layout/arr.rs

@@ -94,6 +94,7 @@ impl ArrangementCalculator for LineArrangement {
             },
             policies,
         );
+
         for (offset, child) in fit.zip(node.child_iter()) {
             let cbox = if self.is_column() {
                 PixelBox::from_origin_and_size(
@@ -129,40 +130,115 @@ impl ArrangementCalculator for LineArrangement {
     }
 }
 
+/// Calculate the fit of a number of size policies inside a given length.
+///
+/// Returns the endpoints of each item.
 fn do_fit<'l>(
     total: i32,
     policies: impl Iterator<Item = SizePolicy> + Clone,
 ) -> impl Iterator<Item = i32> {
     // first pass over children: collect sum total of minimum/desired sizes and/slack weights
     let policy_sum = policies.clone().reduce(SizePolicy::add).unwrap_or_default();
+    let (sum_min, sum_desired, sum_slack) = (policy_sum.minimum as i32, policy_sum.desired as i32, policy_sum.slack_weight as i32);
+
+    if total < sum_min {
+        todo!("not enough space to distribute all children")
+    }
+
+    let (fit_space, slack_space) = if sum_desired <= total {
+        (sum_desired - sum_min, total - sum_desired)
+    } else {
+        (total - sum_min, 0)
+    };
 
-    if (policy_sum.desired - policy_sum.minimum) == 0 {}
+    let total_fit_units = sum_desired - sum_min;
 
-    // how much of the desired size can we fit?
-    let fit_coeff = (total - policy_sum.minimum as i32) as f32
-        / (policy_sum.desired - policy_sum.minimum) as f32;
-    let fit_coeff = if fit_coeff.is_nan() {
-        1.0
+    let (fit_per, fit_per_rem) = if total_fit_units > 0 {
+        (fit_space / total_fit_units, fit_space % total_fit_units)
     } else {
-        // not more than 100% of desired
-        fit_coeff.min(1.0)
+        (0, 0)
     };
 
-    // how much slack space do we have left?
-    let slack_coeff = if policy_sum.slack_weight > 0 && fit_coeff >= 1.0 {
-        (total - policy_sum.desired as i32) as f32 / policy_sum.slack_weight as f32
+    let (slack_per, slack_per_rem) = if sum_slack > 0 {
+        (slack_space / sum_slack, slack_space % sum_slack)
     } else {
-        0.0
+        (0, 0)
     };
 
-    let mut offset = 0;
-    // second pass over children: actually space out according to size policies and calculated coefficients
-    policies.map(move |policy| {
-        let mut amount = policy.minimum as f32;
-        amount += (policy.desired - policy.minimum) as f32 * fit_coeff;
-        amount += policy.slack_weight as f32 * slack_coeff;
-        let amount = amount.round() as i32;
-        offset += amount;
-        offset
+    let mut allocated_fit = 0;
+    let mut allocated_fit_rem = 0;
+
+    let mut allocated_slack = 0;
+    let mut allocated_slack_rem = 0;
+
+    let mut min_allocation = 0;
+
+    policies.map(move |w| {
+        min_allocation += w.minimum as i32;
+
+        let fit_units = (w.desired - w.minimum) as i32;
+        if fit_units > 0 {
+            allocated_fit += fit_per * fit_units;
+            allocated_fit_rem += fit_per_rem * fit_units;
+            allocated_fit += allocated_fit_rem / total_fit_units;
+            allocated_fit_rem %= total_fit_units;
+        }
+
+        let slack_units = w.slack_weight as i32;
+        if slack_units > 0 {
+            allocated_slack += slack_per * slack_units;
+            allocated_slack_rem += slack_per_rem * slack_units;
+            allocated_slack += allocated_slack_rem / sum_slack;
+            allocated_slack_rem %= sum_slack;
+        }
+
+        min_allocation + allocated_fit + allocated_slack
     })
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::layout::SizePolicy;
+
+    use super::do_fit;
+
+    #[test]
+    fn distribute_2_even() {
+        let policies = vec![SizePolicy::expanding(1), SizePolicy::expanding(1)];
+
+        assert_eq!(
+            do_fit(2, policies.clone().into_iter()).collect::<Vec<_>>(),
+            vec![1, 2]
+        );
+        assert_eq!(
+            do_fit(3, policies.clone().into_iter()).collect::<Vec<_>>(),
+            vec![1, 3]
+        );
+    }
+
+    #[test]
+    fn distribute_3_even() {
+        let policies = vec![SizePolicy::expanding(1), SizePolicy::expanding(1), SizePolicy::expanding(1)];
+
+        assert_eq!(
+            do_fit(3, policies.clone().into_iter()).collect::<Vec<_>>(),
+            vec![1, 2, 3]
+        );
+        assert_eq!(
+            do_fit(4, policies.clone().into_iter()).collect::<Vec<_>>(),
+            vec![1, 2, 4]
+        );
+        assert_eq!(
+            do_fit(5, policies.clone().into_iter()).collect::<Vec<_>>(),
+            vec![1, 3, 5]
+        );
+        assert_eq!(
+            do_fit(6, policies.clone().into_iter()).collect::<Vec<_>>(),
+            vec![2, 4, 6]
+        );
+        assert_eq!(
+            do_fit(7, policies.clone().into_iter()).collect::<Vec<_>>(),
+            vec![2, 4, 7]
+        );
+    }
+}

+ 3 - 4
src/layout/cache.rs

@@ -56,12 +56,11 @@ impl LayoutCache {
     pub fn propagate_rerenders(&self) {
         let mut renq = self.render_queue.borrow_mut();
         let mut states = self.states.borrow_mut();
+
         while let Some(next) = renq.pop() {
             states.get_mut(&next).map(|v| {
-                if !v.needs_render {
-                    v.needs_render = true;
-                    renq.extend(v.children.iter());
-                }
+                v.needs_render = true;
+                renq.extend(v.children.iter());
             });
         }
     }

+ 155 - 1
src/layout/calc.rs

@@ -67,7 +67,7 @@ mod test {
 
     use crate::layout::{
         cache::LayoutCache, ChildArrangement, LayoutNode, LayoutNodeAccess, LayoutNodeContainer,
-        NodeBehaviour, SizePolicy,
+        NodeBehaviour, SizePolicy, dump_node_tree,
     };
 
     use super::recalculate;
@@ -152,4 +152,158 @@ mod test {
             ))
         );
     }
+
+    #[test]
+    fn distribution_test() {
+        let cache = LayoutCache::new();
+
+        let mut root = LayoutTree {
+            children: vec![
+                {
+                    let mut lt = LayoutTree {
+                        children: vec![],
+                        node: LayoutNode::new(cache.clone()),
+                    };
+                    lt.node.set_height_policy(SizePolicy {
+                        minimum: 1,
+                        desired: 1,
+                        slack_weight: 1,
+                    });
+                    lt
+                },
+                {
+                    let mut lt = LayoutTree {
+                        children: vec![],
+                        node: LayoutNode::new(cache.clone()),
+                    };
+                    lt.node.set_height_policy(SizePolicy {
+                        minimum: 1,
+                        desired: 1,
+                        slack_weight: 1,
+                    });
+                    lt
+                },
+                {
+                    let mut lt = LayoutTree {
+                        children: vec![],
+                        node: LayoutNode::new(cache.clone()),
+                    };
+                    lt.node.set_height_policy(SizePolicy {
+                        minimum: 1,
+                        desired: 1,
+                        slack_weight: 1,
+                    });
+                    lt
+                },
+            ],
+            node: LayoutNode::new(cache.clone()),
+        };
+
+        root.node.child_arrangement = ChildArrangement::Column;
+
+        for h in 3..10 {
+            let area = PixelBox::from_origin_and_size(PixelPoint::new(1, 1), PixelSize::new(2, h));
+            root.node.set_behaviour(NodeBehaviour::Fixed { area });
+            recalculate(LayoutNodeAccess::new(&root));
+            
+            let child_areas = root.children.iter().map(|c| c.node.render_area().expect("layout did not give node a size")).collect::<Vec<_>>();
+
+            for i in 0..child_areas.len() {
+                for j in (i+1)..child_areas.len() {
+                    if child_areas[i].intersects(&child_areas[j]) {
+                        panic!("layout children given overlapping areas!");
+                    }
+                }
+
+                if !area.contains_box(&child_areas[i]) {
+                    println!("area: {area:?} child_area: {:?}", child_areas[i]);
+                    panic!("layout child exceeds parent area!")
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn rerender_test() {
+        let cache = LayoutCache::new();
+
+        let make_node = |children| {
+            let mut lt = LayoutTree {
+                children,
+                node: LayoutNode::new(cache.clone()),
+            };
+            lt.node.set_height_policy(SizePolicy {
+                minimum: 1,
+                desired: 1,
+                slack_weight: 1,
+            });
+            lt
+        };
+
+        let mut root = LayoutTree {
+            children: vec![
+                make_node(vec![
+                    make_node(vec![
+                        make_node(vec![
+                            make_node(vec![]),
+                            make_node(vec![]),
+                        ])
+                    ]),
+                    make_node(vec![
+                        make_node(vec![
+                            make_node(vec![]),
+                            make_node(vec![]),
+                        ])
+                    ])
+                ])
+            ],
+            node: LayoutNode::new(cache.clone()),
+        };
+
+        root.node.child_arrangement = ChildArrangement::Column;
+        root.node.set_behaviour(NodeBehaviour::Fixed { area: PixelBox::from_size(PixelSize::new(40, 40)) });
+        let mut dump = String::new();
+        dump_node_tree(LayoutNodeAccess::new(&root), &mut dump);
+        println!("before recalculate:\n{dump}");
+
+        recalculate(LayoutNodeAccess::new(&root));
+
+        let mut dump = String::new();
+        dump_node_tree(LayoutNodeAccess::new(&root), &mut dump);
+        println!("after recalculate:\n{dump}");
+
+        // clear needs_render on everything to simulate rendering
+        fn clear_render_flag(lna: &LayoutNodeAccess) {
+            assert!(lna.render_check());
+            for ch in lna.child_iter() {
+                clear_render_flag(&ch);
+            }
+        }
+        clear_render_flag(&LayoutNodeAccess::new(&root));
+
+        let mut dump = String::new();
+        dump_node_tree(LayoutNodeAccess::new(&root), &mut dump);
+        println!("after fake rendering:\n{dump}");
+
+        // now mark the root node as needing render, which should translate to everything needing
+        // to be rendered
+        root.node.render_needed();
+        cache.propagate_rerenders();
+
+        fn check_render_flag(lna: &LayoutNodeAccess) {
+            assert!(lna.render_check());
+            for ch in lna.child_iter() {
+                check_render_flag(&ch);
+            }
+        }
+        check_render_flag(&LayoutNodeAccess::new(&root));
+
+        // finally, mark the root and one of the root's children as needing to be rendered.
+        // everything should still need to be rendered.
+        root.node.render_needed();
+        root.children[0].node.render_needed();
+        cache.propagate_rerenders();
+
+        check_render_flag(&LayoutNodeAccess::new(&root));
+    }
 }

+ 7 - 3
src/theme.rs

@@ -3,6 +3,7 @@ use crate::text::TextLine;
 use kahlo::colour::Colour;
 
 pub struct Theme {
+    pub active: Colour,
     pub border: Colour,
     pub border_width: usize,
     pub background: Colour,
@@ -20,12 +21,15 @@ impl Theme {
 
     pub fn default_with_font(ui_font: fontdue::Font) -> Self {
         Self {
-            border: Colour::rgb(192, 168, 32),
+            active: Colour::hex_rgb("#c16e70").unwrap(),
+            border: Colour::hex_rgb("#dc9e82").unwrap(),
             border_width: 3,
-            background: Colour::rgb(32, 32, 32),
-            panel: Colour::rgb(32, 48, 32),
+            background: Colour::hex_rgb("#030027").unwrap(),
+            panel: Colour::hex_rgb("#151e3f").unwrap(),
             foreground: Colour::WHITE,
 
+            // other colour in palette: #F2F3D9
+
             ui_font,
             ui_font_size: 16.0,
         }

+ 6 - 3
src/ui.rs

@@ -1,6 +1,6 @@
 use std::collections::HashMap;
 
-use kahlo::math::PixelPoint;
+use kahlo::math::{PixelPoint, PixelSize};
 
 use crate::{
     component::Component,
@@ -24,7 +24,6 @@ pub(crate) struct UIState {
     pub(crate) layout_cache: std::rc::Rc<LayoutCache>,
     pub(crate) window_states:
         HashMap<winit::window::WindowId, std::rc::Weak<dyn WindowStateAccess>>,
-    // pub(crate) text_renderer: TextRenderer,
     pub(crate) theme: Theme,
 }
 
@@ -188,7 +187,11 @@ impl<UIC: UIComponent> winit::application::ApplicationHandler<()> for UI<UIC> {
                 wsa.request_redraw();
                 self.pump_events(event_loop);
             }
-            _ => (),
+            winit::event::WindowEvent::Resized(newsize) => {
+                wsa.notify_resize(PixelSize::new(newsize.width as i32, newsize.height as i32));
+                wsa.request_redraw();
+            },
+            _ => { },
         }
     }
 }

+ 5 - 5
src/widget.rs

@@ -8,6 +8,7 @@ use crate::{
     layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy},
     theme::Theme,
     ui::UIHandle,
+    window::RenderFormat,
 };
 
 mod label;
@@ -24,7 +25,7 @@ pub trait Widget<C: Component> {
 
     fn layout_node(&self) -> LayoutNodeAccess;
     fn layout_node_mut(&mut self) -> &mut LayoutNode;
-    fn render(&self, theme: &Theme, target: &mut kahlo::RgbaBitmap);
+    fn render(&self, theme: &Theme, target: &mut kahlo::BitmapMut<RenderFormat>);
 }
 
 pub struct Spacer<C: Component> {
@@ -36,8 +37,8 @@ impl<C: Component> Spacer<C> {
     pub fn new(uih: &UIHandle) -> Self {
         let mut lnode = LeafLayoutNode::new(uih.new_layout_node());
         lnode
-            .set_width_policy(SizePolicy::expands(1))
-            .set_height_policy(SizePolicy::expands(1));
+            .set_width_policy(SizePolicy::expanding(1))
+            .set_height_policy(SizePolicy::expanding(1));
         Self {
             lnode,
             _ghost: Default::default(),
@@ -61,10 +62,9 @@ impl<C: Component> Widget<C> for Spacer<C> {
         self.lnode.deref_mut()
     }
 
-    fn render(&self, theme: &Theme, target: &mut kahlo::RgbaBitmap) {
+    fn render(&self, theme: &Theme, target: &mut kahlo::BitmapMut<RenderFormat>) {
         let region = self.layout_node().render_area().unwrap();
         if self.lnode.render_check() {
-            println!("rendering spacer");
             target.fill_region(region, theme.background);
         }
     }

+ 10 - 7
src/widget/button.rs

@@ -1,10 +1,10 @@
-use kahlo::prelude::*;
+use kahlo::{prelude::*, math::PixelSideOffsets};
 
 use crate::{
     component::Component,
     input::MouseButton,
-    layout::{LayoutNode, LayoutNodeAccess, LayoutNodeContainer, SizePolicy},
-    ui::UIHandle,
+    layout::{LayoutNode, LayoutNodeAccess, LayoutNodeContainer, SizePolicy, HorizontalAlignment, VerticalAlignment},
+    ui::UIHandle, window::RenderFormat,
 };
 
 use super::{Label, Widget};
@@ -27,12 +27,14 @@ impl<C: Component> Button<C> {
     pub fn new(uih: &UIHandle) -> Self {
         let mut layout = uih.new_layout_node();
         layout
-            .set_width_policy(SizePolicy::expands(1))
+            .set_width_policy(SizePolicy::expanding(1))
             .set_height_policy(SizePolicy {
                 minimum: uih.theme().ui_font_size as usize,
                 desired: (uih.theme().ui_font_size * 1.5) as usize,
                 slack_weight: 0,
-            });
+            })
+            .set_valign(VerticalAlignment::Centre)
+            .set_halign(HorizontalAlignment::Centre);
         Self {
             layout,
             label: Label::new(uih),
@@ -102,14 +104,15 @@ impl<C: Component> Widget<C> for Button<C> {
         &mut self.layout
     }
 
-    fn render(&self, theme: &crate::theme::Theme, target: &mut kahlo::RgbaBitmap) {
+    fn render(&self, theme: &crate::theme::Theme, target: &mut kahlo::BitmapMut<RenderFormat>) {
         let colour = match self.state {
             ButtonState::Idle => theme.background,
             ButtonState::Hovered => theme.panel,
-            ButtonState::Clicked => theme.foreground,
+            ButtonState::Clicked => theme.active,
         };
         if self.layout.render_check() {
             target.fill_region(self.layout.render_area().unwrap(), colour);
+            target.rectangle(self.layout.render_area().unwrap(), theme.border_width, theme.border);
         }
         self.label.render(theme, target);
     }

+ 4 - 4
src/widget/frame.rs

@@ -4,7 +4,7 @@ use crate::{
     component::Component,
     layout::{LayoutNode, LayoutNodeAccess, LayoutNodeContainer, SizePolicy},
     theme::Theme,
-    ui::UIHandle,
+    ui::UIHandle, window::RenderFormat,
 };
 
 use super::Widget;
@@ -19,8 +19,8 @@ impl<C: Component> Frame<C> {
     pub fn new(uih: &UIHandle) -> Self {
         let mut nnode = uih.new_layout_node();
         nnode
-            .set_height_policy(SizePolicy::expands(1))
-            .set_width_policy(SizePolicy::expands(1));
+            .set_height_policy(SizePolicy::expanding(1))
+            .set_width_policy(SizePolicy::expanding(1));
         Self {
             layout: nnode,
             child: None,
@@ -68,7 +68,7 @@ impl<C: Component> Widget<C> for Frame<C> {
     fn layout_node_mut(&mut self) -> &mut LayoutNode {
         &mut self.layout
     }
-    fn render(&self, theme: &Theme, target: &mut kahlo::RgbaBitmap) {
+    fn render(&self, theme: &Theme, target: &mut kahlo::BitmapMut<RenderFormat>) {
         let area = self.layout.render_area().unwrap();
 
         target.fill_region(area, theme.border);

+ 2 - 2
src/widget/group.rs

@@ -4,7 +4,7 @@ use crate::{
     layout::{LayoutNode, LayoutNodeAccess, LayoutNodeContainer},
     theme::Theme,
     ui::UIHandle,
-    widget::Widget,
+    widget::Widget, window::RenderFormat,
 };
 
 pub struct PlainGroup<C: Component> {
@@ -53,7 +53,7 @@ impl<C: Component> Widget<C> for PlainGroup<C> {
             .flatten()
             .collect()
     }
-    fn render(&self, theme: &Theme, target: &mut kahlo::RgbaBitmap) {
+    fn render(&self, theme: &Theme, target: &mut kahlo::BitmapMut<RenderFormat>) {
         for child in self.children.iter() {
             child.render(theme, target);
         }

+ 14 - 10
src/widget/label.rs

@@ -1,12 +1,12 @@
 use std::cell::RefCell;
 
-use kahlo::{math::PixelBox, prelude::*};
+use kahlo::{math::{PixelBox, PixelSideOffsets}, prelude::*};
 
 use crate::{
     component::Component,
-    layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy},
+    layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy, HorizontalAlignment},
     theme::Theme,
-    ui::UIHandle,
+    ui::UIHandle, window::RenderFormat,
 };
 
 use super::Widget;
@@ -21,12 +21,8 @@ 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::expands(1))
-            .set_height_policy(SizePolicy {
-                minimum: 20,
-                desired: 20,
-                slack_weight: 0,
-            });
+        node.set_width_policy(SizePolicy::expanding(1))
+            .set_height_policy(SizePolicy::fixed(uih.theme().ui_font_size.ceil() as usize));
         Self {
             lnode: LeafLayoutNode::new(node),
             text: None,
@@ -77,7 +73,7 @@ impl<C: Component> Widget<C> for Label<C> {
         &mut self.lnode
     }
 
-    fn render(&self, theme: &Theme, surface: &mut kahlo::RgbaBitmap) {
+    fn render(&self, theme: &Theme, surface: &mut kahlo::BitmapMut<RenderFormat>) {
         if !self.lnode.render_check() {
             return
         }
@@ -87,6 +83,14 @@ 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()),

+ 22 - 21
src/window.rs

@@ -11,6 +11,8 @@ use crate::layout::{self, LayoutNode, LayoutNodeAccess, LinearAccess};
 use crate::widget::Widget;
 use crate::{component::Component, ui::UIHandle};
 
+pub type RenderFormat = kahlo::formats::Bgr32;
+
 #[derive(Debug)]
 pub enum WindowEvent {
     CloseRequested,
@@ -32,12 +34,13 @@ pub(crate) struct WindowState<WC: WindowComponent> {
     events: Vec<WindowEvent>,
     istate: InputState,
     surface: softbuffer::Surface<Rc<winit::window::Window>, Rc<winit::window::Window>>,
-    bitmap: RefCell<Option<kahlo::RgbaBitmap>>,
+    bitmap: RefCell<Option<kahlo::Bitmap<RenderFormat>>>,
 }
 
 impl<WC: WindowComponent> WindowState<WC> {
     fn redraw(&mut self, uih: &UIHandle) {
         let size = self.window.inner_size();
+        // if size.width != self.surface.buffer_mut().unwrap().
         self.surface
             .resize(
                 size.width.try_into().unwrap(),
@@ -53,30 +56,14 @@ impl<WC: WindowComponent> WindowState<WC> {
         let layout = LinearAccess::new(&self.root_node, self.wc.root_widget().layout_node());
 
         let mut bitmap = self.bitmap.borrow_mut();
-        let needs_recreate = match bitmap.as_mut() {
-            None => true,
-            Some(bitmap) => {
-                if bitmap.size() != PixelSize::new(size.width as i32, size.height as i32) {
-                    true
-                } else {
-                    false
-                }
-            }
-        };
-        if needs_recreate {
+        if bitmap.as_ref().map(|v| v.size()) != Some(PixelSize::new(size.width as i32, size.height as i32)) {
             bitmap.take();
         }
 
-        let mut bitmap = bitmap.get_or_insert_with(|| {
-            kahlo::RgbaBitmap::new(size.width as usize, size.height as usize)
+        let bitmap = bitmap.get_or_insert_with(|| {
+            kahlo::Bitmap::new(size.width as usize, size.height as usize)
         });
 
-        layout::recalculate(LayoutNodeAccess::new(&layout));
-        let before = std::time::Instant::now();
-        self.wc.root_widget().render(uih.theme(), &mut bitmap);
-        let after = std::time::Instant::now();
-        print!("render time: {:?}\r", (after - before));
-
         let mut windowbuffer = kahlo::BitmapMut::<kahlo::formats::Bgr32>::new(
             unsafe {
                 std::slice::from_raw_parts_mut(buf[..].as_mut_ptr() as *mut u8, buf.len() * 4)
@@ -84,11 +71,17 @@ impl<WC: WindowComponent> WindowState<WC> {
             size.width as usize,
             size.height as usize,
         );
+
+        layout::recalculate(LayoutNodeAccess::new(&layout));
+        let before = std::time::Instant::now();
+        self.wc.root_widget().render(uih.theme(), &mut bitmap.as_mut());
         windowbuffer.copy_from(
             bitmap,
             PixelBox::from_size(bitmap.size()),
-            PixelPoint::origin(),
+            PixelPoint::zero()
         );
+        let after = std::time::Instant::now();
+        print!("render time: {:?}        \r", (after - before));
 
         buf.present().unwrap();
     }
@@ -110,6 +103,7 @@ impl<WC: WindowComponent> WindowState<WC> {
 }
 
 pub(crate) trait WindowStateAccess {
+    fn notify_resize(&self, new_size: PixelSize);
     fn push_event(&self, we: WindowEvent);
     fn redraw(&self, uih: &UIHandle);
     fn request_redraw(&self);
@@ -118,6 +112,13 @@ pub(crate) trait WindowStateAccess {
 }
 
 impl<WC: WindowComponent> WindowStateAccess for RefCell<WindowState<WC>> {
+    fn notify_resize(&self, new_size: PixelSize) {
+        if Some(new_size) != self.borrow().bitmap.borrow().as_ref().map(|v| v.size()) {
+            self.borrow_mut().bitmap.take();
+            self.borrow_mut().wc.root_widget().layout_node().relayout_tree();
+            self.borrow_mut().wc.root_widget().layout_node().render_needed();
+        }
+    }
     fn push_event(&self, we: WindowEvent) {
         self.borrow_mut().events.push(we);
     }