use crate::session::ToolState;
use crate::{
CanvasConnectionEndpointRole, CanvasDefaultEdgeRouter, CanvasDocument, CanvasEdgeRoute,
CanvasEdgeRouteKind, CanvasEdgeRouter, CanvasEditor, CanvasEndpoint, CanvasKindRegistry,
CanvasRuntime, CanvasSelection, CanvasSnapGuide, CanvasViewport, HitTarget,
};
use open_gpui::{Hsla, Pixels, TextAlign, px, rgb};
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum CanvasPaintInteractionState {
Idle,
Selecting {
origin: open_gpui::Point<Pixels>,
current: open_gpui::Point<Pixels>,
},
Connecting {
source: CanvasEndpoint,
current: open_gpui::Point<Pixels>,
},
Reconnecting {
edge_id: crate::EdgeId,
endpoint: CanvasConnectionEndpointRole,
fixed: CanvasEndpoint,
current: open_gpui::Point<Pixels>,
},
Transforming {
snap_guides: Vec<CanvasSnapGuide>,
},
}
#[derive(Clone, Debug)]
pub struct CanvasPaintModel {
pub(super) document: Arc<CanvasDocument>,
pub(super) runtime: Arc<CanvasRuntime>,
pub(super) kind_registry: Arc<CanvasKindRegistry>,
pub(super) viewport: CanvasViewport,
pub(super) interaction: CanvasPaintInteraction,
}
impl CanvasPaintModel {
pub fn new(document: CanvasDocument, viewport: CanvasViewport) -> Self {
Self::new_with_router(document, viewport, &CanvasDefaultEdgeRouter)
}
pub fn new_with_kind_registry(
document: CanvasDocument,
viewport: CanvasViewport,
kind_registry: CanvasKindRegistry,
) -> Self {
Self::new_with_router_and_kind_registry(
document,
viewport,
&CanvasDefaultEdgeRouter,
kind_registry,
)
}
pub fn new_with_router<R>(
document: CanvasDocument,
viewport: CanvasViewport,
router: &R,
) -> Self
where
R: CanvasEdgeRouter + ?Sized,
{
Self::new_with_router_and_kind_registry(
document,
viewport,
router,
CanvasKindRegistry::open(),
)
}
pub fn new_with_router_and_kind_registry<R>(
document: CanvasDocument,
viewport: CanvasViewport,
router: &R,
kind_registry: CanvasKindRegistry,
) -> Self
where
R: CanvasEdgeRouter + ?Sized,
{
let runtime =
CanvasRuntime::rebuild_with_router_and_kind_registry(&document, router, &kind_registry);
Self {
document: Arc::new(document),
runtime: Arc::new(runtime),
kind_registry: Arc::new(kind_registry),
viewport,
interaction: CanvasPaintInteraction::default(),
}
}
pub fn document(&self) -> &CanvasDocument {
self.document.as_ref()
}
pub fn runtime(&self) -> &CanvasRuntime {
self.runtime.as_ref()
}
pub fn kind_registry(&self) -> &CanvasKindRegistry {
self.kind_registry.as_ref()
}
pub fn viewport(&self) -> CanvasViewport {
self.viewport
}
pub fn interaction(&self) -> &CanvasPaintInteraction {
&self.interaction
}
pub fn with_interaction(mut self, interaction: CanvasPaintInteraction) -> Self {
self.interaction = interaction;
self
}
pub fn with_selection(mut self, selection: CanvasSelection) -> Self {
self.interaction = self.interaction.with_selection(selection);
self
}
}
impl From<&CanvasEditor> for CanvasPaintModel {
fn from(editor: &CanvasEditor) -> Self {
let session = editor.session_snapshot();
Self {
document: editor.document_snapshot(),
runtime: editor.runtime_snapshot(),
kind_registry: editor.kind_registry_snapshot(),
viewport: session.viewport,
interaction: CanvasPaintInteraction::new(session.selection)
.with_internal_tool_state(session.state),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CanvasPaintInteraction {
selection: CanvasSelection,
state: CanvasPaintInteractionState,
hovered_target: Option<HitTarget>,
}
impl CanvasPaintInteraction {
pub fn new(selection: CanvasSelection) -> Self {
Self {
selection,
state: CanvasPaintInteractionState::Idle,
hovered_target: None,
}
}
pub fn selection(&self) -> &CanvasSelection {
&self.selection
}
pub(crate) fn state(&self) -> &CanvasPaintInteractionState {
&self.state
}
pub fn hovered_target(&self) -> Option<&HitTarget> {
self.hovered_target.as_ref()
}
pub fn with_selection(mut self, selection: CanvasSelection) -> Self {
self.selection = selection;
self
}
pub fn with_hovered_target(mut self, target: Option<HitTarget>) -> Self {
self.hovered_target = target;
self
}
pub(crate) fn with_internal_tool_state(mut self, state: ToolState) -> Self {
self.state = CanvasPaintInteractionState::from_tool_state(state);
self
}
}
impl Default for CanvasPaintInteraction {
fn default() -> Self {
Self {
selection: CanvasSelection::default(),
state: CanvasPaintInteractionState::Idle,
hovered_target: None,
}
}
}
impl CanvasPaintInteractionState {
pub(crate) fn from_tool_state(state: ToolState) -> Self {
match state {
ToolState::Idle
| ToolState::Pointing { .. }
| ToolState::PendingTranslation { .. }
| ToolState::Panning { .. } => Self::Idle,
ToolState::Selecting {
origin, current, ..
} => Self::Selecting { origin, current },
ToolState::Connecting { source, current } => Self::Connecting { source, current },
ToolState::Reconnecting {
edge_id,
endpoint,
fixed,
current,
..
} => Self::Reconnecting {
edge_id,
endpoint,
fixed,
current,
},
ToolState::Translating { snap_guides, .. }
| ToolState::Resizing { snap_guides, .. } => Self::Transforming { snap_guides },
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum CanvasConnectionPreviewRoute {
#[default]
Straight,
Polyline,
Orthogonal,
CubicBezier,
}
impl CanvasConnectionPreviewRoute {
pub(crate) fn edge_route(self) -> CanvasEdgeRoute {
match self {
Self::Straight => CanvasEdgeRoute::straight(),
Self::Polyline => CanvasEdgeRoute::polyline(std::iter::empty()),
Self::Orthogonal => CanvasEdgeRoute::orthogonal(),
Self::CubicBezier => CanvasEdgeRoute::new(CanvasEdgeRouteKind::CUBIC_BEZIER),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CanvasPaintOptions {
pub include_hidden: bool,
pub include_handles: bool,
pub include_interaction_feedback: bool,
pub cull_margin: Pixels,
pub connection_preview_route: CanvasConnectionPreviewRoute,
}
impl Default for CanvasPaintOptions {
fn default() -> Self {
Self {
include_hidden: false,
include_handles: false,
include_interaction_feedback: true,
cull_margin: Pixels::ZERO,
connection_preview_route: CanvasConnectionPreviewRoute::default(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CanvasPaintTheme {
pub background: Option<Hsla>,
pub node_fill: Hsla,
pub node_stroke: Hsla,
pub node_stroke_width: Pixels,
pub node_corner_radius: Pixels,
pub shape_fill: Hsla,
pub shape_stroke: Hsla,
pub shape_stroke_width: Pixels,
pub edge_stroke: Hsla,
pub edge_stroke_width: Pixels,
pub handle_fill: Hsla,
pub handle_stroke: Hsla,
pub handle_stroke_width: Pixels,
pub handle_corner_radius: Pixels,
pub selection_fill: Hsla,
pub selection_stroke: Hsla,
pub selection_stroke_width: Pixels,
pub selection_corner_radius: Pixels,
pub selection_bounds_fill: Hsla,
pub selection_bounds_stroke: Hsla,
pub selection_bounds_stroke_width: Pixels,
pub connection_preview_stroke: Hsla,
pub connection_preview_stroke_width: Pixels,
pub connection_free_target_fill: Hsla,
pub connection_free_target_stroke: Hsla,
pub connection_valid_target_fill: Hsla,
pub connection_valid_target_stroke: Hsla,
pub connection_invalid_target_fill: Hsla,
pub connection_invalid_target_stroke: Hsla,
pub snap_guide_stroke: Hsla,
pub snap_guide_stroke_width: Pixels,
pub label_color: Hsla,
pub label_font_size: Pixels,
pub label_line_height: Pixels,
pub label_line_clamp: Option<usize>,
pub label_text_align: TextAlign,
}
impl Default for CanvasPaintTheme {
fn default() -> Self {
Self {
background: None,
node_fill: Hsla::from(rgb(0xffffff)),
node_stroke: Hsla::from(rgb(0xd0d7de)),
node_stroke_width: px(1.0),
node_corner_radius: px(6.0),
shape_fill: Hsla::from(rgb(0xf6f8fa)),
shape_stroke: Hsla::from(rgb(0xd0d7de)),
shape_stroke_width: px(1.0),
edge_stroke: Hsla::from(rgb(0x57606a)),
edge_stroke_width: px(2.0),
handle_fill: Hsla::from(rgb(0x0969da)),
handle_stroke: Hsla::from(rgb(0xffffff)),
handle_stroke_width: px(1.0),
handle_corner_radius: px(6.0),
selection_fill: Hsla::from(rgb(0x0969da)).alpha(0.08),
selection_stroke: Hsla::from(rgb(0x0969da)),
selection_stroke_width: px(2.0),
selection_corner_radius: px(7.0),
selection_bounds_fill: Hsla::from(rgb(0x0969da)).alpha(0.08),
selection_bounds_stroke: Hsla::from(rgb(0x0969da)).alpha(0.7),
selection_bounds_stroke_width: px(1.0),
connection_preview_stroke: Hsla::from(rgb(0x0969da)).alpha(0.7),
connection_preview_stroke_width: px(2.0),
connection_free_target_fill: Hsla::from(rgb(0x0969da)).alpha(0.12),
connection_free_target_stroke: Hsla::from(rgb(0x0969da)).alpha(0.7),
connection_valid_target_fill: Hsla::from(rgb(0x1a7f37)).alpha(0.14),
connection_valid_target_stroke: Hsla::from(rgb(0x1a7f37)).alpha(0.9),
connection_invalid_target_fill: Hsla::from(rgb(0xcf222e)).alpha(0.12),
connection_invalid_target_stroke: Hsla::from(rgb(0xcf222e)).alpha(0.9),
snap_guide_stroke: Hsla::from(rgb(0xbf8700)).alpha(0.9),
snap_guide_stroke_width: px(1.0),
label_color: Hsla::from(rgb(0x24292f)),
label_font_size: px(14.0),
label_line_height: px(18.0),
label_line_clamp: Some(3),
label_text_align: TextAlign::Center,
}
}
}