Browse Source

Minor changes to streamline examples, start of table layout.

Kestrel 6 days ago
parent
commit
d037e38cee
9 changed files with 341 additions and 140 deletions
  1. 1 2
      Cargo.toml
  2. 11 15
      examples/button.rs
  3. 51 0
      examples/table_layout.rs
  4. 6 3
      src/layout.rs
  5. 4 117
      src/layout/arr.rs
  6. 121 0
      src/layout/arr/line.rs
  7. 33 0
      src/layout/arr/table.rs
  8. 73 2
      src/widget/group.rs
  9. 41 1
      src/window.rs

+ 1 - 2
Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "patina"
-version = "0.1.0"
+version = "0.0.1"
 edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -17,5 +17,4 @@ softbuffer = "0.4.2"
 kahlo = { git = "https://git.flying-kestrel.ca/kestrel/kahlo-rs.git", rev = "69001b5" }
 fontdue = "0.9.0"
 
-
 [dev-dependencies]

+ 11 - 15
examples/button.rs

@@ -1,5 +1,4 @@
 use patina::{
-    layout::SizePolicy,
     prelude::*,
     ui::{UIControlMsg, UIHandle},
     widget,
@@ -15,20 +14,17 @@ struct ButtonWindow {
 
 impl ButtonWindow {
     fn new(uih: &mut UIHandle) -> Self {
-        let mut group = widget::PlainGroup::new(uih);
-        group
-            .layout_node_mut()
-            .set_arrangement(patina::layout::ChildArrangement::Column)
-            .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);
-            button.set_label("Button label");
-            button.set_hook(Box::new(|| Some(ButtonWindowMsg::Shutdown)));
-            button
-        }));
-        group.append(Box::new(widget::Spacer::new(uih)));
+        let mut group = widget::PlainGroup::new_column(uih);
+        group.extend(vec![
+            Box::new(widget::Spacer::new(uih)) as Box<dyn Widget<Self>>,
+            Box::new({
+                let mut button = widget::Button::new(uih);
+                button.set_label("Button label");
+                button.set_hook(Box::new(|| Some(ButtonWindowMsg::Shutdown)));
+                button
+            }),
+            Box::new(widget::Spacer::new(uih)),
+        ].into_iter());
         Self { group }
     }
 }

+ 51 - 0
examples/table_layout.rs

@@ -0,0 +1,51 @@
+use patina::{
+    prelude::*,
+    ui::{UIControlMsg, UIHandle},
+    widget,
+};
+
+struct TableWindow {
+    root: widget::PlainGroup<Self>,
+}
+
+impl TableWindow {
+    fn new(uih: &mut UIHandle) -> Self {
+        let mut group = widget::PlainGroup::new_table(uih);
+        group.extend(vec![
+            Box::new(widget::Spacer::new(uih)) as Box<dyn Widget<Self>>,
+            Box::new(widget::Spacer::new(uih)),
+        ].into_iter());
+        Self { root: group }
+    }
+}
+
+impl Component for TableWindow {
+    type ParentMsg = UIControlMsg;
+    type Msg = ();
+
+    fn process(&mut self, _msg: Self::Msg) -> Vec<Self::ParentMsg> {
+        vec![UIControlMsg::Terminate]
+    }
+}
+
+impl WindowComponent for TableWindow {
+    fn map_window_event(&self, we: patina::window::WindowEvent) -> Option<Self::Msg> {
+        match we {
+            patina::window::WindowEvent::CloseRequested => Some(()),
+            _ => None,
+        }
+    }
+
+    type RootWidget = widget::PlainGroup<Self>;
+    fn root_widget(&self) -> &Self::RootWidget {
+        &self.root
+    }
+    fn root_widget_mut(&mut self) -> &mut Self::RootWidget {
+        &mut self.root
+    }
+}
+
+fn main() {
+    patina::ui::make_opinionated_ui(TableWindow::new).run();
+}
+

+ 6 - 3
src/layout.rs

@@ -6,7 +6,7 @@
 //! - **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 std::{ops::Deref, rc::Rc, collections::HashMap};
 
 use cache::{Layer, LayoutCacheKey, NodeState};
 use kahlo::math::{PixelBox, PixelSideOffsets};
@@ -152,7 +152,7 @@ impl Deref for ChildArrangement {
             Self::Custom(calc) => calc.as_ref(),
             Self::Column => &arr::LineArrangement::Column,
             Self::Row => &arr::LineArrangement::Row,
-            Self::Table => todo!(),
+            Self::Table => &arr::TableArrangement,
         }
     }
 }
@@ -178,6 +178,8 @@ pub struct LayoutNode {
     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_cells: Option<HashMap<(usize, usize), LayoutCacheKey>>,
 }
 
 impl std::fmt::Debug for LayoutNode {
@@ -209,6 +211,7 @@ impl LayoutNode {
             halign: HorizontalAlignment::default(),
             valign: VerticalAlignment::default(),
             margin: PixelSideOffsets::new_all_same(0),
+            table_cells: None,
         }
     }
 
@@ -329,6 +332,7 @@ impl Clone for LayoutNode {
             halign: self.halign,
             valign: self.valign,
             margin: self.margin,
+            table_cells: self.table_cells.clone(), 
         }
     }
 }
@@ -372,7 +376,6 @@ fn dump_node_tree_helper(lna: LayoutNodeAccess, indent: usize, out: &mut String)
 }
 
 pub fn dump_node_tree(lna: LayoutNodeAccess, out: &mut String) {
-    out.clear();
     dump_node_tree_helper(lna, 0, out);
 }
 

+ 4 - 117
src/layout/arr.rs

@@ -12,123 +12,10 @@ pub trait ArrangementCalculator {
     fn layout_step(&self, node: LayoutNodeAccess, inside: PixelBox);
 }
 
-#[derive(Clone, Debug)]
-pub enum LineArrangement {
-    Column,
-    Row,
-}
-
-impl LineArrangement {
-    fn is_column(&self) -> bool {
-        match self {
-            Self::Column => true,
-            Self::Row => false,
-        }
-    }
-}
-
-impl ArrangementCalculator for LineArrangement {
-    fn arrange_step(
-        &self,
-        node: LayoutNodeAccess,
-        child_policies: Vec<(SizePolicy, SizePolicy)>,
-    ) -> (SizePolicy, SizePolicy) {
-        if child_policies.is_empty() {
-            return (node.width_policy, node.height_policy);
-        }
-
-        let cw_policy = child_policies
-            .iter()
-            .map(|v| v.0)
-            .reduce(if self.is_column() {
-                SizePolicy::max
-            } else {
-                SizePolicy::add
-            })
-            .unwrap();
-        let ch_policy: SizePolicy = child_policies
-            .iter()
-            .map(|v| v.1)
-            .reduce(if self.is_column() {
-                SizePolicy::add
-            } else {
-                SizePolicy::max
-            })
-            .unwrap();
-        (
-            node.width_policy.max_preserve_slack(cw_policy),
-            node.height_policy.max_preserve_slack(ch_policy),
-        )
-    }
-
-    fn layout_step(&self, node: LayoutNodeAccess, inside: PixelBox) {
-        // do the final children layouts
-        node.cache
-            .with_state(node.cache_key, |ns| {
-                if Some(inside) != ns.area {
-                    ns.area = Some(inside);
-                    ns.needs_render = true;
-                }
-            });
-
-        if node.child_len() == 0 {
-            return;
-        }
-
-        // expansion direction extraction lambda
-        let ee = if self.is_column() {
-            |ns: &mut NodeState| ns.net_policy.1
-        } else {
-            |ns: &mut NodeState| ns.net_policy.0
-        };
-
-        let policies = node
-            .child_iter()
-            .map(|c| node.cache.with_state(c.cache_key, ee).unwrap());
-        let mut last_offset = 0;
-        let fit = do_fit(
-            if self.is_column() {
-                inside.height() as i32
-            } else {
-                inside.width() as i32
-            },
-            policies,
-        );
-
-        for (offset, child) in fit.zip(node.child_iter()) {
-            let cbox = if self.is_column() {
-                PixelBox::from_origin_and_size(
-                    PixelPoint {
-                        x: inside.min.x,
-                        y: inside.min.y + last_offset,
-                        ..Default::default()
-                    },
-                    PixelSize {
-                        width: inside.width(),
-                        height: (offset - last_offset) as i32,
-                        ..Default::default()
-                    },
-                )
-            } else {
-                PixelBox::from_origin_and_size(
-                    PixelPoint {
-                        x: inside.min.x + last_offset,
-                        y: inside.min.y,
-                        ..Default::default()
-                    },
-                    PixelSize {
-                        width: (offset - last_offset) as i32,
-                        height: inside.height(),
-                        ..Default::default()
-                    },
-                )
-            };
-
-            self.layout_step(child, cbox);
-            last_offset = offset;
-        }
-    }
-}
+mod line;
+pub use line::LineArrangement;
+mod table;
+pub use table::TableArrangement;
 
 /// Calculate the fit of a number of size policies inside a given length.
 ///

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

@@ -0,0 +1,121 @@
+use crate::{math::{PixelPoint, PixelBox, PixelSize}, layout::{SizePolicy, LayoutNodeAccess, cache::NodeState}};
+use super::{ArrangementCalculator, do_fit};
+use std::ops::Add;
+
+#[derive(Clone, Debug)]
+pub enum LineArrangement {
+    Column,
+    Row,
+}
+
+impl LineArrangement {
+    fn is_column(&self) -> bool {
+        match self {
+            Self::Column => true,
+            Self::Row => false,
+        }
+    }
+}
+
+impl ArrangementCalculator for LineArrangement {
+    fn arrange_step(
+        &self,
+        node: LayoutNodeAccess,
+        child_policies: Vec<(SizePolicy, SizePolicy)>,
+    ) -> (SizePolicy, SizePolicy) {
+        if child_policies.is_empty() {
+            return (node.width_policy, node.height_policy);
+        }
+
+        let cw_policy = child_policies
+            .iter()
+            .map(|v| v.0)
+            .reduce(if self.is_column() {
+                SizePolicy::max
+            } else {
+                SizePolicy::add
+            })
+            .unwrap();
+        let ch_policy: SizePolicy = child_policies
+            .iter()
+            .map(|v| v.1)
+            .reduce(if self.is_column() {
+                SizePolicy::add
+            } else {
+                SizePolicy::max
+            })
+            .unwrap();
+        (
+            node.width_policy.max_preserve_slack(cw_policy),
+            node.height_policy.max_preserve_slack(ch_policy),
+        )
+    }
+
+    fn layout_step(&self, node: LayoutNodeAccess, inside: PixelBox) {
+        // do the final children layouts
+        node.cache
+            .with_state(node.cache_key, |ns| {
+                if Some(inside) != ns.area {
+                    ns.area = Some(inside);
+                    ns.needs_render = true;
+                }
+            });
+
+        if node.child_len() == 0 {
+            return;
+        }
+
+        // expansion direction extraction lambda
+        let ee = if self.is_column() {
+            |ns: &mut NodeState| ns.net_policy.1
+        } else {
+            |ns: &mut NodeState| ns.net_policy.0
+        };
+
+        let policies = node
+            .child_iter()
+            .map(|c| node.cache.with_state(c.cache_key, ee).unwrap());
+        let mut last_offset = 0;
+        let fit = do_fit(
+            if self.is_column() {
+                inside.height() as i32
+            } else {
+                inside.width() as i32
+            },
+            policies,
+        );
+
+        for (offset, child) in fit.zip(node.child_iter()) {
+            let cbox = if self.is_column() {
+                PixelBox::from_origin_and_size(
+                    PixelPoint {
+                        x: inside.min.x,
+                        y: inside.min.y + last_offset,
+                        ..Default::default()
+                    },
+                    PixelSize {
+                        width: inside.width(),
+                        height: (offset - last_offset) as i32,
+                        ..Default::default()
+                    },
+                )
+            } else {
+                PixelBox::from_origin_and_size(
+                    PixelPoint {
+                        x: inside.min.x + last_offset,
+                        y: inside.min.y,
+                        ..Default::default()
+                    },
+                    PixelSize {
+                        width: (offset - last_offset) as i32,
+                        height: inside.height(),
+                        ..Default::default()
+                    },
+                )
+            };
+
+            self.layout_step(child, cbox);
+            last_offset = offset;
+        }
+    }
+}

+ 33 - 0
src/layout/arr/table.rs

@@ -0,0 +1,33 @@
+use crate::{math::{PixelPoint, PixelBox, PixelSize}, layout::{SizePolicy, LayoutNodeAccess, cache::NodeState}};
+use super::{ArrangementCalculator, do_fit};
+use std::ops::Add;
+
+#[derive(Clone, Debug)]
+pub struct TableArrangement;
+
+impl TableArrangement {
+    fn max_coord(&self, node: &LayoutNodeAccess) -> Option<(usize, usize)> {
+        let Some(tc) = &node.table_cells else { return None };
+        let mut max_row = None;
+        let mut max_col = None;
+        for cell in tc.iter() {
+            max_col = max_col.max(Some(cell.0.0));
+            max_row = max_row.max(Some(cell.0.1));
+        }
+        max_row.zip(max_col)
+    }
+}
+
+impl ArrangementCalculator for TableArrangement {
+    fn arrange_step(
+        &self,
+        node: LayoutNodeAccess,
+        child_policies: Vec<(SizePolicy, SizePolicy)>,
+    ) -> (SizePolicy, SizePolicy) {
+        // pull row/column information from child node properties
+        todo!()
+    }
+
+    fn layout_step(&self, node: LayoutNodeAccess, inside: PixelBox) {
+    }
+}

+ 73 - 2
src/widget/group.rs

@@ -1,7 +1,7 @@
 use crate::{
     component::Component,
     input::InputState,
-    layout::{LayoutNode, LayoutNodeAccess, LayoutNodeContainer},
+    layout::{LayoutNode, LayoutNodeAccess, LayoutNodeContainer, SizePolicy, ChildArrangement},
     theme::Theme,
     ui::UIHandle,
     widget::Widget, window::RenderFormat,
@@ -15,14 +15,41 @@ pub struct PlainGroup<C: Component> {
 impl<C: Component> PlainGroup<C> {
     pub fn new(uih: &UIHandle) -> Self {
         Self {
-            lnode: uih.new_layout_node(),
+            lnode: {
+                let mut node = uih.new_layout_node();
+                node.set_width_policy(SizePolicy::expanding(1));
+                node.set_height_policy(SizePolicy::expanding(1));
+                node
+            },
             children: vec![],
         }
     }
 
+    pub fn new_column(uih: &UIHandle) -> Self {
+        let mut ret = Self::new(uih);
+        ret.lnode.set_arrangement(ChildArrangement::Column);
+        ret
+    }
+
+    pub fn new_row(uih: &UIHandle) -> Self {
+        let mut ret = Self::new(uih);
+        ret.lnode.set_arrangement(ChildArrangement::Row);
+        ret
+    }
+
+    pub fn new_table(uih: &UIHandle) -> Self {
+        let mut ret = Self::new(uih);
+        ret.lnode.set_arrangement(ChildArrangement::Table);
+        ret
+    }
+
     pub fn append(&mut self, widget: Box<dyn Widget<C>>) {
         self.children.push(widget);
     }
+
+    pub fn extend(&mut self, with: impl Iterator<Item = Box<dyn Widget<C>>>) {
+        self.children.extend(with);
+    }
 }
 
 impl<C: Component> LayoutNodeContainer for PlainGroup<C> {
@@ -59,3 +86,47 @@ impl<C: Component> Widget<C> for PlainGroup<C> {
         }
     }
 }
+
+use super::Label;
+
+pub struct FormGroup<C: Component> {
+    lnode: LayoutNode,
+    labelnode: LayoutNode,
+    widgetnode: LayoutNode,
+    labels: Vec<Label<C>>,
+    widgets: Vec<Box<dyn Widget<C>>>,
+}
+
+struct LabelContainer<'l, C: Component>(&'l FormGroup<C>);
+struct WidgetContainer<'l, C: Component>(&'l FormGroup<C>);
+
+impl<C: Component> LayoutNodeContainer for FormGroup<C> {
+    fn layout_node(&self) -> &LayoutNode {
+        &self.lnode
+    }
+    fn layout_child(&self, ndx: usize) -> Option<LayoutNodeAccess<'_>> {
+        match ndx {
+            0 => todo!(), // Some(LayoutNodeAccess::new(LabelContainer(self))),
+            1 => todo!(),
+            _ => None
+        }
+    }
+    fn layout_child_count(&self) -> usize {
+        2
+    }
+}
+
+impl<C: Component> Widget<C> for FormGroup<C> {
+    fn layout_node(&self) -> LayoutNodeAccess {
+        todo!()
+    }
+    fn layout_node_mut(&mut self) -> &mut LayoutNode {
+        todo!()
+    }
+    fn poll(&mut self, uih: &mut UIHandle, input_state: Option<&InputState>) -> Vec<<C as Component>::Msg> {
+        todo!()
+    }
+    fn render(&self, theme: &Theme, target: &mut kahlo::BitmapMut<RenderFormat>) {
+        
+    }
+}

+ 41 - 1
src/window.rs

@@ -28,6 +28,7 @@ pub trait WindowComponent: 'static + Sized + Component {
 }
 
 pub(crate) struct WindowState<WC: WindowComponent> {
+    render_generation: usize,
     wc: WC,
     root_node: LayoutNode,
     window: Rc<winit::window::Window>,
@@ -74,6 +75,14 @@ impl<WC: WindowComponent> WindowState<WC> {
 
         layout::recalculate(LayoutNodeAccess::new(&layout));
         let before = std::time::Instant::now();
+
+        // save the layout tree for later dumping
+        let mut pre_render_dump = format!("render generation: {}\n", self.render_generation);
+        {
+            self.render_generation += 1;
+            crate::layout::dump_node_tree(LayoutNodeAccess::new(&layout), &mut pre_render_dump);
+        }
+
         self.wc.root_widget().render(uih.theme(), &mut bitmap.as_mut());
         windowbuffer.copy_from(
             bitmap,
@@ -81,7 +90,37 @@ impl<WC: WindowComponent> WindowState<WC> {
             PixelPoint::zero()
         );
         let after = std::time::Instant::now();
-        print!("render time: {:?}        \r", (after - before));
+
+        // put the render time on the screen
+        // we're going to do this the very dumb way for now
+        let render_time = format!("time: {:.03}ms", (after - before).as_micros() as f32 / 1000.0);
+        let line = uih.theme().make_line(render_time.as_str()).render_line();
+
+        windowbuffer.fill_region_masked(
+            &line,
+            PixelBox::from_size(line.size()),
+            PixelPoint::new(size.width as i32 - line.size().width, size.height as i32 - line.size().height),
+            uih.theme().foreground,
+            kahlo::colour::BlendMode::Simple
+        );
+
+        // now put the pre-render layout dump on the window
+        // again, we're doing this the dumb way
+        let mut offset = 0;
+        for text in pre_render_dump.split("\n") {
+            if text.len() == 0 { continue }
+            let line = uih.theme().make_line(text).render_line();
+
+            windowbuffer.fill_region_masked(
+                &line,
+                PixelBox::from_size(line.size()),
+                PixelPoint::new(0, offset),
+                uih.theme().foreground,
+                kahlo::colour::BlendMode::Simple
+            );
+
+            offset += line.height() as i32 + 1;
+        }
 
         buf.present().unwrap();
     }
@@ -160,6 +199,7 @@ impl<'r, 'l: 'r> WindowBuilder<'r, 'l> {
 
         let wc = wc(self.ui_handle);
         let wstate = Rc::new(RefCell::new(WindowState {
+            render_generation: 0,
             wc,
             root_node: LayoutNode::new(self.ui_handle.state.layout_cache.clone()),
             window,