mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2025-02-20 14:29:16 +00:00
pixel editor lazy updates
This commit is contained in:
parent
2889ef33bd
commit
6cea0772bf
@ -502,4 +502,11 @@ div.asset_editor {
|
||||
padding:16px;
|
||||
margin:16px;
|
||||
background-color:#999;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
}
|
||||
div.asset_toolbar {
|
||||
padding:8px;
|
||||
margin:8px;
|
||||
}
|
||||
|
@ -108,6 +108,7 @@ TODO:
|
||||
- metasprites
|
||||
- update nested data, palette/tile refs properly
|
||||
- throw errors when bad/no refs
|
||||
- careful with mouse capture out of frame
|
||||
|
||||
WEB WORKER FORMAT
|
||||
|
||||
|
@ -317,6 +317,33 @@ var PREDEF_LAYOUTS : {[id:string]:PixelEditorPaletteLayout} = {
|
||||
|
||||
/////
|
||||
|
||||
function equalArrays(a:UintArray, b:UintArray) : boolean {
|
||||
if (a == null || b == null)
|
||||
return false;
|
||||
if (a.length !== b.length)
|
||||
return false;
|
||||
if (a === b)
|
||||
return true;
|
||||
for (var i=0; i<a.length; i++) {
|
||||
if (a[i] !== b[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function equalNestedArrays(a:UintArray[], b:UintArray[]) : boolean {
|
||||
if (a == null || b == null)
|
||||
return false;
|
||||
if (a.length !== b.length)
|
||||
return false;
|
||||
if (a === b)
|
||||
return true;
|
||||
for (var i=0; i<a.length; i++) {
|
||||
if (!equalArrays(a[i], b[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export abstract class PixNode {
|
||||
left : PixNode; // toward text editor
|
||||
right : PixNode; // toward pixel editor
|
||||
@ -326,8 +353,8 @@ export abstract class PixNode {
|
||||
rgbimgs? : Uint32Array[]; // array of rgba imgages
|
||||
palette? : Uint32Array; // array of rgba
|
||||
|
||||
abstract updateLeft(); // update coming from right
|
||||
abstract updateRight(); // update coming from left
|
||||
abstract updateLeft() : boolean; // update coming from right
|
||||
abstract updateRight() : boolean; // update coming from left
|
||||
|
||||
refreshLeft() {
|
||||
var p : PixNode = this;
|
||||
@ -364,20 +391,25 @@ abstract class CodeProjectDataNode extends PixNode {
|
||||
|
||||
export class FileDataNode extends CodeProjectDataNode {
|
||||
|
||||
constructor(project:ProjectWindows, fileid:string, data:Uint8Array) {
|
||||
constructor(project:ProjectWindows, fileid:string) {
|
||||
super();
|
||||
this.project = project;
|
||||
this.fileid = fileid;
|
||||
this.label = fileid;
|
||||
this.words = data;
|
||||
}
|
||||
updateLeft() {
|
||||
//if (equalArrays(this.words, this.right.words)) return false;
|
||||
this.words = this.right.words;
|
||||
if (this.project) {
|
||||
this.project.updateFile(this.fileid, this.words as Uint8Array);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
updateRight() {
|
||||
if (this.project) {
|
||||
this.words = this.project.project.getFile(this.fileid) as Uint8Array;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,12 +418,12 @@ export class TextDataNode extends CodeProjectDataNode {
|
||||
start : number;
|
||||
end : number;
|
||||
|
||||
constructor(project:ProjectWindows, fileid:string, label:string, text:string, start:number, end:number) {
|
||||
// TODO: what if file size/layout changes?
|
||||
constructor(project:ProjectWindows, fileid:string, label:string, start:number, end:number) {
|
||||
super();
|
||||
this.project = project;
|
||||
this.fileid = fileid;
|
||||
this.label = label;
|
||||
this.text = text;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
@ -407,12 +439,17 @@ export class TextDataNode extends CodeProjectDataNode {
|
||||
this.project.updateFile(this.fileid, this.text);
|
||||
//this.project.replaceTextRange(this.fileid, this.start, this.end, datastr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
updateRight() {
|
||||
if (this.project) {
|
||||
this.text = this.project.project.getFile(this.fileid) as string;
|
||||
}
|
||||
var datastr = this.text.substring(this.start, this.end);
|
||||
datastr = convertToHexStatements(datastr); // TODO?
|
||||
var words = parseHexWords(datastr);
|
||||
this.words = words; //new Uint8Array(words); // TODO: 16/32?
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -422,9 +459,11 @@ export class Compressor extends PixNode {
|
||||
|
||||
updateLeft() {
|
||||
// TODO: can't modify length of rle bytes
|
||||
return false;
|
||||
}
|
||||
updateRight() {
|
||||
this.words = rle_unpack(new Uint8Array(this.left.words));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@ -440,13 +479,17 @@ export class Mapper extends PixNode {
|
||||
this.fmt = fmt;
|
||||
}
|
||||
updateLeft() {
|
||||
//if (equalNestedArrays(this.images, this.right.images)) return false;
|
||||
this.images = this.right.images;
|
||||
this.words = convertImagesToWords(this.images, this.fmt);
|
||||
return true;
|
||||
}
|
||||
updateRight() {
|
||||
if (equalArrays(this.words, this.left.words)) return false;
|
||||
// convert each word array to images
|
||||
this.words = this.left.words;
|
||||
this.images = convertWordsToImages(this.words, this.fmt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,18 +524,21 @@ export class Palettizer extends PixNode {
|
||||
updateLeft() {
|
||||
this.rgbimgs = this.right.rgbimgs;
|
||||
var pal = new RGBAPalette(this.palette);
|
||||
this.images = this.rgbimgs.map( (im:Uint32Array) => {
|
||||
var newimages = this.rgbimgs.map( (im:Uint32Array) => {
|
||||
var out = new Uint8Array(im.length);
|
||||
for (var i=0; i<im.length; i++) {
|
||||
out[i] = pal.indexOf(im[i]);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
// have to do it this way b/c pixel editor modifies arrays
|
||||
//if (equalNestedArrays(newimages, this.images)) return false;
|
||||
this.images = newimages;
|
||||
return true;
|
||||
}
|
||||
updateRight() {
|
||||
this.updateRefs();
|
||||
if (!this.updateRefs() && equalNestedArrays(this.images, this.left.images)) return false;
|
||||
this.images = this.left.images;
|
||||
if (!this.palette || !this.images) return;
|
||||
var mask = this.palette.length - 1; // must be power of 2
|
||||
// for each image, map bytes to RGB colors
|
||||
this.rgbimgs = this.images.map( (im:Uint8Array) => {
|
||||
@ -502,20 +548,25 @@ export class Palettizer extends PixNode {
|
||||
}
|
||||
return out;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
updateRefs() {
|
||||
var newpalette;
|
||||
if (this.context != null) {
|
||||
this.paloptions = this.context.getPalettes(this.ncolors);
|
||||
if (this.paloptions && this.paloptions.length > 0) {
|
||||
this.palette = this.paloptions[this.palindex].palette;
|
||||
newpalette = this.paloptions[this.palindex].palette;
|
||||
}
|
||||
}
|
||||
if (this.palette == null) {
|
||||
if (newpalette == null) {
|
||||
if (this.ncolors <= 2)
|
||||
this.palette = new Uint32Array([0xff000000, 0xffffffff]);
|
||||
newpalette = new Uint32Array([0xff000000, 0xffffffff]);
|
||||
else
|
||||
this.palette = new Uint32Array([0xff000000, 0xffff00ff, 0xffffff00, 0xffffffff]); // TODO: more palettes
|
||||
newpalette = new Uint32Array([0xff000000, 0xffff00ff, 0xffffff00, 0xffffffff]); // TODO: more palettes
|
||||
}
|
||||
if (equalArrays(this.palette, newpalette)) return false;
|
||||
this.palette = newpalette;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -533,9 +584,9 @@ function dedupPalette(cols : UintArray) : Uint32Array {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
export class PaletteFormatToRGB extends PixNode {
|
||||
|
||||
|
||||
words : UintArray;
|
||||
rgbimgs : Uint32Array[];
|
||||
palette : Uint32Array;
|
||||
@ -548,8 +599,10 @@ export class PaletteFormatToRGB extends PixNode {
|
||||
}
|
||||
updateLeft() {
|
||||
//TODO
|
||||
return true;
|
||||
}
|
||||
updateRight() {
|
||||
if (equalArrays(this.words, this.left.words)) return false;
|
||||
this.words = this.left.words;
|
||||
this.palette = dedupPalette(convertPaletteFormat(this.words, this.palfmt));
|
||||
this.layout = PREDEF_LAYOUTS[this.palfmt.layout];
|
||||
@ -557,6 +610,7 @@ export class PaletteFormatToRGB extends PixNode {
|
||||
this.palette.forEach( (rgba:number) => {
|
||||
this.rgbimgs.push(new Uint32Array([rgba]));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
getAllColors() {
|
||||
var arr = [];
|
||||
@ -581,13 +635,15 @@ export abstract class Compositor extends PixNode {
|
||||
super();
|
||||
this.context = context;
|
||||
}
|
||||
updateRefs() {
|
||||
updateRefs() : boolean {
|
||||
var oldtilemap = this.tilemap;
|
||||
if (this.context != null) {
|
||||
this.tileoptions = this.context.getTilemaps(256);
|
||||
if (this.tileoptions && this.tileoptions.length > 0) {
|
||||
this.tilemap = this.tileoptions[this.tileindex].images;
|
||||
}
|
||||
}
|
||||
return !equalNestedArrays(oldtilemap, this.tilemap);
|
||||
}
|
||||
}
|
||||
|
||||
@ -605,6 +661,7 @@ export class MetaspriteCompositor extends Compositor {
|
||||
}
|
||||
updateLeft() {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
updateRight() {
|
||||
this.updateRefs();
|
||||
@ -615,6 +672,7 @@ export class MetaspriteCompositor extends Compositor {
|
||||
this.metadefs.forEach((meta) => {
|
||||
// TODO
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -628,11 +686,11 @@ export class NESNametableConverter extends Compositor {
|
||||
}
|
||||
updateLeft() {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
updateRight() {
|
||||
if (!this.updateRefs() && equalArrays(this.words, this.left.words)) return false;
|
||||
this.words = this.left.words;
|
||||
this.updateRefs();
|
||||
if (!this.words || !this.tilemap) return;
|
||||
this.cols = 32;
|
||||
this.rows = 30;
|
||||
this.width = this.cols * 8;
|
||||
@ -665,6 +723,7 @@ export class NESNametableConverter extends Compositor {
|
||||
}
|
||||
}
|
||||
// TODO
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -682,7 +741,6 @@ export class ImageChooser {
|
||||
var cscale = Math.max(2, Math.ceil(16/this.width)); // TODO
|
||||
var imgsperline = this.width <= 8 ? 16 : 8; // TODO
|
||||
var span = null;
|
||||
if (!this.rgbimgs) return;
|
||||
this.rgbimgs.forEach((imdata, i) => {
|
||||
var viewer = new Viewer();
|
||||
viewer.width = this.width;
|
||||
@ -730,9 +788,11 @@ export class CharmapEditor extends PixNode {
|
||||
}
|
||||
|
||||
updateLeft() {
|
||||
return true;
|
||||
}
|
||||
|
||||
updateRight() {
|
||||
if (equalNestedArrays(this.rgbimgs, this.left.rgbimgs)) return false;
|
||||
this.rgbimgs = this.left.rgbimgs;
|
||||
var adual = newDiv(this.parentdiv.empty(), "asset_dual"); // contains grid and editor
|
||||
var agrid = newDiv(adual);
|
||||
@ -765,6 +825,7 @@ export class CharmapEditor extends PixNode {
|
||||
palizer.refreshRight();
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
createEditor(aeditor : JQuery, viewer : Viewer, escale : number) : PixEditor {
|
||||
@ -901,7 +962,7 @@ class PixEditor extends Viewer {
|
||||
|
||||
createPaletteButtons() {
|
||||
this.palbtns = [];
|
||||
var span = $(document.createElement('div'));
|
||||
var span = newDiv(null, "asset_toolbar");
|
||||
for (var i=0; i<this.palette.length; i++) {
|
||||
var btn = $(document.createElement('button')).addClass('palbtn');
|
||||
var rgb = this.palette[i] & 0xffffff;
|
||||
|
45
src/views.ts
45
src/views.ts
@ -974,8 +974,9 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
}
|
||||
if (div) {
|
||||
this.cureditordiv = div;
|
||||
this.cureditordiv.show(timeout);
|
||||
this.cureditordiv.show();
|
||||
this.cureditordiv[0].scrollIntoView({behavior: "smooth", block: "center"});
|
||||
//setTimeout(() => { this.cureditordiv[0].scrollIntoView({behavior: "smooth", block: "center"}) }, timeout);
|
||||
}
|
||||
}
|
||||
if (this.cureditelem) {
|
||||
@ -1039,37 +1040,42 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
return result;
|
||||
}
|
||||
|
||||
addPaletteEditorViews(parentdiv:JQuery, words, palette, layout, allcolors, callback) {
|
||||
addPaletteEditorViews(parentdiv:JQuery, pal2rgb:pixed.PaletteFormatToRGB, callback) {
|
||||
var adual = $('<div class="asset_dual"/>').appendTo(parentdiv);
|
||||
var aeditor = $('<div class="asset_editor"/>').hide(); // contains editor, when selected
|
||||
// TODO: they need to update when refreshed from right
|
||||
var allrgbimgs = [];
|
||||
allcolors.forEach((rgba) => { allrgbimgs.push(new Uint32Array([rgba])); }); // array of array of 1 rgb color (for picker)
|
||||
pal2rgb.getAllColors().forEach((rgba) => { allrgbimgs.push(new Uint32Array([rgba])); }); // array of array of 1 rgb color (for picker)
|
||||
var atable = $('<table/>').appendTo(adual);
|
||||
aeditor.appendTo(adual);
|
||||
// make default layout if not exists
|
||||
var layout = pal2rgb.layout;
|
||||
if (!layout) {
|
||||
var len = palette.length;
|
||||
var len = pal2rgb.palette.length;
|
||||
var imgsperline = len > 32 ? 8 : 4; // TODO: use 'n'?
|
||||
layout = [];
|
||||
for (var i=0; i<len; i+=imgsperline) {
|
||||
layout.push(["", i, Math.min(len-i,imgsperline)]);
|
||||
}
|
||||
}
|
||||
function updateCell(cell, j) {
|
||||
var val = pal2rgb.words[j];
|
||||
var rgb = pal2rgb.palette[j];
|
||||
var hexcol = '#'+hex(rgb2bgr(rgb),6);
|
||||
var textcol = (rgb & 0x008000) ? 'black' : 'white';
|
||||
cell.text(hex(val,2)).css('background-color',hexcol).css('color',textcol);
|
||||
}
|
||||
// iterate over each row of the layout
|
||||
layout.forEach( ([name, start, len]) => {
|
||||
if (start < palette.length) { // skip row if out of range
|
||||
if (start < pal2rgb.palette.length) { // skip row if out of range
|
||||
var arow = $('<tr/>').appendTo(atable);
|
||||
$('<td/>').text(name).appendTo(arow);
|
||||
var inds = [];
|
||||
for (var k=start; k<start+len; k++)
|
||||
inds.push(k);
|
||||
inds.forEach( (i) => {
|
||||
var val = words[i];
|
||||
var rgb = palette[i];
|
||||
var hexcol = '#'+hex(rgb2bgr(rgb),6);
|
||||
var textcol = (rgb & 0x008000) ? 'black' : 'white';
|
||||
var cell = $('<td/>').addClass('asset_cell asset_editable').text(hex(val,2)).css('background-color',hexcol).css('color',textcol).appendTo(arow);
|
||||
var cell = $('<td/>').addClass('asset_cell asset_editable').appendTo(arow);
|
||||
updateCell(cell, i);
|
||||
cell.click((e) => {
|
||||
var chooser = new pixed.ImageChooser();
|
||||
chooser.rgbimgs = allrgbimgs;
|
||||
@ -1077,6 +1083,7 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
chooser.height = 1;
|
||||
chooser.recreate(aeditor, (index, newvalue) => {
|
||||
callback(i, index);
|
||||
updateCell(cell, i);
|
||||
});
|
||||
this.setCurrentEditor(aeditor, cell);
|
||||
});
|
||||
@ -1106,13 +1113,14 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
firstnode.refreshRight();
|
||||
// TODO: add view objects
|
||||
// TODO: show which one is selected?
|
||||
this.addPaletteEditorViews(parentdiv, firstnode.words,
|
||||
pal2rgb.palette, pal2rgb.layout, pal2rgb.getAllColors(),
|
||||
this.addPaletteEditorViews(parentdiv, pal2rgb,
|
||||
(index, newvalue) => {
|
||||
console.log('set entry', index, '=', newvalue);
|
||||
// TODO: this forces update of palette rgb colors and file data
|
||||
firstnode.words[index] = newvalue;
|
||||
//firstnode.refreshRight();
|
||||
firstnode.refreshLeft();
|
||||
pal2rgb.words = null;
|
||||
pal2rgb.updateRight();
|
||||
pal2rgb.refreshLeft();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1124,16 +1132,17 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
// 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);
|
||||
let node = new pixed.FileDataNode(projectWindows, fileid);
|
||||
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, true);
|
||||
nassets++;
|
||||
} else if (typeof data === 'string') {
|
||||
let textfrags = this.scanFileTextForAssets(fileid, data);
|
||||
for (let frag of textfrags) {
|
||||
if (frag.fmt) {
|
||||
let label = fileid; // TODO: label
|
||||
let node : pixed.PixNode = new pixed.TextDataNode(projectWindows, fileid, label, data, frag.start, frag.end);
|
||||
let node : pixed.PixNode = new pixed.TextDataNode(projectWindows, fileid, label, frag.start, frag.end);
|
||||
let first = node;
|
||||
// rle-compressed?
|
||||
if (frag.fmt.comp == 'rletag') {
|
||||
@ -1194,6 +1203,10 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
|
||||
console.log("Found " + this.rootnodes.length + " assets");
|
||||
this.deferrednodes.forEach((node) => { node.refreshRight(); });
|
||||
this.deferrednodes = [];
|
||||
} else {
|
||||
for (var node of this.rootnodes) {
|
||||
node.refreshRight();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,13 +13,15 @@ describe('Pixel editor', function() {
|
||||
var palfmt = {pal:332,n:16};
|
||||
|
||||
var paldatastr = " 0x00, 0x03, 0x19, 0x50, 0x52, 0x07, 0x1f, 0x37, 0xe0, 0xa4, 0xfd, 0xff, 0x38, 0x70, 0x7f, 0xf8, ";
|
||||
var node4 = new pixed.TextDataNode(null, null, null, paldatastr, 0, paldatastr.length);
|
||||
var node4 = new pixed.TextDataNode(null, null, null, 0, paldatastr.length);
|
||||
node4.text = paldatastr;
|
||||
var node5 = new pixed.PaletteFormatToRGB(palfmt);
|
||||
node4.addRight(node5);
|
||||
node4.refreshRight();
|
||||
|
||||
var datastr = "1,2, 0x00,0x00,0xef,0xef,0xe0,0x00,0x00, 0x00,0xee,0xee,0xfe,0xee,0xe0,0x00, 0x0e,0xed,0xef,0xef,0xed,0xee,0x00, 0x0e,0xee,0xdd,0xdd,0xde,0xee,0x00, 0x0e,0xee,0xed,0xde,0xee,0xee,0x00, 0x00,0xee,0xee,0xde,0xee,0xe0,0x00, 0x00,0xee,0xee,0xde,0xee,0xe0,0x00, 0x00,0x00,0xed,0xdd,0xe0,0x00,0x0d, 0xdd,0xdd,0xee,0xee,0xed,0xdd,0xd0, 0x0d,0xee,0xee,0xee,0xee,0xee,0x00, 0x0e,0xe0,0xee,0xee,0xe0,0xee,0x00, 0x0e,0xe0,0xee,0xee,0xe0,0xee,0x00, 0x0e,0xe0,0xdd,0xdd,0xd0,0xde,0x00, 0x0d,0x00,0xee,0x0e,0xe0,0x0d,0x00, 0x00,0x00,0xed,0x0e,0xe0,0x00,0x00, 0x00,0x0d,0xdd,0x0d,0xdd,0x00,0x18,";
|
||||
var node1 = new pixed.TextDataNode(null, null, null, datastr, 0, datastr.length);
|
||||
var node1 = new pixed.TextDataNode(null, null, null, 0, datastr.length);
|
||||
node1.text = datastr;
|
||||
var node2 = new pixed.Mapper(fmt);
|
||||
node1.addRight(node2);
|
||||
var node3 = new pixed.Palettizer(null, fmt);
|
||||
|
Loading…
x
Reference in New Issue
Block a user