class History { static steps: TStep[]; static undoI = 0; // Undo layer static undos = 0; // Undos available static redos = 0; // Redos available ///if (is_paint || is_sculpt) static pushUndo = false; // Store undo on next paint static undoLayers: SlotLayer[] = null; ///end ///if is_sculpt static pushUndo2 = false; ///end static undo = () => { if (History.undos > 0) { let active = History.steps.length - 1 - History.redos; let step = History.steps[active]; if (step.name == tr("Edit Nodes")) { History.swapCanvas(step); } ///if (is_paint || is_sculpt) else if (step.name == tr("New Layer") || step.name == tr("New Black Mask") || step.name == tr("New White Mask") || step.name == tr("New Fill Mask")) { Context.raw.layer = Project.layers[step.layer]; Context.raw.layer.delete(); Context.raw.layer = Project.layers[step.layer > 0 ? step.layer - 1 : 0]; } else if (step.name == tr("New Group")) { Context.raw.layer = Project.layers[step.layer]; // The layer below is the only layer in the group. Its layer masks are automatically unparented, too. Project.layers[step.layer - 1].parent = null; Context.raw.layer.delete(); Context.raw.layer = Project.layers[step.layer > 0 ? step.layer - 1 : 0]; } else if (step.name == tr("Delete Layer")) { let parent = step.layer_parent > 0 ? Project.layers[step.layer_parent - 1] : null; let l = new SlotLayer("", step.layer_type, parent); Project.layers.splice(step.layer, 0, l); Context.setLayer(l); History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let lay = History.undoLayers[History.undoI]; l.swap(lay); l.maskOpacity = step.layer_opacity; l.blending = step.layer_blending; l.objectMask = step.layer_object; MakeMaterial.parseMeshMaterial(); // Undo at least second time in order to avoid empty groups if (step.layer_type == LayerSlotType.SlotGroup) { Base.notifyOnNextFrame(() => { // 1. Undo deleting group masks let n = 1; while (History.steps[active - n].layer_type == LayerSlotType.SlotMask) { History.undo(); ++n; } // 2. Undo a mask to have a non empty group History.undo(); }); } } else if (step.name == tr("Clear Layer")) { History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let lay = History.undoLayers[History.undoI]; Context.raw.layer.swap(lay); Context.raw.layerPreviewDirty = true; } else if (step.name == tr("Duplicate Layer")) { let children = Project.layers[step.layer].getRecursiveChildren(); let position = step.layer + 1; if (children != null) position += children.length; Context.raw.layer = Project.layers[position]; Context.raw.layer.delete(); } else if (step.name == tr("Order Layers")) { let target = Project.layers[step.prev_order]; Project.layers[step.prev_order] = Project.layers[step.layer]; Project.layers[step.layer] = target; } else if (step.name == tr("Merge Layers")) { Context.raw.layer = Project.layers[step.layer]; Context.raw.layer.delete(); let parent = step.layer_parent > 0 ? Project.layers[step.layer_parent - 2] : null; let l = new SlotLayer("", step.layer_type, parent); Project.layers.splice(step.layer, 0, l); Context.setLayer(l); History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let lay = History.undoLayers[History.undoI]; Context.raw.layer.swap(lay); l = new SlotLayer("", step.layer_type, parent); Project.layers.splice(step.layer + 1, 0, l); Context.setLayer(l); History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; lay = History.undoLayers[History.undoI]; Context.raw.layer.swap(lay); Context.raw.layer.maskOpacity = step.layer_opacity; Context.raw.layer.blending = step.layer_blending; Context.raw.layer.objectMask = step.layer_object; Context.raw.layersPreviewDirty = true; MakeMaterial.parseMeshMaterial(); } else if (step.name == tr("Apply Mask")) { // First restore the layer(s) let maskPosition = step.layer; let currentLayer = null; // The layer at the old mask position is a mask, i.e. the layer had multiple masks before. if (Project.layers[maskPosition].isMask()) currentLayer = Project.layers[maskPosition].parent; else if (Project.layers[maskPosition].isLayer() || Project.layers[maskPosition].isGroup()) currentLayer = Project.layers[maskPosition]; let layersToRestore = currentLayer.isGroup() ? currentLayer.getChildren() : [currentLayer]; layersToRestore.reverse(); for (let layer of layersToRestore) { // Replace the current layer's content with the old one Context.raw.layer = layer; History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let oldLayer = History.undoLayers[History.undoI]; Context.raw.layer.swap(oldLayer); } // Now restore the applied mask History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let mask = History.undoLayers[History.undoI]; Base.newMask(false, currentLayer, maskPosition); Context.raw.layer.swap(mask); Context.raw.layersPreviewDirty = true; Context.setLayer(Context.raw.layer); } else if (step.name == tr("Invert Mask")) { let _next = () => { Context.raw.layer = Project.layers[step.layer]; Context.raw.layer.invertMask(); } App.notifyOnInit(_next); } else if (step.name == "Apply Filter") { History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let lay = History.undoLayers[History.undoI]; Context.setLayer(Project.layers[step.layer]); Context.raw.layer.swap(lay); Base.newMask(false, Context.raw.layer); Context.raw.layer.swap(lay); Context.raw.layerPreviewDirty = true; } else if (step.name == tr("To Fill Layer") || step.name == tr("To Fill Mask")) { Context.raw.layer.toPaintLayer(); History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let lay = History.undoLayers[History.undoI]; Context.raw.layer.swap(lay); } else if (step.name == tr("To Paint Layer") || step.name == tr("To Paint Mask")) { History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let lay = History.undoLayers[History.undoI]; Context.raw.layer.swap(lay); Context.raw.layer.fill_layer = Project.materials[step.material]; } else if (step.name == tr("Layer Opacity")) { Context.setLayer(Project.layers[step.layer]); let t = Context.raw.layer.maskOpacity; Context.raw.layer.maskOpacity = step.layer_opacity; step.layer_opacity = t; MakeMaterial.parseMeshMaterial(); } else if (step.name == tr("Layer Blending")) { Context.setLayer(Project.layers[step.layer]); let t = Context.raw.layer.blending; Context.raw.layer.blending = step.layer_blending; step.layer_blending = t; MakeMaterial.parseMeshMaterial(); } else if (step.name == tr("Delete Node Group")) { Project.materialGroups.splice(step.canvas_group, 0, { canvas: null, nodes: new Nodes() }); History.swapCanvas(step); } else if (step.name == tr("New Material")) { Context.raw.material = Project.materials[step.material]; step.canvas = Context.raw.material.canvas; Context.raw.material.delete(); } else if (step.name == tr("Delete Material")) { Context.raw.material = new SlotMaterial(Project.materials[0].data); Project.materials.splice(step.material, 0, Context.raw.material); Context.raw.material.canvas = step.canvas; UINodes.inst.canvasChanged(); UINodes.inst.getNodes().handle = new Handle(); UINodes.inst.hwnd.redraws = 2; } else if (step.name == tr("Duplicate Material")) { Context.raw.material = Project.materials[step.material]; step.canvas = Context.raw.material.canvas; Context.raw.material.delete(); } else { // Paint operation History.undoI = History.undoI - 1 < 0 ? Config.raw.undo_steps - 1 : History.undoI - 1; let lay = History.undoLayers[History.undoI]; Context.selectPaintObject(Project.paintObjects[step.object]); Context.setLayer(Project.layers[step.layer]); Context.raw.layer.swap(lay); Context.raw.layerPreviewDirty = true; } ///end History.undos--; History.redos++; Context.raw.ddirty = 2; ///if (is_paint || is_sculpt) UIBase.inst.hwnds[TabArea.TabSidebar0].redraws = 2; UIBase.inst.hwnds[TabArea.TabSidebar1].redraws = 2; if (UIView2D.inst.show) { UIView2D.inst.hwnd.redraws = 2; } if (Config.raw.touch_ui) { // Refresh undo & redo buttons UIMenubar.inst.menuHandle.redraws = 2; } ///end } } static redo = () => { if (History.redos > 0) { let active = History.steps.length - History.redos; let step = History.steps[active]; if (step.name == tr("Edit Nodes")) { History.swapCanvas(step); } ///if (is_paint || is_sculpt) else if (step.name == tr("New Layer") || step.name == tr("New Black Mask") || step.name == tr("New White Mask") || step.name == tr("New Fill Mask")) { let parent = step.layer_parent > 0 ? Project.layers[step.layer_parent - 1] : null; let l = new SlotLayer("", step.layer_type, parent); Project.layers.splice(step.layer, 0, l); if (step.name == tr("New Black Mask")) { Base.notifyOnNextFrame(() => { l.clear(0x00000000); }); } else if (step.name == tr("New White Mask")) { Base.notifyOnNextFrame(() => { l.clear(0xffffffff); }); } else if (step.name == tr("New Fill Mask")) { Base.notifyOnNextFrame(() => { Context.raw.material = Project.materials[step.material]; l.toFillLayer(); }); } Context.raw.layerPreviewDirty = true; Context.setLayer(l); } else if (step.name == tr("New Group")) { let l = Project.layers[step.layer - 1]; let group = Base.newGroup(); array_remove(Project.layers, group); Project.layers.splice(step.layer, 0, group); l.parent = group; Context.setLayer(group); } else if (step.name == tr("Delete Layer")) { Context.raw.layer = Project.layers[step.layer]; History.swapActive(); Context.raw.layer.delete(); // Redoing the last delete would result in an empty group // Redo deleting all group masks + the group itself if (step.layer_type == LayerSlotType.SlotLayer && History.steps.length >= active + 2 && (History.steps[active + 1].layer_type == LayerSlotType.SlotGroup || History.steps[active + 1].layer_type == LayerSlotType.SlotMask)) { let n = 1; while (History.steps[active + n].layer_type == LayerSlotType.SlotMask) { ++n; } Base.notifyOnNextFrame(() => { for (let i = 0; i < n; ++i) History.redo(); }); } } else if (step.name == tr("Clear Layer")) { Context.raw.layer = Project.layers[step.layer]; History.swapActive(); Context.raw.layer.clear(); Context.raw.layerPreviewDirty = true; } else if (step.name == tr("Duplicate Layer")) { Context.raw.layer = Project.layers[step.layer]; let _next = () => { Base.duplicateLayer(Context.raw.layer); } Base.notifyOnNextFrame(_next); } else if (step.name == tr("Order Layers")) { let target = Project.layers[step.prev_order]; Project.layers[step.prev_order] = Project.layers[step.layer]; Project.layers[step.layer] = target; } else if (step.name == tr("Merge Layers")) { Context.raw.layer = Project.layers[step.layer + 1]; App.notifyOnInit(History.redoMergeLayers); App.notifyOnInit(Base.mergeDown); } else if (step.name == tr("Apply Mask")) { Context.raw.layer = Project.layers[step.layer]; if (Context.raw.layer.isGroupMask()) { let group = Context.raw.layer.parent; let layers = group.getChildren(); layers.splice(0, 0, Context.raw.layer); History.copyMergingLayers2(layers); } else History.copyMergingLayers2([Context.raw.layer, Context.raw.layer.parent]); let _next = () => { Context.raw.layer.applyMask(); Context.setLayer(Context.raw.layer); Context.raw.layersPreviewDirty = true; } Base.notifyOnNextFrame(_next); } else if (step.name == tr("Invert Mask")) { let _next = () => { Context.raw.layer = Project.layers[step.layer]; Context.raw.layer.invertMask(); } App.notifyOnInit(_next); } else if (step.name == tr("Apply Filter")) { let lay = History.undoLayers[History.undoI]; Context.setLayer(Project.layers[step.layer]); Context.raw.layer.swap(lay); Base.newMask(false, lay); Context.raw.layer.swap(lay); Context.raw.layerPreviewDirty = true; History.undoI = (History.undoI + 1) % Config.raw.undo_steps; } else if (step.name == tr("To Fill Layer") || step.name == tr("To Fill Mask")) { let lay = History.undoLayers[History.undoI]; Context.raw.layer.swap(lay); Context.raw.layer.fill_layer = Project.materials[step.material]; History.undoI = (History.undoI + 1) % Config.raw.undo_steps; } else if (step.name == tr("To Paint Layer") || step.name == tr("To Paint Mask")) { Context.raw.layer.toPaintLayer(); let lay = History.undoLayers[History.undoI]; Context.raw.layer.swap(lay); History.undoI = (History.undoI + 1) % Config.raw.undo_steps; } else if (step.name == tr("Layer Opacity")) { Context.setLayer(Project.layers[step.layer]); let t = Context.raw.layer.maskOpacity; Context.raw.layer.maskOpacity = step.layer_opacity; step.layer_opacity = t; MakeMaterial.parseMeshMaterial(); } else if (step.name == tr("Layer Blending")) { Context.setLayer(Project.layers[step.layer]); let t = Context.raw.layer.blending; Context.raw.layer.blending = step.layer_blending; step.layer_blending = t; MakeMaterial.parseMeshMaterial(); } else if (step.name == tr("Delete Node Group")) { History.swapCanvas(step); array_remove(Project.materialGroups, Project.materialGroups[step.canvas_group]); } else if (step.name == tr("New Material")) { Context.raw.material = new SlotMaterial(Project.materials[0].data); Project.materials.splice(step.material, 0, Context.raw.material); Context.raw.material.canvas = step.canvas; UINodes.inst.canvasChanged(); UINodes.inst.getNodes().handle = new Handle(); UINodes.inst.hwnd.redraws = 2; } else if (step.name == tr("Delete Material")) { Context.raw.material = Project.materials[step.material]; step.canvas = Context.raw.material.canvas; Context.raw.material.delete(); } else if (step.name == tr("Duplicate Material")) { Context.raw.material = new SlotMaterial(Project.materials[0].data); Project.materials.splice(step.material, 0, Context.raw.material); Context.raw.material.canvas = step.canvas; UINodes.inst.canvasChanged(); UINodes.inst.getNodes().handle = new Handle(); UINodes.inst.hwnd.redraws = 2; } else { // Paint operation let lay = History.undoLayers[History.undoI]; Context.selectPaintObject(Project.paintObjects[step.object]); Context.setLayer(Project.layers[step.layer]); Context.raw.layer.swap(lay); Context.raw.layerPreviewDirty = true; History.undoI = (History.undoI + 1) % Config.raw.undo_steps; } ///end History.undos++; History.redos--; Context.raw.ddirty = 2; ///if (is_paint || is_sculpt) UIBase.inst.hwnds[TabArea.TabSidebar0].redraws = 2; UIBase.inst.hwnds[TabArea.TabSidebar1].redraws = 2; if (UIView2D.inst.show) UIView2D.inst.hwnd.redraws = 2; if (Config.raw.touch_ui) { // Refresh undo & redo buttons UIMenubar.inst.menuHandle.redraws = 2; } ///end } } static reset = () => { ///if (is_paint || is_sculpt) History.steps = [{name: tr("New"), layer: 0, layer_type: LayerSlotType.SlotLayer, layer_parent: -1, object: 0, material: 0, brush: 0}]; ///end ///if is_lab History.steps = [{name: tr("New")}]; ///end History.undos = 0; History.redos = 0; History.undoI = 0; } ///if (is_paint || is_sculpt) static editNodes = (canvas: TNodeCanvas, canvas_type: i32, canvas_group: Null = null) => { ///end ///if is_lab static editNodes = (canvas: TNodeCanvas, canvas_group: Null = null) => { ///end let step = History.push(tr("Edit Nodes")); step.canvas_group = canvas_group; ///if (is_paint || is_sculpt) step.canvas_type = canvas_type; ///end step.canvas = JSON.parse(JSON.stringify(canvas)); } ///if (is_paint || is_sculpt) static paint = () => { let isMask = Context.raw.layer.isMask(); History.copyToUndo(Context.raw.layer.id, History.undoI, isMask); History.pushUndo = false; History.push(tr(UIToolbar.inst.toolNames[Context.raw.tool])); } static newLayer = () => { History.push(tr("New Layer")); } static newBlackMask = () => { History.push(tr("New Black Mask")); } static newWhiteMask = () => { History.push(tr("New White Mask")); } static newFillMask = () => { History.push(tr("New Fill Mask")); } static newGroup = () => { History.push(tr("New Group")); } static duplicateLayer = () => { History.push(tr("Duplicate Layer")); } static deleteLayer = () => { History.swapActive(); History.push(tr("Delete Layer")); } static clearLayer = () => { History.swapActive(); History.push(tr("Clear Layer")); } static orderLayers = (prevOrder: i32) => { let step = History.push(tr("Order Layers")); step.prev_order = prevOrder; } static mergeLayers = () => { History.copyMergingLayers(); let step = History.push(tr("Merge Layers")); step.layer -= 1; // Merge down if (Context.raw.layer.hasMasks()) { step.layer -= Context.raw.layer.getMasks().length; } History.steps.shift(); // Merge consumes 2 steps History.undos--; // TODO: use undo layer in App.mergeDown to save memory } static applyMask = () => { if (Context.raw.layer.isGroupMask()) { let group = Context.raw.layer.parent; let layers = group.getChildren(); layers.splice(0, 0, Context.raw.layer); History.copyMergingLayers2(layers); } else History.copyMergingLayers2([Context.raw.layer, Context.raw.layer.parent]); History.push(tr("Apply Mask")); } static invertMask = () => { History.push(tr("Invert Mask")); } // @:keep static applyFilter = () => { History.copyToUndo(Context.raw.layer.id, History.undoI, true); History.push(tr("Apply Filter")); } static toFillLayer = () => { History.copyToUndo(Context.raw.layer.id, History.undoI, false); History.push(tr("To Fill Layer")); } static toFillMask = () => { History.copyToUndo(Context.raw.layer.id, History.undoI, true); History.push(tr("To Fill Mask")); } static toPaintLayer = () => { History.copyToUndo(Context.raw.layer.id, History.undoI, false); History.push(tr("To Paint Layer")); } static toPaintMask = () => { History.copyToUndo(Context.raw.layer.id, History.undoI, true); History.push(tr("To Paint Mask")); } static layerOpacity = () => { History.push(tr("Layer Opacity")); } // static layerObject = () => { // History.push("Layer Object"); // } static layerBlending = () => { History.push(tr("Layer Blending")); } static newMaterial = () => { let step = History.push(tr("New Material")); step.canvas_type = 0; step.canvas = JSON.parse(JSON.stringify(Context.raw.material.canvas)); } static deleteMaterial = () => { let step = History.push(tr("Delete Material")); step.canvas_type = 0; step.canvas = JSON.parse(JSON.stringify(Context.raw.material.canvas)); } static duplicateMaterial = () => { let step = History.push(tr("Duplicate Material")); step.canvas_type = 0; step.canvas = JSON.parse(JSON.stringify(Context.raw.material.canvas)); } static deleteMaterialGroup = (group: TNodeGroup) => { let step = History.push(tr("Delete Node Group")); step.canvas_type = CanvasType.CanvasMaterial; step.canvas_group = Project.materialGroups.indexOf(group); step.canvas = JSON.parse(JSON.stringify(group.canvas)); } ///end static push = (name: string): TStep => { ///if (krom_windows || krom_linux || krom_darwin) let filename = Project.filepath == "" ? UIFiles.filename : Project.filepath.substring(Project.filepath.lastIndexOf(Path.sep) + 1, Project.filepath.length - 4); System.title = filename + "* - " + Manifest.title; ///end if (Config.raw.touch_ui) { // Refresh undo & redo buttons UIMenubar.inst.menuHandle.redraws = 2; } if (History.undos < Config.raw.undo_steps) History.undos++; if (History.redos > 0) { for (let i = 0; i < History.redos; ++i) History.steps.pop(); History.redos = 0; } ///if (is_paint || is_sculpt) let opos = Project.paintObjects.indexOf(Context.raw.paintObject); let lpos = Project.layers.indexOf(Context.raw.layer); let mpos = Project.materials.indexOf(Context.raw.material); let bpos = Project.brushes.indexOf(Context.raw.brush); History.steps.push({ name: name, layer: lpos, layer_type: Context.raw.layer.isMask() ? LayerSlotType.SlotMask : Context.raw.layer.isGroup() ? LayerSlotType.SlotGroup : LayerSlotType.SlotLayer, layer_parent: Context.raw.layer.parent == null ? -1 : Project.layers.indexOf(Context.raw.layer.parent), object: opos, material: mpos, brush: bpos, layer_opacity: Context.raw.layer.maskOpacity, layer_object: Context.raw.layer.objectMask, layer_blending: Context.raw.layer.blending }); ///end ///if is_lab History.steps.push({ name: name }); ///end while (History.steps.length > Config.raw.undo_steps + 1) History.steps.shift(); return History.steps[History.steps.length - 1]; } ///if (is_paint || is_sculpt) static redoMergeLayers = () => { History.copyMergingLayers(); } static copyMergingLayers = () => { let lay = Context.raw.layer; History.copyToUndo(lay.id, History.undoI, Context.raw.layer.isMask()); let below = Project.layers.indexOf(lay) - 1; lay = Project.layers[below]; History.copyToUndo(lay.id, History.undoI, Context.raw.layer.isMask()); } static copyMergingLayers2 = (layers: SlotLayer[]) => { for (let layer of layers) History.copyToUndo(layer.id, History.undoI, layer.isMask()); } static swapActive = () => { let undoLayer = History.undoLayers[History.undoI]; undoLayer.swap(Context.raw.layer); History.undoI = (History.undoI + 1) % Config.raw.undo_steps; } static copyToUndo = (fromId: i32, toId: i32, isMask: bool) => { let path = RenderPath.active; ///if is_sculpt isMask = true; ///end if (isMask) { path.setTarget("texpaint_undo" + toId); path.bindTarget("texpaint" + fromId, "tex"); // path.drawShader("shader_datas/copy_pass/copyR8_pass"); path.drawShader("shader_datas/copy_pass/copy_pass"); } else { path.setTarget("texpaint_undo" + toId, ["texpaint_nor_undo" + toId, "texpaint_pack_undo" + toId]); path.bindTarget("texpaint" + fromId, "tex0"); path.bindTarget("texpaint_nor" + fromId, "tex1"); path.bindTarget("texpaint_pack" + fromId, "tex2"); path.drawShader("shader_datas/copy_mrt3_pass/copy_mrt3_pass"); } History.undoI = (History.undoI + 1) % Config.raw.undo_steps; } ///end static getCanvasOwner = (step: TStep): any => { ///if (is_paint || is_sculpt) return step.canvas_group == null ? Project.materials[step.material] : Project.materialGroups[step.canvas_group]; ///end ///if is_lab return null; ///end } static swapCanvas = (step: TStep) => { ///if (is_paint || is_sculpt) if (step.canvas_type == 0) { let _canvas = History.getCanvasOwner(step).canvas; History.getCanvasOwner(step).canvas = step.canvas; step.canvas = _canvas; Context.raw.material = Project.materials[step.material]; } else { let _canvas = Project.brushes[step.brush].canvas; Project.brushes[step.brush].canvas = step.canvas; step.canvas = _canvas; Context.raw.brush = Project.brushes[step.brush]; } ///end ///if is_lab let _canvas = History.getCanvasOwner(step).canvas; History.getCanvasOwner(step).canvas = step.canvas; step.canvas = _canvas; ///end UINodes.inst.canvasChanged(); UINodes.inst.getNodes().handle = new Handle(); UINodes.inst.hwnd.redraws = 2; } } type TStep = { name: string; canvas?: TNodeCanvas; // Node history canvas_group?: i32; ///if (is_paint || is_sculpt) layer: i32; layer_type: LayerSlotType; layer_parent: i32; object: i32; material: i32; brush: i32; layer_opacity?: f32; layer_object?: i32; layer_blending?: i32; prev_order?: i32; // Previous layer position canvas_type?: i32; ///end }