open-gpui-canvas 0.2.0

Extensible infinite canvas model and interaction primitives for Open GPUI.
Documentation
use crate::layer::{
    CanvasLayerRecord, CanvasZOrderCommand, reorder_layer_z_indices, sort_layer_records,
};
use crate::record_scope::{CanvasRecordScopeOptions, collect_selection_record_scope};
use crate::{CanvasDocument, CanvasRecordId, CanvasSelection, CanvasTransaction, DocumentCommand};
use indexmap::IndexSet;

pub(crate) fn reorder_selection_transaction(
    document: &CanvasDocument,
    selection: &CanvasSelection,
    command: CanvasZOrderCommand,
) -> CanvasTransaction {
    let selection_records = z_order_selection_record_ids(document, selection);
    let mut records = z_order_records(document, &selection_records);
    let commands = reorder_layer_z_indices(&mut records, command)
        .into_iter()
        .filter_map(|(record_id, z_index)| z_order_update_command(document, &record_id, z_index))
        .collect::<Vec<_>>();

    CanvasTransaction::new(commands)
}

fn z_order_selection_record_ids(
    document: &CanvasDocument,
    selection: &CanvasSelection,
) -> IndexSet<CanvasRecordId> {
    collect_selection_record_scope(
        document,
        selection,
        CanvasRecordScopeOptions::structural_with_internal_edges(),
        |record_id| is_reorderable_record(document, record_id),
    )
}

fn is_reorderable_record(document: &CanvasDocument, record_id: &CanvasRecordId) -> bool {
    match record_id {
        CanvasRecordId::Node(id) => document.node(id).is_some_and(|node| !node.locked),
        CanvasRecordId::Edge(id) => document.edge(id).is_some_and(|edge| !edge.locked),
        CanvasRecordId::Shape(id) => document.shape(id).is_some_and(|shape| !shape.locked),
    }
}

fn z_order_records(
    document: &CanvasDocument,
    selection_records: &IndexSet<CanvasRecordId>,
) -> Vec<CanvasLayerRecord> {
    let mut ordinal = 0;
    let mut records = Vec::new();

    records.extend(document.nodes().map(|node| {
        let record = CanvasLayerRecord {
            id: CanvasRecordId::Node(node.id.clone()),
            z_index: node.z_index,
            ordinal,
            selected: selection_records.contains(&CanvasRecordId::Node(node.id.clone())),
        };
        ordinal += 1;
        record
    }));
    records.extend(document.shapes().map(|shape| {
        let record = CanvasLayerRecord {
            id: CanvasRecordId::Shape(shape.id.clone()),
            z_index: shape.z_index,
            ordinal,
            selected: selection_records.contains(&CanvasRecordId::Shape(shape.id.clone())),
        };
        ordinal += 1;
        record
    }));
    records.extend(document.edges().map(|edge| {
        let record = CanvasLayerRecord {
            id: CanvasRecordId::Edge(edge.id.clone()),
            z_index: edge.z_index,
            ordinal,
            selected: selection_records.contains(&CanvasRecordId::Edge(edge.id.clone())),
        };
        ordinal += 1;
        record
    }));

    sort_layer_records(&mut records);
    records
}

fn z_order_update_command(
    document: &CanvasDocument,
    record_id: &CanvasRecordId,
    z_index: i32,
) -> Option<DocumentCommand> {
    match record_id {
        CanvasRecordId::Node(id) => {
            let mut node = document.node(id)?.clone();
            node.z_index = z_index;
            Some(DocumentCommand::UpdateNode(node))
        }
        CanvasRecordId::Edge(id) => {
            let mut edge = document.edge(id)?.clone();
            edge.z_index = z_index;
            Some(DocumentCommand::UpdateEdge(edge))
        }
        CanvasRecordId::Shape(id) => {
            let mut shape = document.shape(id)?.clone();
            shape.z_index = z_index;
            Some(DocumentCommand::UpdateShape(shape))
        }
    }
}