From f41d181aab7855c3482d405f5f228d39f90df29c Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Wed, 20 Mar 2019 14:10:50 -0400 Subject: [PATCH] more work on asset editors; document.title --- css/ui.css | 13 +++++ doc/notes.txt | 10 ++-- src/pixed/pixeleditor.ts | 85 ++++++++++++++++++++--------- src/ui.ts | 1 + src/views.ts | 112 +++++++++++++++++++++++++++------------ 5 files changed, 160 insertions(+), 61 deletions(-) diff --git a/css/ui.css b/css/ui.css index f09e2ad8..c17d30aa 100644 --- a/css/ui.css +++ b/css/ui.css @@ -472,3 +472,16 @@ div.asset_grid canvas { padding: 0px; border: 1px solid black; } +div.asset_grid canvas:hover { + border: 1px solid white; +} +div.asset_dual { + display: flex; + align-items: center; +} +div.asset_editor { + border-radius:16px; + padding:16px; + margin:16px; + background-color:#999; +} diff --git a/doc/notes.txt b/doc/notes.txt index a8e7f96a..edd4a5f9 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -40,7 +40,6 @@ TODO: - NES crt should mark raster pos when debugging - intro/help text for each platform - vscode/atom extension? -- navigator.getGamepads - VCS asm library - better VCS single stepping, maybe also listings - VCS skips step on lsr/lsr after run to line @@ -64,7 +63,6 @@ TODO: - update bootstrap - $readmemb/h - batariBasic: proper line numbers, debugging -- show player controls for each platform, allow touch support - granular control over time scrubbing, show CPU state - error showing replay div before rom starts - compiler flags for final ROM build @@ -93,7 +91,13 @@ TODO: - profiler restarts when paused - it's pretty easy to add a new file named like a library file (bcd.c) - put globals into view/controller objects -- open asset editor twice first time? +- cr/lf in files +- upload binary files doesn't do what's expected, changing pulldown and whatnot +- chrome autostart audio: https://github.com/processing/p5.js-sound/issues/249 +- show player controls for each platform, allow touch support, navigator.getGamepads +- better undo/diff for mistakes? +- get rid of "illegal PC" instruction, replace with status msg + WEB WORKER FORMAT diff --git a/src/pixed/pixeleditor.ts b/src/pixed/pixeleditor.ts index e42b2efb..81b5f2be 100644 --- a/src/pixed/pixeleditor.ts +++ b/src/pixed/pixeleditor.ts @@ -3,7 +3,7 @@ import { hex } from "../util"; import { current_project } from "../ui"; -type PixelEditorImageFormat = { +export type PixelEditorImageFormat = { w:number h:number count?:number @@ -18,7 +18,7 @@ type PixelEditorImageFormat = { xform?:string }; -type PixelEditorPaletteFormat = { +export type PixelEditorPaletteFormat = { pal?:number n?:number }; @@ -400,7 +400,8 @@ function convertImagesToWords(images:Uint8Array[], fmt:PixelEditorImageFormat) : return words; } -function convertPaletteBytes(arr:number[],r0,r1,g0,g1,b0,b1) : number[] { +// TODO +function convertPaletteBytes(arr:number[]|Uint8Array,r0,r1,g0,g1,b0,b1) : number[] { var result = []; for (var i=0; i 0) { + var rr = Math.floor(Math.abs(pal/100) % 10); + var gg = Math.floor(Math.abs(pal/10) % 10); + var bb = Math.floor(Math.abs(pal) % 10); + // TODO: n + if (currentPaletteFmt.pal >= 0) + newpalette = convertPaletteBytes(palbytes, 0, rr, rr, gg, rr+gg, bb); + else + newpalette = convertPaletteBytes(palbytes, rr+gg, bb, rr, gg, 0, rr); + } else { + var paltable = PREDEF_PALETTES[pal]; + if (paltable) { + newpalette = new Uint32Array(palbytes).map((i) => { return paltable[i & (paltable.length-1)]; }); + } else { + throw new Error("No palette named " + pal); + } + } + return newpalette; +} + export function pixelEditorDecodeMessage(e) { parentSource = e.source; parentOrigin = e.origin; @@ -439,24 +463,7 @@ export function pixelEditorDecodeMessage(e) { var newpalette = [0xff000000, 0xffffffff]; // TODO if (currentPaletteStr) { var palbytes = parseHexWords(data.palstr); - var pal = currentPaletteFmt.pal; - if (pal > 0) { - var rr = Math.floor(Math.abs(pal/100) % 10); - var gg = Math.floor(Math.abs(pal/10) % 10); - var bb = Math.floor(Math.abs(pal) % 10); - // TODO: n - if (currentPaletteFmt.pal >= 0) - newpalette = convertPaletteBytes(palbytes, 0, rr, rr, gg, rr+gg, bb); - else - newpalette = convertPaletteBytes(palbytes, rr+gg, bb, rr, gg, 0, rr); - } else { - var paltable = PREDEF_PALETTES[pal]; - if (paltable) { - newpalette = palbytes.map((i) => { return paltable[i]; }); - } else { - alert("No palette named " + pal); - } - } + newpalette = convertPaletteFormat(palbytes, currentPaletteFmt) || newpalette; if (currentPaletteFmt.n) { paletteSets = []; for (var i=0; i { + this.output.push(new Uint32Array([rgba])); + }); + } +} + export class PixelViewer { - parentdiv : HTMLElement; width : number; height : number; canvas : HTMLCanvasElement; @@ -710,10 +738,16 @@ export class PixelViewer { recreate() { this.canvas = this.newCanvas(); - this.ctx = this.canvas.getContext('2d'); this.pixdata = this.ctx.createImageData(this.width, this.height); } + createWith(pv : PixelViewer) { + this.width = pv.width; + this.height = pv.height; + this.pixdata = pv.pixdata; + this.canvas = this.newCanvas(); + } + newCanvas() : HTMLCanvasElement { var c = document.createElement('canvas'); c.width = this.width; @@ -721,11 +755,14 @@ export class PixelViewer { //if (fmt.xform) c.style.transform = fmt.xform; c.classList.add("pixels"); c.classList.add("pixelated"); + this.ctx = c.getContext('2d'); return c; } updateImage(imdata : Uint32Array) { - new Uint32Array(this.pixdata.data.buffer).set(imdata); + if (imdata) { + new Uint32Array(this.pixdata.data.buffer).set(imdata); + } this.ctx.putImageData(this.pixdata, 0, 0); } } diff --git a/src/ui.ts b/src/ui.ts index f07a572c..d7ef0df9 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1458,6 +1458,7 @@ export function startUI(loadplatform : boolean) { console.log("loaded platform", platform_id); startPlatform(); showWelcomeMessage(); + document.title = document.title + " [" + platform_id + "] - " + main_file_id; }); } else { startPlatform(); diff --git a/src/views.ts b/src/views.ts index 8afcf3b8..82b551d7 100644 --- a/src/views.ts +++ b/src/views.ts @@ -8,7 +8,7 @@ import { Platform, EmuState, ProfilerOutput, lookupSymbol } from "./baseplatform import { hex, lpad, rpad, safeident } from "./util"; import { CodeAnalyzer } from "./analysis"; import { platform, platform_id, compparams, current_project, lastDebugState, projectWindows } from "./ui"; -import { PixelViewer, PixelMapper, PixelPalettizer, PixelFileDataNode, PixelTextDataNode, parseHexWords } from "./pixed/pixeleditor"; +import { PixelEditorImageFormat, PixelViewer, PixelMapper, PixelPalettizer, PixelFileDataNode, PixelTextDataNode, PixelPaletteFormatToRGB, parseHexWords } from "./pixed/pixeleditor"; export interface ProjectView { createDiv(parent:HTMLElement, text:string) : HTMLElement; @@ -954,6 +954,7 @@ export class ProfileView implements ProjectView { export class AssetEditorView implements ProjectView { maindiv : JQuery; + cureditordiv : JQuery; createDiv(parent : HTMLElement) { this.maindiv = $('
'); @@ -961,6 +962,19 @@ export class AssetEditorView implements ProjectView { return this.maindiv[0]; } + setCurrentEditor(div : JQuery) { + if (this.cureditordiv != div) { + if (this.cureditordiv) { + this.cureditordiv.hide(250); + this.cureditordiv = null; + } + if (div) { + this.cureditordiv = div; + this.cureditordiv.show(250); + } + } + } + scanFileTextForAssets(id : string, data : string) { // scan file for assets // /*{json}*/ or ;;{json};; @@ -991,18 +1005,51 @@ export class AssetEditorView implements ProjectView { return result; } + addPixelEditorViews(filediv : JQuery, images : Uint32Array[], fmt : PixelEditorImageFormat) { + var adual = $('
'); // contains grid and editor + var agrid = $('
'); // grid (or 1) of preview images + var aeditor = $('
').hide(); // contains editor, when selected + adual.append(agrid, aeditor).appendTo(filediv); + // TODO: they need to update when refreshed from right + var imgsperline = fmt.w <= 8 ? 16 : 8; // TODO + // TODO? + var cscale = Math.ceil(16/fmt.w); + var escale = Math.ceil(192/fmt.w); + var i = 0; + var span = null; + images.forEach( (imdata) => { + var viewer = new PixelViewer(); + viewer.width = fmt.w | 0; + viewer.height = fmt.h | 0; + viewer.recreate(); + viewer.canvas.style.width = (viewer.width*cscale)+'px'; // TODO + viewer.updateImage(imdata); // TODO + $(viewer.canvas).click((e) => { + var editview = new PixelViewer(); + editview.createWith(viewer); + editview.updateImage(null); + editview.canvas.style.width = (viewer.width*escale)+'px'; // TODO + aeditor.empty().append(editview.canvas); + this.setCurrentEditor(aeditor); + }); + if (!span) { + span = $(''); + agrid.append(span); + } + span.append(viewer.canvas); + if (++i == imgsperline) { + agrid.append($("
")); + span = null; + i = 0; + } + }); + } + addPixelEditor(filediv:JQuery, firstnode:PixelFileDataNode|PixelTextDataNode, fmt?) { - var adual = $('
'); - // create tile set - var aedit = $('
'); - adual.append(aedit); - filediv.append(adual); // data -> pixels var mapper = new PixelMapper(); - fmt = fmt || {w:8,h:8,bpp:1,count:256/*(data.length>>4)*/,brev:true,np:2,pofs:8,remap:[0,1,2,4,5,6,7,8,9,10,11,12]}; // TODO fmt.xform = 'scale(2)'; mapper.fmt = fmt; - var imgsperline = fmt.w == 8 ? 16 : 8; // TODO // TODO: rotate node? firstnode.addRight(mapper); // pixels -> RGBA @@ -1010,35 +1057,23 @@ export class AssetEditorView implements ProjectView { if (fmt.bpp*(fmt.np|1) == 1) palizer.palette = new Uint32Array([0xff000000, 0xffffffff]); else - palizer.palette = new Uint32Array([0xff000000, 0xffff00ff, 0xffffff00, 0xffffffff]); // TODO + palizer.palette = new Uint32Array([0x00000000, 0xffff00ff, 0xffffff00, 0xffffffff]); // TODO mapper.addRight(palizer); // refresh firstnode.refreshRight(); // add view objects (TODO) - // TODO: they need to update when refreshed from right - var i = 0; - var span = null; - for (var imdata of palizer.output) { - var viewer = new PixelViewer(); - viewer.width = mapper.fmt.w | 0; - viewer.height = mapper.fmt.h | 0; - viewer.recreate(); - viewer.canvas.style.width = (viewer.width*2)+'px'; // TODO - viewer.updateImage(imdata); // TODO - $(viewer.canvas).click((e) => { - console.log(e); // TODO - }); - if (!span) { - span = $(''); - aedit.append(span); - } - span.append(viewer.canvas); - if (++i == imgsperline) { - aedit.append($("
")); - span = null; - i = 0; - } - } + this.addPixelEditorViews(filediv, palizer.output, fmt); + } + + addPaletteEditor(filediv:JQuery, firstnode:PixelFileDataNode|PixelTextDataNode, palfmt?) { + // palette -> RGBA + var pal2rgb = new PixelPaletteFormatToRGB(); + pal2rgb.palfmt = palfmt; + firstnode.addRight(pal2rgb); + firstnode.refreshRight(); + // add view objects (TODO) + var imgfmt = {w:1,h:1}; + this.addPixelEditorViews(filediv, pal2rgb.output, imgfmt); } refreshAssetsInFile(fileid : string, data : FileData) { @@ -1046,8 +1081,10 @@ export class AssetEditorView implements ProjectView { // TODO // TODO: check if open if (fileid.endsWith('.chr') && data instanceof Uint8Array) { + // is this a NES CHR? let node = new PixelFileDataNode(fileid, data); - this.addPixelEditor(filediv, node); + 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); } else if (typeof data === 'string') { let textfrags = this.scanFileTextForAssets(fileid, data); for (let frag of textfrags) { @@ -1055,6 +1092,11 @@ export class AssetEditorView implements ProjectView { if (frag.fmt && frag.fmt.w > 0 && frag.fmt.h > 0) { let node = new PixelTextDataNode(fileid, data, frag.start, frag.end); this.addPixelEditor(filediv, node, frag.fmt); + } + // is this a palette? + else if (frag.fmt && frag.fmt.pal) { + let node = new PixelTextDataNode(fileid, data, frag.start, frag.end); + this.addPaletteEditor(filediv, node, frag.fmt); } else { // TODO: other kinds of resources? } @@ -1066,6 +1108,7 @@ export class AssetEditorView implements ProjectView { return '__asset__' + safeident(id); } +// TODO: recreate editors when refreshing refresh() { this.maindiv.empty(); current_project.iterateFiles((id, data) => { @@ -1074,6 +1117,7 @@ export class AssetEditorView implements ProjectView { var body = $('
').attr('id',divid); var filediv = $('
').append(header, body).appendTo(this.maindiv); this.refreshAssetsInFile(id, data); + // TODO: what if crash while parsing? }); }