diff --git a/index.html b/index.html index 470a263..d5b2c6c 100644 --- a/index.html +++ b/index.html @@ -131,7 +131,6 @@ - diff --git a/js/matrix.js b/js/matrix.js index 034c551..8520c2a 100644 --- a/js/matrix.js +++ b/js/matrix.js @@ -320,3 +320,231 @@ Matrix.prototype.irssi = function(opts){ } return txt } + +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') +} + +// state is an undo or redo state that might contain these props +// { lexs: {'0,0': LexState, ...}, // for sparse lex changes (eg brush, fill) +// focus: {x:, y: }, +// size: {w:, h: }, +// rects: [{x:, y:, w:, h:, lexs: [LexState, ...]}, ...] +// } +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){ + if (w === 0 || h === 0) return; + state = state || current_undo; + state.rects = state.rects || [] + var aa = canvas.aa; + var rect = {x: xpos, y: ypos, w: w, h: h, lexs: []} + var lexs = rect.lexs + var xlen = xpos + w + var ylen = ypos + h + for (var y = ypos; y < ylen; y++){ + var aay = aa[y] + for (var x = xpos; x < xlen; x++){ + lexs.push(new LexState(aay[x])) + } + } + state.rects.push(rect) +} +var save_resize = function(w, h, old_w, old_h, state){ + state = state || current_undo + save_size(old_w, old_h, state) + if (old_w > w){ + // .---XX + // | XX + // |___XX + save_rect(w, 0, old_w - w, old_h, state) + if (old_h > h){ + // .----. + // | | + // XXXX_| + save_rect(0, h, w, old_h - h, state) + } + } else if (old_h > h){ + // .----. + // | | + // XXXXXX + save_rect(0, h, old_w, old_h - h, 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 lex, lexs; + + if (make_redo){ + state.redo = new_redo() + + // copy saved rects that intersect with current canvas size + // important to do this before resizing canvas + if ('rects' in state){ + for (var ri=0, rect; rect=state.rects[ri]; ri++){ + if (rect.x >= canvas.w || + rect.y >= canvas.h) continue; + var w = Math.min(rect.w, canvas.w - rect.x) + var h = Math.min(rect.h, canvas.h - rect.y) + save_rect(rect.x, rect.y, w, h, state.redo) + } + } + if ('size' in state){ + save_resize(state.size.w, state.size.h, canvas.w, canvas.h, state.redo) + } + } + + if ('size' in state){ + canvas.resize(state.size.w, state.size.h, true); + } + + if ('rects' in state){ + for (var ri=0, rect; rect=state.rects[ri]; ri++){ + lexs = rect.lexs + for (var li=0; lex=lexs[li]; li++){ + var x = (li % rect.w) + rect.x + var y = ((li / rect.w)|0) + rect.y + aa[y][x].assign(lex) + } + } + } + + lexs = state.lexs + for (var key in lexs){ + var xy = key.split(','); + 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 (current_canvas === 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, + save_resize: save_resize, + undo: undo, + redo: redo +} + +})() diff --git a/js/undo.js b/js/undo.js deleted file mode 100644 index 35a27d9..0000000 --- a/js/undo.js +++ /dev/null @@ -1,227 +0,0 @@ -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') -} - -// state is an undo or redo state that might contain these props -// { lexs: {'0,0': LexState, ...}, // for sparse lex changes (eg brush, fill) -// focus: {x:, y: }, -// size: {w:, h: }, -// rects: [{x:, y:, w:, h:, lexs: [LexState, ...]}, ...] -// } -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){ - if (w === 0 || h === 0) return; - state = state || current_undo; - state.rects = state.rects || [] - var aa = canvas.aa; - var rect = {x: xpos, y: ypos, w: w, h: h, lexs: []} - var lexs = rect.lexs - var xlen = xpos + w - var ylen = ypos + h - for (var y = ypos; y < ylen; y++){ - var aay = aa[y] - for (var x = xpos; x < xlen; x++){ - lexs.push(new LexState(aay[x])) - } - } - state.rects.push(rect) -} -var save_resize = function(w, h, old_w, old_h, state){ - state = state || current_undo - save_size(old_w, old_h, state) - if (old_w > w){ - // .---XX - // | XX - // |___XX - save_rect(w, 0, old_w - w, old_h, state) - if (old_h > h){ - // .----. - // | | - // XXXX_| - save_rect(0, h, w, old_h - h, state) - } - } else if (old_h > h){ - // .----. - // | | - // XXXXXX - save_rect(0, h, old_w, old_h - h, 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 lex, lexs; - - if (make_redo){ - state.redo = new_redo() - - // copy saved rects that intersect with current canvas size - // important to do this before resizing canvas - if ('rects' in state){ - for (var ri=0, rect; rect=state.rects[ri]; ri++){ - if (rect.x >= canvas.w || - rect.y >= canvas.h) continue; - var w = Math.min(rect.w, canvas.w - rect.x) - var h = Math.min(rect.h, canvas.h - rect.y) - save_rect(rect.x, rect.y, w, h, state.redo) - } - } - if ('size' in state){ - save_resize(state.size.w, state.size.h, canvas.w, canvas.h, state.redo) - } - } - - if ('size' in state){ - canvas.resize(state.size.w, state.size.h, true); - } - - if ('rects' in state){ - for (var ri=0, rect; rect=state.rects[ri]; ri++){ - lexs = rect.lexs - for (var li=0; lex=lexs[li]; li++){ - var x = (li % rect.w) + rect.x - var y = ((li / rect.w)|0) + rect.y - aa[y][x].assign(lex) - } - } - } - - lexs = state.lexs - for (var key in lexs){ - var xy = key.split(','); - 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 (current_canvas === 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, - save_resize: save_resize, - undo: undo, - redo: redo -} - -})()