Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions darkdraw/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from .box import *
from .charbrowser import *
from .drawing import *
from .animation import *
from .upgrade import *
from .stamps import *

from .ansihtml import * # save to .ansihtml
from .save import *
from .load_ans import *
from .save_ans import *
from .load_dur import *
from .save_dur import *
from .boxdraw import *
from .flip import *
from .draw_ops import *
from .frame_mgmt import *

from .loader_scr import * # deprecated 2020 format, remove anytime

Expand Down
54 changes: 54 additions & 0 deletions darkdraw/frame_mgmt.ddw
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{"id": "0", "type": "frame", "x": null, "y": null, "text": "", "color": "", "tags": [], "group": "", "frame": null, "rows": null, "duration_ms": 100, "ref": null}
{"id": "1", "type": "frame", "text": "", "color": "", "tags": [], "group": "", "duration_ms": 100}
{"id": "2", "type": "frame", "text": "", "color": "", "tags": [], "group": "", "duration_ms": 100}
{"id": "3", "type": "frame", "text": "", "color": "", "tags": [], "group": "", "duration_ms": 100}
{"id": "4", "type": "frame", "text": "", "color": "", "tags": [], "group": "", "duration_ms": 100}
{"x": 3, "y": 1, "text": "frame_mgmt.py", "color": "14", "tags": [], "group": "", "frame": ""}
{"x": 33, "y": 3, "text": "Frame ID Linking", "color": "13", "tags": [], "group": "", "frame": "0"}
{"x": 3, "y": 5, "text": "In the backing sheet, changes to the id value of a frame object are propagated", "color": "", "tags": [], "group": "", "frame": "0"}
{"x": 3, "y": 6, "text": "to all text objects referencing that frame id, but not the other way around.", "color": "", "tags": [], "group": "", "frame": "0"}
{"x": 3, "y": 7, "text": "Open a second pane (Z), and open the backing sheet there (`). Edit (e) the", "color": "", "tags": [], "group": "", "frame": "0"}
{"x": 3, "y": 8, "text": "id field of the first item in the backing sheet, type TEST (or anything but 0)", "color": "", "tags": [], "group": "", "frame": "0"}
{"x": 3, "y": 9, "text": "and confirm the edit (Enter). The frame values of all objects on this frame", "color": "", "tags": [], "group": "", "frame": "0"}
{"x": 3, "y": 10, "text": "have changed to match the new frame id. Editing the frame value of these objects", "color": "", "tags": [], "group": "", "frame": "0"}
{"x": 3, "y": 11, "text": "will not affect the frame's id value.", "color": "", "tags": [], "group": "", "frame": "0"}
{"x": 23, "y": 7, "text": "Z", "color": "11", "tags": [], "group": "", "frame": "0"}
{"x": 61, "y": 7, "text": "`", "color": "11", "tags": [], "group": "", "frame": "0"}
{"x": 71, "y": 7, "text": "e", "color": "11", "tags": [], "group": "", "frame": "0"}
{"x": 25, "y": 9, "text": "Enter", "color": "11", "tags": [], "group": "", "frame": "0"}
{"x": 31, "y": 14, "text": "to duplicate-frame ]", "color": "10", "tags": [], "group": "", "frame": "0"}
{"x": 30, "y": 3, "text": "duplicate-frame", "color": "13", "tags": [], "group": "", "frame": "1"}
{"x": 3, "y": 5, "text": "In the canvas, press gz] to create a copy of the current frame.", "color": "", "tags": [], "group": "", "frame": "1"}
{"x": 3, "y": 6, "text": "(gz[ would also work, but later steps in this tutorial assume that you", "color": "", "tags": [], "group": "", "frame": "1"}
{"x": 3, "y": 7, "text": "will have pressed gz] here.)", "color": "", "tags": [], "group": "", "frame": "1"}
{"x": 4, "y": 6, "text": "gz[", "color": "11", "tags": [], "group": "", "frame": "1"}
{"x": 21, "y": 7, "text": "gz]", "color": "11", "tags": [], "group": "", "frame": "1"}
{"x": 24, "y": 5, "text": "gz]", "color": "11", "tags": [], "group": "", "frame": "1"}
{"x": 27, "y": 14, "text": "to combine-duplicates ]", "color": "10", "tags": [], "group": "", "frame": "1"}
{"x": 33, "y": 3, "text": "combine-duplicates", "color": "13", "tags": [], "group": "", "frame": "2"}
{"x": 3, "y": 6, "text": "Press Space, and enter command longname combine-duplicates.", "color": "", "tags": [], "group": "", "frame": "2"}
{"x": 3, "y": 7, "text": "The duplicated items on the previous frame/the new copy are now combined", "color": "", "tags": [], "group": "", "frame": "2"}
{"x": 3, "y": 8, "text": "into single items with two frame ids.", "color": "", "tags": [], "group": "", "frame": "2"}
{"x": 3, "y": 5, "text": "Identical objects on multiple frames can be combined to declutter the backing sheet.", "color": "", "tags": [], "group": "", "frame": "2"}
{"x": 9, "y": 6, "text": "Space", "color": "11", "tags": [], "group": "", "frame": "2"}
{"x": 43, "y": 6, "text": "combine-duplicates", "color": "11", "tags": [], "group": "", "frame": "2"}
{"x": 30, "y": 14, "text": "to self-contain-frames ]", "color": "10", "tags": [], "group": "", "frame": "2"}
{"x": 26, "y": 3, "text": "self-contain-frames", "color": "13", "tags": [], "group": "", "frame": "3"}
{"x": 3, "y": 5, "text": "The opposite can also be done, to prevent edits on a given frame", "color": "", "tags": [], "group": "", "frame": "3"}
{"x": 3, "y": 6, "text": "from affecting the contents of other frames.", "color": "", "tags": [], "group": "", "frame": "3"}
{"x": 3, "y": 7, "text": "Enter command longname self-contain-frames.", "color": "", "tags": [], "group": "", "frame": "3"}
{"x": 3, "y": 8, "text": "The multi-frame objects created from frame 1 have again", "color": "", "tags": [], "group": "", "frame": "3"}
{"x": 3, "y": 9, "text": "become separate single-frame objects", "color": "", "tags": [], "group": "", "frame": "3"}
{"x": 26, "y": 7, "text": "self-contain-frames", "color": "11", "tags": [], "group": "", "frame": "3"}
{"x": 19, "y": 14, "text": "to delete-orphan-frame-elements ]", "color": "10", "tags": [], "group": "", "frame": "3"}
{"x": 21, "y": 3, "text": "delete-orphan-frame-elements", "color": "13", "tags": [], "group": "", "frame": "4"}
{"x": 3, "y": 6, "text": "In the backing sheet, delete ONLY the frame object for frame 1-2.", "color": "", "tags": [], "group": "", "frame": "4"}
{"x": 3, "y": 7, "text": "Then, enter command longname delete-orphan-frame-elements.", "color": "", "tags": [], "group": "", "frame": "4"}
{"x": 3, "y": 8, "text": "The text objects from frame 1-2 have been pruned.", "color": "", "tags": [], "group": "", "frame": "4"}
{"x": 3, "y": 5, "text": "Objects that are unused by any frame can be trimmed, all at once.", "color": "", "tags": [], "group": "", "frame": "4"}
{"x": 64, "y": 6, "text": "1-2", "color": "11", "tags": [], "group": "", "frame": "4"}
{"x": 32, "y": 7, "text": "delete-orphan-frame-elements", "color": "11", "tags": [], "group": "", "frame": "4"}
{"x": 31, "y": 8, "text": "1-2", "color": "11", "tags": [], "group": "", "frame": "4"}
{"x": 32, "y": 10, "text": "__", "color": "9", "tags": [], "group": "", "frame": "4"}
{"x": 31, "y": 11, "text": "/_' _/", "color": "9", "tags": [], "group": "", "frame": "4"}
{"x": 30, "y": 12, "text": "/_,/)(/o", "color": "9", "tags": [], "group": "", "frame": "4"}
169 changes: 169 additions & 0 deletions darkdraw/frame_mgmt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
from copy import copy as _copy

from visidata import vd, VisiData, ItemColumn

from .drawing import DrawingSheet, Drawing


def _link_id_setter(col, row, val):
old = getattr(row, 'id', None)
if not (getattr(row, 'type', '') == 'frame' and old and old != val):
return
n = 0
for r in col.sheet.rows:
if r is row or getattr(r, 'type', '') or not getattr(r, 'frame', ''): continue
ids = r.frame.split()
if old not in ids: continue
vd.addUndo(setattr, r, 'frame', r.frame)
r.frame = ' '.join(val if x == old else x for x in ids)
n += 1
if n: vd.status(f'link_frame_ids: updated {n} element(s) {old!r} → {val!r}')


for _i, _c in enumerate(DrawingSheet.columns):
if _c.name == 'id':
DrawingSheet.columns[_i] = ItemColumn('id', type=str, setter=_link_id_setter)
break


### DUPLICATE-FRAME ##########################################################
@Drawing.api
def duplicate_frame(sheet, before=False):
src = sheet.source
if not src.frames:
return src.new_between_frame(-1, 0)

cur = src.frames[sheet.cursorFrameIndex]
if before:
adj = src.frames[sheet.cursorFrameIndex-1] if sheet.cursorFrameIndex > 0 else None
if adj:
base = str(adj.id)+'-'+str(cur.id)
else:
try: base = str(int(cur.id)-1)
except ValueError: base = f'{cur.id}-pre'
else:
adj = src.frames[sheet.cursorFrameIndex+1] if sheet.cursorFrameIndex+1 < len(src.frames) else None
if adj:
base = str(cur.id)+'-'+str(adj.id)
else:
try: base = str(int(cur.id)+1)
except ValueError: base = f'{cur.id}-post'

existing = {r.id for r in src.rows if r.type == 'frame'}
name = base
i = 2
while name in existing:
name = f'{base}-{i}'
i += 1

newf = src.newRow()
newf.type = 'frame'
newf.id = name
newf.duration_ms = cur.duration_ms or 100

for i, r in enumerate(src.rows):
if r is cur:
vd.clearCaches()
src.addRow(newf, index=i if before else i+1)
break

dup_rows = [_copy(r) for r in src.rows if cur.id in (r.frame or '').split()]
for r in dup_rows:
r.frame = newf.id
src.addRow(r)
return newf

Drawing.addCommand('gz[', 'duplicate-frame-before', 'sheet.duplicate_frame(before=True)', 'insert new frame before current with copy of current frame objects')
Drawing.addCommand('gz]', 'duplicate-frame-after', 'sheet.duplicate_frame(before=False); sheet.cursorFrameIndex += 1', 'insert new frame after current with copy of current frame objects')
##############################################################################

### DELETE ORPHAN-FRAME ELEMENTS ##############################################
@DrawingSheet.api
def delete_orphan_frame_elements(self):
'Delete elements whose .frame references no existing frame id.'
valid = {r.id for r in self.rows if r.type == 'frame'}
orphans = [r for r in self.rows
if not r.type and r.frame
and not (set(r.frame.split()) & valid)]
if not orphans:
vd.status('no orphan-frame elements')
return
oids = set(map(id, orphans))
self.deleteBy(lambda r, oids=oids: id(r) in oids)
vd.status(f'deleted {len(orphans)} orphan-frame element(s)')

DrawingSheet.addCommand(None, 'delete-orphan-frame-elements', 'sheet.delete_orphan_frame_elements()', 'delete elements whose frame field references no existing frame')
Drawing.addCommand(None, 'delete-orphan-frame-elements', 'sheet.source.delete_orphan_frame_elements()', 'delete elements whose frame field references no existing frame')
##############################################################################

### COMBINE DUPLICATES #######################################################
@DrawingSheet.api
def combine_duplicates(self):
'Merge elements sharing x,y,text,color,tags,group: union their frame ids onto one, delete the rest.'
groups = {}
for r in self.rows:
if r.type: continue
key = (r.x, r.y, r.text, r.color, frozenset(r.tags or []), r.group)
groups.setdefault(key, []).append(r)

to_delete = []
merged = 0
for rs in groups.values():
if len(rs) < 2: continue
frames = set()
any_empty = False
for r in rs:
if not r.frame:
any_empty = True
else:
frames.update(r.frame.split())
keeper = rs[0]
new_frame = '' if any_empty else ' '.join(sorted(frames))
if keeper.frame != new_frame:
vd.addUndo(setattr, keeper, 'frame', keeper.frame)
keeper.frame = new_frame
to_delete.extend(rs[1:])
merged += 1

if not to_delete:
vd.status('no duplicates')
return
oids = set(map(id, to_delete))
self.deleteBy(lambda r, oids=oids: id(r) in oids)
vd.status(f'combined {merged} group(s), removed {len(to_delete)} duplicate(s)')

DrawingSheet.addCommand(None, 'combine-duplicates', 'sheet.combine_duplicates()', 'merge elements with same x,y,text,color,tags,group: union frame ids, delete extras')
Drawing.addCommand(None, 'combine-duplicates', 'sheet.source.combine_duplicates()', 'merge elements with same x,y,text,color,tags,group: union frame ids, delete extras')
##############################################################################

### SELF-CONTAIN FRAMES ######################################################
@DrawingSheet.api
def self_contain_frames(self):
'Split each element with >1 frame ids into one copy per frame; delete originals. Skips empty/null frame field.'
to_delete = []
new_rows = []
n_split = 0
for r in list(self.rows):
if r.type: continue
if not r.frame: continue
ids = r.frame.split()
if len(ids) < 2: continue
for fid in ids:
new_r = _copy(r)
new_r.frame = fid
self.addRow(new_r)
new_rows.append(new_r)
to_delete.append(r)
n_split += 1
if not to_delete:
vd.status('no multi-frame elements')
return
new_ids = set(map(id, new_rows))
vd.addUndo(self.deleteBy, lambda r, ids=new_ids: id(r) in ids)
oids = set(map(id, to_delete))
self.deleteBy(lambda r, oids=oids: id(r) in oids)
vd.status(f'split {n_split} element(s) into {len(new_rows)} copies')

DrawingSheet.addCommand(None, 'self-contain-frames', 'sheet.self_contain_frames()', 'split each multi-frame element into one copy per frame; delete originals')
Drawing.addCommand(None, 'self-contain-frames', 'sheet.source.self_contain_frames()', 'split each multi-frame element into one copy per frame; delete originals')
##############################################################################