|
|
|
|
@ -146,7 +146,14 @@ function createNewElement(type: MonoDisplay.ElementType): MonoDisplay.MonoFormat |
|
|
|
|
const flags: Record<string, any> = {}; |
|
|
|
|
(EL_FLAGS[MonoDisplay.ElementTypeToString[type]] as ElFlagMeta[] || []) |
|
|
|
|
.forEach(f => (flags[f.key] = f.default ?? false)); |
|
|
|
|
return { type, ...fields, flags } as MonoDisplay.MonoFormatElement; |
|
|
|
|
const el: any = { type, ...fields, flags }; |
|
|
|
|
if (type === MonoDisplay.ElementType.Image2D) { |
|
|
|
|
el.image = { pixels: new Uint8Array(el.width * el.height), width: el.width, height: el.height }; |
|
|
|
|
} |
|
|
|
|
if (type === MonoDisplay.ElementType.Animation) { |
|
|
|
|
el.frames = [{ pixels: new Uint8Array(el.width * el.height), width: el.width, height: el.height }]; |
|
|
|
|
} |
|
|
|
|
return el as MonoDisplay.MonoFormatElement; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function getSectionByIndex(i: number | null): MonoDisplay.MonoFormatSection | undefined { |
|
|
|
|
@ -416,6 +423,8 @@ function FieldInput(si: number, ei: number, field: ElFieldMeta, el: MonoDisplay. |
|
|
|
|
...DEFAULT_FONTS.map((name, i) => m("option", { value: i, selected: val === i }, name)), |
|
|
|
|
...getCustomFonts().map(cf => m("option", { value: cf.index, selected: val === cf.index }, cf.fontname)), |
|
|
|
|
]); |
|
|
|
|
case "list": |
|
|
|
|
return m("span"); // rendered by dedicated editor
|
|
|
|
|
default: |
|
|
|
|
return m("input[type=number]", { |
|
|
|
|
value: val, |
|
|
|
|
@ -424,7 +433,7 @@ function FieldInput(si: number, ei: number, field: ElFieldMeta, el: MonoDisplay. |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ─── Image2D pixel editor ─────────────────────────────────────────────────────
|
|
|
|
|
// --- Shared pixel canvas ------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
const PIXEL_SCALE = 4; |
|
|
|
|
|
|
|
|
|
@ -432,7 +441,7 @@ function getPixelIndex(img: MonoDisplay.MonoFormatPixelImage, x: number, y: numb |
|
|
|
|
return y * img.width + x; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function drawImage2DCanvas(canvas: HTMLCanvasElement, img: MonoDisplay.MonoFormatPixelImage) { |
|
|
|
|
function drawPixelCanvas(canvas: HTMLCanvasElement, img: MonoDisplay.MonoFormatPixelImage) { |
|
|
|
|
const ctx = canvas.getContext("2d")!; |
|
|
|
|
ctx.fillStyle = "#000"; |
|
|
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
@ -446,23 +455,13 @@ function drawImage2DCanvas(canvas: HTMLCanvasElement, img: MonoDisplay.MonoForma |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interface Image2DEditorState { |
|
|
|
|
drawing: boolean; |
|
|
|
|
drawValue: number; // 0 or 1
|
|
|
|
|
} |
|
|
|
|
interface PixelCanvasState { drawing: boolean; drawValue: number; } |
|
|
|
|
|
|
|
|
|
const Image2DEditor: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFormatImage2D }, Image2DEditorState> = { |
|
|
|
|
const PixelCanvas: m.Component<{ img: MonoDisplay.MonoFormatPixelImage; onpaint: () => void }, PixelCanvasState> = { |
|
|
|
|
drawing: false, |
|
|
|
|
drawValue: 1, |
|
|
|
|
|
|
|
|
|
view({ attrs: { si, ei, el }, state }) { |
|
|
|
|
if (!el.image) { |
|
|
|
|
const w = (el as any).width || W; |
|
|
|
|
const h = (el as any).height || H; |
|
|
|
|
el.image = { pixels: new Uint8Array(w * h), width: w, height: h }; |
|
|
|
|
} |
|
|
|
|
const img = el.image; |
|
|
|
|
|
|
|
|
|
view({ attrs: { img, onpaint }, state }) { |
|
|
|
|
function pixelFromEvent(e: MouseEvent): { x: number; y: number } | null { |
|
|
|
|
const canvas = e.currentTarget as HTMLCanvasElement; |
|
|
|
|
const rect = canvas.getBoundingClientRect(); |
|
|
|
|
@ -477,20 +476,16 @@ const Image2DEditor: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoF |
|
|
|
|
function paint(e: MouseEvent) { |
|
|
|
|
const p = pixelFromEvent(e); |
|
|
|
|
if (!p) return; |
|
|
|
|
const idx = getPixelIndex(img, p.x, p.y); |
|
|
|
|
img.pixels[idx] = state.drawValue; |
|
|
|
|
markDirty(); triggerPreview(); |
|
|
|
|
drawImage2DCanvas(e.currentTarget as HTMLCanvasElement, img); |
|
|
|
|
img.pixels[getPixelIndex(img, p.x, p.y)] = state.drawValue; |
|
|
|
|
onpaint(); |
|
|
|
|
drawPixelCanvas(e.currentTarget as HTMLCanvasElement, img); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return m("canvas.pixel-editor", { |
|
|
|
|
width: img.width * PIXEL_SCALE, |
|
|
|
|
height: img.height * PIXEL_SCALE, |
|
|
|
|
oncreate: ({ dom }: m.VnodeDOM) => { |
|
|
|
|
(dom as any)._el = el; |
|
|
|
|
drawImage2DCanvas(dom as HTMLCanvasElement, img); |
|
|
|
|
}, |
|
|
|
|
onupdate: ({ dom }: m.VnodeDOM) => drawImage2DCanvas(dom as HTMLCanvasElement, img), |
|
|
|
|
oncreate: ({ dom }: m.VnodeDOM) => drawPixelCanvas(dom as HTMLCanvasElement, img), |
|
|
|
|
onupdate: ({ dom }: m.VnodeDOM) => drawPixelCanvas(dom as HTMLCanvasElement, img), |
|
|
|
|
onmousedown: (e: MouseEvent) => { |
|
|
|
|
state.drawing = true; |
|
|
|
|
const p = pixelFromEvent(e); |
|
|
|
|
@ -504,6 +499,80 @@ const Image2DEditor: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoF |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// --- Image2D editor -----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
const Image2DEditor: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFormatImage2D }> = { |
|
|
|
|
view({ attrs: { si, ei, el } }) { |
|
|
|
|
if (!el.image) { |
|
|
|
|
const w = (el as any).width || W; |
|
|
|
|
const h = (el as any).height || H; |
|
|
|
|
el.image = { pixels: new Uint8Array(w * h), width: w, height: h }; |
|
|
|
|
} |
|
|
|
|
return m(PixelCanvas, { |
|
|
|
|
img: el.image, |
|
|
|
|
onpaint: () => { markDirty(); triggerPreview(); }, |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// --- Animation editor ---------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
interface AnimationEditorState { activeFrame: number; } |
|
|
|
|
|
|
|
|
|
const AnimationEditor: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFormatAnimation }, AnimationEditorState> = { |
|
|
|
|
activeFrame: 0, |
|
|
|
|
|
|
|
|
|
view({ attrs: { si, ei, el }, state }) { |
|
|
|
|
const w = el.width || W; |
|
|
|
|
const h = el.height || H; |
|
|
|
|
if (!el.frames) el.frames = []; |
|
|
|
|
if (!el.frames.length) { |
|
|
|
|
el.frames.push({ pixels: new Uint8Array(w * h), width: w, height: h }); |
|
|
|
|
} |
|
|
|
|
state.activeFrame = Math.min(state.activeFrame, el.frames.length - 1); |
|
|
|
|
const frame = el.frames[state.activeFrame]; |
|
|
|
|
|
|
|
|
|
function addFrame() { |
|
|
|
|
el.frames.push({ pixels: new Uint8Array(w * h), width: w, height: h }); |
|
|
|
|
state.activeFrame = el.frames.length - 1; |
|
|
|
|
markDirty(); triggerPreview(); m.redraw(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function removeFrame(fi: number) { |
|
|
|
|
el.frames.splice(fi, 1); |
|
|
|
|
if (!el.frames.length) |
|
|
|
|
el.frames.push({ pixels: new Uint8Array(w * h), width: w, height: h }); |
|
|
|
|
state.activeFrame = Math.min(state.activeFrame, el.frames.length - 1); |
|
|
|
|
markDirty(); triggerPreview(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return m(".anim-editor", |
|
|
|
|
m(".anim-frame-tabs", |
|
|
|
|
el.frames.map((_, fi) => |
|
|
|
|
m(".anim-frame-tab", { |
|
|
|
|
class: fi === state.activeFrame ? "active" : "", |
|
|
|
|
onclick: () => { state.activeFrame = fi; }, |
|
|
|
|
}, |
|
|
|
|
m("span", fi + 1), |
|
|
|
|
el.frames.length > 1 |
|
|
|
|
? m("button.x-btn-sm", { |
|
|
|
|
onclick: (e: Event) => { e.stopPropagation(); removeFrame(fi); }, |
|
|
|
|
}, "×") |
|
|
|
|
: null, |
|
|
|
|
) |
|
|
|
|
), |
|
|
|
|
m("button.add-el", { onclick: addFrame }, "+ frame"), |
|
|
|
|
), |
|
|
|
|
m("div", { key: state.activeFrame }, |
|
|
|
|
m(PixelCanvas, { |
|
|
|
|
img: frame, |
|
|
|
|
onpaint: () => { markDirty(); triggerPreview(); }, |
|
|
|
|
}), |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const ElementItem: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFormatElement }> = { |
|
|
|
|
view({ attrs: { si, ei, el } }) { |
|
|
|
|
const isActive = activeSecIndex === si && activeElIndex === ei; |
|
|
|
|
@ -558,6 +627,11 @@ const ElementItem: m.Component<{ si: number; ei: number; el: MonoDisplay.MonoFor |
|
|
|
|
m("label", "Pixels"), |
|
|
|
|
m(Image2DEditor, { si, ei, el: el as MonoDisplay.MonoFormatImage2D }), |
|
|
|
|
) |
|
|
|
|
: el.type === MonoDisplay.ElementType.Animation |
|
|
|
|
? m(".field.full", |
|
|
|
|
m("label", "Frames"), |
|
|
|
|
m(AnimationEditor, { si, ei, el: el as MonoDisplay.MonoFormatAnimation }), |
|
|
|
|
) |
|
|
|
|
: null, |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
|