import analyser and migrate to solidjs, untested

This commit is contained in:
Robin Appelman 2023-04-22 15:54:14 +02:00
commit fff554c3d3
42 changed files with 2910 additions and 4 deletions

View file

@ -0,0 +1,102 @@
import {Viewport, Point} from './Viewport';
export interface CenteredPanZoomOptions {
screenWidth: number;
screenHeight: number;
scale?: number;
contentSize: {
width: number;
height: number;
}
}
export class CenteredPanZoom {
screen: Viewport;
viewport: Viewport;
scale: number;
contentSize: {
width: number;
height: number;
};
constructor(options: CenteredPanZoomOptions) {
this.screen = new Viewport({
x: 0,
y: 0,
width: options.screenWidth,
height: options.screenHeight
});
this.viewport = new Viewport({
x: 0,
y: 0,
width: options.screenWidth,
height: options.screenHeight
});
this.scale = options.scale || 1;
this.contentSize = options.contentSize;
}
setSize(width, height) {
this.screen.width = width;
this.screen.height = height;
this.viewport.width = width * this.scale;
this.viewport.height = height * this.scale;
this.constrainPan(true);
}
setContentSize(width, height) {
this.contentSize = {width, height};
this.constrainPan(true);
}
pan(screenX, screenY) {
this.viewport.x = this.viewport.x + screenX;
this.viewport.y = this.viewport.y + screenY;
this.constrainPan();
}
constrainPan(strict = false) {
const minX = (strict) ? 0 : this.screen.width * 0.5;
const minY = (strict) ? 0 : this.screen.height * 0.5;
this.viewport.x = Math.min(minX, this.viewport.x);
this.viewport.y = Math.min(minY * 0.5, this.viewport.y);
const maxY = (this.screen.height - (this.contentSize.height * this.scale)) - (strict ? 0 : this.screen.height * 0.5);
const maxX = (this.screen.width - (this.contentSize.width * this.scale)) - (strict ? 0 : this.screen.width * 0.5);
this.viewport.y = Math.max(maxY, this.viewport.y);
this.viewport.x = Math.max(maxX, this.viewport.x);
if (maxY > ((strict) ? 0 : this.screen.height * 0.5)) {
this.viewport.y = Math.floor(maxY / 2);
}
if (maxX > ((strict) ? 0 : this.screen.width * 0.5)) {
this.viewport.x = Math.floor(maxX / 2);
}
}
panFrom(screenStart: Point, screenEnd: Point) {
this.pan(screenEnd.x - screenStart.x, screenEnd.y - screenStart.y);
}
//find zoom point in pre-zoom viewport
//make that point the same in the post-zoom viewport
zoom(scale: number, screenCenter: Point) {
const v1 = Viewport.convert(screenCenter, {
from: this.screen,
to: this.viewport
});
this.viewport.x = this.viewport.x * (scale / this.scale);
this.viewport.y = this.viewport.y * (scale / this.scale);
this.viewport.width = this.screen.width * scale;
this.viewport.height = this.screen.height * scale;
const minScale = Math.min(this.screen.width / this.contentSize.width, this.screen.height / this.contentSize.height);
scale = Math.max(minScale, scale);
this.scale = scale;
const v2 = Viewport.convert(screenCenter, {
from: this.screen,
to: this.viewport
});
const deltaX = v2.x - v1.x;
const deltaY = v2.y - v1.y;
this.pan(deltaX * scale, deltaY * scale);
}
}

View file

@ -0,0 +1,54 @@
.pan-zoom-element {
border: 1px solid #ccc;
position: relative;
overflow: hidden;
cursor: default;
.content-container {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.noselect {
user-select: none;
}
}
.zoommenu {
position: fixed;
color: #888;
bottom: 100px;
right: 0;
margin: 10px;
font-size: 200%;
opacity: 0.5;
transition: all 0.5s;
&:hover {
opacity: 1;
}
background-color: #f8f8f8;
border-radius: 5px;
div {
transition: all 0.5s;
width: 32px;
text-align: center;
cursor: pointer;
user-select: none;
&:hover {
color: #000;
}
}
div:first-child {
border-bottom: 1px solid #ddd;
margin: 0;
}
}

View file

@ -0,0 +1,116 @@
import {CenteredPanZoom} from './CenteredPanZoom';
import {createSignal} from "solid-js";
export interface PannerProps {
width: number;
height: number;
scale: number;
contentSize: {
width: number;
height: number;
};
onScale?: (scale: number) => any;
children: Element;
}
export const Panner = (props: PannerProps) => {
const [scale, setScale] = createSignal(0);
const [translateX, setTranslateX] = createSignal(0);
const [translateY, setTranslateY] = createSignal(0);
const panner = new CenteredPanZoom({
screenHeight: props.height,
screenWidth: props.width,
scale: props.scale,
contentSize: props.contentSize
});
let startX = 0;
let startY = 0;
const mouseMove = (event) => {
const {pageX, pageY} = event;
panner.panFrom(
{
x: startX,
y: startY
},
{
x: pageX,
y: pageY
}
);
startX = event.pageX;
startY = event.pageY;
setTranslateX(panner.viewport.x);
setTranslateY(panner.viewport.y);
setScale(panner.scale);
}
const mouseDown = (event) => {
startX = event.pageX;
startY = event.pageY;
const mouseUp = () => {
document.removeEventListener('mouseup', mouseUp, true);
document.removeEventListener('mousemove', mouseMove, true);
};
document.addEventListener('mouseup', mouseUp, true);
document.addEventListener('mousemove', mouseMove, true);
}
const applyZoom = (factor: number, center) => {
panner.zoom(factor, center);
setTranslateX(panner.viewport.x);
setTranslateY(panner.viewport.y);
setScale(panner.scale);
if (props.onScale) {
props.onScale(panner.scale);
}
}
const mouseWheel = (event) => {
event.preventDefault();
const center = {x: event.pageX, y: event.pageY};
let zoomFactor = (event.deltaY < 0) ? 1.05 : 0.95;
const factor = scale() * zoomFactor;
applyZoom(factor, center);
}
const center = () => ({
x: Math.floor(panner.screen.width / 2),
y: Math.floor(panner.screen.height / 2)
});
return (
<div class="pan-zoom-element"
style={{width: `${props.width}px`, height: `${props.height}px`}}
onMouseDown={mouseDown}
onWheel={mouseWheel}>
<div class="content-container noselect"
style={{
transform: `translate(${translateX()}px, ${translateY()}px) scale(${scale()})`,
"transform-origin": 'top left'
}}>
{props.children}
</div>
<div class="zoommenu">
<div class="plus"
onClick={() => {
applyZoom(1.10, center())
}}>+
</div>
<div class="minus"
onClick={() => {
applyZoom(0.90, center())
}}>
-
</div>
</div>
</div>
);
}

View file

@ -0,0 +1,36 @@
export interface ViewPortOptions {
x?: number;
y?: number;
width: number;
height: number;
}
export interface Point {
x: number;
y: number;
}
export class Viewport {
x: number;
y: number;
width: number;
height: number;
constructor(options: ViewPortOptions) {
this.x = options.x || 0;
this.y = options.y || 0;
this.width = options.width;
this.height = options.height;
}
static convert(point: Point, {from, to}: {from: Viewport, to: Viewport}): Point {
const widthScale = from.width / to.width;
const heightScale = from.height / to.height;
return {
x: point.x * widthScale - to.x * widthScale,
y: point.y * heightScale - to.y * heightScale
};
}
}