Browse Source

Only rerender widgets as needed, and allow render requests to be combined.

Kestrel 5 months ago
parent
commit
6375fc2cce
9 changed files with 66 additions and 17 deletions
  1. 12 0
      src/layout.rs
  2. 6 1
      src/layout/arr.rs
  3. 16 0
      src/layout/cache.rs
  4. 7 2
      src/layout/calc.rs
  5. 1 1
      src/ui.rs
  6. 4 1
      src/widget.rs
  7. 8 2
      src/widget/button.rs
  8. 4 0
      src/widget/label.rs
  9. 8 10
      src/window.rs

+ 12 - 0
src/layout.rs

@@ -197,6 +197,18 @@ impl LayoutNode {
             .flatten()
             .map(|pb| pb.inner_box(self.margin))
     }
+
+    pub fn render_check(&self) -> bool {
+        self.cache.with_state(self.cache_key, |ns| {
+            let ret = ns.needs_render;
+            ns.needs_render = false;
+            ret
+        }).unwrap_or(false)
+    }
+
+    pub fn render_needed(&self) {
+        self.cache.render_queue.borrow_mut().push(self.cache_key);
+    }
 }
 
 /// Accessors

+ 6 - 1
src/layout/arr.rs

@@ -64,7 +64,12 @@ impl ArrangementCalculator for LineArrangement {
     fn layout_step(&self, node: LayoutNodeAccess, inside: PixelBox) {
         // do the final children layouts
         node.cache
-            .with_state(node.cache_key, |ns| ns.area = Some(inside));
+            .with_state(node.cache_key, |ns| {
+                if Some(inside) != ns.area {
+                    ns.area = Some(inside);
+                    ns.needs_render = true;
+                }
+            });
 
         if node.child_len() == 0 {
             return;

+ 16 - 0
src/layout/cache.rs

@@ -14,6 +14,7 @@ impl LayoutCacheKey {
 #[derive(Clone, Debug)]
 pub struct NodeState {
     pub(super) needs_update: bool,
+    pub(super) needs_render: bool,
     pub(super) net_policy: (SizePolicy, SizePolicy),
     pub(super) layer: Layer,
     pub(super) area: Option<kahlo::math::PixelBox>,
@@ -23,6 +24,7 @@ pub struct NodeState {
 #[derive(Debug, Default)]
 pub struct LayoutCache {
     pub(super) update_queue: RefCell<Vec<LayoutCacheKey>>,
+    pub(super) render_queue: RefCell<Vec<LayoutCacheKey>>,
     states: RefCell<HashMap<LayoutCacheKey, NodeState>>,
     parents: RefCell<HashMap<LayoutCacheKey, LayoutCacheKey>>,
 }
@@ -31,6 +33,7 @@ impl LayoutCache {
     pub fn new() -> Rc<LayoutCache> {
         Rc::new(Self {
             update_queue: Default::default(),
+            render_queue: Default::default(),
             states: Default::default(),
             parents: Default::default(),
         })
@@ -50,6 +53,19 @@ impl LayoutCache {
         }
     }
 
+    pub fn propagate_rerenders(&self) {
+        let mut renq = self.render_queue.borrow_mut();
+        let mut states = self.states.borrow_mut();
+        while let Some(next) = renq.pop() {
+            states.get_mut(&next).map(|v| {
+                if !v.needs_render {
+                    v.needs_render = true;
+                    renq.extend(v.children.iter());
+                }
+            });
+        }
+    }
+
     pub fn with_state<R, F: FnOnce(&mut NodeState) -> R>(
         &self,
         ckey: LayoutCacheKey,

+ 7 - 2
src/layout/calc.rs

@@ -6,6 +6,8 @@ pub fn recalculate(node: LayoutNodeAccess) {
 
         // propagate relayout flags up to the root
         node.cache.propagate_relayouts();
+        // propagate rerender flags down to the leaves
+        node.cache.propagate_rerenders();
 
         arrangement_pass(None, node, layer);
 
@@ -37,9 +39,11 @@ fn arrangement_pass(parent: Option<LayoutCacheKey>, node: LayoutNodeAccess, laye
 
     let (wpol, hpol) = node.child_arrangement.arrange_step(node, child_policies);
 
+    let child_ids = node.child_iter().map(|ch| ch.cache_key).collect();
     if node.cache.has_state_for(node.cache_key) {
         node.cache.with_state(node.cache_key, |ns| {
             ns.net_policy = (wpol, hpol);
+            ns.children = child_ids;
         });
     } else {
         node.cache.store(
@@ -47,10 +51,11 @@ fn arrangement_pass(parent: Option<LayoutCacheKey>, node: LayoutNodeAccess, laye
             parent,
             NodeState {
                 needs_update: false,
+                needs_render: true,
                 net_policy: (wpol, hpol),
                 area: None,
                 layer,
-                children: vec![],
+                children: child_ids,
             },
         );
     }
@@ -118,7 +123,7 @@ mod test {
             node: LayoutNode::new(cache.clone()),
         };
         root.node.set_behaviour(NodeBehaviour::Fixed {
-            rect: PixelRect::new(PixelPoint::new(1, 1), PixelSize::new(2, 5)),
+            area: PixelBox::from_origin_and_size(PixelPoint::new(1, 1), PixelSize::new(2, 5)),
         });
         root.node.child_arrangement = ChildArrangement::Column;
 

+ 1 - 1
src/ui.rs

@@ -170,7 +170,7 @@ impl<UIC: UIComponent> winit::application::ApplicationHandler<()> for UI<UIC> {
                 self.state.window_states.remove(&window_id);
             }
             winit::event::WindowEvent::RedrawRequested => {
-                wsa.request_redraw();
+                wsa.redraw(&UIHandle { eloop: event_loop, state: &mut self.state });
                 self.pump_events(event_loop);
             }
             winit::event::WindowEvent::CursorMoved { position, .. } => {

+ 4 - 1
src/widget.rs

@@ -63,6 +63,9 @@ impl<C: Component> Widget<C> for Spacer<C> {
 
     fn render(&self, theme: &Theme, target: &mut kahlo::RgbaBitmap) {
         let region = self.layout_node().render_area().unwrap();
-        target.fill_region(region, theme.background);
+        if self.lnode.render_check() {
+            println!("rendering spacer");
+            target.fill_region(region, theme.background);
+        }
     }
 }

+ 8 - 2
src/widget/button.rs

@@ -9,6 +9,7 @@ use crate::{
 
 use super::{Label, Widget};
 
+#[derive(Clone, Copy, PartialEq)]
 enum ButtonState {
     Idle,
     Hovered,
@@ -73,8 +74,8 @@ impl<C: Component> Widget<C> for Button<C> {
     ) -> Vec<<C as Component>::Msg> {
         let mut result: Vec<<C as Component>::Msg> = vec![];
         if let (Some(istate), Some(area)) = (input_state, self.layout.render_area()) {
+            let before = self.state;
             if area.contains_inclusive(istate.mouse.pos) {
-                println!("cursor on button");
                 if istate.mouse.buttons.active(MouseButton::Left) {
                     self.state = ButtonState::Clicked;
                 } else if istate.mouse.released().active(MouseButton::Left) {
@@ -86,6 +87,9 @@ impl<C: Component> Widget<C> for Button<C> {
             } else {
                 self.state = ButtonState::Idle;
             }
+            if self.state != before {
+                self.layout.render_needed();
+            }
         }
         result.extend(self.label.poll(uih, input_state).into_iter());
         result
@@ -104,7 +108,9 @@ impl<C: Component> Widget<C> for Button<C> {
             ButtonState::Hovered => theme.panel,
             ButtonState::Clicked => theme.foreground,
         };
-        target.fill_region(self.layout.render_area().unwrap(), colour);
+        if self.layout.render_check() {
+            target.fill_region(self.layout.render_area().unwrap(), colour);
+        }
         self.label.render(theme, target);
     }
 }

+ 4 - 0
src/widget/label.rs

@@ -53,6 +53,7 @@ impl<C: Component> Label<C> {
         let line = uih.theme().make_line(text.as_str());
         let rendered = line.render_line();
         *self.rendered.borrow_mut() = Some(rendered);
+        self.lnode.render_needed();
     }
 }
 
@@ -77,6 +78,9 @@ impl<C: Component> Widget<C> for Label<C> {
     }
 
     fn render(&self, theme: &Theme, surface: &mut kahlo::RgbaBitmap) {
+        if !self.lnode.render_check() {
+            return
+        }
         let rcon = self.rendered.borrow();
         let Some(rendered) = rcon.as_ref() else {
             return;

+ 8 - 10
src/window.rs

@@ -31,15 +31,12 @@ pub(crate) struct WindowState<WC: WindowComponent> {
     window: Rc<winit::window::Window>,
     events: Vec<WindowEvent>,
     istate: InputState,
-    draw_pending: bool,
     surface: softbuffer::Surface<Rc<winit::window::Window>, Rc<winit::window::Window>>,
     bitmap: RefCell<Option<kahlo::RgbaBitmap>>,
 }
 
 impl<WC: WindowComponent> WindowState<WC> {
     fn redraw(&mut self, uih: &UIHandle) {
-        self.draw_pending = false;
-
         let size = self.window.inner_size();
         self.surface
             .resize(
@@ -74,9 +71,11 @@ impl<WC: WindowComponent> WindowState<WC> {
             kahlo::RgbaBitmap::new(size.width as usize, size.height as usize)
         });
 
-        bitmap.fill(uih.theme().background);
         layout::recalculate(LayoutNodeAccess::new(&layout));
+        let before = std::time::Instant::now();
         self.wc.root_widget().render(uih.theme(), &mut bitmap);
+        let after = std::time::Instant::now();
+        print!("render time: {:?}\r", (after - before));
 
         let mut windowbuffer = kahlo::BitmapMut::<kahlo::formats::Bgr32>::new(
             unsafe {
@@ -106,16 +105,13 @@ impl<WC: WindowComponent> WindowState<WC> {
 
         self.istate.tick();
 
-        if self.draw_pending {
-            self.redraw(uih);
-        }
-
         ret
     }
 }
 
 pub(crate) trait WindowStateAccess {
     fn push_event(&self, we: WindowEvent);
+    fn redraw(&self, uih: &UIHandle);
     fn request_redraw(&self);
     fn update_mouse_pos(&self, pos: PixelPoint);
     fn update_mouse_button(&self, which: MouseButton, to: bool);
@@ -125,8 +121,11 @@ impl<WC: WindowComponent> WindowStateAccess for RefCell<WindowState<WC>> {
     fn push_event(&self, we: WindowEvent) {
         self.borrow_mut().events.push(we);
     }
+    fn redraw(&self, uih: &UIHandle) {
+        self.borrow_mut().redraw(uih);
+    }
     fn request_redraw(&self) {
-        self.borrow_mut().draw_pending = true;
+        self.borrow().window.request_redraw();
     }
     fn update_mouse_pos(&self, pos: PixelPoint) {
         self.borrow_mut().istate.mouse.pos = pos;
@@ -165,7 +164,6 @@ impl<'r, 'l: 'r> WindowBuilder<'r, 'l> {
             window,
             events: Default::default(),
             istate: InputState::default().into(),
-            draw_pending: false.into(),
             surface: surface.into(),
             bitmap: None.into(),
         }));