Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions src/components/app/pf-project-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export class PFProjectBrowser extends BaseComponent {

.header-actions,
.card-actions,
.rename-actions {
.rename-actions,
.empty-actions {
display: flex;
align-items: center;
gap: 8px;
Expand Down Expand Up @@ -182,6 +183,11 @@ export class PFProjectBrowser extends BaseComponent {
min-height: 226px;
}

.new-card.guided {
border-color: color-mix(in srgb, var(--pf-color-accent) 62%, var(--pf-color-border));
background: var(--pf-color-primary-transparent);
}

.thumbnail {
display: grid;
place-items: center;
Expand Down Expand Up @@ -344,11 +350,14 @@ export class PFProjectBrowser extends BaseComponent {
</div>
<section class="shell" aria-label="Project library">
<div class="toolbar">
<p class="subtitle">Open a saved sprite or start a fresh canvas.</p>
<p class="subtitle">Open a saved sprite, start fresh, or use an image as guidance.</p>
<form method="dialog" class="header-actions">
<button class="primary" type="submit" value="new-project">
New Project
</button>
<button type="submit" value="guided-drawing">
Guided Drawing
</button>
${this.canClose
? html`
<button type="submit" value="close">
Expand Down Expand Up @@ -379,11 +388,18 @@ export class PFProjectBrowser extends BaseComponent {
return html`
<div class="empty">
<p>No projects saved yet.</p>
<form method="dialog" class="empty-action">
<button class="primary" type="submit" value="new-project">
Create First Project
</button>
</form>
<div class="empty-actions">
<form method="dialog" class="empty-action">
<button class="primary" type="submit" value="new-project">
Create First Project
</button>
</form>
<form method="dialog" class="empty-action">
<button type="submit" value="guided-drawing">
Create Guided Drawing
</button>
</form>
</div>
</div>
`;
}
Expand All @@ -396,6 +412,12 @@ export class PFProjectBrowser extends BaseComponent {
<span class="meta">Choose a size and start drawing.</span>
</button>
</form>
<form method="dialog" class="new-card-form">
<button class="new-card guided" type="submit" value="guided-drawing">
<strong>Guided Drawing</strong>
<span class="meta">Start from an image in a separate numbered project.</span>
</button>
</form>
${this.projects.map((project) => this.renderProject(project))}
</div>
`;
Expand Down Expand Up @@ -693,6 +715,13 @@ export class PFProjectBrowser extends BaseComponent {
return;
}

if (returnValue === 'guided-drawing') {
this.dispatchEvent(
new CustomEvent('show-paint-by-number-dialog', { bubbles: true, composed: true })
);
return;
}

if (!this.canDismissBrowser()) {
this.showNativeDialog(this.browserDialog);
return;
Expand Down
1 change: 1 addition & 0 deletions src/components/app/pixel-forge-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@

private handleShowOpenFileDialog = () => {
// Trigger the menu bar's open file method
const menuBar = this.shadowRoot?.querySelector("pf-menu-bar") as any;

Check warning on line 373 in src/components/app/pixel-forge-app.ts

View workflow job for this annotation

GitHub Actions / checks

Unexpected any. Specify a different type
menuBar?.openFile();
};

Expand Down Expand Up @@ -683,6 +683,7 @@
<pf-project-browser
.canClose=${this.hasLibraryProject}
@show-new-project-dialog=${this.handleShowNewProjectDialog}
@show-paint-by-number-dialog=${this.handleShowPaintByNumberDialog}
@project-browser-close=${this.handleProjectBrowserClose}
@project-opened=${this.handleProjectOpened}
@current-project-deleted=${this.handleCurrentProjectDeleted}
Expand Down
90 changes: 79 additions & 11 deletions src/components/canvas/pf-guided-number-overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,33 @@ export interface GuidedNumberViewport {
viewportHeight: number;
}

export function collectVisibleGuidedTargetCells(
target: Uint8Array,
width: number,
viewport: GuidedNumberViewport,
): GuidedNumberCell[] {
const cells: GuidedNumberCell[] = [];
for (let index = 0; index < target.length; index += 1) {
const guideNumber = target[index];
if (guideNumber === 0) continue;

const x = index % width;
const y = Math.floor(index / width);
const screenX = x * viewport.zoom + viewport.panX;
const screenY = y * viewport.zoom + viewport.panY;
if (!isCellVisible(screenX, screenY, viewport)) continue;

cells.push({
guideNumber,
screenX,
screenY,
size: viewport.zoom,
});
}

return cells;
}

export function collectVisibleGuidedNumberCells(
target: Uint8Array,
pixels: Uint8ClampedArray,
Expand Down Expand Up @@ -121,6 +148,8 @@ export class PFGuidedNumberOverlay extends CanvasOverlay {

render() {
const session = this.context.guidedDrawing.session.value;
const numbersVisible = this.context.guidedDrawing.numbersVisible.value;
void this.context.guidedDrawing.targetPreviewVisible.value;
const zoom = this.context.viewport.zoom.value;
void this.context.viewport.panX.value;
void this.context.viewport.panY.value;
Expand All @@ -129,7 +158,7 @@ export class PFGuidedNumberOverlay extends CanvasOverlay {

return html`
<canvas aria-label="Guided drawing numbers"></canvas>
${session && zoom < NUMBER_ZOOM_THRESHOLD
${session && numbersVisible && zoom < NUMBER_ZOOM_THRESHOLD
? html`
<p class="zoom-hint" role="status" aria-live="polite">
Zoom in to see guide numbers
Expand All @@ -155,23 +184,62 @@ export class PFGuidedNumberOverlay extends CanvasOverlay {
if (!snapshot) return;

const { viewport } = this.context;
const cells = collectVisibleGuidedNumberCells(
const frame = {
zoom: viewport.zoom.value,
panX: viewport.panX.value,
panY: viewport.panY.value,
viewportWidth: this.clientWidth,
viewportHeight: this.clientHeight,
};

if (this.context.guidedDrawing.targetPreviewVisible.value) {
const targetCells = collectVisibleGuidedTargetCells(
snapshot.session.target,
snapshot.session.width,
frame,
);
this.drawTargetPreview(
drawingContext,
targetCells,
this.context.palette.mainColors.value,
);
}

if (!this.context.guidedDrawing.numbersVisible.value) return;

const numberCells = collectVisibleGuidedNumberCells(
snapshot.session.target,
snapshot.pixels,
snapshot.session.width,
{
zoom: viewport.zoom.value,
panX: viewport.panX.value,
panY: viewport.panY.value,
viewportWidth: this.clientWidth,
viewportHeight: this.clientHeight,
},
frame,
);

this.drawCells(drawingContext, cells);
this.drawNumbers(drawingContext, numberCells);
}

private drawTargetPreview(
context: CanvasRenderingContext2D,
cells: GuidedNumberCell[],
palette: string[],
) {
context.save();
context.globalAlpha = 0.72;
for (const cell of cells) {
const color = palette[cell.guideNumber - 1];
if (!color) continue;

context.fillStyle = color;
context.fillRect(
Math.floor(cell.screenX),
Math.floor(cell.screenY),
Math.ceil(cell.size),
Math.ceil(cell.size),
);
}
context.restore();
}

private drawCells(context: CanvasRenderingContext2D, cells: GuidedNumberCell[]) {
private drawNumbers(context: CanvasRenderingContext2D, cells: GuidedNumberCell[]) {
const fontFamily = getComputedStyle(this).fontFamily || '"Departure Mono", monospace';
context.textAlign = 'center';
context.textBaseline = 'middle';
Expand Down
Loading
Loading