pixel-widgets 0.9.1

Component based UI library for graphical rust applications
Documentation
use std::cell::{Cell, RefCell, RefMut};
use std::collections::hash_map::DefaultHasher;
use std::future::Future;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::ptr::null_mut;
use std::task::Poll;

use futures::{FutureExt, Stream, StreamExt};

use crate::bitset::BitSet;
use crate::component::{Component, Context};
use crate::draw::Primitive;
use crate::event::Event;
use crate::layout::{Rectangle, Size};
use crate::node::{GenericNode, Node};
use crate::style::tree::Query;
use crate::tracker::{ManagedState, ManagedStateTracker};
use crate::widget::Context as WidgetContext;

pub struct ComponentNode<'a, C: 'a + Component> {
    props: Box<C>,
    state: RefCell<Option<&'a mut ManagedState>>,
    view: RefCell<Option<Node<'a, C::Message>>>,
    component_state: Cell<*mut (C::State, Runtime<C::Message>)>,
    style_query: Option<Query>,
    style_position: (usize, usize),
    style_matches: BitSet,
    key: u64,
}

pub struct Runtime<Message> {
    futures: Vec<Box<dyn Future<Output = Message> + Send + Sync + Unpin>>,
    streams: Vec<Box<dyn Stream<Item = Message> + Send + Sync + Unpin>>,
    modified: bool,
}

/// Mutable state accessor.
/// By wrapping the mutable reference, the runtime knows if the state was mutated at all.
pub struct State<'a, T> {
    inner: &'a mut T,
    dirty: &'a mut bool,
}

impl<'a, C: 'a + Component> ComponentNode<'a, C> {
    pub fn new(props: C) -> Self {
        let mut hasher = DefaultHasher::new();
        std::any::type_name::<C>().hash(&mut hasher);
        Self {
            props: Box::new(props),
            state: RefCell::new(None),
            view: RefCell::new(None),
            component_state: Cell::new(null_mut()),
            style_query: None,
            style_position: (0, 1),
            style_matches: BitSet::new(),
            key: hasher.finish(),
        }
    }

    pub fn dirty(&self) -> bool {
        self.view.borrow().is_none()
    }

    pub fn set_dirty(&self) {
        self.view.replace(None);
    }

    pub fn props(&self) -> &C {
        self.props.as_ref()
    }

    pub fn props_mut(&mut self) -> &mut C {
        self.set_dirty();
        self.props.as_mut()
    }

    pub fn update(&mut self, message: C::Message, context: &mut WidgetContext<C::Output>) {
        let mut dirty = false;

        let (state, runtime) = unsafe { self.component_state.get().as_mut().unwrap() };

        self.props.update(
            message,
            State {
                inner: state,
                dirty: &mut dirty,
            },
            Context::new(context, runtime),
        );

        if dirty {
            self.set_dirty();
        }
    }

    pub fn view(&self) -> RefMut<Node<'a, C::Message>> {
        if self.dirty() {
            let mut tracker = unsafe {
                self.state
                    .borrow_mut()
                    .as_mut()
                    .map(|s| (*s) as *mut ManagedState)
                    .unwrap_or(null_mut())
                    .as_mut()
                    .unwrap()
                    .tracker()
            };

            let state = tracker.begin(0, || (self.props.mount(), Runtime::default()));
            self.component_state.set(state as *mut _);

            let mut root = unsafe { (self.props.as_ref() as *const C).as_ref().unwrap() }.view(&state.0);
            let mut query = self.style_query.clone().unwrap();
            root.acquire_state(&mut tracker);
            root.style(&mut query, self.style_position);

            self.view.replace(Some(root));
        }
        RefMut::map(self.view.borrow_mut(), |b| b.as_mut().unwrap())
    }

    pub(crate) fn needs_poll(&self) -> bool {
        let (_, runtime) = unsafe { self.component_state.get().as_mut().unwrap() };
        runtime.modified
    }
}

impl<'a, C: 'a + Component> GenericNode<'a, C::Output> for ComponentNode<'a, C> {
    fn get_key(&self) -> u64 {
        self.key
    }

    fn set_key(&mut self, key: u64) {
        self.key = key;
    }

    fn set_class(&mut self, _: &'a str) {}

    fn acquire_state(&mut self, tracker: &mut ManagedStateTracker<'a>) {
        self.state
            .replace(Some(tracker.begin::<ManagedState, _>(self.key, ManagedState::default)));
        tracker.end();
    }

    fn size(&self) -> (Size, Size) {
        self.view().size()
    }

    fn hit(&self, layout: Rectangle, clip: Rectangle, x: f32, y: f32) -> bool {
        self.view().hit(layout, clip, x, y)
    }

    fn focused(&self) -> bool {
        self.view().focused()
    }

    fn draw(&mut self, layout: Rectangle, clip: Rectangle) -> Vec<Primitive<'a>> {
        self.view().draw(layout, clip)
    }

    fn style(&mut self, query: &mut Query, position: (usize, usize)) {
        self.style_matches = query.match_widget::<String>(
            std::any::type_name::<C>(),
            "",
            &[],
            self.style_position.0,
            self.style_position.1,
        );
        self.style_query = Some(Query {
            style: query.style.clone(),
            ancestors: {
                let mut a = query.ancestors.clone();
                a.push(self.style_matches.clone());
                a
            },
            siblings: Vec::new(),
        });
        self.style_position = position;

        self.set_dirty();

        query.siblings.push(self.style_matches.clone());
    }

    fn add_matches(&mut self, query: &mut Query) {
        let additions = query.match_widget::<String>(
            std::any::type_name::<C>(),
            "",
            &[],
            self.style_position.0,
            self.style_position.1,
        );

        let new_style = self.style_matches.union(&additions);
        if new_style != self.style_matches {
            self.style_matches = new_style;
        }

        query.ancestors.push(additions);
        let own_siblings = std::mem::take(&mut query.siblings);
        self.view().add_matches(query);
        query.siblings = own_siblings;
        query.siblings.push(query.ancestors.pop().unwrap());
    }

    fn remove_matches(&mut self, query: &mut Query) {
        let removals = query.match_widget::<String>(
            std::any::type_name::<C>(),
            "",
            &[],
            self.style_position.0,
            self.style_position.1,
        );

        let new_style = self.style_matches.difference(&removals);
        if new_style != self.style_matches {
            self.style_matches = new_style;
        }

        query.ancestors.push(removals);
        let own_siblings = std::mem::take(&mut query.siblings);
        self.view().remove_matches(query);
        query.siblings = own_siblings;
        query.siblings.push(query.ancestors.pop().unwrap());
    }

    fn event(
        &mut self,
        layout: Rectangle,
        clip: Rectangle,
        event: Event,
        context: &mut WidgetContext<<C as Component>::Output>,
    ) {
        let mut sub_context = context.sub_context();
        self.view().event(layout, clip, event, &mut sub_context);

        if sub_context.redraw_requested() {
            context.redraw();
        }

        for message in sub_context {
            self.update(message, context);
        }

        let (_, runtime) = unsafe { self.component_state.get().as_mut().unwrap() };
        while runtime.modified {
            for message in runtime.poll(&mut context.task_context()) {
                self.update(message, context);
            }
        }
    }

    fn poll(&mut self, context: &mut WidgetContext<<C as Component>::Output>) {
        let mut sub_context = context.sub_context();
        self.view().poll(&mut sub_context);

        if sub_context.redraw_requested() {
            context.redraw();
        }

        for message in sub_context {
            self.update(message, context);
        }

        let (_, runtime) = unsafe { self.component_state.get().as_mut().unwrap() };
        loop {
            for message in runtime.poll(&mut context.task_context()) {
                self.update(message, context);
            }
            if !runtime.modified {
                break;
            }
        }
    }
}

unsafe impl<'a, C: 'a + Component> Send for ComponentNode<'a, C> {}

impl<'a, C: 'a + Component> Drop for ComponentNode<'a, C> {
    fn drop(&mut self) {
        self.view.replace(None);
    }
}

impl<Message> Default for Runtime<Message> {
    fn default() -> Self {
        Self {
            futures: Vec::new(),
            streams: Vec::new(),
            modified: false,
        }
    }
}

impl<Message> Runtime<Message> {
    pub fn wait<F: 'static + Future<Output = Message> + Send + Sync + Unpin>(&mut self, fut: F) {
        self.futures.push(Box::new(fut));
        self.modified = true;
    }

    pub fn stream<S: 'static + Stream<Item = Message> + Send + Sync + Unpin>(&mut self, stream: S) {
        self.streams.push(Box::new(stream));
        self.modified = true;
    }

    pub(crate) fn poll(&mut self, cx: &mut std::task::Context) -> Vec<Message> {
        self.modified = false;

        let mut result = Vec::new();

        let mut i = 0;
        while i < self.futures.len() {
            match self.futures[i].poll_unpin(&mut *cx) {
                Poll::Ready(message) => {
                    result.push(message);
                    drop(self.futures.remove(i));
                }
                Poll::Pending => {
                    i += 1;
                }
            }
        }

        let mut i = 0;
        while i < self.streams.len() {
            match self.streams[i].poll_next_unpin(&mut *cx) {
                Poll::Ready(Some(message)) => {
                    result.push(message);
                }
                Poll::Ready(None) => {
                    drop(self.streams.remove(i));
                }
                Poll::Pending => {
                    i += 1;
                }
            }
        }

        result
    }
}

impl<'a, T> Deref for State<'a, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.inner
    }
}

impl<'a, T> DerefMut for State<'a, T> {
    fn deref_mut(&mut self) -> &mut T {
        *self.dirty = true;
        self.inner
    }
}