more work on asset editors; document.title
This commit is contained in:
parent
22c2fb3c2f
commit
f41d181aab
13
css/ui.css
13
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<arr.length; i++) {
|
||||
var d = arr[i];
|
||||
|
@ -426,6 +427,29 @@ export var currentPaletteStr : string;
|
|||
export var currentPaletteFmt : PixelEditorPaletteFormat;
|
||||
export var allthumbs;
|
||||
|
||||
function convertPaletteFormat(palbytes: number[]|Uint8Array, palfmt: PixelEditorPaletteFormat) : number[] {
|
||||
var pal = palfmt.pal;
|
||||
var newpalette;
|
||||
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 = 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<newpalette.length; i+=currentPaletteFmt.n) {
|
||||
|
@ -573,6 +580,7 @@ function pixelEditorKeypress(e) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: reversed?
|
||||
var PREDEF_PALETTES = {
|
||||
'nes':[
|
||||
0xFF7C7C7C ,0xFF0000FC ,0xFF0000BC ,0xFF4428BC ,0xFF940084 ,0xFFA80020 ,0xFFA81000 ,0xFF881400
|
||||
|
@ -591,6 +599,7 @@ var PREDEF_PALETTES = {
|
|||
export abstract class PixelNode {
|
||||
left : PixelNode; // toward text editor
|
||||
right : PixelNode; // toward pixel editor
|
||||
// TODO: in/out(...) for each type?
|
||||
input?
|
||||
output?
|
||||
|
||||
|
@ -699,9 +708,28 @@ export class PixelPalettizer extends PixelNode {
|
|||
}
|
||||
}
|
||||
|
||||
export class PixelPaletteFormatToRGB extends PixelNode {
|
||||
|
||||
input : Uint8Array;
|
||||
output : Uint32Array[];
|
||||
palette : Uint32Array;
|
||||
palfmt : PixelEditorPaletteFormat;
|
||||
|
||||
updateLeft() {
|
||||
//TODO
|
||||
}
|
||||
updateRight() {
|
||||
this.input = this.left.output;
|
||||
this.palette = new Uint32Array(convertPaletteFormat(this.input, this.palfmt));
|
||||
this.output = [];
|
||||
this.palette.forEach( (rgba:number) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
112
src/views.ts
112
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 = $('<div class="vertical-scroll"/>');
|
||||
|
@ -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 = $('<div class="asset_dual"/>'); // contains grid and editor
|
||||
var agrid = $('<div class="asset_grid"/>'); // grid (or 1) of preview images
|
||||
var aeditor = $('<div class="asset_editor"/>').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 = $('<span/>');
|
||||
agrid.append(span);
|
||||
}
|
||||
span.append(viewer.canvas);
|
||||
if (++i == imgsperline) {
|
||||
agrid.append($("<br/>"));
|
||||
span = null;
|
||||
i = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addPixelEditor(filediv:JQuery, firstnode:PixelFileDataNode|PixelTextDataNode, fmt?) {
|
||||
var adual = $('<div class="asset_dual"/>');
|
||||
// create tile set
|
||||
var aedit = $('<div class="asset_grid"/>');
|
||||
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 = $('<span/>');
|
||||
aedit.append(span);
|
||||
}
|
||||
span.append(viewer.canvas);
|
||||
if (++i == imgsperline) {
|
||||
aedit.append($("<br/>"));
|
||||
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 = $('<div/>').attr('id',divid);
|
||||
var filediv = $('<div class="asset_file"/>').append(header, body).appendTo(this.maindiv);
|
||||
this.refreshAssetsInFile(id, data);
|
||||
// TODO: what if crash while parsing?
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue