more work on asset editors; document.title

This commit is contained in:
Steven Hugg 2019-03-20 14:10:50 -04:00
parent 22c2fb3c2f
commit f41d181aab
5 changed files with 160 additions and 61 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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?
});
}