var undo = (function(){ var max_states = 200; // undotimetotal = 0; var stack = {undo: [], redo: []}; var current_undo = null; var dom = {undo: undo_el, redo: redo_el}; dom.undo.is_visible = dom.redo.is_visible = false var LexState = function(lex){ this.fg = lex.fg; this.bg = lex.bg; this.char = lex.char; this.opacity = lex.opacity; }; var update_dom_visibility = function(type){ var el = dom[type] if (el.is_visible){ if (stack[type].length === 0) { el.classList.add('hidden') el.is_visible = false } } else if (stack[type].length > 0){ el.classList.remove('hidden') el.is_visible = true } } var update_dom = function(){ update_dom_visibility('undo') update_dom_visibility('redo') } var new_state = function(){ var state = {lexs:{}}; save_focus(canvas.focus_x, canvas.focus_y, state) return state } var new_redo = function(){ return new_state() } var new_undo = function(){ current_undo = new_state() stack.redo = [] stack.undo.push(current_undo) if (stack.undo.length > max_states) stack.undo.shift(); update_dom() return current_undo } var save_focus = function(x, y, state){ state = state || current_undo state.focus = {x:x, y:y} } var save_size = function(w, h, state){ state = state || current_undo state.size = {w:w, h:h}; } // the reason for stringifying the x y coords is so that each // coordinate is saved only once in an undo state. // otherwise there would be problems with, eg, a brush stroke // that passed over the same grid cell twice. var save_lex = function(x, y, lex, state){ // var start = Date.now() state = state || current_undo var lexs = state.lexs; var xy = x + "," + y; if (xy in lexs) return; lexs[xy] = new LexState(lex) // undotimetotal += Date.now() - start } var save_focused_lex = function(state){ state = state || current_undo var x = canvas.focus_x var y = canvas.focus_y save_lex(x, y, canvas.aa[y][x], state) } var save_rect = function(xpos, ypos, w, h, state){ state = state || current_undo; var aa = canvas.aa; var xlen = xpos + w var ylen = ypos + h for (var x = xpos; x < xlen; x++){ for (var y = ypos; y < ylen; y++){ save_lex(x, y, aa[y][x], state) } } } var restore_state = function(state){ // all redo states will have a cached undo state on them // an undo state might have a cached redo state // if it doesn't have one, generate one var make_redo = ! ('redo' in state || 'undo' in state); var aa = canvas.aa var lexs = state.lexs if (make_redo){ state.redo = new_redo() } if ('size' in state){ if (make_redo){ save_size(canvas.w, canvas.h, state.redo) // note that resizing canvas redo saves the whole canvas instead of // just the changed part which is inefficient... save_rect(0,0, canvas.w, canvas.h, state.redo) make_redo = false // already saved whole canvas, skip lexs below } canvas.resize(state.size.w, state.size.h); } for (var key in lexs){ var xy = key.split(','); var lex = aa[xy[1]][xy[0]] if (make_redo) save_lex(xy[0], xy[1], lex, state.redo) lex.assign(lexs[key]) } if ('focus' in state){ canvas.focus_x = state.focus.x canvas.focus_y = state.focus.y if (focused_input === canvas) canvas.focus() } } var undo = function(){ var state = stack.undo.pop(); if (!state) return; restore_state(state) // now take the applied undo state and store it on the redo state // and push the redo state to the redo stack state.redo.undo = state stack.redo.push(state.redo) delete state.redo update_dom() } var redo = function(){ var state = stack.redo.pop(); if (!state) return; restore_state(state) state.undo.redo = state stack.undo.push(state.undo) delete state.undo update_dom() } return { stack: stack, new: new_undo, // new_redo: new_redo, save_focus: save_focus, save_size: save_size, save_lex: save_lex, save_focused_lex: save_focused_lex, save_rect: save_rect, undo: undo, redo: redo } })()