darkly 0.5.0

A GPU-native paint engine on wgpu: brushes, layers, blend modes, masks, selections, and undo.
Documentation
use super::UndoAction;
use crate::document::Document;
use crate::gpu::compositor::Compositor;
use crate::gpu::region_store::UndoRegionEntry;
use crate::layer::LayerId;
use std::collections::{HashMap, HashSet};

/// Groups multiple undo actions into a single step.
///
/// Actions are stored in forward (execution) order.
/// Undo iterates in reverse; redo iterates forward.
pub struct CompoundAction {
    actions: Vec<Box<dyn UndoAction>>,
}

impl CompoundAction {
    pub fn new(actions: Vec<Box<dyn UndoAction>>) -> Self {
        CompoundAction { actions }
    }
}

impl UndoAction for CompoundAction {
    fn undo(&mut self, doc: &mut Document) -> HashMap<LayerId, HashSet<(i32, i32)>> {
        let mut all = HashMap::new();
        for action in self.actions.iter_mut().rev() {
            for (id, coords) in action.undo(doc) {
                all.entry(id).or_insert_with(HashSet::new).extend(coords);
            }
        }
        all
    }

    fn redo(&mut self, doc: &mut Document) -> HashMap<LayerId, HashSet<(i32, i32)>> {
        let mut all = HashMap::new();
        for action in self.actions.iter_mut() {
            for (id, coords) in action.redo(doc) {
                all.entry(id).or_insert_with(HashSet::new).extend(coords);
            }
        }
        all
    }

    fn gpu_region_entry_mut(&mut self) -> Option<&mut UndoRegionEntry> {
        self.actions
            .iter_mut()
            .find_map(|a| a.gpu_region_entry_mut())
    }

    fn gpu_region_entries_mut(&mut self) -> Vec<&mut UndoRegionEntry> {
        self.actions
            .iter_mut()
            .flat_map(|a| a.gpu_region_entries_mut())
            .collect()
    }

    fn on_evict(&mut self, compositor: &mut Compositor) {
        for action in self.actions.iter_mut() {
            action.on_evict(compositor);
        }
    }

    fn byte_cost(&self) -> u64 {
        self.actions
            .iter()
            .map(|a| a.byte_cost())
            .fold(0u64, u64::saturating_add)
    }
}