Browse Source

Swapover to kahlo-rs instead of tiny-skia.

Kestrel 4 months ago
parent
commit
b6a4ab8c82
12 changed files with 142 additions and 103 deletions
  1. 2 1
      Cargo.toml
  2. 2 3
      src/input.rs
  3. 3 2
      src/layout.rs
  4. 25 16
      src/layout/arr.rs
  5. 1 1
      src/layout/cache.rs
  6. 4 4
      src/layout/calc.rs
  7. 1 1
      src/lib.rs
  8. 55 19
      src/text.rs
  9. 9 9
      src/theme.rs
  10. 7 25
      src/widget.rs
  11. 28 18
      src/widget/label.rs
  12. 5 4
      src/window.rs

+ 2 - 1
Cargo.toml

@@ -14,7 +14,8 @@ winit = { version = "0.30.0", default-features = false, features = ["x11", "wayl
 
 # for rendering
 softbuffer = "0.4.2"
-tiny-skia = "0.11"
+# tiny-skia = "0.11"
+kahlo = { path = "../kahlo-rs/", version = "0.1.0" }
 fontdue = "0.9.0"
 
 [dev-dependencies]

+ 2 - 3
src/input.rs

@@ -1,6 +1,5 @@
 mod button;
 pub use button::ButtonSet;
-use tiny_skia::Point;
 
 #[derive(Clone, Copy, Debug)]
 #[repr(u8)]
@@ -24,8 +23,8 @@ impl Into<u8> for MouseButton {
 
 #[derive(Default)]
 pub struct MouseState {
-    pub pos: Point,
-    pub last_pos: Point,
+    pub pos: kahlo::math::PixelPoint,
+    pub last_pos: kahlo::math::PixelPoint,
     pub buttons: ButtonSet<MouseButton>,
     pub last_buttons: ButtonSet<MouseButton>,
 }

+ 3 - 2
src/layout.rs

@@ -1,6 +1,7 @@
 use std::{ops::Deref, rc::Rc};
 
 use cache::{Layer, LayoutCacheKey, NodeState};
+use kahlo::math::{PixelBox, PixelRect};
 
 mod arr;
 mod cache;
@@ -88,7 +89,7 @@ impl BoxMeasure {
 #[derive(Clone, Copy, PartialEq, Debug)]
 pub enum NodeBehaviour {
     /// A fixed rendering area, probably a root node.
-    Fixed { rect: tiny_skia::IntRect },
+    Fixed { rect: PixelRect },
     /// An ordinary box sitting inside another node, hinting its size and receiving a final
     /// size assignment from its parent.
     Element,
@@ -176,7 +177,7 @@ impl LayoutNode {
         self.cache.update_queue.borrow_mut().push(self.cache_key);
     }
 
-    pub fn cached_rect(&self) -> Option<tiny_skia::IntRect> {
+    pub fn cached_rect(&self) -> Option<PixelRect> {
         self.cache
             .with_state(self.cache_key, |ns| ns.rect)
             .flatten()

+ 25 - 16
src/layout/arr.rs

@@ -1,7 +1,6 @@
+use kahlo::math::{PixelPoint, PixelRect, PixelSize};
 use std::ops::Add;
 
-use tiny_skia::IntRect;
-
 use super::{cache::NodeState, LayoutNodeAccess, SizePolicy};
 
 pub trait ArrangementCalculator {
@@ -10,7 +9,7 @@ pub trait ArrangementCalculator {
         node: LayoutNodeAccess,
         child_policies: Vec<(SizePolicy, SizePolicy)>,
     ) -> (SizePolicy, SizePolicy);
-    fn layout_step(&self, node: LayoutNodeAccess, inside: IntRect);
+    fn layout_step(&self, node: LayoutNodeAccess, inside: PixelRect);
 }
 
 #[derive(Clone, Debug)]
@@ -62,7 +61,7 @@ impl ArrangementCalculator for LineArrangement {
         )
     }
 
-    fn layout_step(&self, node: LayoutNodeAccess, inside: IntRect) {
+    fn layout_step(&self, node: LayoutNodeAccess, inside: PixelRect) {
         // do the final children layouts
         node.cache
             .with_state(node.cache_key, |ns| ns.rect = Some(inside));
@@ -92,21 +91,31 @@ impl ArrangementCalculator for LineArrangement {
         );
         for (offset, child) in fit.zip(node.child_iter()) {
             let crect = if self.is_column() {
-                IntRect::from_xywh(
-                    inside.left(),
-                    inside.top() + last_offset,
-                    inside.width(),
-                    (offset - last_offset) as u32,
+                PixelRect::new(
+                    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()
+                    },
                 )
-                .unwrap()
             } else {
-                IntRect::from_xywh(
-                    inside.left() + last_offset,
-                    inside.top(),
-                    (offset - last_offset) as u32,
-                    inside.height(),
+                PixelRect::new(
+                    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()
+                    },
                 )
-                .unwrap()
             };
 
             self.layout_step(child, crect);

+ 1 - 1
src/layout/cache.rs

@@ -16,7 +16,7 @@ pub struct NodeState {
     pub(super) needs_update: bool,
     pub(super) net_policy: (SizePolicy, SizePolicy),
     pub(super) layer: Layer,
-    pub(super) rect: Option<tiny_skia::IntRect>,
+    pub(super) rect: Option<kahlo::math::PixelRect>,
     pub(super) children: Vec<LayoutCacheKey>,
 }
 

+ 4 - 4
src/layout/calc.rs

@@ -58,7 +58,7 @@ fn arrangement_pass(parent: Option<LayoutCacheKey>, node: LayoutNodeAccess, laye
 
 #[cfg(test)]
 mod test {
-    use tiny_skia::IntRect;
+    use kahlo::math::{PixelPoint, PixelRect, PixelSize};
 
     use crate::layout::{
         cache::LayoutCache, ChildArrangement, LayoutNode, LayoutNodeAccess, LayoutNodeContainer,
@@ -118,7 +118,7 @@ mod test {
             node: LayoutNode::new(cache.clone()),
         };
         root.node.set_behaviour(NodeBehaviour::Fixed {
-            rect: IntRect::from_xywh(1, 1, 2, 5).unwrap(),
+            rect: PixelRect::new(PixelPoint::new(1, 1), PixelSize::new(2, 5)),
         });
         root.node.child_arrangement = ChildArrangement::Column;
 
@@ -131,14 +131,14 @@ mod test {
             cache
                 .with_state(root.node.cache_key, |ns| ns.rect)
                 .flatten(),
-            Some(IntRect::from_xywh(1, 1, 2, 5).unwrap())
+            Some(PixelRect::new(PixelPoint::new(1, 1), PixelSize::new(2, 5)))
         );
 
         assert_eq!(
             cache
                 .with_state(root.children[0].node.cache_key, |ns| ns.rect)
                 .flatten(),
-            Some(IntRect::from_xywh(1, 1, 2, 2).unwrap())
+            Some(PixelRect::new(PixelPoint::new(1, 1), PixelSize::new(2, 2)))
         );
     }
 }

+ 1 - 1
src/lib.rs

@@ -14,5 +14,5 @@ pub mod prelude {
 
 pub mod re_exports {
     pub use fontdue;
-    pub use tiny_skia;
+    pub use kahlo;
 }

+ 55 - 19
src/text.rs

@@ -1,3 +1,8 @@
+use kahlo::{
+    math::{PixelBox, PixelPoint, PixelRect},
+    prelude::*,
+};
+
 pub struct TextLine<'l> {
     line: &'l str,
     font: &'l fontdue::Font,
@@ -9,34 +14,65 @@ impl<'l> TextLine<'l> {
         Self { line, font, size }
     }
 
-    pub fn render_line(&self) -> tiny_skia::Pixmap {
+    pub fn render_line(&self) -> kahlo::Alphamap {
         // pass 1: determine required size
-        let mut total_width = 0;
-        let mut last_char = None;
-        for ch in self.line.chars() {
-            let metrics = self.font.metrics(ch, self.size);
+        let baseline_offsets = || {
+            let mut running_offset = 0;
+            let mut last_char = None;
 
-            let glyph_width = if let Some(last) = last_char.take() {
-                metrics.advance_width + self.font.horizontal_kern(last, ch, self.size).unwrap_or(0.)
-            } else {
-                metrics.advance_width
-            };
+            self.line.chars().map(move |ch| {
+                let metrics = self.font.metrics(ch, self.size);
 
-            total_width += glyph_width.round() as u32;
+                let glyph_width = if let Some(last) = last_char.take() {
+                    metrics.advance_width
+                        + self.font.horizontal_kern(last, ch, self.size).unwrap_or(0.)
+                } else {
+                    metrics.advance_width
+                };
 
-            last_char = Some(ch);
-        }
-        println!("width: {total_width}");
+                last_char = Some(ch);
+
+                let expose = (running_offset, glyph_width.round() as u32, ch);
+                running_offset += glyph_width.round() as u32;
+                expose
+            })
+        };
+
+        let total_width = {
+            let ofs = baseline_offsets().last().unwrap();
+            ofs.0 + ofs.1
+        };
 
         let line_metrics = self.font.horizontal_line_metrics(self.size).unwrap();
-        let mut pixmap = tiny_skia::Pixmap::new(total_width, line_metrics.new_line_size.ceil() as u32).unwrap();
+        let line_height = line_metrics.new_line_size.ceil() as usize;
+        let baseline = line_metrics.ascent.ceil() as usize;
+        let mut alphamap = kahlo::Alphamap::new(total_width as usize, line_height * 2);
 
-        pixmap.fill(tiny_skia::Color::from_rgba8(128, 192, 128, 255));
+        // pass 2: generate alphamap
+        for (offset, _width, ch) in baseline_offsets() {
+            let (metrics, raster) = self.font.rasterize(ch, self.size);
+            let character_alphamap = kahlo::BitmapRef::<kahlo::formats::A8>::new(
+                raster.as_slice(),
+                metrics.width,
+                metrics.height,
+                metrics.width,
+            );
 
-        // pass 2: generate pixmap
-        // todo!()
+            // ascent height 10
+            // descent height 4
+            // glyph height 2
+            // glyph offset: 1
+            // (should be placed in y-range [7,8])
+            // so use offset (ascent height - (glyph height + glyph offset))
+            alphamap.copy_from(
+                &character_alphamap,
+                &PixelBox::from_size(character_alphamap.size()),
+                &PixelPoint::new(metrics.xmin + offset as i32, baseline as i32 - (metrics.height as i32 + metrics.ymin as i32)),
+            );
+            println!("offset: {offset} ymin: {}", metrics.ymin);
+        }
 
-        pixmap
+        alphamap
     }
 }
 

+ 9 - 9
src/theme.rs

@@ -1,11 +1,11 @@
-use tiny_skia::Color;
-
 use crate::text::TextLine;
 
+use kahlo::colour::Colour;
+
 pub struct Theme {
-    pub border: Color,
-    pub background: Color,
-    pub foreground: Color,
+    pub border: Colour,
+    pub background: Colour,
+    pub foreground: Colour,
 
     pub ui_font: fontdue::Font,
     pub ui_font_size: f32,
@@ -18,12 +18,12 @@ impl Theme {
 
     pub fn default_with_font(ui_font: fontdue::Font) -> Self {
         Self {
-            border: Color::from_rgba8(0, 0, 0, 255),
-            background: Color::from_rgba8(0, 0, 0, 255),
-            foreground: Color::from_rgba8(0, 0, 0, 255),
+            border: Colour::GREEN,
+            background: Colour::BLUE,
+            foreground: Colour::WHITE,
 
             ui_font,
-            ui_font_size: 12.0,
+            ui_font_size: 64.0,
         }
     }
 }

+ 7 - 25
src/widget.rs

@@ -1,22 +1,15 @@
 use std::ops::DerefMut;
 
+use kahlo::prelude::*;
+
 use crate::{
     component::Component,
     input::InputState,
-    layout::{LayoutCache, LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy},
+    layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy},
     theme::Theme,
     ui::UIHandle,
 };
 
-/*mod button;
-mod frame;
-mod group;
-mod label;
-
-pub use frame::Frame;
-pub use group::PlainGroup;
-pub use label::Label;*/
-
 mod label;
 pub use label::Label;
 
@@ -25,7 +18,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 tiny_skia::Pixmap);
+    fn render(&self, theme: &Theme, target: &mut kahlo::RgbaBitmap);
 }
 
 pub struct Spacer<C: Component> {
@@ -62,19 +55,8 @@ impl<C: Component> Widget<C> for Spacer<C> {
         self.lnode.deref_mut()
     }
 
-    fn render(&self, theme: &Theme, target: &mut tiny_skia::Pixmap) {
-        let mut path = tiny_skia::PathBuilder::new();
-        path.push_rect(self.layout_node().cached_rect().unwrap().to_rect());
-        let path = path.finish().unwrap();
-        let mut paint = tiny_skia::Paint::default();
-        paint.set_color_rgba8(192, 168, 32, 255);
-        target.fill_path(
-            &path,
-            &paint,
-            tiny_skia::FillRule::Winding,
-            tiny_skia::Transform::identity(),
-            None,
-        );
-        target.fill(tiny_skia::Color::from_rgba(1.0, 0.5, 0.5, 1.0).unwrap());
+    fn render(&self, theme: &Theme, target: &mut kahlo::RgbaBitmap) {
+        let region = self.layout_node().cached_rect().unwrap().to_box2d();
+        target.fill_region(&region, theme.background);
     }
 }

+ 28 - 18
src/widget/label.rs

@@ -1,4 +1,11 @@
-use crate::{component::Component, layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy}, theme::Theme, ui::UIHandle};
+use kahlo::{colour::Colour, math::PixelBox, prelude::*};
+
+use crate::{
+    component::Component,
+    layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy},
+    theme::Theme,
+    ui::UIHandle,
+};
 
 use super::Widget;
 
@@ -11,7 +18,12 @@ 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::expands(1))
+            .set_height_policy(SizePolicy {
+                minimum: 20,
+                desired: 20,
+                slack_weight: 0,
+            });
         Self {
             lnode: LeafLayoutNode::new(node),
             text: "example text here".to_string(),
@@ -21,7 +33,11 @@ impl<C: Component> Label<C> {
 }
 
 impl<C: Component> Widget<C> for Label<C> {
-    fn poll(&mut self, _uih: &mut UIHandle, _input_state: Option<&crate::input::InputState>) -> Vec<<C as Component>::Msg> {
+    fn poll(
+        &mut self,
+        _uih: &mut UIHandle,
+        _input_state: Option<&crate::input::InputState>,
+    ) -> Vec<<C as Component>::Msg> {
         vec![]
     }
 
@@ -32,24 +48,18 @@ impl<C: Component> Widget<C> for Label<C> {
         &mut self.lnode
     }
 
-    fn render(&self, theme: &Theme, surface: &mut tiny_skia::Pixmap) {
+    fn render(&self, theme: &Theme, surface: &mut kahlo::RgbaBitmap) {
         let line = theme.make_line(self.text.as_str());
         let rendered = line.render_line();
 
-        let render_location = self.layout_node().cached_rect().unwrap().to_rect();
-
-        surface.fill_rect(render_location, &tiny_skia::Paint {
-            shader: tiny_skia::Pattern::new(
-                rendered.as_ref(), tiny_skia::SpreadMode::Pad, tiny_skia::FilterQuality::Nearest, 1.0, tiny_skia::Transform::identity()
-            ),
-            blend_mode: tiny_skia::BlendMode::SourceOver,
-            ..Default::default()
-        }, tiny_skia::Transform::identity(), None);
-        /*painter.fill(
-            self.layout_node().cached_rect().unwrap(),
-            Colour::new_rgb(64, 64, 64),
+        let render_location = self.layout_node().cached_rect().unwrap();
+
+        surface.fill_masked(
+            &rendered,
+            &PixelBox::from_size(rendered.size()),
+            &render_location.origin,
+            theme.foreground,
+            kahlo::colour::BlendMode::SourceAlpha,
         );
-        let text_surf = ti.render_text(&self.buffer, 0.0);
-        painter.blit(&text_surf, IRect::new_from_size(IPoint::ORIGIN, text_surf.size()), self.layout_node().cached_rect().unwrap().tl());*/
     }
 }

+ 5 - 4
src/window.rs

@@ -1,7 +1,7 @@
 use std::cell::RefCell;
 use std::rc::Rc;
 
-use tiny_skia::IntRect;
+use kahlo::{math::PixelRect, math::PixelSize, prelude::*};
 
 use crate::input::InputState;
 use crate::layout::{self, LayoutNode, LayoutNodeAccess, LinearAccess};
@@ -46,19 +46,20 @@ impl<WC: WindowComponent> WindowState<WC> {
         let mut buf = self.surface.buffer_mut().unwrap();
 
         self.root_node.set_behaviour(layout::NodeBehaviour::Fixed {
-            rect: IntRect::from_xywh(0, 0, size.width, size.height).unwrap(),
+            rect: PixelRect::from_size(PixelSize::new(size.width as i32, size.height as i32)),
         });
 
         let layout = LinearAccess::new(&self.root_node, self.wc.root_widget().layout_node());
 
-        let mut pixmap = tiny_skia::Pixmap::new(size.width, size.height).unwrap();
+        let mut pixmap = kahlo::RgbaBitmap::new(size.width as usize, size.height as usize);
+        pixmap.fill(kahlo::colour::Colour::rgb(128, 128, 128));
         layout::recalculate(LayoutNodeAccess::new(&layout));
         self.wc.root_widget().render(uih.theme(), &mut pixmap);
 
         // expensive ABGR -> RGB0 conversion
         let mut pxdata = pixmap.data();
         for t in buf.iter_mut() {
-            *t = u32::from_be_bytes(pxdata[0..4].try_into().unwrap()) >> 8; //((pxdata[0] as u32) << 16) | ((pxdata[1] as u32) << 8) | (pxdata[2] as u32);
+            *t = u32::from_be_bytes(pxdata[0..4].try_into().unwrap()) >> 8;
             pxdata = &pxdata[4..];
         }