Просмотр исходного кода

Add unaccelerated software rendering platform crate.

Kestrel 5 месяцев назад
Родитель
Сommit
660929505b

+ 1 - 1
Cargo.toml

@@ -1,3 +1,3 @@
 [workspace]
 resolver = "2"
-members = ["patina", "patina-sdl"]
+members = ["patina", "patina-sdl", "patina-unaccel"]

+ 2 - 1
patina-sdl/Cargo.toml

@@ -7,4 +7,5 @@ edition = "2021"
 
 [dependencies]
 patina = { path = "../patina/" }
-sdl2 = { version = "0.36", features = ["ttf"] }
+patina-unaccel = { path = "../patina-unaccel" }
+sdl2 = { version = "0.36" }

+ 11 - 2
patina-sdl/src/convert.rs

@@ -1,5 +1,5 @@
-use patina::platform::render as pr;
 use patina::geom::*;
+use patina::platform::render as pr;
 
 pub trait ToSDL {
     type SDLEquivalent;
@@ -33,6 +33,15 @@ pub trait FromSDL {
 impl FromSDL for sdl2::rect::Rect {
     type PatinaEquivalent = IRect;
     fn from_sdl(self) -> Self::PatinaEquivalent {
-        IRect::new_from_size(IPoint { x: self.x, y: self.y }, IVector { x: self.w, y: self.h })
+        IRect::new_from_size(
+            IPoint {
+                x: self.x,
+                y: self.y,
+            },
+            IVector {
+                x: self.w,
+                y: self.h,
+            },
+        )
     }
 }

+ 34 - 7
patina-sdl/src/lib.rs

@@ -1,10 +1,12 @@
 use patina::platform;
+use patina_unaccel::{UnaccelSurface, UnaccelText};
+use patina::platform::render::Surface;
 
 mod convert;
-mod render;
+// mod render;
 
 mod prelude {
-    pub use crate::convert::{FromSDL,ToSDL};
+    pub use crate::convert::{FromSDL, ToSDL};
 }
 
 pub struct SDLInitFlags {
@@ -27,7 +29,9 @@ pub struct SDLRenderInterface {
     sdl: sdl2::Sdl,
     video: sdl2::VideoSubsystem,
     // window: sdl2::video::Window,
-    canvas: render::WindowSurface,
+    canvas: sdl2::render::WindowCanvas,
+    // canvas: render::WindowSurface,
+    surf: UnaccelSurface,
 }
 
 impl SDLRenderInterface {
@@ -52,7 +56,9 @@ impl SDLRenderInterface {
             Self {
                 sdl,
                 video,
-                canvas: canvas.into(),
+                canvas,
+                surf: UnaccelSurface::new(sdlif.window_width, sdlif.window_height),
+                // canvas: canvas.into(),
             },
             SDLEventInterface { evt, next: None },
         )
@@ -60,9 +66,18 @@ impl SDLRenderInterface {
 }
 
 impl platform::RenderInterface for SDLRenderInterface {
-    type RenderTarget = render::WindowSurface;
-    fn render_target_mut(&mut self) -> &mut Self::RenderTarget {
-        &mut self.canvas
+    type Surface = UnaccelSurface;
+
+    fn render_target_mut(&mut self) -> &mut Self::Surface {
+        &mut self.surf
+    }
+
+    fn submit(&mut self) {
+        let txc = self.canvas.texture_creator();
+        let mut tex = txc.create_texture_target(sdl2::pixels::PixelFormatEnum::RGBA32, self.surf.size().x as u32, self.surf.size().y as u32).unwrap();
+        tex.update(None, self.surf.raw_argb32_data(), self.surf.size().x as usize * 4).unwrap();
+        self.canvas.copy(&tex, None, None).unwrap();
+        self.canvas.present();
     }
 }
 
@@ -101,11 +116,21 @@ impl platform::EventInterface for SDLEventInterface {
     }
 }
 
+pub type SDLPlatformSpec = (SDLRenderInterface, SDLEventInterface, UnaccelText);
+pub fn init_sdl_platform(sdlif: SDLInitFlags) -> SDLPlatformSpec {
+    let (sdl, event) = SDLRenderInterface::new(sdlif);
+    let text = UnaccelText::new();
+    (sdl, event, text)
+}
+
+/*
 pub struct SDLTextInterface {
     ctx: sdl2::ttf::Sdl2TtfContext,
 }
 
 impl platform::TextInterface for SDLTextInterface {
+    type Surface = render::SDLSurface;
+
     fn render_text(&self, what: &str, _sz: f32) -> Box<dyn patina::platform::render::Surface + '_> {
         let font = self.ctx.load_font("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16).unwrap();
         let render = font.render(what);
@@ -121,3 +146,5 @@ pub fn init_sdl_platform(
     let ttf = SDLTextInterface { ctx: sdl2::ttf::init().unwrap() };
     (sdl, event, ttf)
 }
+
+*/

+ 35 - 6
patina-sdl/src/render.rs

@@ -3,20 +3,45 @@ use crate::prelude::*;
 use patina::geom::{IPoint, IRect, IVector};
 use patina::platform::render as pr;
 
-pub struct SDLSurface<'l> {
-    pub surface: sdl2::surface::Surface<'l>,
+pub enum SDLSurface {
+    Window(sdl2::render::WindowCanvas),
+    Surface(()),
 }
 
-impl<'l> pr::Surface for SDLSurface<'l> {
+impl pr::Surface for SDLSurface {
     fn size(&self) -> IVector {
-        self.surface.rect().from_sdl().size()
+        let sz = match self {
+            Self::Window(canvas) => {
+                canvas.logical_size()
+            }
+            Self::Surface(surf) => {
+                surf.size()
+            },
+        };
+        IVector::new(sz.0 as i32, sz.1 as i32)
     }
 
-    fn painter(&mut self) -> Option<Box<dyn pr::Painter + '_>> {
+    fn painter(&mut self) -> Option<Self::Painter<'_>> {
         None
     }
+
+    type Painter<'p> = SDLPainter<'p> where Self: 'p;
+}
+
+pub enum SDLPainter<'l> {
+    Window(sdl2::render::WindowCanvas),
+    Surface(sdl2::surface::Surface<'l>),
+}
+
+impl<'p> pr::Painter for SDLPainter<'p> {
+    type Surface<'s> = SDLSurface<'s> where Self: 's;
+
+    fn blit(&mut self, source: &Self::Surface<'p>, source_area: IRect, target: IPoint) { }
+    fn fill(&mut self, area: IRect, col: pr::Colour) { }
+    fn outline_rect(&mut self, rect: IRect, col: pr::Colour) {}
 }
 
+/*
 pub struct WindowSurface {
     canvas: sdl2::render::WindowCanvas,
 }
@@ -27,7 +52,8 @@ impl From<sdl2::render::WindowCanvas> for WindowSurface {
     }
 }
 
-impl pr::Surface for WindowSurface {
+impl<'l> pr::Surface<'l> for WindowSurface {
+    type Painter = CanvasPainter
     fn size(&self) -> IVector {
         let sz = self.canvas.window().size();
         IVector {
@@ -43,7 +69,9 @@ impl pr::Surface for WindowSurface {
         }))
     }
 }
+*/
 
+/*
 pub struct CanvasPainter<'l> {
     canvas: &'l mut sdl2::render::WindowCanvas,
     _ghost: std::marker::PhantomData<(&'l (),)>,
@@ -66,3 +94,4 @@ impl<'l> Drop for CanvasPainter<'l> {
         self.canvas.present()
     }
 }
+*/

+ 13 - 0
patina-unaccel/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "patina-unaccel"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+patina = { path = "../patina/" }
+schrift-rs = { version = "0.1.0" }
+
+[dev-dependencies]
+ductr = "0.0.1"

+ 23 - 0
patina-unaccel/render_output.pgm

@@ -0,0 +1,23 @@
+P2
+279 20
+255
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20 38 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 29 28 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 29 28 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20 38 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 22 36 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 126 241 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 31 56 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 187 179 0 0 0 0 0 0 31 56 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 31 56 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 187 179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 126 241 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 139 229 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 126 241 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 131 237 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 187 179 0 0 0 0 0 0 131 237 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 131 237 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 187 179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 83 160 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 139 229 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 126 241 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 131 237 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 187 179 0 0 0 0 0 0 131 237 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 131 237 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 187 179 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 139 229 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 123 152 0 0 0 0 143 131 0 0 105 172 28 168 225 164 60 0 0 0 0 42 158 216 233 163 75 5 0 0 0 0 24 156 212 236 207 113 0 0 0 24 156 212 236 207 113 0 0 0 24 158 214 229 180 46 0 0 0 126 241 0 0 0 24 158 214 229 180 46 0 0 105 172 31 169 225 135 0 0 42 158 216 233 163 75 5 0 109 224 251 191 191 170 0 0 0 24 158 214 229 180 46 0 0 0 0 60 191 231 185 61 187 179 0 0 0 0 0 109 224 251 191 191 170 0 0 0 24 158 214 229 180 46 0 11 175 139 0 0 0 38 190 96 109 224 251 191 191 170 0 0 0 0 0 0 105 172 31 169 225 135 0 0 0 24 158 214 229 180 46 0 0 105 172 28 168 225 164 60 0 0 0 0 0 60 191 231 185 61 187 179 0 0 0 24 158 214 229 180 46 0 0 105 172 31 169 225 135 0 0 94 181 0 105 172 28 168 225 164 60 0 0 0 0 0 20 129 224 184 61 140 134 0 0 0 0 0 0 0 42 158 216 233 163 75 5 0 0 139 229 28 168 225 164 60 0 0 0 0 0 44 177 226 217 158 22 0 71 191 29 0 0 0 0 130 161 0
+ 0 163 203 0 0 0 0 191 175 0 0 139 240 214 167 101 194 255 40 0 0 0 91 159 95 81 147 237 144 0 0 0 31 237 243 154 79 112 157 0 0 31 237 243 154 79 112 157 0 0 29 235 218 128 102 204 248 43 0 0 126 241 0 0 29 235 218 128 102 204 248 43 0 139 241 220 181 96 71 0 0 91 159 95 81 147 237 144 0 53 177 244 94 94 83 0 0 29 235 218 128 102 204 248 43 0 0 54 249 224 106 150 236 225 179 0 0 0 0 0 53 177 244 94 94 83 0 0 29 235 218 128 102 204 248 43 0 82 254 96 0 8 207 209 9 53 177 244 94 94 83 0 0 0 0 0 0 139 241 220 181 96 71 0 0 29 235 218 128 102 204 248 43 0 139 240 214 167 101 194 255 40 0 0 0 54 249 224 106 150 236 225 179 0 0 29 235 218 128 102 204 248 43 0 139 241 220 181 96 71 0 0 126 241 0 139 240 214 167 101 194 255 40 0 0 0 47 244 218 104 145 233 225 179 0 0 0 0 0 0 0 91 159 95 81 147 237 144 0 0 139 240 214 167 101 194 255 40 0 0 0 49 246 232 117 142 246 227 15 14 243 127 0 0 0 17 246 125 0
+ 0 163 203 0 0 0 0 191 175 0 0 139 255 106 0 0 7 245 112 0 0 0 0 0 0 0 0 112 223 0 0 0 171 251 25 0 0 0 0 0 0 171 251 25 0 0 0 0 0 0 166 243 16 0 0 3 231 172 0 0 126 241 0 0 166 243 16 0 0 3 231 172 0 139 255 116 0 0 0 0 0 0 0 0 0 0 112 223 0 0 131 237 0 0 0 0 0 166 243 16 0 0 3 231 172 0 0 184 235 6 0 0 80 255 179 0 0 0 0 0 0 131 237 0 0 0 0 0 166 243 16 0 0 3 231 172 0 0 146 244 42 150 243 40 0 0 131 237 0 0 0 0 0 0 0 0 0 139 255 116 0 0 0 0 0 166 243 16 0 0 3 231 172 0 139 255 106 0 0 7 245 112 0 0 0 184 235 6 0 0 80 255 179 0 0 166 243 16 0 0 3 231 172 0 139 255 116 0 0 0 0 0 126 241 0 139 255 106 0 0 7 245 112 0 0 0 129 230 3 0 0 72 255 179 0 0 0 0 0 0 0 0 0 0 0 0 112 223 0 0 139 255 106 0 0 7 245 112 0 0 0 183 243 12 0 0 55 255 131 0 153 226 3 0 0 110 251 26 0
+ 0 163 203 0 0 0 0 191 175 0 0 139 252 24 0 0 0 187 181 0 0 0 0 1 43 107 133 150 255 49 0 0 240 191 0 0 0 0 0 0 0 240 191 0 0 0 0 0 0 0 238 171 38 39 39 39 167 224 0 0 126 241 0 0 238 171 38 39 39 39 167 224 0 139 255 39 0 0 0 0 0 0 1 43 107 133 150 255 49 0 131 237 0 0 0 0 0 238 171 38 39 39 39 167 224 0 1 245 169 0 0 0 13 250 179 0 0 0 0 0 0 131 237 0 0 0 0 0 238 171 38 39 39 39 167 224 0 0 7 204 230 255 93 0 0 0 131 237 0 0 0 0 0 0 0 0 0 139 255 39 0 0 0 0 0 238 171 38 39 39 39 167 224 0 139 252 24 0 0 0 187 181 0 0 1 245 169 0 0 0 13 250 179 0 0 238 171 38 39 39 39 167 224 0 139 255 39 0 0 0 0 0 126 241 0 139 252 24 0 0 0 187 181 0 0 0 206 163 0 0 0 9 247 179 0 0 0 0 0 0 0 0 1 43 107 133 150 255 49 0 139 252 24 0 0 0 187 181 0 0 1 244 180 0 0 0 1 233 193 0 48 255 76 0 0 211 175 0 0
+ 0 163 203 0 0 0 0 191 175 0 0 139 229 0 0 0 0 167 199 0 0 0 44 232 237 195 158 162 255 90 0 19 255 123 0 0 0 0 0 0 19 255 123 0 0 0 0 0 0 18 255 250 247 247 247 247 247 244 0 0 126 241 0 18 255 250 247 247 247 247 247 244 0 139 235 0 0 0 0 0 0 44 232 237 195 158 162 255 90 0 131 237 0 0 0 0 18 255 250 247 247 247 247 247 244 0 21 255 112 0 0 0 0 205 179 0 0 0 0 0 0 131 237 0 0 0 0 18 255 250 247 247 247 247 247 244 0 0 0 84 255 205 0 0 0 0 131 237 0 0 0 0 0 0 0 0 0 139 235 0 0 0 0 0 18 255 250 247 247 247 247 247 244 0 139 229 0 0 0 0 167 199 0 0 21 255 112 0 0 0 0 205 179 0 18 255 250 247 247 247 247 247 244 0 139 235 0 0 0 0 0 0 126 241 0 139 229 0 0 0 0 167 199 0 0 12 254 111 0 0 0 0 204 179 0 0 0 0 0 0 0 44 232 237 195 158 162 255 90 0 139 229 0 0 0 0 167 199 0 0 20 255 120 0 0 0 0 174 224 0 0 198 178 0 58 255 72 0 0
+ 0 156 212 0 0 0 0 218 175 0 0 139 229 0 0 0 0 167 199 0 0 0 157 209 1 0 0 69 255 90 0 3 248 170 0 0 0 0 0 0 3 248 170 0 0 0 0 0 0 4 248 151 0 0 0 0 0 0 0 0 126 241 0 4 248 151 0 0 0 0 0 0 0 139 229 0 0 0 0 0 0 157 209 1 0 0 69 255 90 0 131 237 0 0 0 0 4 248 151 0 0 0 0 0 0 0 4 251 152 0 0 0 4 241 179 0 0 0 0 0 0 131 237 0 0 0 0 4 248 151 0 0 0 0 0 0 0 0 17 223 219 255 90 0 0 0 131 237 0 0 0 0 0 0 0 0 0 139 229 0 0 0 0 0 4 248 151 0 0 0 0 0 0 0 139 229 0 0 0 0 167 199 0 0 4 251 152 0 0 0 4 241 179 0 4 248 151 0 0 0 0 0 0 0 139 229 0 0 0 0 0 0 126 241 0 139 229 0 0 0 0 167 199 0 0 0 209 160 0 0 0 8 246 179 0 0 0 0 0 0 0 157 209 1 0 0 69 255 90 0 139 229 0 0 0 0 167 199 0 0 4 250 161 0 0 0 0 216 202 0 0 93 252 28 160 223 2 0 0
+ 0 94 253 17 0 0 46 255 175 0 0 139 229 0 0 0 0 167 199 0 0 1 237 131 0 0 0 151 255 90 0 0 199 242 7 0 0 0 0 0 0 199 242 7 0 0 0 0 0 0 199 241 11 0 0 0 0 0 0 0 126 241 0 0 199 241 11 0 0 0 0 0 0 139 229 0 0 0 0 0 1 237 131 0 0 0 151 255 90 0 104 250 8 0 0 0 0 199 241 11 0 0 0 0 0 0 0 209 218 0 0 0 58 255 179 0 0 0 0 0 0 104 250 8 0 0 0 0 199 241 11 0 0 0 0 0 0 1 174 232 25 154 242 37 0 0 104 250 8 0 0 0 0 0 0 0 0 139 229 0 0 0 0 0 0 199 241 11 0 0 0 0 0 0 139 229 0 0 0 0 167 199 0 0 0 209 218 0 0 0 58 255 179 0 0 199 241 11 0 0 0 0 0 0 139 229 0 0 0 0 0 0 126 241 0 139 229 0 0 0 0 167 199 0 0 0 132 228 1 0 0 68 255 179 0 0 0 0 0 0 1 237 131 0 0 0 151 255 90 0 139 229 0 0 0 0 167 199 0 0 0 208 230 1 0 0 30 255 156 0 0 8 235 143 246 122 0 0 0
+ 0 23 253 158 39 85 200 241 175 0 0 139 229 0 0 0 0 167 199 0 0 0 160 223 59 27 132 229 255 90 0 0 63 254 193 86 12 44 102 0 0 63 254 193 86 12 44 102 0 0 58 253 187 88 13 30 89 113 0 0 126 241 0 0 58 253 187 88 13 30 89 113 0 139 229 0 0 0 0 0 0 160 223 59 27 132 229 255 90 0 33 255 131 57 46 0 0 58 253 187 88 13 30 89 113 0 0 91 255 163 39 82 212 243 179 0 0 0 0 0 0 33 255 131 57 46 0 0 58 253 187 88 13 30 89 113 0 109 253 70 0 9 210 205 7 0 33 255 131 57 46 0 0 0 0 0 0 139 229 0 0 0 0 0 0 58 253 187 88 13 30 89 113 0 139 229 0 0 0 0 167 199 0 0 0 91 255 163 39 82 212 243 179 0 0 58 253 187 88 13 30 89 113 0 139 229 0 0 0 0 0 0 126 241 0 139 229 0 0 0 0 167 199 0 0 0 50 248 210 94 135 231 228 179 0 0 0 0 0 0 0 160 223 59 27 132 229 255 90 0 139 229 0 0 0 0 167 199 0 0 0 84 255 175 49 74 203 250 37 0 0 0 139 254 250 25 0 0 0
+ 0 0 105 215 255 243 100 191 175 0 0 139 229 0 0 0 0 167 199 0 0 0 44 188 252 255 217 62 255 90 0 0 0 75 226 255 255 253 164 0 0 0 75 226 255 255 253 164 0 0 0 67 215 255 255 255 232 110 0 0 126 241 0 0 0 67 215 255 255 255 232 110 0 139 229 0 0 0 0 0 0 44 188 252 255 217 62 255 90 0 0 117 192 249 227 0 0 0 67 215 255 255 255 232 110 0 0 1 122 250 255 246 117 187 179 0 0 0 0 0 0 0 117 192 249 227 0 0 0 67 215 255 255 255 232 110 50 248 134 0 0 0 41 244 147 0 0 117 192 249 227 0 0 0 0 0 0 139 229 0 0 0 0 0 0 0 67 215 255 255 255 232 110 0 139 229 0 0 0 0 167 199 0 0 0 1 122 250 255 246 117 187 179 0 0 0 67 215 255 255 255 232 110 0 139 229 0 0 0 0 0 0 126 241 0 139 229 0 0 0 0 167 199 0 0 0 0 26 139 234 194 68 215 160 0 0 0 0 0 0 0 44 188 252 255 217 62 255 90 0 139 229 0 0 0 0 167 199 0 0 0 0 102 242 255 255 225 66 0 0 0 0 36 255 172 0 0 0 0
+ 0 0 0 1 38 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 25 0 0 0 0 0 0 0 0 0 27 48 19 0 0 0 0 0 0 27 48 19 0 0 0 0 0 0 20 50 29 2 0 0 0 0 0 0 0 0 0 0 20 50 29 2 0 0 0 0 0 0 0 0 0 0 0 0 25 25 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20 50 29 2 0 0 0 0 0 9 43 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20 50 29 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20 50 29 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 43 6 0 0 0 0 0 0 0 0 20 50 29 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 39 255 127 0 0 0 0 0 0 0 0 0 25 25 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 39 30 0 0 0 0 0 0 65 255 69 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 77 3 1 58 177 249 34 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 199 190 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 14 249 248 235 255 237 89 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 164 245 244 46 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 11 59 77 51 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 61 70 9 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

+ 224 - 0
patina-unaccel/src/lib.rs

@@ -0,0 +1,224 @@
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use patina::prelude::*;
+use patina::geom::*;
+use patina::platform as pp;
+
+/// ABGR32 surface (assumes little-endian CPU)
+///
+/// Colourmasks:
+/// - 0x000000ff: alpha
+/// - 0x0000ff00: blue
+/// - 0x00ff0000: green
+/// - 0xff000000: red
+pub struct UnaccelSurface {
+    data: Vec<u32>,
+    width: usize,
+    height: usize,
+}
+
+impl UnaccelSurface {
+    pub fn new(width: usize, height: usize) -> Self {
+        let mut data = vec![];
+        data.resize(width * height, 0);
+        Self {
+            width,
+            height,
+            data,
+        }
+    }
+
+    pub fn get_pixel(&self, x: usize, y: usize) -> u32 {
+        self.data[x + y * self.width]
+    }
+    pub fn set_pixel(&mut self, x: usize, y: usize, val: u32) {
+        self.data[x + y * self.width] = val;
+    }
+
+    pub fn raw_argb32_data(&self) -> &[u8] {
+        let buf = self.data.as_slice().as_ptr();
+        let buflen = self.data.len() * 4;
+        unsafe { std::slice::from_raw_parts(buf as *const u8, buflen) }
+    }
+}
+
+impl pp::render::Surface for UnaccelSurface {
+    fn size(&self) -> IVector {
+        IVector::new(self.width as i32, self.height as i32)
+    }
+
+    type Painter<'l> = UnaccelPainter<'l>;
+    fn painter(&mut self) -> Option<Self::Painter<'_>> {
+        Some(UnaccelPainter { surf: self })
+    }
+}
+
+pub struct UnaccelPainter<'l> {
+    surf: &'l mut UnaccelSurface,
+}
+
+impl<'l> pp::render::Painter for UnaccelPainter<'l> {
+    type Surface = UnaccelSurface;
+
+    fn blit(&mut self, source: &Self::Surface, source_area: IRect, target: IPoint) {
+        for y in 0..source_area.height() {
+            for x in 0..source_area.width() {
+                let data = source.get_pixel(
+                    (source_area.left() + x) as usize,
+                    (source_area.top() + y) as usize,
+                );
+                let tx = x + target.x;
+                let ty = y + target.y;
+                if tx < 0 || ty < 0 || tx >= self.surf.width as i32 || ty >= self.surf.height as i32
+                {
+                    continue;
+                } else {
+                    self.surf.set_pixel(tx as usize, ty as usize, data);
+                }
+            }
+        }
+    }
+
+    fn fill(&mut self, area: IRect, col: pp::render::Colour) {
+        let rgba = col.to_rgba();
+        for yoff in 0..area.height() {
+            for xoff in 0..area.width() {
+                self.surf.set_pixel(
+                    (xoff + area.left()) as usize,
+                    (yoff + area.top()) as usize,
+                    rgba,
+                );
+            }
+        }
+    }
+    fn outline_rect(&mut self, rect: IRect, col: pp::render::Colour) {
+        todo!()
+    }
+}
+
+// text rendering via cosmic-text
+pub struct UnaccelText {
+    font: Rc<schrift_rs::Font>,
+    cached: RefCell<schrift_rs::CachedSchrift>,
+}
+
+impl UnaccelText {
+    pub fn new() -> Self {
+        let font =
+            schrift_rs::Font::load_from_file("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
+                .unwrap();
+        let cached = schrift_rs::CachedSchrift::new(
+            schrift_rs::Schrift::build(font.clone())
+                .with_scale(16.0)
+                .flag_y_downwards()
+                .build(),
+        );
+
+        Self {
+            font,
+            cached: cached.into(),
+        }
+    }
+}
+
+impl pp::TextInterface for UnaccelText {
+    type Surface = UnaccelSurface;
+
+    fn render_text<'l>(&'l self, what: &pp::text::TextBuffer, sz: f32) -> Self::Surface {
+        let line = what.line_iter().next().unwrap();
+
+        let mut sch = self.cached.borrow_mut();
+
+        let lm = sch.line_metrics();
+        let ascender_offset = lm.ascender;
+        let descender_offset = lm.descender;
+
+        // two passes: first, compute the needed width
+        let mut cumulative_width = 0;
+        // let mut max_height = 0;
+        let glyphs = line
+            .chars()
+            .map(|v| sch.glyph_lookup(v))
+            .collect::<Vec<_>>();
+        for glyph in glyphs.iter() {
+            let gm = sch.glyph_metrics(*glyph);
+            cumulative_width += gm.advance.round() as usize;
+        }
+        // add one pixel of padding
+        cumulative_width += 1;
+        // use ascender and descender sizes for image height, with one pixel of padding
+        let height = (ascender_offset.abs().round() + descender_offset.abs().round() + 1.0) as usize;
+
+        let mut surf = UnaccelSurface::new(cumulative_width, height);
+
+        // second pass: copy the glyph images over
+        let mut cursor = 0;
+        let mut last_glyph = None;
+        for glyph in glyphs.iter() {
+            let kerning_offset = match last_glyph.take() {
+                Some(lglyph) => {
+                    let gk = sch.glyph_kerning(lglyph, *glyph);
+                    (gk.x_shift, gk.y_shift)
+                }
+                None => {
+                    (0., 0.)
+                }
+            };
+            let (left_bearing, advance, y_offset) = {
+                let gm = sch.glyph_metrics(*glyph);
+                (gm.left_bearing, gm.advance, gm.y_offset + (kerning_offset.1 + ascender_offset).round() as i32)
+            };
+            let img = sch.glyph_image(*glyph);
+            let x_offset = (left_bearing + kerning_offset.0).round() as i32;
+            for x in 0..img.1 {
+                for y in 0..img.2 {
+                    let intensity = img.0[(y * img.1 + x) as usize] as u32;
+                    surf.set_pixel(
+                        ((cursor + x).checked_add_signed(x_offset).unwrap()) as usize,
+                        (y as i32 + y_offset) as usize,
+                        // 0xffffff00 | intensity,
+                        intensity
+                        // 0x00ffffff | (intensity << 24)
+                    );
+                }
+            }
+            cursor += advance.round() as u32;
+            last_glyph = Some(*glyph);
+        }
+
+        surf
+    }
+}
+
+#[cfg(test)]
+#[test]
+fn test_unaccel_text() {
+    use pp::render::Surface;
+    use pp::TextInterface;
+    let ut = UnaccelText::new();
+
+    let tb = pp::text::TextBuffer::new("unaccelerated text rendering ahoy");
+
+    let surf = ut.render_text(&tb, 16.0);
+    println!("{:?}", surf.size());
+
+    let mut pgm = String::new();
+    pgm.push_str("P2\n");
+    pgm.push_str(format!("{} {}\n", surf.size().x, surf.size().y).as_str());
+    pgm.push_str("255\n");
+    for y in 0..surf.size().y {
+        for x in 0..surf.size().x {
+            pgm.push_str(format!(" {}", surf.get_pixel(x as usize, y as usize) & 0xff).as_str());
+        }
+        pgm.push('\n');
+    }
+
+    std::fs::write("render_output.pgm", pgm).unwrap();
+
+    // surf
+
+    // ductr::AnymapImage::ppm(data, 255, height, width);
+
+    // let rendered = write_as_ascii("unaccelerated.ppm").unwrap();
+}

+ 1 - 0
patina/Cargo.toml

@@ -7,6 +7,7 @@ edition = "2021"
 
 [dependencies]
 log = "0.4"
+jumprope = "1.1"
 
 [dev-dependencies]
 patina-sdl = { path = "../patina-sdl/" }

+ 21 - 10
patina/examples/sdl_plain.rs

@@ -1,12 +1,11 @@
 // use patina::prelude::*;
 
-use patina::{
-    layout::SizePolicy,
-    widget::Widget,
-};
+use patina::{layout::SizePolicy, widget::Widget};
+
+use patina_sdl::SDLPlatformSpec as PS;
 
 struct SimpleRoot {
-    frame: patina::widget::Frame<Self>,
+    frame: patina::widget::Frame<PS, Self>,
 }
 
 #[derive(Debug)]
@@ -25,9 +24,9 @@ impl patina::component::Component for SimpleRoot {
     }
 }
 
-impl patina::UIComponent for SimpleRoot {
+impl patina::UIComponent<PS> for SimpleRoot {
     fn build(bc: &patina::widget::BuildContext) -> Self {
-        let mut frame = patina::widget::Frame::<Self>::new(bc);
+        let mut frame = patina::widget::Frame::<PS, Self>::new(bc);
         frame
             .layout_node_mut()
             .set_width_policy(SizePolicy {
@@ -42,8 +41,20 @@ impl patina::UIComponent for SimpleRoot {
             })
             .set_label("frame");
 
-        let mut label = patina::widget::Label::<Self>::new(bc);
-        label.layout_node_mut().set_height_policy(SizePolicy { minimum: 20, desired: 20, slack_weight: 0 }).set_width_policy(SizePolicy { minimum: 10, desired: 100, slack_weight: 1 }).set_label("label");
+        let mut label = patina::widget::Label::<PS, Self>::new(bc);
+        label
+            .layout_node_mut()
+            .set_height_policy(SizePolicy {
+                minimum: 20,
+                desired: 20,
+                slack_weight: 0,
+            })
+            .set_width_policy(SizePolicy {
+                minimum: 10,
+                desired: 100,
+                slack_weight: 1,
+            })
+            .set_label("label");
         frame.set_child(Box::new(label));
 
         Self { frame }
@@ -55,7 +66,7 @@ impl patina::UIComponent for SimpleRoot {
             _ => None,
         }
     }
-    type RootWidget = patina::widget::Frame<Self>;
+    type RootWidget = patina::widget::Frame<PS, Self>;
     fn root_widget(&self) -> &Self::RootWidget {
         &self.frame
     }

+ 48 - 9
patina/src/geom.rs

@@ -1,3 +1,18 @@
+//! Geometry routines
+//!
+//! ### Coordinate system
+//!
+//! This module assumes a screen-like coordinate system:
+//! ```text
+//!           -y
+//!            ^
+//!            |
+//!     -x <---+---> +x
+//!            |
+//!            v
+//!           +y
+//! ```
+
 pub type IVector = Vector<i32>;
 pub type IPoint = Point<i32>;
 pub type IRect = Rect<i32>;
@@ -24,6 +39,7 @@ pub trait GeomField:
     + PartialOrd
 {
     const ZERO: Self;
+    const ONE: Self;
 
     fn min(&self, other: Self) -> Self {
         if *self < other {
@@ -44,10 +60,12 @@ pub trait GeomField:
 
 impl GeomField for i32 {
     const ZERO: Self = 0;
+    const ONE: Self = 1;
 }
 
 impl GeomField for f32 {
     const ZERO: Self = 0.;
+    const ONE: Self = 1.;
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
@@ -61,6 +79,10 @@ impl<GF: GeomField> Point<GF> {
         x: GF::ZERO,
         y: GF::ZERO,
     };
+
+    pub fn new(x: GF, y: GF) -> Self {
+        Self { x, y }
+    }
 }
 
 impl<GF: GeomField> Default for Point<GF> {
@@ -100,6 +122,10 @@ impl<GF: GeomField> Vector<GF> {
         x: GF::ZERO,
         y: GF::ZERO,
     };
+
+    pub fn new(x: GF, y: GF) -> Self {
+        Self { x, y }
+    }
 }
 
 impl<GF: GeomField> std::ops::Add for Vector<GF> {
@@ -176,19 +202,36 @@ impl<GF: GeomField> Rect<GF> {
         }
     }
 
-    pub fn ll(&self) -> Point<GF> {
-        self.base
+    pub fn left(&self) -> GF {
+        self.base.x
+    }
+    pub fn right(&self) -> GF {
+        self.base.x + self.size.x - GF::ONE
+    }
+    pub fn top(&self) -> GF {
+        self.base.y
+    }
+    pub fn bottom(&self) -> GF {
+        self.base.y + self.size.y - GF::ONE
     }
 
-    pub fn ul(&self) -> Point<GF> {
+    pub fn ll(&self) -> Point<GF> {
         self.base
             + Vector {
                 x: GF::ZERO,
-                y: self.size.y,
+                y: self.size.y - GF::ONE,
             }
     }
 
-    pub fn lr(&self) -> Point<GF> {
+    pub fn tl(&self) -> Point<GF> {
+        self.base
+    }
+
+    pub fn br(&self) -> Point<GF> {
+        self.extent()
+    }
+
+    pub fn tr(&self) -> Point<GF> {
         self.base
             + Vector {
                 x: self.size.x,
@@ -196,10 +239,6 @@ impl<GF: GeomField> Rect<GF> {
             }
     }
 
-    pub fn ur(&self) -> Point<GF> {
-        self.extent()
-    }
-
     pub fn base(&self) -> Point<GF> {
         self.base
     }

+ 14 - 5
patina/src/layout.rs

@@ -264,16 +264,25 @@ fn dump_node_tree_helper(lna: LayoutNodeAccess, indent: usize, out: &mut String)
         Some(v) => v,
         None => "<anon>",
     });
-    out.push_str(format!(") [wpol: {}/{}/{} hpol: {}/{}/{} behaviour: {:?}]\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).as_str());
+    out.push_str(
+        format!(
+            ") [wpol: {}/{}/{} hpol: {}/{}/{} behaviour: {:?}]\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
+        )
+        .as_str(),
+    );
     out.push_str(ind.as_str());
     out.push_str("        ");
     out.push_str(format!("cached rect: {:?}\n", lna.cached_rect()).as_str());
 
     for child in lna.child_iter() {
-        dump_node_tree_helper(child, indent+1, out);
+        dump_node_tree_helper(child, indent + 1, out);
     }
 }
 

+ 2 - 2
patina/src/layout/arr.rs

@@ -96,7 +96,7 @@ impl ArrangementCalculator for LineArrangement {
                 IRect::new_from_size(
                     IPoint {
                         x: inside.base().x,
-                        y: inside.ul().y + last_offset,
+                        y: inside.tl().y + last_offset,
                     },
                     IVector {
                         x: inside.width(),
@@ -106,7 +106,7 @@ impl ArrangementCalculator for LineArrangement {
             } else {
                 IRect::new_from_size(
                     IPoint {
-                        x: inside.ul().x + last_offset,
+                        x: inside.tl().x + last_offset,
                         y: inside.base().y,
                     },
                     IVector {

+ 0 - 1
patina/src/layout/calc.rs

@@ -149,6 +149,5 @@ mod test {
                 IVector { x: 2, y: 2 }
             ))
         );
-
     }
 }

+ 10 - 9
patina/src/lib.rs

@@ -1,5 +1,3 @@
-use std::ops::{Deref, DerefMut};
-
 use layout::LayoutNodeAccess;
 use platform::{
     render::{Painter, Surface},
@@ -23,27 +21,29 @@ pub enum UIControlMsg {
     Terminate,
 }
 
-pub trait UIComponent: Sized + component::Component<ParentMsg = UIControlMsg> {
+pub trait UIComponent<P: platform::PlatformSpec>:
+    Sized + component::Component<ParentMsg = UIControlMsg>
+{
     fn build(bc: &widget::BuildContext) -> Self;
 
     fn map_ui_event(&self, uievent: platform::event::UIEvent) -> Option<Self::Msg>;
 
-    type RootWidget: widget::Widget<Self>;
+    type RootWidget: widget::Widget<P, Self>;
     fn root_widget(&self) -> &Self::RootWidget;
     fn root_widget_mut(&mut self) -> &mut Self::RootWidget;
 }
 
-pub struct UI<PT: platform::PlatformTuple, UIC: UIComponent> {
+pub struct UI<P: platform::PlatformSpec, UIC: UIComponent<P>> {
     layout_cache: std::rc::Rc<layout::LayoutCache>,
     root_layout_node: layout::LayoutNode,
-    platform: PT,
+    platform: P,
     ui_component: UIC,
     input_state: input::InputState,
     ui_running: bool,
 }
 
-impl<PT: platform::PlatformTuple, UIC: UIComponent> UI<PT, UIC> {
-    pub fn new(pt: PT) -> Self {
+impl<P: platform::PlatformSpec, UIC: UIComponent<P>> UI<P, UIC> {
+    pub fn new(pt: P) -> Self {
         let lcache = layout::LayoutCache::new();
         let bc = widget::BuildContext {
             lcache: lcache.clone(),
@@ -128,10 +128,11 @@ impl<PT: platform::PlatformTuple, UIC: UIComponent> UI<PT, UIC> {
             )));
 
             // now render as needed
-            self.ui_component.root_widget().render(painter.as_mut(), ti);
+            self.ui_component.root_widget().render(&mut painter, ti);
         } else {
             log::warn!("Could not draw UI: no painter available");
         }
+        ri.submit();
     }
 
     pub fn wait(&mut self) {

+ 24 - 7
patina/src/platform.rs

@@ -1,8 +1,10 @@
 pub mod render;
 
 pub trait RenderInterface {
-    type RenderTarget: render::Surface;
-    fn render_target_mut(&mut self) -> &mut Self::RenderTarget;
+    type Surface: render::Surface;
+    fn render_target_mut(&mut self) -> &mut Self::Surface;
+
+    fn submit(&mut self);
 }
 
 pub mod event;
@@ -12,14 +14,19 @@ pub trait EventInterface {
     fn wait_for_event(&mut self);
 }
 
+pub mod text;
+
 pub trait TextInterface {
-    fn render_text(&self, what: &str, sz: f32) -> Box<dyn render::Surface + '_>;
+    type Surface: render::Surface;
+
+    fn render_text<'l>(&'l self, buffer: &text::TextBuffer, sz: f32) -> Self::Surface;
 }
 
-pub trait PlatformTuple {
-    type Render: RenderInterface;
+pub trait PlatformSpec {
+    type Surface: render::Surface;
+    type Render: RenderInterface<Surface = Self::Surface>;
     type Event: EventInterface;
-    type Text: TextInterface;
+    type Text: TextInterface<Surface = Self::Surface>;
 
     fn render(&self) -> &Self::Render;
     fn render_mut(&mut self) -> &mut Self::Render;
@@ -34,7 +41,17 @@ pub trait PlatformTuple {
     fn render_text_mut(&mut self) -> (&mut Self::Render, &mut Self::Text);
 }
 
-impl<RI: RenderInterface, EI: EventInterface, TI: TextInterface> PlatformTuple for (RI, EI, TI) {
+pub type PlatformPainter<'p, P> =
+    <<<P as PlatformSpec>::Render as RenderInterface>::Surface as render::Surface>::Painter<'p>;
+
+impl<
+        'l,
+        RI: 'l + RenderInterface,
+        EI: 'l + EventInterface,
+        TI: 'l + TextInterface<Surface = RI::Surface>,
+    > PlatformSpec for (RI, EI, TI)
+{
+    type Surface = RI::Surface;
     type Render = RI;
     type Event = EI;
     type Text = TI;

+ 11 - 10
patina/src/platform/render.rs

@@ -34,6 +34,10 @@ impl Colour {
     pub fn a(&self) -> u8 {
         self.comp[3]
     }
+
+    pub fn to_rgba(&self) -> u32 {
+        u32::from_le_bytes(self.comp)
+    }
 }
 
 pub struct ColourMask {
@@ -57,21 +61,18 @@ pub trait SurfaceFormat {
 }
 
 pub trait Surface {
-    // type Format: SurfaceFormat;
-
     fn size(&self) -> IVector;
 
-    fn painter(&mut self) -> Option<Box<dyn Painter + '_>>;
+    type Painter<'l>: Painter<Surface = Self> + 'l
+    where
+        Self: 'l;
+    fn painter(&mut self) -> Option<Self::Painter<'_>>;
 }
 
 pub trait Painter {
-    fn blit(&mut self, source: &dyn Surface, source_area: IRect, target: IPoint);
+    type Surface: Surface;
+
+    fn blit(&mut self, source: &Self::Surface, source_area: IRect, target: IPoint);
     fn fill(&mut self, area: IRect, col: Colour);
     fn outline_rect(&mut self, rect: IRect, col: Colour);
 }
-
-/*pub struct DefaultPainter<TS: Surface> {
-    surf: TS,
-}
-
-impl<TS: Surface> */

+ 16 - 0
patina/src/platform/text.rs

@@ -0,0 +1,16 @@
+#[derive(Default)]
+pub struct TextBuffer {
+    lines: Vec<String>,
+}
+
+impl TextBuffer {
+    pub fn new(line: &str) -> Self {
+        Self {
+            lines: vec![line.to_string()],
+        }
+    }
+
+    pub fn line_iter(&self) -> impl Iterator<Item = &str> {
+        self.lines.iter().map(String::as_str)
+    }
+}

+ 7 - 13
patina/src/widget.rs

@@ -2,33 +2,27 @@ use crate::{
     component::Component,
     input::InputState,
     layout::{LayoutCache, LayoutNode, LayoutNodeAccess},
-    platform::{render::Painter, TextInterface},
+    platform::{
+        render::{Painter, Surface},
+        PlatformPainter, PlatformSpec, RenderInterface, TextInterface,
+    },
 };
 
+mod button;
 mod frame;
 mod group;
-mod button;
 mod label;
 
 pub use frame::Frame;
 pub use group::PlainGroup;
 pub use label::Label;
 
-#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
-pub struct WidgetID(usize);
-static NEXT_WIDGET_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(1);
-impl WidgetID {
-    pub fn new() -> Self {
-        Self(NEXT_WIDGET_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst))
-    }
-}
-
-pub trait Widget<C: Component>: 'static {
+pub trait Widget<P: PlatformSpec, C: Component> {
     fn poll(&mut self, input_state: Option<&InputState>) -> Vec<C::Msg>;
 
     fn layout_node(&self) -> LayoutNodeAccess;
     fn layout_node_mut(&mut self) -> &mut LayoutNode;
-    fn render(&self, painter: &mut dyn Painter, ti: &dyn TextInterface);
+    fn render(&self, painter: &mut PlatformPainter<'_, P>, ti: &P::Text);
 }
 
 pub struct BuildContext {

+ 1 - 0
patina/src/widget/button.rs

@@ -0,0 +1 @@
+

+ 21 - 10
patina/src/widget/frame.rs

@@ -1,18 +1,21 @@
 use crate::{
     component::Component,
     layout::{LayoutNode, LayoutNodeAccess, LayoutNodeContainer},
-    platform::{render::{Colour, Painter}, TextInterface},
+    platform::{
+        render::{Colour, Painter},
+        PlatformPainter, PlatformSpec,
+    },
 };
 
 use super::{BuildContext, Widget};
 
-pub struct Frame<C: Component> {
+pub struct Frame<P: PlatformSpec, C: Component> {
     layout: LayoutNode,
-    child: Option<Box<dyn Widget<C>>>,
+    child: Option<Box<dyn Widget<P, C>>>,
     _ghost: std::marker::PhantomData<C>,
 }
 
-impl<C: Component> Frame<C> {
+impl<P: PlatformSpec, C: Component> Frame<P, C> {
     pub fn new(bc: &BuildContext) -> Self {
         Self {
             layout: LayoutNode::new(bc.lcache.clone()),
@@ -21,25 +24,33 @@ impl<C: Component> Frame<C> {
         }
     }
 
-    pub fn set_child(&mut self, child: Box<dyn Widget<C>>) {
+    pub fn set_child(&mut self, child: Box<dyn Widget<P, C>>) {
         self.child = Some(child);
         self.layout.relayout();
     }
 }
 
-impl<C: Component> LayoutNodeContainer for Frame<C> {
+impl<P: PlatformSpec, C: Component> LayoutNodeContainer for Frame<P, C> {
     fn layout_node(&self) -> &LayoutNode {
         &self.layout
     }
     fn layout_child(&self, ndx: usize) -> Option<LayoutNodeAccess> {
-        if ndx == 0 { Some(self.child.as_ref()?.layout_node()) } else { None }
+        if ndx == 0 {
+            Some(self.child.as_ref()?.layout_node())
+        } else {
+            None
+        }
     }
     fn layout_child_count(&self) -> usize {
-        if self.child.is_some() { 1 } else { 0 }
+        if self.child.is_some() {
+            1
+        } else {
+            0
+        }
     }
 }
 
-impl<C: Component> Widget<C> for Frame<C> {
+impl<P: PlatformSpec, C: Component> Widget<P, C> for Frame<P, C> {
     fn poll(&mut self, _input_state: Option<&crate::input::InputState>) -> Vec<C::Msg> {
         vec![]
     }
@@ -49,7 +60,7 @@ impl<C: Component> Widget<C> for Frame<C> {
     fn layout_node_mut(&mut self) -> &mut LayoutNode {
         &mut self.layout
     }
-    fn render(&self, painter: &mut dyn Painter, ti: &dyn TextInterface) {
+    fn render(&self, painter: &mut PlatformPainter<P>, ti: &P::Text) {
         let rect = self.layout.cached_rect().unwrap();
         painter.fill(rect, Colour::new_rgb(50, 42, 0));
         if let Some(child) = self.child.as_ref() {

+ 8 - 13
patina/src/widget/group.rs

@@ -1,20 +1,15 @@
 use crate::{
-    layout::{LayoutChildIter, LayoutNode, LayoutNodeAccess, LayoutNodeContainer}, platform::{render::Painter, TextInterface}, Component, Widget
+    layout::{LayoutChildIter, LayoutNode, LayoutNodeAccess, LayoutNodeContainer},
+    platform::{render::Painter, PlatformPainter, PlatformSpec, TextInterface},
+    Component, Widget,
 };
 
-pub struct PlainGroup<C: Component> {
+pub struct PlainGroup<P: PlatformSpec, C: Component> {
     lnode: LayoutNode,
-    children: Vec<Box<dyn Widget<C>>>,
+    children: Vec<Box<dyn Widget<P, C>>>,
 }
 
-impl<C: Component> std::ops::Deref for PlainGroup<C> {
-    type Target = LayoutNode;
-    fn deref(&self) -> &Self::Target {
-        &self.lnode
-    }
-}
-
-impl<C: Component> LayoutNodeContainer for PlainGroup<C> {
+impl<P: PlatformSpec, C: Component> LayoutNodeContainer for PlainGroup<P, C> {
     fn layout_node(&self) -> &LayoutNode {
         &self.lnode
     }
@@ -28,7 +23,7 @@ impl<C: Component> LayoutNodeContainer for PlainGroup<C> {
     }
 }
 
-impl<C: Component> Widget<C> for PlainGroup<C> {
+impl<P: PlatformSpec, C: Component> Widget<P, C> for PlainGroup<P, C> {
     fn layout_node(&self) -> LayoutNodeAccess {
         LayoutNodeAccess::new(self)
     }
@@ -41,7 +36,7 @@ impl<C: Component> Widget<C> for PlainGroup<C> {
     ) -> Vec<<C as Component>::Msg> {
         vec![]
     }
-    fn render(&self, painter: &mut dyn Painter, ti: &dyn TextInterface) {
+    fn render(&self, painter: &mut PlatformPainter<P>, ti: &P::Text) {
         for child in self.children.iter() {
             child.render(painter, ti);
         }

+ 22 - 8
patina/src/widget/label.rs

@@ -1,23 +1,32 @@
-use crate::{layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode}, platform::{render::{Colour, Painter}, TextInterface}, Component, Widget};
+use crate::{
+    geom::{IPoint, IRect}, layout::{LayoutNode, LayoutNodeAccess, LeafLayoutNode}, platform::{
+        render::{Colour, Painter, Surface}, text::TextBuffer, PlatformPainter, PlatformSpec, TextInterface
+    }, Component, Widget
+};
 
 use super::BuildContext;
 
-pub struct Label<C: Component> {
+pub struct Label<P: PlatformSpec, C: Component> {
     lnode: LeafLayoutNode,
-    _ghost: std::marker::PhantomData<C>,
+    buffer: TextBuffer,
+    _ghost: std::marker::PhantomData<(P, C)>,
 }
 
-impl<C: Component> Label<C> {
+impl<P: PlatformSpec, C: Component> Label<P, C> {
     pub fn new(bc: &BuildContext) -> Self {
         Self {
             lnode: LayoutNode::new(bc.lcache.clone()).into(),
+            buffer: TextBuffer::new("example text here"),
             _ghost: Default::default(),
         }
     }
 }
 
-impl<C: Component> Widget<C> for Label<C> {
-    fn poll(&mut self, input_state: Option<&crate::input::InputState>) -> Vec<<C as Component>::Msg> {
+impl<P: PlatformSpec, C: Component> Widget<P, C> for Label<P, C> {
+    fn poll(
+        &mut self,
+        input_state: Option<&crate::input::InputState>,
+    ) -> Vec<<C as Component>::Msg> {
         vec![]
     }
 
@@ -28,7 +37,12 @@ impl<C: Component> Widget<C> for Label<C> {
         &mut self.lnode
     }
 
-    fn render(&self, painter: &mut dyn Painter, _ti: &dyn TextInterface) {
-        painter.fill(self.layout_node().cached_rect().unwrap(), Colour::new_rgb(64, 64, 64));
+    fn render(&self, painter: &mut PlatformPainter<P>, ti: &P::Text) {
+        painter.fill(
+            self.layout_node().cached_rect().unwrap(),
+            Colour::new_rgb(64, 64, 64),
+        );
+        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());
     }
 }