first overlay render
This commit is contained in:
parent
ddd692bcbd
commit
7a15fa556b
|
@ -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);
|
|
@ -127,11 +127,14 @@ const TemplateItemEdit = ({
|
|||
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>
|
||||
))}
|
||||
{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>
|
||||
|
|
|
@ -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';
|
||||
|
@ -35,7 +34,7 @@ const TemplateSettings = () => {
|
|||
<>
|
||||
<h3>{t`Templates`}</h3>
|
||||
<p>
|
||||
{t`Tired of always spaming one single color? Want to create art instead, but you have to count pixels from some other image? Templates can help you with that! Templates can show as overlay and you can draw over them. One pixel on the template, should be one pixel on the canvas.`}
|
||||
{t`Tired of always spaming one single color? Want to create art instead, but you have to count pixels from some other image? Templates can help you with that! Templates can show as overlay and you can draw over them. One pixel on the template, should be one pixel on the canvas.`}
|
||||
</p>
|
||||
<div className="content">
|
||||
{list.map(({
|
||||
|
@ -103,17 +102,17 @@ const TemplateSettings = () => {
|
|||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={async () => importRef.current?.click()}
|
||||
>{t`Import templates`}</span>
|
||||
<input
|
||||
type="file"
|
||||
key="impin"
|
||||
accept="image/*"
|
||||
ref={importRef}
|
||||
style={{ display: 'none' }}
|
||||
onChange={(evt) => {
|
||||
templateLoader.importTemplates(evt.target.files?.[0]);
|
||||
}}
|
||||
/>
|
||||
>{t`Import templates`}</span>
|
||||
<input
|
||||
type="file"
|
||||
key="impin"
|
||||
accept="image/*"
|
||||
ref={importRef}
|
||||
style={{ display: 'none' }}
|
||||
onChange={(evt) => {
|
||||
templateLoader.importTemplates(evt.target.files?.[0]);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user