Wall: unify paint toggle and consistent outlines; arch spacing tweaks
This commit is contained in:
parent
346f6ff917
commit
7ba62b3d2b
38
classic.js
38
classic.js
@ -375,7 +375,8 @@
|
||||
const base = shape.base || {};
|
||||
const scale = cellScale(cell);
|
||||
const expandedOn = model.manualMode && (model.explodedGapPx || 0) > 0;
|
||||
const manualScale = expandedOn ? 1.35 : 1;
|
||||
// Keep arch geometry consistent when expanded; only scale the alpha (wireframe) ring slightly to improve hit targets.
|
||||
const manualScale = expandedOn && model.patternName?.toLowerCase().includes('arch') ? 1 : (expandedOn ? 1.15 : 1);
|
||||
const transform = [(base.transform||''), `scale(${scale * manualScale})`].join(' ');
|
||||
const isUnpainted = !colorInfo || explicitFill === 'none';
|
||||
const wireframe = !!opts.wireframe || (model.manualMode && isUnpainted);
|
||||
@ -457,13 +458,18 @@
|
||||
const gap = model.explodedGapPx || 0;
|
||||
const isArch = (model.patternName || '').toLowerCase().includes('arch');
|
||||
if (isArch) {
|
||||
// Move along the arch tangent to increase spacing without distorting the curve.
|
||||
// Move outward along the radial vector and add a tangential nudge for even spread; push ends a bit more.
|
||||
const dist = Math.hypot(xPx, yPx) || 1;
|
||||
const tx = -yPx / dist;
|
||||
const ty = xPx / dist;
|
||||
const push = rowIndex * gap;
|
||||
xPx += tx * push;
|
||||
yPx += ty * push;
|
||||
const maxRow = Math.max(1, (pattern.cellsPerRow * model.rowCount) - 1);
|
||||
const t = Math.max(0, Math.min(1, y / maxRow)); // 0 first row, 1 last row
|
||||
const radialPush = gap * (1.6 + Math.abs(t - 0.5) * 1.6); // ends > crown
|
||||
const tangentialPush = (t - 0.5) * (gap * 0.8); // small along-arc spread
|
||||
const nx = xPx / dist;
|
||||
const ny = yPx / dist;
|
||||
const tx = -ny;
|
||||
const ty = nx;
|
||||
xPx += nx * radialPush + tx * tangentialPush;
|
||||
yPx += ny * radialPush + ty * tangentialPush;
|
||||
} else {
|
||||
yPx += rowIndex * gap; // columns: separate along the vertical path
|
||||
}
|
||||
@ -626,14 +632,14 @@ function distinctPaletteSlots(palette) {
|
||||
if (isArch) {
|
||||
// Radial slide outward; preserve layout.
|
||||
const dist = Math.hypot(c.x, c.y) || 1;
|
||||
const offset = 80;
|
||||
const offset = (model.manualMode && (model.explodedGapPx || 0) > 0) ? 120 : 80;
|
||||
const nx = c.x / dist, ny = c.y / dist;
|
||||
slideX = nx * offset;
|
||||
slideY = ny * offset;
|
||||
// Slight tangent spread (~5px) to separate balloons without reshaping the quad.
|
||||
const txDirX = -ny;
|
||||
const txDirY = nx;
|
||||
const fan = spread * 10;
|
||||
const fan = spread * ((model.manualMode && (model.explodedGapPx || 0) > 0) ? 16 : 10);
|
||||
slideX += txDirX * fan;
|
||||
slideY += txDirY * fan;
|
||||
}
|
||||
@ -773,13 +779,14 @@ function distinctPaletteSlots(palette) {
|
||||
const svgDefs = svg('defs', {}, patternsDefs);
|
||||
|
||||
const mainGroup = svg('g', null, kids);
|
||||
const zoomPercent = classicZoom * 100;
|
||||
m.render(container, svg('svg', {
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
width:'100%',
|
||||
height:'100%',
|
||||
viewBox: vb,
|
||||
preserveAspectRatio:'xMidYMid meet',
|
||||
style: `isolation:isolate; transform:scale(${classicZoom}); transform-origin:center center;`
|
||||
style: `isolation:isolate; width:${zoomPercent}%; height:${zoomPercent}%; min-width:${zoomPercent}%; min-height:${zoomPercent}%; transform-origin:center center;`
|
||||
}, [svgDefs, mainGroup]));
|
||||
}
|
||||
|
||||
@ -1496,6 +1503,8 @@ function distinctPaletteSlots(palette) {
|
||||
const toolbarZoomOut = document.getElementById('classic-toolbar-zoomout');
|
||||
const toolbarReset = document.getElementById('classic-toolbar-reset');
|
||||
const focusLabelCanvas = document.getElementById('classic-focus-label-canvas');
|
||||
const reverseLabel = reverseCb?.closest('label');
|
||||
const reverseHint = reverseLabel?.parentElement?.querySelector('.hint');
|
||||
const quadModal = document.getElementById('classic-quad-modal');
|
||||
const quadModalClose = document.getElementById('classic-quad-modal-close');
|
||||
const quadModalDisplay = document.getElementById('classic-quad-modal-display');
|
||||
@ -1981,9 +1990,12 @@ function distinctPaletteSlots(palette) {
|
||||
topperControls.classList.toggle('hidden', !showTopper);
|
||||
if (numberTintRow) numberTintRow.classList.toggle('hidden', !(showTopper && isNumberTopper));
|
||||
if (nudgeOpenBtn) nudgeOpenBtn.classList.toggle('hidden', !showTopper);
|
||||
const showReverse = patternLayout === 'spiral' && !manualOn;
|
||||
if (reverseLabel) reverseLabel.classList.toggle('hidden', !showReverse);
|
||||
if (reverseHint) reverseHint.classList.toggle('hidden', !showReverse);
|
||||
if (reverseCb) {
|
||||
reverseCb.disabled = manualOn;
|
||||
if (manualOn) reverseCb.checked = false;
|
||||
reverseCb.disabled = manualOn || !showReverse;
|
||||
if (!showReverse) reverseCb.checked = false;
|
||||
}
|
||||
|
||||
GC.setTopperEnabled(showTopper);
|
||||
@ -2002,7 +2014,7 @@ function distinctPaletteSlots(palette) {
|
||||
const expandedOn = manualOn && manualExpandedState;
|
||||
GC.setExplodedSettings({
|
||||
scale: expandedOn ? 1.18 : 1,
|
||||
gapPx: expandedOn ? 26 : 0,
|
||||
gapPx: expandedOn ? 90 : 0,
|
||||
staggerPx: expandedOn ? 6 : 0
|
||||
});
|
||||
if (display) {
|
||||
|
||||
160
wall.js
160
wall.js
@ -18,7 +18,6 @@
|
||||
|
||||
let wallState = null;
|
||||
let selectedColorIdx = 0; // This should be synced with organic's selectedColorIdx
|
||||
let wallToolMode = 'paint';
|
||||
|
||||
// DOM elements
|
||||
const wallDisplay = document.getElementById('wall-display');
|
||||
@ -326,6 +325,18 @@
|
||||
return { mode: 'auto' };
|
||||
};
|
||||
|
||||
// Shared stroke helpers:
|
||||
// - Outline only when filled AND outline is enabled.
|
||||
// - Wireframe only when empty AND wireframes are enabled.
|
||||
const strokeFor = (isEmpty, { outline = '#111827', wire = '#cbd5e1' } = {}) => {
|
||||
if (isEmpty) return showWireframes ? wire : 'none';
|
||||
return showOutline ? outline : 'none';
|
||||
};
|
||||
const strokeWidthFor = (isEmpty, { outline = 0.6, wire = 1.4 } = {}) => {
|
||||
if (isEmpty) return showWireframes ? wire : 0;
|
||||
return showOutline ? outline : 0;
|
||||
};
|
||||
|
||||
// Helper to create a shine ellipse with coordinates relative to (0,0)
|
||||
const shineNodeRelative = (rx, ry, hex, rot = -20) => {
|
||||
const shine = shineStyle(hex || WALL_FALLBACK_COLOR);
|
||||
@ -351,13 +362,14 @@
|
||||
|
||||
const meta = wallColorMeta(customIdx);
|
||||
const patId = ensurePattern(meta);
|
||||
const fill = invisible ? hitFill : (isEmpty ? hitFill : (patId ? `url(#${patId})` : meta.hex));
|
||||
const stroke = invisible ? 'none' : (isEmpty ? (showWireframes ? '#cbd5e1' : 'none') : (showOutline ? '#111827' : 'none'));
|
||||
const strokeW = invisible ? 0 : (isEmpty ? (showWireframes ? 1.4 : 0) : (showOutline ? 0.6 : 0));
|
||||
const fill = invisible ? hitFill : (isEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
||||
const stroke = invisible ? 'none' : strokeFor(isEmpty);
|
||||
const strokeW = invisible ? 0 : strokeWidthFor(isEmpty);
|
||||
const filter = (isEmpty || invisible) ? '' : `filter="url(#${smallShadow})"`;
|
||||
const shine = isEmpty ? '' : shineNodeRelative(fiveInchDims.rx, fiveInchDims.ry, meta.hex);
|
||||
|
||||
smallNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" style="cursor:pointer; pointer-events:all;" transform="translate(${pos.x},${pos.y})">
|
||||
const displayIdx = isEmpty ? -1 : (customIdx ?? -1);
|
||||
smallNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" data-wall-color="${displayIdx}" style="cursor:pointer; pointer-events:all;" transform="translate(${pos.x},${pos.y})">
|
||||
<circle cx="0" cy="0" r="${fiveInchDims.rx}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
|
||||
${shine}
|
||||
</g>`);
|
||||
@ -380,14 +392,14 @@
|
||||
|
||||
const meta = wallColorMeta(customIdx);
|
||||
const patId = ensurePattern(meta);
|
||||
const fill = invisible ? hitFill : (isEmpty ? hitFill : (patId ? `url(#${patId})` : meta.hex));
|
||||
console.log(`h-r-c: keyId: ${keyId}, customIdx: ${customIdx}, isEmpty: ${isEmpty}, invisible: ${invisible}, fill: ${fill}, meta:`, meta);
|
||||
const stroke = invisible ? 'none' : (isEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
|
||||
const strokeW = invisible ? 0 : (isEmpty ? 1.4 : (showOutline ? 0.6 : 0));
|
||||
const fill = invisible ? hitFill : (isEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
||||
const stroke = invisible ? 'none' : strokeFor(isEmpty);
|
||||
const strokeW = invisible ? 0 : strokeWidthFor(isEmpty, { outline: 0.6, wire: 1.4 });
|
||||
const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||
const shine = isEmpty ? '' : shineNodeRelative(linkDims.rx, linkDims.ry, meta.hex);
|
||||
|
||||
bigNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y})">
|
||||
const displayIdx = isEmpty ? -1 : (customIdx ?? -1);
|
||||
bigNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" data-wall-color="${displayIdx}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y})">
|
||||
<ellipse cx="0" cy="0" rx="${linkDims.rx}" ry="${linkDims.ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
|
||||
${shine}
|
||||
</g>`);
|
||||
@ -409,13 +421,14 @@
|
||||
|
||||
const meta = wallColorMeta(customIdx);
|
||||
const patId = ensurePattern(meta);
|
||||
const fill = invisible ? hitFill : (isEmpty ? hitFill : (patId ? `url(#${patId})` : meta.hex));
|
||||
const stroke = invisible ? 'none' : (isEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
|
||||
const strokeW = invisible ? 0 : (isEmpty ? 1.4 : (showOutline ? 0.6 : 0));
|
||||
const fill = invisible ? hitFill : (isEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
||||
const stroke = invisible ? 'none' : strokeFor(isEmpty, { outline: '#111827', wire: '#cbd5e1' });
|
||||
const strokeW = invisible ? 0 : strokeWidthFor(isEmpty, { outline: 0.6, wire: 1.4 });
|
||||
const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||
const shine = isEmpty ? '' : shineNodeRelative(linkDims.rx, linkDims.ry, meta.hex);
|
||||
|
||||
bigNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y}) rotate(90)">
|
||||
const displayIdx = isEmpty ? -1 : (customIdx ?? -1);
|
||||
bigNodes.push(`<g data-wall-cell="1" data-wall-key="${keyId}" data-wall-color="${displayIdx}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y}) rotate(90)">
|
||||
<ellipse cx="0" cy="0" rx="${linkDims.rx}" ry="${linkDims.ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
|
||||
${shine}
|
||||
</g>`);
|
||||
@ -438,13 +451,14 @@
|
||||
const invisible = isEmpty;
|
||||
const meta = wallColorMeta(gapIdx);
|
||||
const patId = ensurePattern(meta);
|
||||
const fill = invisible ? hitFill : (patId ? `url(#${patId})` : meta.hex);
|
||||
const stroke = invisible || isEmpty ? 'none' : (showOutline ? '#111827' : 'none');
|
||||
const strokeW = invisible || isEmpty ? 0 : (showOutline ? 0.6 : 0);
|
||||
const fill = invisible ? hitFill : (isEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
||||
const stroke = invisible ? 'none' : strokeFor(isEmpty);
|
||||
const strokeW = invisible ? 0 : strokeWidthFor(isEmpty, { outline: 0.6, wire: 1.4 });
|
||||
const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||
const rGap = bigR * 0.82; // slightly smaller 11" gap balloon
|
||||
const shineGap = isEmpty ? '' : shineNodeRelative(rGap, rGap, meta.hex);
|
||||
bigNodes.push(`<g data-wall-gap="1" data-wall-key="${gapKey}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${center.x},${center.y})">
|
||||
const displayIdx = isEmpty ? -1 : (gapIdx ?? -1);
|
||||
bigNodes.push(`<g data-wall-gap="1" data-wall-key="${gapKey}" data-wall-color="${displayIdx}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${center.x},${center.y})">
|
||||
<circle cx="0" cy="0" r="${rGap}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
|
||||
${shineGap}
|
||||
</g>`);
|
||||
@ -468,13 +482,13 @@
|
||||
const meta = wallColorMeta(centerCustomIdx);
|
||||
const patId = ensurePattern(meta);
|
||||
const fill = invisible ? 'rgba(0,0,0,0.001)' : (centerIsEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
||||
console.log(`c-r-c: keyId: ${centerKey}, customIdx: ${centerCustomIdx}, isEmpty: ${centerIsEmpty}, invisible: ${invisible}, fill: ${fill}, meta:`, meta);
|
||||
const stroke = invisible ? 'none' : (centerIsEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
|
||||
const strokeW = invisible ? 0 : (centerIsEmpty ? 1.4 : (showOutline ? 0.6 : 0));
|
||||
const stroke = invisible ? 'none' : strokeFor(centerIsEmpty);
|
||||
const strokeW = invisible ? 0 : strokeWidthFor(centerIsEmpty, { outline: 0.6, wire: 1.4 });
|
||||
const filter = centerIsEmpty || invisible ? '' : `filter="url(#${smallShadow})"`;
|
||||
const shine = centerIsEmpty ? '' : shineNodeRelative(fiveInchDims.rx, fiveInchDims.ry, meta.hex);
|
||||
|
||||
smallNodes.push(`<g data-wall-cell="1" data-wall-key="${centerKey}" style="cursor:pointer; pointer-events:all;" transform="translate(${center.x},${center.y})">
|
||||
const displayIdxCenter = centerCustomIdx ?? -1;
|
||||
smallNodes.push(`<g data-wall-cell="1" data-wall-key="${centerKey}" data-wall-color="${displayIdxCenter}" style="cursor:pointer; pointer-events:all;" transform="translate(${center.x},${center.y})">
|
||||
<circle cx="0" cy="0" r="${fiveInchDims.rx}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
|
||||
${shine}
|
||||
</g>`);
|
||||
@ -492,19 +506,21 @@
|
||||
const linkCustomIdx = linkOverride.mode === 'color' ? linkOverride.idx : null;
|
||||
const linkIsEmpty = linkOverride.mode === 'empty' || linkCustomIdx === null;
|
||||
|
||||
const invisibleLink = linkIsEmpty && !showWireframes;
|
||||
|
||||
const meta = wallColorMeta(linkCustomIdx);
|
||||
const patId = ensurePattern(meta);
|
||||
const fill = invisibleLink ? 'rgba(0,0,0,0.001)' : (linkIsEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
||||
console.log(`l#-r-c: keyId: ${linkKey}, customIdx: ${linkCustomIdx}, isEmpty: ${linkIsEmpty}, invisible: ${invisibleLink}, fill: ${fill}, meta:`, meta);
|
||||
// Outline only when filled; light wireframe when empty and wireframes shown.
|
||||
const stroke = invisibleLink ? 'none' : (linkIsEmpty ? (showWireframes ? '#cbd5e1' : 'none') : (showOutline ? '#111827' : 'none'));
|
||||
const strokeW = invisibleLink ? 0 : (linkIsEmpty ? (showWireframes ? 1.2 : 0) : (showOutline ? 0.8 : 0));
|
||||
const filter = invisibleLink || linkIsEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||
const fill = linkIsEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex);
|
||||
// Wireframe shows hit area when empty; outline shows only when filled and outline enabled.
|
||||
const stroke = linkIsEmpty
|
||||
? (showWireframes ? '#cbd5e1' : 'none')
|
||||
: (showOutline ? '#111827' : 'none');
|
||||
const strokeW = linkIsEmpty
|
||||
? (showWireframes ? 1.0 : 0)
|
||||
: (showOutline ? 0.9 : 0);
|
||||
const filter = linkIsEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||
const shine = linkIsEmpty ? '' : shineNodeRelative(linkDims.rx, linkDims.ry, meta.hex);
|
||||
|
||||
bigNodes.push(`<g data-wall-cell="1" data-wall-key="${linkKey}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y}) rotate(${angle})">
|
||||
const displayIdxLink = linkIsEmpty ? -1 : (linkCustomIdx ?? -1);
|
||||
bigNodes.push(`<g data-wall-cell="1" data-wall-key="${linkKey}" data-wall-color="${displayIdxLink}" style="cursor:pointer; pointer-events:all;" transform="translate(${mid.x},${mid.y}) rotate(${angle})">
|
||||
<ellipse cx="0" cy="0" rx="${linkDims.rx}" ry="${linkDims.ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
|
||||
${shine}
|
||||
</g>`);
|
||||
@ -527,9 +543,9 @@
|
||||
const fillerInvisible = fillerEmpty && !showWireframes;
|
||||
const fillerMeta = wallColorMeta(fillerIdx);
|
||||
const fillerPat = ensurePattern(fillerMeta);
|
||||
const fillerFill = fillerInvisible ? 'rgba(0,0,0,0.001)' : (fillerEmpty ? (showWireframes ? 'none' : 'rgba(0,0,0,0.001)') : (fillerPat ? `url(#${fillerPat})` : fillerMeta.hex));
|
||||
const fillerStroke = fillerInvisible ? 'none' : (fillerEmpty ? (showWireframes ? '#cbd5e1' : 'none') : 'none');
|
||||
const fillerStrokeW = fillerInvisible ? 0 : (fillerEmpty ? (showWireframes ? 1.2 : 0) : 0);
|
||||
const fillerFill = fillerInvisible ? 'rgba(0,0,0,0.001)' : (fillerEmpty ? 'none' : (fillerPat ? `url(#${fillerPat})` : fillerMeta.hex));
|
||||
const fillerStroke = fillerInvisible ? 'none' : strokeFor(fillerEmpty);
|
||||
const fillerStrokeW = fillerInvisible ? 0 : strokeWidthFor(fillerEmpty, { outline: 0.6, wire: 1.2 });
|
||||
const fillerFilter = fillerInvisible || fillerEmpty ? '' : `filter="url(#${smallShadow})"`;
|
||||
const fillerShine = fillerEmpty ? '' : shineNodeRelative(fiveInchDims.rx, fiveInchDims.ry, fillerMeta.hex);
|
||||
smallNodes.push(`<g data-wall-cell="1" data-wall-key="${fillerKey}" style="cursor:pointer; pointer-events:all;" transform="translate(${pos.x},${pos.y})">
|
||||
@ -553,12 +569,13 @@
|
||||
const patId = ensurePattern(meta);
|
||||
const invisible = isEmpty && !showGaps;
|
||||
const fill = invisible ? 'rgba(0,0,0,0.001)' : (isEmpty ? 'none' : (patId ? `url(#${patId})` : meta.hex));
|
||||
const stroke = invisible ? 'none' : (isEmpty ? '#cbd5e1' : (showOutline ? '#111827' : 'none'));
|
||||
const strokeW = invisible ? 0 : (isEmpty ? 1.4 : (showOutline ? 0.6 : 0));
|
||||
const stroke = invisible ? 'none' : strokeFor(isEmpty);
|
||||
const strokeW = invisible ? 0 : strokeWidthFor(isEmpty, { outline: 0.6, wire: 1.4 });
|
||||
const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||
const rGap = bigR * 0.82;
|
||||
const shineGap = isEmpty ? '' : shineNodeRelative(rGap, rGap, meta.hex);
|
||||
bigNodes.push(`<g data-wall-gap="1" data-wall-key="${key}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${mid.x},${mid.y})">
|
||||
const displayIdx = isEmpty ? -1 : (gapIdx ?? -1);
|
||||
bigNodes.push(`<g data-wall-gap="1" data-wall-key="${key}" data-wall-color="${displayIdx}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${mid.x},${mid.y})">
|
||||
<circle cx="0" cy="0" r="${rGap}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
|
||||
${shineGap}
|
||||
</g>`);
|
||||
@ -584,7 +601,8 @@
|
||||
const filter = invisible || isEmpty ? '' : `filter="url(#${bigShadow})"`;
|
||||
const rGap = bigR * 0.82;
|
||||
const shineGap = isEmpty ? '' : shineNodeRelative(rGap, rGap, meta.hex);
|
||||
bigNodes.push(`<g data-wall-gap="1" data-wall-key="${key}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${mid.x},${mid.y})">
|
||||
const displayIdx = isEmpty ? -1 : (gapIdx ?? -1);
|
||||
bigNodes.push(`<g data-wall-gap="1" data-wall-key="${key}" data-wall-color="${displayIdx}" style="cursor:pointer; pointer-events:all; cursor:crosshair;" transform="translate(${mid.x},${mid.y})">
|
||||
<circle cx="0" cy="0" r="${rGap}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeW}" ${filter} pointer-events="all" />
|
||||
${shineGap}
|
||||
</g>`);
|
||||
@ -847,6 +865,17 @@
|
||||
return val;
|
||||
}
|
||||
|
||||
// Resolve current color (custom override only).
|
||||
function getCurrentColorIdxForKey(key) {
|
||||
if (!wallState) wallState = wallDefaultState();
|
||||
ensureWallGridSize(wallState.rows, wallState.cols);
|
||||
const raw = wallState.customColors?.[key];
|
||||
const parsed = Number.isInteger(raw) ? raw : Number.parseInt(raw, 10);
|
||||
if (!Number.isInteger(parsed)) return null;
|
||||
if (parsed < 0) return null;
|
||||
return normalizeColorIdx(parsed);
|
||||
}
|
||||
|
||||
function updateWallActiveChip(idx) {
|
||||
if (!wallActiveChip || !wallActiveLabel) return;
|
||||
ensureFlatColors();
|
||||
@ -866,19 +895,6 @@
|
||||
wallActiveLabel.textContent = meta.name || meta.hex || '';
|
||||
}
|
||||
|
||||
function setWallToolMode(mode) {
|
||||
wallToolMode = mode === 'erase' ? 'erase' : 'paint';
|
||||
if (wallToolPaintBtn && wallToolEraseBtn) {
|
||||
const isErase = wallToolMode === 'erase';
|
||||
wallToolPaintBtn.setAttribute('aria-pressed', String(!isErase));
|
||||
wallToolEraseBtn.setAttribute('aria-pressed', String(isErase));
|
||||
wallToolPaintBtn.classList.toggle('tab-active', !isErase);
|
||||
wallToolEraseBtn.classList.toggle('tab-active', isErase);
|
||||
wallToolPaintBtn.classList.toggle('tab-idle', isErase);
|
||||
wallToolEraseBtn.classList.toggle('tab-idle', !isErase);
|
||||
}
|
||||
}
|
||||
|
||||
// Paint a specific group of nodes with the active color.
|
||||
function paintWallGroup(group) {
|
||||
ensureWallGridSize(wallState.rows, wallState.cols);
|
||||
@ -1022,7 +1038,29 @@
|
||||
else if (window.organic?.getColor) selectedColorIdx = normalizeColorIdx(window.organic.getColor());
|
||||
else selectedColorIdx = defaultActiveColorIdx();
|
||||
setActiveColor(selectedColorIdx);
|
||||
setWallToolMode('paint');
|
||||
// Hide legacy paint/erase toggles; behavior is always click-to-paint, click-again-to-clear.
|
||||
if (wallToolPaintBtn) {
|
||||
wallToolPaintBtn.classList.add('hidden');
|
||||
wallToolPaintBtn.setAttribute('aria-hidden', 'true');
|
||||
wallToolPaintBtn.tabIndex = -1;
|
||||
}
|
||||
if (wallToolEraseBtn) {
|
||||
wallToolEraseBtn.classList.add('hidden');
|
||||
wallToolEraseBtn.setAttribute('aria-hidden', 'true');
|
||||
wallToolEraseBtn.tabIndex = -1;
|
||||
}
|
||||
// Hide legacy paint/erase toggles; always use click-to-paint/click-again-to-clear.
|
||||
if (wallToolPaintBtn) {
|
||||
wallToolPaintBtn.classList.add('hidden');
|
||||
wallToolPaintBtn.setAttribute('aria-hidden', 'true');
|
||||
wallToolPaintBtn.tabIndex = -1;
|
||||
}
|
||||
if (wallToolEraseBtn) {
|
||||
wallToolEraseBtn.classList.add('hidden');
|
||||
wallToolEraseBtn.setAttribute('aria-hidden', 'true');
|
||||
wallToolEraseBtn.tabIndex = -1;
|
||||
}
|
||||
|
||||
// Allow picking active wall color by clicking the chip.
|
||||
if (wallActiveChip && window.openColorPicker) {
|
||||
wallActiveChip.style.cursor = 'pointer';
|
||||
@ -1093,8 +1131,7 @@
|
||||
wallReplaceToSel?.addEventListener('change', updateWallReplacePreview);
|
||||
wallReplaceFromChip?.addEventListener('click', () => openWallReplacePicker('from'));
|
||||
wallReplaceToChip?.addEventListener('click', () => openWallReplacePicker('to'));
|
||||
wallToolPaintBtn?.addEventListener('click', () => setWallToolMode('paint'));
|
||||
wallToolEraseBtn?.addEventListener('click', () => setWallToolMode('erase'));
|
||||
// Remove explicit paint/erase toggles; behavior is always click-to-paint, click-again-to-clear.
|
||||
|
||||
const findWallNode = (el) => {
|
||||
let cur = el;
|
||||
@ -1119,12 +1156,11 @@
|
||||
const key = hit.dataset.wallKey;
|
||||
if (!key) return;
|
||||
|
||||
const activeColor = getActiveWallColorIdx();
|
||||
const activeColor = normalizeColorIdx(getActiveWallColorIdx());
|
||||
if (!Number.isInteger(activeColor)) return;
|
||||
const rawStored = wallState.customColors?.[key];
|
||||
const parsedStored = Number.isInteger(rawStored) ? rawStored : Number.parseInt(rawStored, 10);
|
||||
const storedColor = Number.isInteger(parsedStored) && parsedStored >= 0 ? normalizeColorIdx(parsedStored) : null;
|
||||
const hasStoredColor = Number.isInteger(storedColor) && storedColor >= 0;
|
||||
const datasetColor = Number.parseInt(hit.dataset.wallColor ?? '', 10);
|
||||
const currentColor = Number.isInteger(datasetColor) ? datasetColor : getCurrentColorIdxForKey(key);
|
||||
const hasCurrent = Number.isInteger(currentColor) && currentColor >= 0;
|
||||
|
||||
if (e.altKey) {
|
||||
if (Number.isInteger(storedColor)) {
|
||||
@ -1135,9 +1171,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Paint/erase based on tool mode; modifiers still erase.
|
||||
const isEraseClick = wallToolMode === 'erase' || e.shiftKey || e.metaKey || e.ctrlKey;
|
||||
wallState.customColors[key] = isEraseClick ? -1 : activeColor;
|
||||
// Simple toggle: click paints with active; clicking again with the same active clears it.
|
||||
const sameAsActive = hasCurrent && currentColor === activeColor;
|
||||
wallState.customColors[key] = sameAsActive ? -1 : activeColor;
|
||||
|
||||
saveActivePatternState();
|
||||
saveWallState();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user