open-gpui-canvas 0.2.0

Extensible infinite canvas model and interaction primitives for Open GPUI.
Documentation
use std::{error::Error, fmt};

use crate::{CanvasEvent, CanvasToolId, DocumentError};
use indexmap::IndexMap;

use super::{CanvasToolContext, CanvasToolIntent};

pub trait CanvasToolReducer {
    fn handle_event(
        &mut self,
        context: CanvasToolContext<'_>,
        event: CanvasEvent,
    ) -> Result<Vec<CanvasToolIntent>, DocumentError>;
}

impl<F> CanvasToolReducer for F
where
    F: for<'a> FnMut(
        CanvasToolContext<'a>,
        CanvasEvent,
    ) -> Result<Vec<CanvasToolIntent>, DocumentError>,
{
    fn handle_event(
        &mut self,
        context: CanvasToolContext<'_>,
        event: CanvasEvent,
    ) -> Result<Vec<CanvasToolIntent>, DocumentError> {
        self(context, event)
    }
}

#[derive(Default)]
pub struct CanvasToolRegistry {
    reducers: IndexMap<CanvasToolId, Box<dyn CanvasToolReducer>>,
}

impl CanvasToolRegistry {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert<T>(
        &mut self,
        id: impl Into<CanvasToolId>,
        reducer: T,
    ) -> Option<Box<dyn CanvasToolReducer>>
    where
        T: CanvasToolReducer + 'static,
    {
        self.reducers.insert(id.into(), Box::new(reducer))
    }

    pub fn insert_boxed(
        &mut self,
        id: impl Into<CanvasToolId>,
        reducer: Box<dyn CanvasToolReducer>,
    ) -> Option<Box<dyn CanvasToolReducer>> {
        self.reducers.insert(id.into(), reducer)
    }

    pub fn remove(&mut self, id: &CanvasToolId) -> Option<Box<dyn CanvasToolReducer>> {
        self.reducers.shift_remove(id)
    }

    pub fn contains(&self, id: &CanvasToolId) -> bool {
        self.reducers.contains_key(id)
    }

    pub fn reducer_mut(&mut self, id: &CanvasToolId) -> Option<&mut (dyn CanvasToolReducer + '_)> {
        let reducer = self.reducers.get_mut(id)?;
        Some(reducer.as_mut())
    }

    pub fn ids(&self) -> impl Iterator<Item = &CanvasToolId> {
        self.reducers.keys()
    }

    pub fn len(&self) -> usize {
        self.reducers.len()
    }

    pub fn is_empty(&self) -> bool {
        self.reducers.is_empty()
    }
}

#[derive(Debug, Eq, PartialEq)]
pub enum CanvasToolRegistryError {
    MissingTool(CanvasToolId),
    Document(DocumentError),
}

impl From<DocumentError> for CanvasToolRegistryError {
    fn from(value: DocumentError) -> Self {
        Self::Document(value)
    }
}

impl fmt::Display for CanvasToolRegistryError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::MissingTool(id) => write!(f, "canvas custom tool `{id}` is not registered"),
            Self::Document(error) => fmt::Display::fmt(error, f),
        }
    }
}

impl Error for CanvasToolRegistryError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::MissingTool(_) => None,
            Self::Document(error) => Some(error),
        }
    }
}