first overlay render

This commit is contained in:
HF 2024-02-04 14:33:19 +01:00
parent ddd692bcbd
commit 7a15fa556b
8 changed files with 112 additions and 163 deletions

View File

@ -1,129 +0,0 @@
/*
* Settings of minimap / overlay
*/
import React, { useState, useEffect, useRef } from 'react';
import { useSelector, shallowEqual } from 'react-redux';
import { t } from 'ttag';
import { coordsFromUrl } from '../core/utils';
import templateLoader from '../ui/templateLoader';
const AddTemplate = ({ close, triggerClose: refClose }) => {
const [render, setRender] = useState(false);
const [coords, setCoords] = useState(null);
const [dimensions, setDimensions] = useState(null);
const [title, setTitle] = useState('');
const [file, setFile] = useState(null);
const imgRef = useRef();
const [
canvasId,
canvases,
] = useSelector((state) => [
state.canvas.canvasId,
state.canvas.canvases,
], shallowEqual);
const [selectedCanvas, selectCanvas] = useState(canvasId);
useEffect(() => {
window.setTimeout(() => setRender(true), 10);
refClose.current = () => setRender(false);
}, [refClose]);
useEffect(() => {
if (!file || !imgRef.current) {
return;
}
const fr = new FileReader();
fr.onload = () => { imgRef.current.src = fr.result; };
fr.readAsDataURL(file);
}, [file]);
const canSubmit = (imgRef.current && file && coords && title && dimensions);
return (
<div
className="inarea"
style={{
opacity: render ? 1 : 0,
transition: 'opacity 200ms',
}}
onTransitionEnd={() => !render && close()}
>
<form
onSubmit={async (event) => {
event.preventDefault();
if (canSubmit) {
await templateLoader.addFile(
file, title, selectedCanvas, ...coords, imgRef.current,
);
setRender(false);
}
}}
>
<img
src="./logo.svg"
alt="preview"
key="logo"
style={{
maxWidth: 96,
maxHeight: 96,
}}
onLoad={(evt) => setDimensions([
evt.target.naturalWidth,
evt.target.naturalHeight,
])}
onError={() => setDimensions(null)}
ref={imgRef}
/>
<input
type="file"
onChange={(evt) => {
setDimensions(null);
setFile(evt.target.files?.[0]);
}}
/>
<input
value={title}
type="text"
onChange={(evt) => setTitle(evt.target.value)}
placeholder={t`Template Name`}
/>
<select
value={selectedCanvas}
onChange={(e) => {
const sel = e.target;
selectCanvas(sel.options[sel.selectedIndex].value);
}}
>
{Object.keys(canvases).filter((c) => !canvases[c].v).map((canvas) => (
<option key={canvas} value={canvas}>
{canvases[canvas].title}
</option>
))}
</select>
<input
type="text"
style={{
display: 'inline-block',
width: '100%',
maxWidth: '15em',
}}
placeholder="X_Y or URL"
onChange={(evt) => {
let co = evt.target.value.trim();
co = coordsFromUrl(co) || co;
evt.target.value = co;
const newCoords = co.split('_').map((z) => parseInt(z, 10));
setCoords((!newCoords.some(Number.isNaN) && newCoords.length === 2)
? newCoords : null,
);
}}
/>
<button type="submit" disabled={!canSubmit}>{t`Save`}</button>
</form>
</div>
);
};
export default React.memo(AddTemplate);

View File

@ -127,11 +127,14 @@ const TemplateItemEdit = ({
selectCanvas(sel.options[sel.selectedIndex].value);
}}
>
{Object.keys(canvases).filter((c) => !canvases[c].v).map((canvas) => (
{Object.keys(canvases)
.filter((c) => !canvases[c].v && !canvases[c].ed)
.map((canvas) => (
<option key={canvas} value={canvas}>
{canvases[canvas].title}
</option>
))}
),
)}
</select></span>
</p>
<p>

View File

@ -7,7 +7,6 @@ import { useSelector } from 'react-redux';
import fileDownload from 'js-file-download';
import { t } from 'ttag';
import AddTemplate from './AddTemplate';
import TemplateItem from './TemplateItem';
import TemplateItemEdit from './TemplateItemEdit';
import templateLoader from '../ui/templateLoader';

View File

@ -706,7 +706,7 @@ export function bufferToBase64(array) {
}
export async function base64ToBuffer(base64) {
const dataUrl = "data:application/octet-binary;base64," + base64;
const dataUrl = `data:application/octet-binary;base64,${base64}`;
const res = await fetch(dataUrl);
return res.arrayBuffer();
}

View File

@ -22,6 +22,7 @@ import {
renderGrid,
renderPlaceholder,
renderPotatoPlaceholder,
renderOverlay,
} from './render2Delements';
import PixelPainterControls from '../controls/PixelPainterControls';
@ -272,6 +273,7 @@ class Renderer2D extends Renderer {
const [x, y] = getPixelFromChunkOffset(i, j, offset, canvasSize);
// TODO centerChunk is scaled!
const [canX, canY] = this.centerChunk
.map((z) => (z + 0.5) * TILE_SIZE - canvasSize / 2);
const { width: canvasWidth, height: canvasHeight } = this.canvas;
@ -340,9 +342,7 @@ class Renderer2D extends Renderer {
viewport,
viewscale: scale,
} = this;
const {
canvasSize,
} = state.canvas;
const { canvasSize } = state.canvas;
let { relScale } = this;
@ -422,6 +422,11 @@ class Renderer2D extends Renderer {
}
}
context.restore();
// TODO conditions
renderOverlay(
this.canvas, chunkPosition, canvasSize, scale,
this.tiledScale, this.scaleThreshold,
);
}

View File

@ -3,7 +3,9 @@
*
*/
import templateLoader from './templateLoader';
import { screenToWorld, worldToScreen } from '../core/utils';
import { TILE_SIZE } from '../core/constants';
const PLACEHOLDER_SIZE = 1.2;
const PLACEHOLDER_BORDER = 1;
@ -100,3 +102,46 @@ export function renderGrid(
viewportCtx.globalAlpha = 1;
}
/*
* Overlay draws onto offscreen canvas, so its doing weirder math
*/
export function renderOverlay(
$canvas,
centerChunk,
canvasSize,
scale,
tiledScale,
scaleThreshold,
) {
// world coordinates of center of center chunk
const [x, y] = centerChunk
.map((z) => z * TILE_SIZE / tiledScale
+ TILE_SIZE / 2 / tiledScale - canvasSize / 2);
const { width, height } = $canvas;
const horizontalRadius = width / 2 / scale;
const verticalRadius = height / 2 / scale;
const templates = templateLoader.getTemplatesInView(
x, y, horizontalRadius, verticalRadius,
);
if (!templates.length) return;
const context = $canvas.getContext('2d');
if (!context) return;
// if scale > scaleThreshold, then scaling happens in renderer
// instead of offscreen canvas
const offscreenScale = (scale > scaleThreshold) ? 1.0 : scale;
context.save();
context.scale(offscreenScale, offscreenScale);
for (const template of templates) {
const image = templateLoader.getTemplateSync(template.imageId);
if (!image) continue;
context.drawImage(image,
template.x - x + width / 2 / offscreenScale,
template.y - y + height / 2 / offscreenScale,
);
}
context.restore();
}

View File

@ -50,11 +50,35 @@ class TemplateLoader {
return null;
}
/*
getTemplatesInView() {
this.#store.templates
getTemplateSync(id) {
if (!this.ready) {
return null;
}
const template = this.#templates.get(id);
if (template) {
return template.image;
}
// TODO some store action when available
this.loadExistingTemplate(id);
return null;
}
getTemplatesInView(x, y, horizontalRadius, verticalRadius) {
const topX = x - horizontalRadius;
const topY = y - verticalRadius;
const bottomX = x + horizontalRadius;
const bottomY = y + verticalRadius;
const templates = [];
this.#store.getState().templates.list.forEach((template) => {
if (x < bottomX && y < bottomY
&& x + template.width > topX && y + template.height > topY
) {
templates.push(template);
}
});
return templates;
}
*/
/*
* sync database to store,
@ -152,7 +176,7 @@ class TemplateLoader {
deleteTemplate(title) {
const { list } = this.#store.getState().templates;
const tData = list.find((z) => z.title === title)
const tData = list.find((z) => z.title === title);
if (!tData) {
return;
}
@ -197,7 +221,9 @@ class TemplateLoader {
const imageIdList = await this.#fileStorage.saveFile(fileList);
const idsToDelete = [];
for (let i = 0; i < tDataList.length; i += 1) {
const { x, y, width, height, canvasId, title } = tDataList[i];
const {
x, y, width, height, canvasId, title,
} = tDataList[i];
const imageId = imageIdList[i];
const existing = list.find((z) => z.title === title);
if (existing) {