From 4469dd7e363a0b5ab182403b614984f58756df4a Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Fri, 22 Mar 2019 10:51:41 -0400 Subject: [PATCH] working on pixel editor nodes --- doc/notes.txt | 1 + src/pixed/pixeleditor.ts | 84 ++++++++++++++++++++++++++++++------- src/views.ts | 90 ++++++++++++++++++++-------------------- tss | 2 +- 4 files changed, 115 insertions(+), 62 deletions(-) diff --git a/doc/notes.txt b/doc/notes.txt index f0ba32a2..c2d9cba8 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -99,6 +99,7 @@ TODO: - optimization flags for sdcc (oldralloc) - 'src is undefined' when committing old image editor - editor: select palette for chr, select charmap for map (dependencies?) +- global undo/redo at checkpoints (when rom changes) WEB WORKER FORMAT diff --git a/src/pixed/pixeleditor.ts b/src/pixed/pixeleditor.ts index fe4853e6..496444b8 100644 --- a/src/pixed/pixeleditor.ts +++ b/src/pixed/pixeleditor.ts @@ -5,6 +5,10 @@ import { ProjectWindows } from "../windows"; export type UintArray = number[] | Uint8Array | Uint16Array | Uint32Array; //{[i:number]:number}; +export interface EditorContext { + setCurrentEditor(div : JQuery, editing : JQuery) : void; +} + export type PixelEditorImageFormat = { w:number h:number @@ -621,9 +625,9 @@ var PREDEF_LAYOUTS : {[id:string]:PixelEditorPaletteLayout} = { ///// -export abstract class Node { - left : Node; // toward text editor - right : Node; // toward pixel editor +export abstract class PixNode { + left : PixNode; // toward text editor + right : PixNode; // toward pixel editor words? : UintArray; // file data images? : Uint8Array[]; // array of indexed image data @@ -633,26 +637,32 @@ export abstract class Node { abstract updateRight(); // update coming from left refreshLeft() { - var p : Node = this; + var p : PixNode = this; while (p) { p.updateLeft(); p = p.left; } } refreshRight() { - var p : Node = this; + var p : PixNode = this; while (p) { p.updateRight(); p = p.right; } } - addRight(node : Node) { + addRight(node : PixNode) { this.right = node; node.left = this; + return node; + } + addLeft(node : PixNode) { + this.left = node; + node.right = this; + return node; } } -abstract class CodeProjectDataNode extends Node { +abstract class CodeProjectDataNode extends PixNode { project : ProjectWindows; fileid : string; words : UintArray; @@ -706,12 +716,12 @@ export class TextDataNode extends CodeProjectDataNode { } } -export class Compressor extends Node { +export class Compressor extends PixNode { words : UintArray; updateLeft() { - // TODO + // TODO: can't modify length of rle bytes } updateRight() { this.words = rle_unpack(new Uint8Array(this.left.words)); @@ -719,7 +729,7 @@ export class Compressor extends Node { } -export class Mapper extends Node { +export class Mapper extends PixNode { fmt : PixelEditorImageFormat; words : UintArray; @@ -746,7 +756,7 @@ class RGBAPalette { } } -export class Palettizer extends Node { +export class Palettizer extends PixNode { images : Uint8Array[]; rgbimgs : Uint32Array[]; @@ -792,7 +802,7 @@ function dedupPalette(cols : UintArray) : Uint32Array { return res; } -export class PaletteFormatToRGB extends Node { +export class PaletteFormatToRGB extends PixNode { words : UintArray; rgbimgs : Uint32Array[]; @@ -820,7 +830,7 @@ export class PaletteFormatToRGB extends Node { } } -export class Viewer { // TODO: make Node +export class Viewer { // TODO: make PixNode width : number; height : number; @@ -893,8 +903,50 @@ export class ImageChooser { span = null; } }); - } - + } +} + +function newDiv(parent?, cls? : string) { + var div = $(document.createElement("div")); + if (parent) div.appendTo(parent) + if (cls) div.addClass(cls); + return div; +} + +export class CharmapEditor extends PixNode { + + context; + parentdiv; + fmt; + + constructor(context:EditorContext, parentdiv:JQuery, fmt:PixelEditorImageFormat) { + super(); + this.context = context; + this.parentdiv = parentdiv; + this.fmt = fmt; + } + + updateLeft() { } // TODO + + updateRight() { + this.rgbimgs = this.left.rgbimgs; + var adual = newDiv(this.parentdiv.empty(), "asset_dual"); // contains grid and editor + var agrid = newDiv(adual); + var aeditor = newDiv(adual, "asset_editor").hide(); // contains editor, when selected + var chooser = new ImageChooser(); + chooser.rgbimgs = this.rgbimgs; + chooser.width = this.fmt.w || 1; + chooser.height = this.fmt.h || 1; + chooser.recreate(agrid, (index, viewer) => { + var escale = Math.ceil(192 / this.fmt.w); + var editview = new Viewer(); + editview.createWith(viewer); + editview.updateImage(null); + editview.canvas.style.width = (viewer.width*escale)+'px'; // TODO + aeditor.empty().append(editview.canvas); + this.context.setCurrentEditor(aeditor, $(viewer.canvas)); + }); + } + } -// TODO: scroll editors into view diff --git a/src/views.ts b/src/views.ts index 626ef6c3..6cc9b10a 100644 --- a/src/views.ts +++ b/src/views.ts @@ -50,6 +50,13 @@ function getVisibleEditorLineHeight() : number{ return $("#booksMenuButton").first().height(); } +function newDiv(parent?, cls? : string) { + var div = $(document.createElement("div")); + if (parent) div.appendTo(parent) + if (cls) div.addClass(cls); + return div; +} + ///// const MAX_ERRORS = 200; @@ -822,8 +829,7 @@ export class MemoryMapView implements ProjectView { maindiv : JQuery; createDiv(parent : HTMLElement) { - this.maindiv = $('
'); - $(parent).append(this.maindiv); + this.maindiv = newDiv(parent, 'vertical-scroll'); return this.maindiv[0]; } @@ -956,17 +962,25 @@ export class ProfileView implements ProjectView { /// -export class AssetEditorView implements ProjectView { +export class AssetEditorView implements ProjectView, pixed.EditorContext { maindiv : JQuery; cureditordiv : JQuery; cureditelem : JQuery; + rootnodes : pixed.PixNode[]; createDiv(parent : HTMLElement) { - this.maindiv = $('
'); - $(parent).append(this.maindiv); + this.maindiv = newDiv(parent, "vertical-scroll"); return this.maindiv[0]; } + clearAssets() { + this.rootnodes = []; + } + + registerAsset(type:string, node:pixed.PixNode) { + this.rootnodes.push(node); + } + setCurrentEditor(div : JQuery, editing : JQuery) { if (this.cureditordiv != div) { if (this.cureditordiv) { @@ -991,6 +1005,7 @@ export class AssetEditorView implements ProjectView { scanFileTextForAssets(id : string, data : string) { // scan file for assets // /*{json}*/ or ;;{json};; + // TODO: put before ident, look for = { var result = []; var re1 = /[/;][*;]([{].+[}])[*;][/;]/g; var m; @@ -1018,29 +1033,8 @@ export class AssetEditorView implements ProjectView { return result; } - addPixelEditorViews(filediv:JQuery, images:Uint32Array[], fmt:pixed.PixelEditorImageFormat) { - var adual = $('
'); // contains grid and editor - var aeditor = $('
').hide(); // contains editor, when selected - - var chooser = new pixed.ImageChooser(); - chooser.rgbimgs = images; - chooser.width = fmt.w || 1; - chooser.height = fmt.h || 1; - chooser.recreate(adual, (index, viewer) => { - console.log("???",index); - var escale = Math.ceil(192/fmt.w); - var editview = new pixed.Viewer(); - editview.createWith(viewer); - editview.updateImage(null); - editview.canvas.style.width = (viewer.width*escale)+'px'; // TODO - aeditor.empty().append(editview.canvas); - this.setCurrentEditor(aeditor, $(viewer.canvas)); - }); - adual.append(aeditor).appendTo(filediv); - } - - addPaletteEditorViews(filediv:JQuery, words, palette, layout, allcolors, callback) { - var adual = $('
').appendTo(filediv); + addPaletteEditorViews(parentdiv:JQuery, words, palette, layout, allcolors, callback) { + var adual = $('
').appendTo(parentdiv); var aeditor = $('
').hide(); // contains editor, when selected // TODO: they need to update when refreshed from right var allrgbimgs = []; @@ -1085,7 +1079,7 @@ export class AssetEditorView implements ProjectView { }); } - addPixelEditor(filediv:JQuery, firstnode:pixed.Node, fmt:pixed.PixelEditorImageFormat) { + addPixelEditor(parentdiv:JQuery, firstnode:pixed.PixNode, fmt:pixed.PixelEditorImageFormat) { // data -> pixels var mapper = new pixed.Mapper(); fmt.xform = 'scale(2)'; @@ -1099,21 +1093,20 @@ export class AssetEditorView implements ProjectView { else palizer.palette = new Uint32Array([0x00000000, 0xffff00ff, 0xffffff00, 0xffffffff]); // TODO mapper.addRight(palizer); - // refresh - firstnode.refreshRight(); // add view objects - this.addPixelEditorViews(filediv, palizer.rgbimgs, fmt); + palizer.addRight(new pixed.CharmapEditor(this, newDiv(parentdiv), fmt)); } - addPaletteEditor(filediv:JQuery, firstnode:pixed.Node, palfmt?) { + addPaletteEditor(parentdiv:JQuery, firstnode:pixed.PixNode, palfmt?) { // palette -> RGBA var pal2rgb = new pixed.PaletteFormatToRGB(); pal2rgb.palfmt = palfmt; firstnode.addRight(pal2rgb); + // TODO: refresh twice? firstnode.refreshRight(); - // add view objects + // TODO: add view objects // TODO: show which one is selected? - this.addPaletteEditorViews(filediv, firstnode.words, + this.addPaletteEditorViews(parentdiv, firstnode.words, pal2rgb.palette, pal2rgb.layout, pal2rgb.getAllColors(), (index, newvalue) => { console.log('set entry', index, '=', newvalue); @@ -1126,31 +1119,33 @@ export class AssetEditorView implements ProjectView { refreshAssetsInFile(fileid : string, data : FileData) : number { let nassets = 0; let filediv = $('#'+this.getFileDivId(fileid)).empty(); - // TODO - // TODO: check if open + // TODO: check fmt w/h/etc limits + // TODO: defer editor creation + // TODO: only refresh when needed if (fileid.endsWith('.chr') && data instanceof Uint8Array) { // is this a NES CHR? let node = new pixed.FileDataNode(projectWindows, fileid, data); const neschrfmt = {w:8,h:8,bpp:1,count:(data.length>>4),brev:true,np:2,pofs:8,remap:[0,1,2,4,5,6,7,8,9,10,11,12]}; // TODO this.addPixelEditor(filediv, node, neschrfmt); + this.registerAsset("charmap", node); } else if (typeof data === 'string') { let textfrags = this.scanFileTextForAssets(fileid, data); for (let frag of textfrags) { - let node : pixed.Node = new pixed.TextDataNode(projectWindows, fileid, data, frag.start, frag.end); + let node : pixed.PixNode = new pixed.TextDataNode(projectWindows, fileid, data, frag.start, frag.end); + let first = node; if (frag.fmt.comp == 'rletag') { - node.addRight(new pixed.Compressor()); - node.refreshRight(); // TODO - node = node.right; - console.log(node); + node = node.addRight(new pixed.Compressor()); } // is this a bitmap? if (frag.fmt && frag.fmt.w > 0 && frag.fmt.h > 0) { this.addPixelEditor(filediv, node, frag.fmt); + this.registerAsset("charmap", first); nassets++; } // is this a palette? else if (frag.fmt && frag.fmt.pal) { this.addPaletteEditor(filediv, node, frag.fmt); + this.registerAsset("palette", first); nassets++; } else { @@ -1168,11 +1163,12 @@ export class AssetEditorView implements ProjectView { // TODO: recreate editors when refreshing refresh() { this.maindiv.empty(); + this.clearAssets(); current_project.iterateFiles((id, data) => { var divid = this.getFileDivId(id); - var header = $('
').text(id); - var body = $('
').attr('id',divid); - var filediv = $('
').append(header, body).appendTo(this.maindiv); + var filediv = newDiv(this.maindiv, 'asset_file'); + var header = newDiv(filediv, 'asset_file_header').text(id); + var body = newDiv(filediv).attr('id',divid); try { var nassets = this.refreshAssetsInFile(id, data); if (nassets == 0) filediv.hide(); @@ -1181,7 +1177,11 @@ export class AssetEditorView implements ProjectView { filediv.text(e+""); // TODO: error msg? } }); + // refresh all assets + this.rootnodes.forEach((node) => { node.refreshRight(); }); } +// TODO: scroll editors into view + } diff --git a/tss b/tss index 61a1691a..5b5ee67f 160000 --- a/tss +++ b/tss @@ -1 +1 @@ -Subproject commit 61a1691a1de05dca3b694bf603db49ffbaf572cf +Subproject commit 5b5ee67fc06956bc7dce51726e98812d2d897eaa