Remove resize dither buttons and show frontend palette grid
This commit is contained in:
+57
-139
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Render before/after previews directly inside the node.
|
||||
* - Provide clickable controls for dither mode and output sizing.
|
||||
* - Render a palette swatch grid from the frontend-computed active palette.
|
||||
* - Compute local preview with the same nearest + dither logic as backend.
|
||||
*
|
||||
* Event contract:
|
||||
@@ -17,19 +17,19 @@ const NODE_HEADER_HEIGHT = 28;
|
||||
const NODE_PANEL_HEIGHT = 130;
|
||||
const NODE_PADDING = 6;
|
||||
const PREVIEW_GAP = 6;
|
||||
const BUTTON_GAP = 6;
|
||||
const BUTTON_ROW_HEIGHT = 24;
|
||||
const MAX_DIMENSION = 8192;
|
||||
const NODE_WIDGET_ROW_HEIGHT = 24;
|
||||
const NODE_MIN_TOP_RESERVED = 74;
|
||||
const PALETTE_GRID_GAP = 1;
|
||||
const PALETTE_GRID_TOP_INSET = 20;
|
||||
const PALETTE_GRID_BOTTOM_INSET = 4;
|
||||
const PALETTE_GRID_MAX_COLUMNS = 32;
|
||||
|
||||
const TEXT_COLOR_DEFAULT = "#e6e6e6";
|
||||
const BG_PANEL_COLOR = "#2d2d2d";
|
||||
const BG_CONTROL_COLOR = "#2a2a2a";
|
||||
const BUTTON_BG_COLOR = "#3d3d3d";
|
||||
const BUTTON_ACTIVE_BG_COLOR = "#8e5321";
|
||||
const BUTTON_BORDER_COLOR = "#9a9a9a";
|
||||
const PREVIEW_PLACEHOLDER_TEXT_COLOR = "#ebebeb";
|
||||
const PALETTE_SWATCH_BORDER_COLOR = "rgba(0,0,0,0.35)";
|
||||
|
||||
const DITHER_MODES = ["positional", "floyd"];
|
||||
const PALETTE_MODES = ["vga_256", "optimized_x_colors"];
|
||||
@@ -122,9 +122,6 @@ const normalizePalette = (rawPalette) => {
|
||||
|
||||
return null;
|
||||
};
|
||||
const isPointInRect = (x, y, rect) =>
|
||||
!!rect && x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
|
||||
|
||||
app.registerExtension({
|
||||
name: "resize and dither",
|
||||
|
||||
@@ -145,7 +142,6 @@ app.registerExtension({
|
||||
localPreviewCanvas: null,
|
||||
localPreviewCtx: null,
|
||||
hasUserResizedNode: false,
|
||||
controlLayout: null,
|
||||
vgaPalette: [...FALLBACK_PALETTE],
|
||||
paletteFloat: null,
|
||||
paletteInt: null,
|
||||
@@ -429,56 +425,61 @@ app.registerExtension({
|
||||
return area;
|
||||
},
|
||||
|
||||
buildButtonRow: function (area, y, labels, ids) {
|
||||
const width = (area.width - BUTTON_GAP * (labels.length - 1)) / labels.length;
|
||||
return labels.map((label, i) => ({
|
||||
id: ids[i],
|
||||
label,
|
||||
x: area.x + i * (width + BUTTON_GAP),
|
||||
y,
|
||||
width,
|
||||
height: BUTTON_ROW_HEIGHT,
|
||||
}));
|
||||
},
|
||||
|
||||
updateControlLayout: function () {
|
||||
const controlsArea = this.getControlsArea();
|
||||
if (!controlsArea) {
|
||||
this.controlLayout = null;
|
||||
drawPaletteGrid: function (ctx, controlsArea) {
|
||||
const palette = this.paletteInt?.length ? this.paletteInt : FALLBACK_PALETTE;
|
||||
const colorCount = palette.length;
|
||||
if (colorCount === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const row1Y = controlsArea.y;
|
||||
const row2Y = row1Y + BUTTON_ROW_HEIGHT + BUTTON_GAP;
|
||||
const row3Y = row2Y + BUTTON_ROW_HEIGHT + BUTTON_GAP;
|
||||
|
||||
this.controlLayout = {
|
||||
modeButtons: this.buildButtonRow(
|
||||
controlsArea,
|
||||
row1Y,
|
||||
["Positional", "Floyd"],
|
||||
["mode:positional", "mode:floyd"],
|
||||
),
|
||||
scaleButtons: this.buildButtonRow(controlsArea, row2Y, ["1x", "2x", "4x"], ["scale:1", "scale:2", "scale:4"]),
|
||||
adjustButtons: this.buildButtonRow(
|
||||
controlsArea,
|
||||
row3Y,
|
||||
["W-", "W+", "H-", "H+", "Reset"],
|
||||
["w:-16", "w:+16", "h:-16", "h:+16", "reset"],
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
drawButton: function (ctx, button, isActive = false) {
|
||||
ctx.save();
|
||||
ctx.fillStyle = isActive ? BUTTON_ACTIVE_BG_COLOR : BUTTON_BG_COLOR;
|
||||
ctx.fillRect(button.x, button.y, button.width, button.height);
|
||||
ctx.strokeStyle = BUTTON_BORDER_COLOR;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(button.x + 0.5, button.y + 0.5, button.width - 1, button.height - 1);
|
||||
drawStyledText(ctx, button.label, button.x + button.width / 2, button.y + button.height / 2, {
|
||||
const labelY = controlsArea.y + PALETTE_GRID_TOP_INSET - 8;
|
||||
drawStyledText(ctx, `Palette (${colorCount} colors)`, controlsArea.x + controlsArea.width / 2, labelY, {
|
||||
font: "12px Arial",
|
||||
fillStyle: "rgba(235,235,235,0.95)",
|
||||
});
|
||||
|
||||
const gridX = controlsArea.x;
|
||||
const gridY = controlsArea.y + PALETTE_GRID_TOP_INSET;
|
||||
const gridWidth = controlsArea.width;
|
||||
const gridHeight = controlsArea.height - PALETTE_GRID_TOP_INSET - PALETTE_GRID_BOTTOM_INSET;
|
||||
if (gridWidth <= 0 || gridHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const minColumns = Math.min(4, colorCount);
|
||||
const maxColumns = Math.min(PALETTE_GRID_MAX_COLUMNS, colorCount);
|
||||
let bestColumns = minColumns;
|
||||
let bestRows = Math.ceil(colorCount / bestColumns);
|
||||
let bestScore = -1;
|
||||
|
||||
for (let columns = minColumns; columns <= maxColumns; columns++) {
|
||||
const rows = Math.ceil(colorCount / columns);
|
||||
const cellWidth = (gridWidth - PALETTE_GRID_GAP * (columns - 1)) / columns;
|
||||
const cellHeight = (gridHeight - PALETTE_GRID_GAP * (rows - 1)) / rows;
|
||||
const score = Math.min(cellWidth, cellHeight);
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestColumns = columns;
|
||||
bestRows = rows;
|
||||
}
|
||||
}
|
||||
|
||||
const cellWidth = (gridWidth - PALETTE_GRID_GAP * (bestColumns - 1)) / bestColumns;
|
||||
const cellHeight = (gridHeight - PALETTE_GRID_GAP * (bestRows - 1)) / bestRows;
|
||||
|
||||
ctx.save();
|
||||
for (let i = 0; i < colorCount; i++) {
|
||||
const col = i % bestColumns;
|
||||
const row = Math.floor(i / bestColumns);
|
||||
const color = palette[i];
|
||||
const x = gridX + col * (cellWidth + PALETTE_GRID_GAP);
|
||||
const y = gridY + row * (cellHeight + PALETTE_GRID_GAP);
|
||||
|
||||
ctx.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`;
|
||||
ctx.fillRect(x, y, cellWidth, cellHeight);
|
||||
ctx.strokeStyle = PALETTE_SWATCH_BORDER_COLOR;
|
||||
ctx.strokeRect(x + 0.5, y + 0.5, Math.max(0, cellWidth - 1), Math.max(0, cellHeight - 1));
|
||||
}
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
@@ -693,27 +694,6 @@ app.registerExtension({
|
||||
this.setDirtyCanvas(true, true);
|
||||
},
|
||||
|
||||
applyScalePreset: function (scale) {
|
||||
const baseWidth = this.sourcePixelWidth > 0 ? this.sourcePixelWidth : this.getIntWidgetValue("width", 256);
|
||||
const baseHeight = this.sourcePixelHeight > 0 ? this.sourcePixelHeight : this.getIntWidgetValue("height", 256);
|
||||
|
||||
this.setWidgetValue("width", clamp(Math.round(baseWidth * scale), 1, MAX_DIMENSION));
|
||||
this.setWidgetValue("height", clamp(Math.round(baseHeight * scale), 1, MAX_DIMENSION));
|
||||
this.updateBackend();
|
||||
},
|
||||
|
||||
adjustDimension: function (axis, delta) {
|
||||
const widgetName = axis === "w" ? "width" : "height";
|
||||
const current = this.getIntWidgetValue(widgetName, 0);
|
||||
this.setWidgetValue(widgetName, clamp(current + delta, 0, MAX_DIMENSION));
|
||||
this.updateBackend();
|
||||
},
|
||||
|
||||
setMode: function (mode) {
|
||||
this.setWidgetValue("dither_mode", mode);
|
||||
this.updateBackend();
|
||||
},
|
||||
|
||||
onDrawForeground: function (ctx) {
|
||||
if (this.flags?.collapsed) return;
|
||||
|
||||
@@ -722,8 +702,6 @@ app.registerExtension({
|
||||
const controlsArea = this.getControlsArea();
|
||||
if (!topPreviewArea || !bottomPreviewArea || !controlsArea) return;
|
||||
|
||||
this.updateControlLayout();
|
||||
|
||||
ctx.save();
|
||||
ctx.fillStyle = BG_PANEL_COLOR;
|
||||
ctx.fillRect(topPreviewArea.x, topPreviewArea.y, topPreviewArea.width, topPreviewArea.height);
|
||||
@@ -792,17 +770,6 @@ app.registerExtension({
|
||||
ctx.fillRect(controlsArea.x, controlsArea.y, controlsArea.width, controlsArea.height);
|
||||
ctx.restore();
|
||||
|
||||
if (this.controlLayout) {
|
||||
const mode = this.getModeValue();
|
||||
this.controlLayout.modeButtons.forEach((button) => {
|
||||
const isActive = button.id === `mode:${mode}`;
|
||||
this.drawButton(ctx, button, isActive);
|
||||
});
|
||||
|
||||
this.controlLayout.scaleButtons.forEach((button) => this.drawButton(ctx, button, false));
|
||||
this.controlLayout.adjustButtons.forEach((button) => this.drawButton(ctx, button, false));
|
||||
}
|
||||
|
||||
const requestedWidth = this.getIntWidgetValue("width", 0);
|
||||
const requestedHeight = this.getIntWidgetValue("height", 0);
|
||||
const resolved = this.resolveTargetSize(requestedWidth, requestedHeight);
|
||||
@@ -818,52 +785,8 @@ app.registerExtension({
|
||||
fillStyle: "rgba(235,235,235,0.95)",
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
onMouseDown: function (event) {
|
||||
if (event.button !== 0) return false;
|
||||
if (!this.controlLayout) return false;
|
||||
|
||||
const localPos = app.canvas.convertEventToCanvasOffset(event);
|
||||
const mouseX = localPos[0] - this.pos[0];
|
||||
const mouseY = localPos[1] - this.pos[1];
|
||||
|
||||
const hit = (buttons) => buttons.find((button) => isPointInRect(mouseX, mouseY, button));
|
||||
|
||||
const modeButton = hit(this.controlLayout.modeButtons);
|
||||
if (modeButton) {
|
||||
const mode = modeButton.id.split(":")[1];
|
||||
this.setMode(mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
const scaleButton = hit(this.controlLayout.scaleButtons);
|
||||
if (scaleButton) {
|
||||
const scale = Number.parseFloat(scaleButton.id.split(":")[1]);
|
||||
this.applyScalePreset(scale);
|
||||
return true;
|
||||
}
|
||||
|
||||
const adjustButton = hit(this.controlLayout.adjustButtons);
|
||||
if (adjustButton) {
|
||||
if (adjustButton.id === "reset") {
|
||||
const width = this.sourcePixelWidth > 0 ? this.sourcePixelWidth : 256;
|
||||
const height = this.sourcePixelHeight > 0 ? this.sourcePixelHeight : 256;
|
||||
this.setWidgetValue("width", width);
|
||||
this.setWidgetValue("height", height);
|
||||
this.setWidgetValue("dither_mode", "positional");
|
||||
this.setWidgetValue("palette_mode", "vga_256");
|
||||
this.setWidgetValue("palette_colors", 24);
|
||||
this.updateBackend();
|
||||
return true;
|
||||
}
|
||||
|
||||
const [axis, deltaText] = adjustButton.id.split(":");
|
||||
this.adjustDimension(axis, toInt(deltaText, 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
this.drawPaletteGrid(ctx, controlsArea);
|
||||
},
|
||||
|
||||
onResize: function () {
|
||||
@@ -919,11 +842,6 @@ api.addEventListener("game_dev.resize_dither.initialise", ({ detail }) => {
|
||||
);
|
||||
}
|
||||
|
||||
const incomingPalette = normalizePalette(detail.vga_palette);
|
||||
if (incomingPalette) {
|
||||
node.setPalette(incomingPalette);
|
||||
}
|
||||
|
||||
const onBeforeLoad = () => {
|
||||
if (revision !== node.bootstrapRevision) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user