Browse Source

Add make_opinionated_ui, to cut down on some boilerplate.

Kestrel 5 months ago
parent
commit
b4fb9b1cf2
17 changed files with 290 additions and 755 deletions
  1. 2 0
      Cargo.toml
  2. 25 39
      examples/simplest.rs
  3. 1 1
      src/component.rs
  4. 0 270
      src/geom.rs
  5. 4 0
      src/layout.rs
  6. 10 3
      src/layout/arr.rs
  7. 5 7
      src/layout/calc.rs
  8. 2 213
      src/lib.rs
  9. 0 89
      src/platform.rs
  10. 0 11
      src/platform/event.rs
  11. 0 78
      src/platform/render.rs
  12. 0 16
      src/platform/text.rs
  13. 15 0
      src/text.rs
  14. 197 0
      src/ui.rs
  15. 12 4
      src/widget.rs
  16. 0 14
      src/widget/window.rs
  17. 17 10
      src/window.rs

+ 2 - 0
Cargo.toml

@@ -11,8 +11,10 @@ jumprope = "1.1"
 
 # for window initialization
 winit = { version = "0.30.0", default-features = false, features = ["x11", "wayland", "rwh_06"] }
+
 # for rendering
 softbuffer = "0.4.2"
 tiny-skia = "0.11"
+fontdue = "0.9.0"
 
 [dev-dependencies]

+ 25 - 39
examples/simplest.rs

@@ -1,24 +1,40 @@
 use patina::{
-    prelude::*,
-    window::{Window, WindowComponent},
+    prelude::*, ui::UIControlMsg, window::WindowComponent
 };
 
 struct SimpleWindow {
     widget: <Self as WindowComponent>::RootWidget,
 }
 
+impl SimpleWindow {
+    fn new(uih: &patina::ui::UIHandle) -> Self {
+        Self { widget: patina::widget::Spacer::new(uih) }
+    }
+}
+
+enum SimpleWindowMsg {
+    Close,
+}
+
 impl Component for SimpleWindow {
-    type ParentMsg = ();
-    type Msg = ();
+    type ParentMsg = UIControlMsg;
+    type Msg = SimpleWindowMsg;
 
     fn process(&mut self, msg: Self::Msg) -> Vec<Self::ParentMsg> {
-        vec![()]
+        match msg {
+            SimpleWindowMsg::Close => vec![UIControlMsg::Terminate],
+        }
     }
 }
 
 impl WindowComponent for SimpleWindow {
     fn map_window_event(&self, we: patina::window::WindowEvent) -> Option<Self::Msg> {
-        Some(())
+        match we {
+            patina::window::WindowEvent::CloseRequested => {
+                Some(SimpleWindowMsg::Close)
+            },
+            _ => None
+        }
     }
 
     type RootWidget = patina::widget::Spacer<Self>;
@@ -30,38 +46,8 @@ impl WindowComponent for SimpleWindow {
     }
 }
 
-struct Simplest {
-    main_window: Option<Window<SimpleWindow>>,
-}
-
-impl Component for Simplest {
-    type ParentMsg = patina::UIControlMsg;
-    type Msg = ();
-
-    fn process(&mut self, msg: Self::Msg) -> Vec<Self::ParentMsg> {
-        vec![patina::UIControlMsg::Terminate]
-    }
-}
-
-impl patina::UIComponent for Simplest {
-    fn init<'l>(&mut self, mut ui_handle: patina::UIHandle<'_>) {
-        let mut widget = patina::widget::Spacer::new(&ui_handle);
-        widget.layout_node_mut().set_width_policy(patina::layout::SizePolicy { minimum: 0, desired: 0, slack_weight: 1 });
-        widget.layout_node_mut().set_height_policy(patina::layout::SizePolicy { minimum: 0, desired: 0, slack_weight: 1 });
-
-        self.main_window = Some(ui_handle.window_builder().build(|uih| SimpleWindow {
-            widget
-        }));
-    }
-    fn poll(&mut self) -> Vec<patina::UIControlMsg> {
-        let Some(mw) = self.main_window.as_mut() else { return vec![] };
-        let evts = mw.poll();
-        self.process_all(evts.into_iter())
-    }
-}
-
 fn main() {
-    let ui = patina::UI::<Simplest>::new(Simplest { main_window: None });
-
-    ui.run();
+    patina::ui::make_opinionated_ui(|uih| {
+        SimpleWindow::new(uih)
+    }).run();
 }

+ 1 - 1
src/component.rs

@@ -18,7 +18,7 @@ impl<Msg> ComponentStorage<Msg> {
     }
 }
 
-pub trait Component: 'static {
+pub trait Component {
     type ParentMsg;
     type Msg;
 

+ 0 - 270
src/geom.rs

@@ -1,270 +0,0 @@
-//! 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>;
-
-pub type FVector = Vector<f32>;
-pub type FPoint = Point<f32>;
-pub type FRect = Rect<f32>;
-
-pub mod prelude {
-    pub use super::{FPoint, FRect, FVector};
-    pub use super::{IPoint, IRect, IVector};
-}
-
-pub trait GeomField:
-    Sized
-    + Copy
-    + std::ops::Add<Self, Output = Self>
-    + std::ops::AddAssign<Self>
-    + std::ops::Neg<Output = Self>
-    + std::ops::Sub<Self, Output = Self>
-    + std::ops::Mul<Self, Output = Self>
-    + std::ops::MulAssign<Self>
-    + PartialEq
-    + PartialOrd
-{
-    const ZERO: Self;
-    const ONE: Self;
-
-    fn min(&self, other: Self) -> Self {
-        if *self < other {
-            *self
-        } else {
-            other
-        }
-    }
-
-    fn max(&self, other: Self) -> Self {
-        if *self > other {
-            *self
-        } else {
-            other
-        }
-    }
-}
-
-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)]
-pub struct Point<GF: GeomField> {
-    pub x: GF,
-    pub y: GF,
-}
-
-impl<GF: GeomField> Point<GF> {
-    pub const ORIGIN: Self = Self {
-        x: GF::ZERO,
-        y: GF::ZERO,
-    };
-
-    pub fn new(x: GF, y: GF) -> Self {
-        Self { x, y }
-    }
-}
-
-impl<GF: GeomField> Default for Point<GF> {
-    fn default() -> Self {
-        Self::ORIGIN
-    }
-}
-
-impl<GF: GeomField> std::ops::Add<Vector<GF>> for Point<GF> {
-    type Output = Self;
-    fn add(self, rhs: Vector<GF>) -> Self::Output {
-        Self {
-            x: self.x + rhs.x,
-            y: self.x + rhs.x,
-        }
-    }
-}
-
-impl<GF: GeomField> std::ops::Sub for Point<GF> {
-    type Output = Vector<GF>;
-    fn sub(self, rhs: Self) -> Self::Output {
-        Vector {
-            x: self.x - rhs.x,
-            y: self.y - rhs.y,
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
-pub struct Vector<GF: GeomField> {
-    pub x: GF,
-    pub y: GF,
-}
-
-impl<GF: GeomField> Vector<GF> {
-    pub const ZERO: Self = Self {
-        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> {
-    type Output = Self;
-    fn add(self, rhs: Self) -> Self::Output {
-        Self {
-            x: self.x + rhs.x,
-            y: self.y + rhs.y,
-        }
-    }
-}
-
-impl<GF: GeomField> std::ops::Add<Point<GF>> for Vector<GF> {
-    type Output = Point<GF>;
-    fn add(self, rhs: Point<GF>) -> Self::Output {
-        Point {
-            x: self.x + rhs.x,
-            y: self.y + rhs.y,
-        }
-    }
-}
-
-impl<GF: GeomField> std::ops::Sub for Vector<GF> {
-    type Output = Vector<GF>;
-    fn sub(self, rhs: Self) -> Self::Output {
-        Vector {
-            x: self.x - rhs.x,
-            y: self.y - rhs.y,
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)]
-pub struct Rect<GF: GeomField> {
-    pub base: Point<GF>,
-    pub size: Vector<GF>,
-}
-
-impl<GF: GeomField> Rect<GF> {
-    pub fn new_from_size(pt: Point<GF>, sz: Vector<GF>) -> Self {
-        Self { base: pt, size: sz }
-    }
-
-    pub fn new_from_points(p1: Point<GF>, p2: Point<GF>) -> Self {
-        let xmin = p1.x.min(p2.x);
-        let ymin = p1.y.min(p2.y);
-        let xmax = p1.x.max(p2.x);
-        let ymax = p1.y.max(p2.y);
-        Self {
-            base: Point { x: xmin, y: ymin },
-            size: Vector {
-                x: xmax - xmin,
-                y: ymax - ymin,
-            },
-        }
-    }
-
-    pub fn width(&self) -> GF {
-        self.size.x
-    }
-
-    pub fn height(&self) -> GF {
-        self.size.y
-    }
-
-    pub fn normalize(&mut self) {
-        if self.size.x < GF::ZERO {
-            self.base.x += self.size.x;
-            self.size.x = -self.size.x;
-        }
-        if self.size.y < GF::ZERO {
-            self.base.y += self.size.y;
-            self.size.y = -self.size.y;
-        }
-    }
-
-    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 ll(&self) -> Point<GF> {
-        self.base
-            + Vector {
-                x: GF::ZERO,
-                y: self.size.y - GF::ONE,
-            }
-    }
-
-    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,
-                y: GF::ZERO,
-            }
-    }
-
-    pub fn base(&self) -> Point<GF> {
-        self.base
-    }
-
-    pub fn extent(&self) -> Point<GF> {
-        self.base + self.size
-    }
-
-    pub fn size(&self) -> Vector<GF> {
-        self.size
-    }
-
-    pub fn intersect(&self, other: &Self) -> Option<Self> {
-        /*
-        Some(Self {
-            minpoint: Point { x: self.minpoint.x.max(other.minpoint.x), y: self.minpoint.y.max(other.minpoint.y) },
-            maxpoint: Point { x: self.maxpoint.x.min(other.maxpoint.x), y: self.maxpoint.y.min(other.maxpoint.y) },
-        })
-        */
-        None
-    }
-
-    pub fn contains(&self, pt: Point<GF>) -> bool {
-        (self.base.x <= pt.x)
-            && (pt.x < self.extent().x)
-            && (self.base.y <= pt.y)
-            && (pt.y < self.extent().y)
-    }
-}

+ 4 - 0
src/layout.rs

@@ -23,6 +23,10 @@ pub struct SizePolicy {
 }
 
 impl SizePolicy {
+    pub fn expands(weight: usize) -> Self {
+        Self { minimum: 0, desired: 0, slack_weight: weight }
+    }
+
     pub fn max(self, rhs: SizePolicy) -> SizePolicy {
         Self {
             minimum: self.minimum.max(rhs.minimum),

+ 10 - 3
src/layout/arr.rs

@@ -92,14 +92,21 @@ 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).unwrap()
+                IntRect::from_xywh(
+                    inside.left(),
+                    inside.top() + last_offset,
+                    inside.width(),
+                    (offset - last_offset) as u32,
+                )
+                .unwrap()
             } else {
                 IntRect::from_xywh(
                     inside.left() + last_offset,
                     inside.top(),
                     (offset - last_offset) as u32,
-                    inside.height()
-                ).unwrap()
+                    inside.height(),
+                )
+                .unwrap()
             };
 
             self.layout_step(child, crect);

+ 5 - 7
src/layout/calc.rs

@@ -60,12 +60,10 @@ fn arrangement_pass(parent: Option<LayoutCacheKey>, node: LayoutNodeAccess, laye
 mod test {
     use tiny_skia::IntRect;
 
-    use crate::
-        layout::{
-            cache::LayoutCache, ChildArrangement, LayoutNode, LayoutNodeAccess,
-            LayoutNodeContainer, NodeBehaviour, SizePolicy,
-        }
-    ;
+    use crate::layout::{
+        cache::LayoutCache, ChildArrangement, LayoutNode, LayoutNodeAccess, LayoutNodeContainer,
+        NodeBehaviour, SizePolicy,
+    };
 
     use super::recalculate;
 
@@ -120,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: IntRect::from_xywh(1, 1, 2, 5).unwrap(),
         });
         root.node.child_arrangement = ChildArrangement::Column;
 

+ 2 - 213
src/lib.rs

@@ -1,8 +1,8 @@
 pub mod component;
-// pub mod geom;
 pub mod input;
 pub mod layout;
-// pub mod platform;
+pub mod text;
+pub mod ui;
 pub mod widget;
 pub mod window;
 
@@ -10,214 +10,3 @@ pub mod prelude {
     pub use crate::component::Component;
     pub use crate::widget::Widget;
 }
-use std::collections::HashMap;
-
-use prelude::*;
-
-#[derive(Debug)]
-pub enum UIControlMsg {
-    Terminate,
-}
-
-pub trait UIComponent: Sized + component::Component<ParentMsg = UIControlMsg> {
-    fn init(&mut self, ui_handle: UIHandle);
-    fn poll(&mut self) -> Vec<UIControlMsg>;
-    // fn build(bc: &widget::BuildContext) -> Self;
-    // fn map_ui_event(&self, uievent: platform::event::UIEvent) -> Option<Self::Msg>;
-}
-
-pub(crate) struct UIState {
-    pub(crate) layout_cache: std::rc::Rc<layout::LayoutCache>,
-    pub(crate) window_states:
-        HashMap<winit::window::WindowId, std::rc::Weak<dyn window::WindowStateAccess>>,
-}
-
-pub struct UIHandle<'l> {
-    state: &'l mut UIState,
-    eloop: &'l winit::event_loop::ActiveEventLoop,
-}
-
-impl<'l> UIHandle<'l> {
-    pub fn window_builder<'r>(&'r mut self) -> window::WindowBuilder<'r, 'l> {
-        window::WindowBuilder::new(self)
-    }
-
-    pub fn new_layout_node(&self) -> layout::LayoutNode {
-        layout::LayoutNode::new(self.state.layout_cache.clone())
-    }
-}
-
-pub struct UI<UIC: UIComponent> {
-    state: UIState,
-    event_loop: Option<winit::event_loop::EventLoop<()>>,
-    ui_component: UIC,
-    autoclose: bool,
-    initialized: bool,
-}
-
-impl<UIC: UIComponent> UI<UIC> {
-    pub fn new(uic: UIC) -> Self {
-        let lcache = layout::LayoutCache::new();
-
-        let eloop = winit::event_loop::EventLoop::builder().build().unwrap();
-        eloop.set_control_flow(winit::event_loop::ControlFlow::Wait);
-
-        Self {
-            state: UIState {
-                layout_cache: lcache,
-                window_states: Default::default(),
-            },
-            event_loop: Some(eloop),
-            // root_layout_node: layout::LayoutNode::new(lcache.clone()),
-            ui_component: uic,
-            autoclose: true,
-            initialized: false,
-        }
-    }
-
-    pub fn disable_autoclose(mut self) -> Self {
-        self.autoclose = false;
-        self
-    }
-
-    pub fn ui_component(&self) -> &UIC {
-        &self.ui_component
-    }
-    pub fn ui_component_mut(&mut self) -> &mut UIC {
-        &mut self.ui_component
-    }
-
-    /// Public helper function, delivers a message to the UI component and then processes whatever
-    /// control messages result.
-    pub fn deliver_msg(&mut self, msg: UIC::Msg) {
-        let cmsgs = self.ui_component.process(msg);
-        self.process_control_msgs(cmsgs.into_iter());
-    }
-
-    fn process_control_msgs(&mut self, msgs: impl Iterator<Item = UIControlMsg>) {
-        for cmsg in msgs {
-            match cmsg {
-                UIControlMsg::Terminate => {
-                    todo!()
-                    // self.ui_running = false;
-                }
-            }
-        }
-    }
-
-    pub fn update(&mut self) {
-        /*
-        let mut platform_events = vec![];
-        while let Some(evt) = self.platform.poll_event() {
-            match evt {
-                platform::event::WindowEvent::CloseRequest => {
-                    platform_events.extend(
-                        self.ui_component()
-                            .map_ui_event(platform::event::UIEvent::Shutdown)
-                            .into_iter(),
-                    );
-                }
-                _ => {}
-            }
-        }
-
-        // poll for state changes due to user input
-        let poll_msgs = self
-            .ui_component
-            .root_widget_mut()
-            .poll(Some(&self.input_state))
-            .into_iter();
-        let cmsgs = self
-            .ui_component
-            .process_all(platform_events.into_iter().chain(poll_msgs));
-        self.process_control_msgs(cmsgs.into_iter());
-
-        let tgt_size = self.platform.render_size();
-        self.root_layout_node
-            .set_behaviour(layout::NodeBehaviour::Fixed {
-                rect: geom::IRect::new_from_size(geom::IPoint::ORIGIN, tgt_size),
-            });
-        */
-
-        // first, make sure we can paint to the render target
-        /*
-        let (ri, ti) = self.platform.render_text_mut();
-        if let Some(mut painter) = ri.render_target_mut().painter() {
-            // perform layout as needed
-            layout::recalculate(LayoutNodeAccess::new(&layout::LinearAccess::new(
-                &self.root_layout_node,
-                self.ui_component.root_widget().layout_node(),
-            )));
-
-            // now render as needed
-            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) {
-        // self.platform.event_mut().wait_for_event();
-    }
-
-    pub fn run(mut self) {
-        let eloop = self.event_loop.take().unwrap();
-        eloop.run_app(&mut self).unwrap();
-    }
-
-    fn pump_events(&mut self, eloop: &winit::event_loop::ActiveEventLoop) {
-        let evts = self.ui_component.poll();
-        for evt in evts.into_iter() {
-            match evt {
-                UIControlMsg::Terminate => eloop.exit(),
-            }
-        }
-    }
-}
-
-impl<UIC: UIComponent> winit::application::ApplicationHandler<()> for UI<UIC> {
-    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
-        if !self.initialized {
-            self.ui_component.init(UIHandle {
-                eloop: event_loop,
-                state: &mut self.state,
-            });
-            self.initialized = true;
-        }
-    }
-
-    fn window_event(
-        &mut self,
-        event_loop: &winit::event_loop::ActiveEventLoop,
-        window_id: winit::window::WindowId,
-        event: winit::event::WindowEvent,
-    ) {
-        let Some(wsa) = self
-            .state
-            .window_states
-            .get(&window_id)
-            .map(std::rc::Weak::upgrade)
-            .flatten()
-        else {
-            println!("superfluous event: {event:?}");
-            return;
-        };
-
-        match event {
-            winit::event::WindowEvent::CloseRequested => {
-                wsa.push_event(window::WindowEvent::CloseRequested);
-                self.pump_events(event_loop);
-            }
-            winit::event::WindowEvent::Destroyed => {
-                self.state.window_states.remove(&window_id);
-            }
-            winit::event::WindowEvent::RedrawRequested => {
-                wsa.request_redraw();
-                self.pump_events(event_loop);
-            }
-            _ => (),
-        }
-    }
-}

+ 0 - 89
src/platform.rs

@@ -1,89 +0,0 @@
-use crate::{geom::IVector, Component};
-
-pub mod event;
-
-/*
-pub mod render;
-
-pub trait RenderInterface {
-    type Surface: render::Surface;
-    fn render_target_mut(&mut self) -> &mut Self::Surface;
-
-    fn submit(&mut self);
-}
-
-pub mod event;
-
-pub trait EventInterface {
-    fn poll_event(&mut self) -> Option<event::WindowEvent>;
-    fn wait_for_event(&mut self);
-}
-
-pub mod text;
-
-pub trait TextInterface {
-    type Surface: render::Surface;
-
-    fn render_text<'l>(&'l self, buffer: &text::TextBuffer, sz: f32) -> Self::Surface;
-}
-
-pub trait PlatformSpec {
-    type Surface: render::Surface;
-    type Render: RenderInterface<Surface = Self::Surface>;
-    type Event: EventInterface;
-    type Text: TextInterface<Surface = Self::Surface>;
-
-    fn render(&self) -> &Self::Render;
-    fn render_mut(&mut self) -> &mut Self::Render;
-
-    fn event(&self) -> &Self::Event;
-    fn event_mut(&mut self) -> &mut Self::Event;
-
-    fn text(&self) -> &Self::Text;
-    fn text_mut(&mut self) -> &mut Self::Text;
-
-    // subset access
-    fn render_text_mut(&mut self) -> (&mut Self::Render, &mut Self::Text);
-}
-
-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;
-
-    fn render(&self) -> &Self::Render {
-        &self.0
-    }
-    fn render_mut(&mut self) -> &mut Self::Render {
-        &mut self.0
-    }
-
-    fn event(&self) -> &Self::Event {
-        &self.1
-    }
-    fn event_mut(&mut self) -> &mut Self::Event {
-        &mut self.1
-    }
-
-    fn text(&self) -> &Self::Text {
-        &self.2
-    }
-    fn text_mut(&mut self) -> &mut Self::Text {
-        &mut self.2
-    }
-
-    fn render_text_mut(&mut self) -> (&mut Self::Render, &mut Self::Text) {
-        (&mut self.0, &mut self.2)
-    }
-}
-*/

+ 0 - 11
src/platform/event.rs

@@ -1,11 +0,0 @@
-#[derive(Clone, Debug)]
-pub enum UIEvent {
-    Shutdown,
-}
-
-#[derive(Clone, Debug)]
-pub enum WindowEvent {
-    CloseRequest,
-    Resize,
-}
-

+ 0 - 78
src/platform/render.rs

@@ -1,78 +0,0 @@
-use crate::geom::{IPoint, IRect, IVector};
-
-/// Simple colour representation
-pub struct Colour {
-    comp: [u8; 4],
-}
-
-/// Type alias for Americans
-pub type Color = Colour;
-
-impl Colour {
-    pub fn new_rgb(r: u8, g: u8, b: u8) -> Self {
-        Self {
-            comp: [r, g, b, 255u8],
-        }
-    }
-    pub fn new_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
-        Self { comp: [r, g, b, a] }
-    }
-
-    /// Red component of colour
-    pub fn r(&self) -> u8 {
-        self.comp[0]
-    }
-    /// Green component of colour
-    pub fn g(&self) -> u8 {
-        self.comp[1]
-    }
-    /// Blue component of colour
-    pub fn b(&self) -> u8 {
-        self.comp[2]
-    }
-    /// Alpha component of colour (0=transparent,255=opaque)
-    pub fn a(&self) -> u8 {
-        self.comp[3]
-    }
-
-    pub fn to_rgba(&self) -> u32 {
-        u32::from_le_bytes(self.comp)
-    }
-}
-
-pub struct ColourMask {
-    pub base: u8,
-    pub len: u8,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub enum AccessOrder {
-    RowMajor,
-    ColumnMajor,
-}
-
-pub trait SurfaceFormat {
-    const PIXEL_STRIDE: usize;
-    const ACCESS: AccessOrder;
-    const RED_MASK: ColourMask;
-    const GREEN_MASK: ColourMask;
-    const BLUE_MASK: ColourMask;
-    const ALPHA_MASK: ColourMask;
-}
-
-pub trait Surface {
-    fn size(&self) -> IVector;
-
-    type Painter<'l>: Painter<Surface = Self> + 'l
-    where
-        Self: 'l;
-    fn painter(&mut self) -> Option<Self::Painter<'_>>;
-}
-
-pub trait Painter {
-    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);
-}

+ 0 - 16
src/platform/text.rs

@@ -1,16 +0,0 @@
-#[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)
-    }
-}

+ 15 - 0
src/text.rs

@@ -0,0 +1,15 @@
+pub struct TextRenderer {
+    ui_font: fontdue::Font,
+}
+
+impl TextRenderer {
+    pub(crate) fn new() -> Self {
+        let ui_font = fontdue::Font::from_bytes(
+            std::fs::read("/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf")
+                .unwrap(),
+            fontdue::FontSettings::default(),
+        )
+        .unwrap();
+        Self { ui_font: ui_font }
+    }
+}

+ 197 - 0
src/ui.rs

@@ -0,0 +1,197 @@
+use std::collections::HashMap;
+
+use crate::{
+    component::Component, layout::{LayoutCache, LayoutNode}, text::TextRenderer, window::{Window, WindowBuilder, WindowComponent, WindowEvent, WindowStateAccess}
+};
+
+#[derive(Debug)]
+pub enum UIControlMsg {
+    Terminate,
+}
+
+pub trait UIComponent: Sized + Component<ParentMsg = UIControlMsg> {
+    fn init(&mut self, ui_handle: UIHandle);
+    fn poll(&mut self) -> Vec<UIControlMsg>;
+}
+
+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 struct UIHandle<'l> {
+    pub(crate) state: &'l mut UIState,
+    pub(crate) eloop: &'l winit::event_loop::ActiveEventLoop,
+}
+
+impl<'l> UIHandle<'l> {
+    pub fn window_builder<'r>(&'r mut self) -> WindowBuilder<'r, 'l> {
+        WindowBuilder::new(self)
+    }
+
+    pub fn new_layout_node(&self) -> LayoutNode {
+        LayoutNode::new(self.state.layout_cache.clone())
+    }
+}
+
+pub struct UI<UIC: UIComponent> {
+    state: UIState,
+    event_loop: Option<winit::event_loop::EventLoop<()>>,
+    ui_component: UIC,
+    autoclose: bool,
+    initialized: bool,
+}
+
+impl<UIC: UIComponent> UI<UIC> {
+    pub fn new(uic: UIC) -> Self {
+        let lcache = LayoutCache::new();
+
+        let eloop = winit::event_loop::EventLoop::builder().build().unwrap();
+        eloop.set_control_flow(winit::event_loop::ControlFlow::Wait);
+
+        Self {
+            state: UIState {
+                layout_cache: lcache,
+                window_states: Default::default(),
+                text_renderer: TextRenderer::new(),
+            },
+            event_loop: Some(eloop),
+            ui_component: uic,
+            autoclose: true,
+            initialized: false,
+        }
+    }
+
+    pub fn disable_autoclose(mut self) -> Self {
+        self.autoclose = false;
+        self
+    }
+
+    pub fn ui_component(&self) -> &UIC {
+        &self.ui_component
+    }
+    pub fn ui_component_mut(&mut self) -> &mut UIC {
+        &mut self.ui_component
+    }
+
+    /// Public helper function, delivers a message to the UI component and then processes whatever
+    /// control messages result.
+    pub fn deliver_msg(&mut self, msg: UIC::Msg) {
+        let cmsgs = self.ui_component.process(msg);
+        self.process_control_msgs(cmsgs.into_iter());
+    }
+
+    fn process_control_msgs(&mut self, msgs: impl Iterator<Item = UIControlMsg>) {
+        for cmsg in msgs {
+            match cmsg {
+                UIControlMsg::Terminate => {
+                    todo!()
+                    // self.ui_running = false;
+                }
+            }
+        }
+    }
+
+    pub fn run(mut self) {
+        let eloop = self.event_loop.take().unwrap();
+        eloop.run_app(&mut self).unwrap();
+    }
+
+    fn pump_events(&mut self, eloop: &winit::event_loop::ActiveEventLoop) {
+        let evts = self.ui_component.poll();
+        for evt in evts.into_iter() {
+            match evt {
+                UIControlMsg::Terminate => eloop.exit(),
+            }
+        }
+    }
+}
+
+impl<UIC: UIComponent> winit::application::ApplicationHandler<()> for UI<UIC> {
+    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
+        if !self.initialized {
+            self.ui_component.init(UIHandle {
+                eloop: event_loop,
+                state: &mut self.state,
+            });
+            self.initialized = true;
+        }
+    }
+
+    fn window_event(
+        &mut self,
+        event_loop: &winit::event_loop::ActiveEventLoop,
+        window_id: winit::window::WindowId,
+        event: winit::event::WindowEvent,
+    ) {
+        let Some(wsa) = self
+            .state
+            .window_states
+            .get(&window_id)
+            .map(std::rc::Weak::upgrade)
+            .flatten()
+        else {
+            println!("superfluous event: {event:?}");
+            return;
+        };
+
+        match event {
+            winit::event::WindowEvent::CloseRequested => {
+                wsa.push_event(WindowEvent::CloseRequested);
+                self.pump_events(event_loop);
+            }
+            winit::event::WindowEvent::Destroyed => {
+                self.state.window_states.remove(&window_id);
+            }
+            winit::event::WindowEvent::RedrawRequested => {
+                wsa.request_redraw();
+                self.pump_events(event_loop);
+            }
+            _ => (),
+        }
+    }
+}
+
+pub struct OpinionatedUIComponent<'l, WC: WindowComponent> {
+    window: Option<Window<WC>>,
+    builder: Option<Box<dyn FnOnce(&UIHandle) -> WC + 'l>>,
+}
+
+impl<'l, WC: WindowComponent> OpinionatedUIComponent<'l, WC> {
+    
+}
+
+impl<'l, WC: WindowComponent<ParentMsg = UIControlMsg>> Component for OpinionatedUIComponent<'l, WC> {
+    type ParentMsg = UIControlMsg;
+    type Msg = UIControlMsg;
+
+    fn process(&mut self, msg: Self::Msg) -> Vec<Self::ParentMsg> {
+        vec![msg]
+    }
+}
+
+impl<'l, WC: WindowComponent<ParentMsg = UIControlMsg>> UIComponent for OpinionatedUIComponent<'l, WC> {
+    fn init(&mut self, mut ui_handle: UIHandle) {
+        if let Some(builder) = self.builder.take() {
+            self.window = Some(ui_handle.window_builder().build(builder));
+        }
+    }
+    fn poll(&mut self) -> Vec<UIControlMsg> {
+        if let Some(window) = self.window.as_mut() {
+            window.poll()
+        } else {
+            vec![]
+        }
+    }
+}
+
+pub fn make_opinionated_ui<'l, WC: WindowComponent<ParentMsg = UIControlMsg>>(wc: impl 'l + FnOnce(&UIHandle) -> WC) -> UI<OpinionatedUIComponent<'l, WC>> {
+    let ouic = OpinionatedUIComponent {
+        window: None,
+        builder: Some(Box::new(wc)),
+    };
+    let ui = UI::new(ouic);
+    ui
+}

+ 12 - 4
src/widget.rs

@@ -3,8 +3,8 @@ use std::ops::DerefMut;
 use crate::{
     component::Component,
     input::InputState,
-    layout::{LayoutCache, LayoutNode, LayoutNodeAccess, LeafLayoutNode},
-    UIHandle,
+    layout::{LayoutCache, LayoutNode, LayoutNodeAccess, LeafLayoutNode, SizePolicy},
+    ui::UIHandle,
 };
 
 /*mod button;
@@ -31,8 +31,10 @@ pub struct Spacer<C: Component> {
 
 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));
         Self {
-            lnode: LeafLayoutNode::new(uih.new_layout_node()),
+            lnode,
             _ghost: Default::default(),
         }
     }
@@ -56,7 +58,13 @@ impl<C: Component> Widget<C> for Spacer<C> {
         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_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());
     }
 }

+ 0 - 14
src/widget/window.rs

@@ -1,14 +0,0 @@
-use crate::Component;
-
-pub struct Window {
-    
-}
-
-impl Component for Window {
-    type Msg = ();
-    type ParentMsg = ();
-
-    fn process(&mut self, msg: Self::Msg) -> Vec<Self::ParentMsg> {
-        vec![]
-    }
-}

+ 17 - 10
src/window.rs

@@ -6,7 +6,7 @@ use tiny_skia::IntRect;
 use crate::input::InputState;
 use crate::layout::{self, LayoutNode, LayoutNodeAccess, LinearAccess};
 use crate::widget::Widget;
-use crate::{Component, UIHandle};
+use crate::{component::Component, ui::UIHandle};
 
 #[derive(Debug)]
 pub enum WindowEvent {
@@ -14,7 +14,7 @@ pub enum WindowEvent {
     Resized,
 }
 
-pub trait WindowComponent: Sized + Component {
+pub trait WindowComponent: 'static + Sized + Component {
     fn map_window_event(&self, we: WindowEvent) -> Option<Self::Msg>;
 
     type RootWidget: Widget<Self>;
@@ -37,10 +37,17 @@ impl<WC: WindowComponent> WindowState<WC> {
         self.draw_pending = false;
 
         let size = self.window.inner_size();
-        self.surface.resize(size.width.try_into().unwrap(), size.height.try_into().unwrap()).unwrap();
+        self.surface
+            .resize(
+                size.width.try_into().unwrap(),
+                size.height.try_into().unwrap(),
+            )
+            .unwrap();
         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() });
+        self.root_node.set_behaviour(layout::NodeBehaviour::Fixed {
+            rect: IntRect::from_xywh(0, 0, size.width, size.height).unwrap(),
+        });
 
         let layout = LinearAccess::new(&self.root_node, self.wc.root_widget().layout_node());
 
@@ -100,11 +107,12 @@ impl<'r, 'l: 'r> WindowBuilder<'r, 'l> {
     }
 
     pub fn build<WC: WindowComponent>(self, wc: impl FnOnce(&UIHandle) -> WC) -> Window<WC> {
-        let window = Rc::new(self
-            .ui_handle
-            .eloop
-            .create_window(winit::window::WindowAttributes::default())
-            .unwrap());
+        let window = Rc::new(
+            self.ui_handle
+                .eloop
+                .create_window(winit::window::WindowAttributes::default())
+                .unwrap(),
+        );
         let wid = window.id();
 
         let ctx = softbuffer::Context::new(window.clone()).unwrap();
@@ -139,4 +147,3 @@ impl<WC: WindowComponent> Window<WC> {
         self.state.borrow_mut().poll()
     }
 }
-