asciiblaster

- draw irc art in your web browser
git clone git://git.acid.vegas/asciiblaster.git
Log | Files | Refs | Archive | README

undo.js (5528B)

      1 var undo = (function(){
      2 
      3 var max_states = 200;
      4 
      5 // undotimetotal = 0;
      6 
      7 var stack = {undo: [], redo: []};
      8 var current_undo = null;
      9 var dom = {undo: undo_el, redo: redo_el};
     10 dom.undo.is_visible = dom.redo.is_visible = false
     11 
     12 var LexState = function(lex){
     13   this.fg = lex.fg;
     14   this.bg = lex.bg;
     15   this.char = lex.char;
     16   this.opacity = lex.opacity;
     17 };
     18 
     19 var update_dom_visibility = function(type){
     20   var el = dom[type]
     21   if (el.is_visible){
     22     if (stack[type].length === 0) {
     23       el.classList.add('hidden')
     24       el.is_visible = false
     25     }
     26   } else if (stack[type].length > 0){
     27     el.classList.remove('hidden')
     28     el.is_visible = true
     29   }
     30 }
     31 var update_dom = function(){
     32   update_dom_visibility('undo')
     33   update_dom_visibility('redo')
     34 }
     35 
     36 // state is an undo or redo state that might contain these props
     37 // {  lexs: {'0,0': LexState, ...},    // for sparse lex changes (eg brush, fill)
     38 //    focus: {x:, y: },
     39 //    size: {w:, h: },
     40 //    rects: [{x:, y:, w:, h:, lexs: [LexState, ...]}, ...]
     41 // }
     42 var new_state = function(){
     43   var state = {lexs:{}};
     44   save_focus(canvas.focus_x, canvas.focus_y, state)
     45   return state
     46 }
     47 var new_redo = function(){
     48   return new_state()
     49 }
     50 var new_undo = function(){
     51   current_undo = new_state()
     52   stack.redo = []
     53   stack.undo.push(current_undo)
     54   if (stack.undo.length > max_states) stack.undo.shift();
     55   update_dom()
     56   return current_undo
     57 }
     58 
     59 var save_focus = function(x, y, state){
     60   state = state || current_undo
     61   state.focus = {x:x, y:y}
     62 }
     63 var save_size = function(w, h, state){
     64   state = state || current_undo
     65   state.size = {w:w, h:h};
     66 }
     67 // the reason for stringifying the x y coords is so that each
     68 // coordinate is saved only once in an undo state.
     69 // otherwise there would be problems with, eg, a brush stroke 
     70 // that passed over the same grid cell twice. 
     71 var save_lex = function(x, y, lex, state){
     72   // var start = Date.now()
     73   state = state || current_undo
     74   var lexs = state.lexs;
     75   var xy = x + "," + y;
     76   if (xy in lexs) return;
     77   lexs[xy] = new LexState(lex)
     78   // undotimetotal += Date.now() - start
     79 }
     80 var save_focused_lex = function(state){
     81   state = state || current_undo
     82   var x = canvas.focus_x
     83   var y = canvas.focus_y
     84   save_lex(x, y, canvas.aa[y][x], state)
     85 }
     86 var save_rect = function(xpos, ypos, w, h, state){
     87   if (w === 0 || h === 0) return;
     88   state = state || current_undo;
     89   state.rects = state.rects || []
     90   var aa = canvas.aa;
     91   var rect = {x: xpos, y: ypos, w: w, h: h, lexs: []}
     92   var lexs = rect.lexs
     93   var xlen = xpos + w
     94   var ylen = ypos + h
     95   for (var y = ypos; y < ylen; y++){
     96     var aay = aa[y]
     97     for (var x = xpos; x < xlen; x++){
     98       lexs.push(new LexState(aay[x]))
     99     }
    100   }
    101   state.rects.push(rect)
    102 }
    103 var save_resize = function(w, h, old_w, old_h, state){
    104   state = state || current_undo
    105   save_size(old_w, old_h, state)
    106   if (old_w > w){
    107     // .---XX
    108     // |   XX
    109     // |___XX
    110     save_rect(w, 0, old_w - w, old_h, state)
    111     if (old_h > h){
    112       // .----.
    113       // |    |
    114       // XXXX_|
    115       save_rect(0, h, w, old_h - h, state)
    116     }
    117   } else if (old_h > h){
    118     // .----.
    119     // |    |
    120     // XXXXXX
    121     save_rect(0, h, old_w, old_h - h, state)
    122   }
    123 }
    124 
    125 var restore_state = function(state){
    126   // all redo states will have a cached undo state on them
    127   // an undo state might have a cached redo state
    128   // if it doesn't have one, generate one
    129   var make_redo = ! ('redo' in state || 'undo' in state);
    130   var aa = canvas.aa
    131   var lex, lexs;
    132 
    133   if (make_redo){
    134     state.redo = new_redo()
    135 
    136     // copy saved rects that intersect with current canvas size
    137     // important to do this before resizing canvas
    138     if ('rects' in state){
    139       for (var ri=0, rect; rect=state.rects[ri]; ri++){
    140         if (rect.x >= canvas.w ||
    141             rect.y >= canvas.h) continue;
    142         var w = Math.min(rect.w, canvas.w - rect.x)
    143         var h = Math.min(rect.h, canvas.h - rect.y)
    144         save_rect(rect.x, rect.y, w, h, state.redo)
    145       }
    146     }
    147     if ('size' in state){
    148       save_resize(state.size.w, state.size.h, canvas.w, canvas.h, state.redo)
    149     }
    150   }
    151 
    152   if ('size' in state){
    153     canvas.resize(state.size.w, state.size.h, true);
    154   }
    155 
    156   if ('rects' in state){
    157     for (var ri=0, rect; rect=state.rects[ri]; ri++){
    158       lexs = rect.lexs
    159       for (var li=0; lex=lexs[li]; li++){
    160         var x = (li % rect.w) + rect.x
    161         var y = ((li / rect.w)|0) + rect.y
    162         aa[y][x].assign(lex)
    163       }
    164     }
    165   }
    166 
    167   lexs = state.lexs
    168   for (var key in lexs){
    169     var xy = key.split(',');
    170     lex = aa[xy[1]][xy[0]]
    171     if (make_redo)
    172       save_lex(xy[0], xy[1], lex, state.redo)
    173     lex.assign(lexs[key])
    174   }
    175 
    176   if ('focus' in state){
    177     canvas.focus_x = state.focus.x
    178     canvas.focus_y = state.focus.y
    179     if (current_canvas === canvas){
    180       canvas.focus()
    181     }
    182   }
    183 }
    184 
    185 var undo = function(){
    186   var state = stack.undo.pop();
    187   if (!state) return;
    188 
    189   restore_state(state)
    190   
    191   // now take the applied undo state and store it on the redo state
    192   // and push the redo state to the redo stack
    193   state.redo.undo = state
    194   stack.redo.push(state.redo)
    195   delete state.redo
    196 
    197   update_dom()
    198 }
    199 
    200 var redo = function(){
    201   var state = stack.redo.pop();
    202   if (!state) return;
    203   
    204   restore_state(state)
    205 
    206   state.undo.redo = state
    207   stack.undo.push(state.undo)
    208   delete state.undo
    209 
    210   update_dom()
    211 }
    212 
    213 return {
    214   stack: stack,
    215   new: new_undo,
    216 //  new_redo: new_redo,
    217   save_focus: save_focus,
    218   save_size: save_size,
    219   save_lex: save_lex,
    220   save_focused_lex: save_focused_lex,
    221   save_rect: save_rect,
    222   save_resize: save_resize,
    223   undo: undo,
    224   redo: redo
    225 }
    226 
    227 })()