radians for rotation

This commit is contained in:
Orion Reed 2024-12-02 18:15:14 -05:00
parent c7a3580f9f
commit 792cf0683a
2 changed files with 20 additions and 22 deletions

View File

@ -23,7 +23,7 @@
<body> <body>
<folk-shape x="100" y="100" width="50" height="50"></folk-shape> <folk-shape x="100" y="100" width="50" height="50"></folk-shape>
<folk-shape x="100" y="200" width="50" height="50"></folk-shape> <folk-shape x="100" y="200" width="50" height="50"></folk-shape>
<folk-shape x="100" y="300" width="50" height="50" rotate="45"></folk-shape> <folk-shape x="100" y="300" width="50" height="50" rotation="45"></folk-shape>
<script type="module"> <script type="module">
import { FolkShape } from '../src/folk-shape.ts'; import { FolkShape } from '../src/folk-shape.ts';

View File

@ -8,7 +8,7 @@ const resizeObserver = new ResizeObserverManager();
export type Shape = 'rectangle' | 'circle' | 'triangle'; export type Shape = 'rectangle' | 'circle' | 'triangle';
type RotatedDOMRect = DOMRect & { type RotatedDOMRect = DOMRect & {
/** in degrees */ /** in radians */
rotation: number; rotation: number;
/** Returns the center point in worldspace coordinates */ /** Returns the center point in worldspace coordinates */
@ -134,7 +134,7 @@ styles.replaceSync(css`
cursor: var(--fc-nesw-resize, nesw-resize); cursor: var(--fc-nesw-resize, nesw-resize);
} }
[part='rotate'] { [part='rotation'] {
z-index: calc(infinity); z-index: calc(infinity);
display: block; display: block;
position: absolute; position: absolute;
@ -154,7 +154,7 @@ styles.replaceSync(css`
} }
:host(:not(:focus-within)) [part^='resize'], :host(:not(:focus-within)) [part^='resize'],
:host(:not(:focus-within)) [part='rotate'] { :host(:not(:focus-within)) [part='rotation'] {
opacity: 0; opacity: 0;
cursor: default; cursor: default;
} }
@ -256,8 +256,8 @@ export class FolkShape extends HTMLElement {
#startAngle = 0; #startAngle = 0;
#previousRotation = 0; #previousRotation = 0;
// TODO: consider using radians instead of degrees // use degrees in the DOM, but store in radians internally
#rotation = Number(this.getAttribute('rotate')) || 0; #rotation = (Number(this.getAttribute('rotation')) || 0) * (Math.PI / 180);
get rotation(): number { get rotation(): number {
return this.#rotation; return this.#rotation;
@ -266,7 +266,7 @@ export class FolkShape extends HTMLElement {
set rotation(rotation: number) { set rotation(rotation: number) {
this.#previousRotation = this.#rotation; this.#previousRotation = this.#rotation;
this.#rotation = rotation; this.#rotation = rotation;
this.#requestUpdate('rotate'); this.#requestUpdate('rotation');
} }
constructor() { constructor() {
@ -282,7 +282,7 @@ export class FolkShape extends HTMLElement {
// Ideally we would creating these lazily on first focus, but the resize handlers need to be around for delegate focus to work. // Ideally we would creating these lazily on first focus, but the resize handlers need to be around for delegate focus to work.
// Maybe can add the first resize handler here, and lazily instantiate the rest when needed? // Maybe can add the first resize handler here, and lazily instantiate the rest when needed?
// I can see it becoming important at scale // I can see it becoming important at scale
shadowRoot.innerHTML = html` <button part="rotate"></button> shadowRoot.innerHTML = html` <button part="rotation"></button>
<button part="resize-nw"></button> <button part="resize-nw"></button>
<button part="resize-ne"></button> <button part="resize-ne"></button>
<button part="resize-se"></button> <button part="resize-se"></button>
@ -294,12 +294,11 @@ export class FolkShape extends HTMLElement {
} }
connectedCallback() { connectedCallback() {
this.#update(new Set(['type', 'x', 'y', 'height', 'width', 'rotate'])); this.#update(new Set(['type', 'x', 'y', 'height', 'width', 'rotation']));
} }
getClientRect(): RotatedDOMRect { getClientRect(): RotatedDOMRect {
const { x, y, width, height, rotation } = this; const { x, y, width, height, rotation } = this;
const radians = (rotation * Math.PI) / 180;
return { return {
x, x,
@ -325,14 +324,13 @@ export class FolkShape extends HTMLElement {
corners() { corners() {
const center = this.center(); const center = this.center();
const radians = (this.rotation * Math.PI) / 180; const { x, y, width, height, rotation } = this;
const { x, y, width, height } = this;
return [ return [
Vector.rotateAround({ x, y }, center, radians), Vector.rotateAround({ x, y }, center, rotation),
Vector.rotateAround({ x: x + width, y }, center, radians), Vector.rotateAround({ x: x + width, y }, center, rotation),
Vector.rotateAround({ x: x + width, y: y + height }, center, radians), Vector.rotateAround({ x: x + width, y: y + height }, center, rotation),
Vector.rotateAround({ x, y: y + height }, center, radians), Vector.rotateAround({ x, y: y + height }, center, rotation),
]; ];
}, },
@ -358,7 +356,7 @@ export class FolkShape extends HTMLElement {
const target = event.composedPath()[0] as HTMLElement; const target = event.composedPath()[0] as HTMLElement;
// Store initial angle on rotation start // Store initial angle on rotation start
if (target.getAttribute('part') === 'rotate') { if (target.getAttribute('part') === 'rotation') {
// We need to store initial rotation/angle somewhere. // We need to store initial rotation/angle somewhere.
// This is a little awkward as we'll want to do *quite a lot* of this kind of thing. // This is a little awkward as we'll want to do *quite a lot* of this kind of thing.
// Might be an argument for making elements dumber (i.e. not have them manage their own state) and do this from the outside. // Might be an argument for making elements dumber (i.e. not have them manage their own state) and do this from the outside.
@ -441,13 +439,13 @@ export class FolkShape extends HTMLElement {
return; return;
} }
if (part === 'rotate') { if (part === 'rotation') {
const centerX = this.#x + this.width / 2; const centerX = this.#x + this.width / 2;
const centerY = this.#y + this.height / 2; const centerY = this.#y + this.height / 2;
const currentAngle = Math.atan2(event.clientY - centerY, event.clientX - centerX); const currentAngle = Math.atan2(event.clientY - centerY, event.clientX - centerX);
const deltaAngle = currentAngle - this.#startAngle; const deltaAngle = currentAngle - this.#startAngle;
this.rotation = this.#initialRotation + (deltaAngle * 180) / Math.PI; this.rotation = this.#initialRotation + deltaAngle;
return; return;
} }
@ -535,13 +533,13 @@ export class FolkShape extends HTMLElement {
} }
} }
if (updatedProperties.has('rotate')) { if (updatedProperties.has('rotation')) {
// Although the change in resize isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics // Although the change in resize isn't useful inside this component, the outside world might find it helpful to calculate acceleration and other physics
const notCancelled = this.dispatchEvent(new RotateEvent({ rotate: this.#rotation - this.#previousRotation })); const notCancelled = this.dispatchEvent(new RotateEvent({ rotate: this.#rotation - this.#previousRotation }));
if (notCancelled) { if (notCancelled) {
if (updatedProperties.has('rotate')) { if (updatedProperties.has('rotation')) {
this.style.rotate = `${this.#rotation}deg`; this.style.rotate = `${this.#rotation}rad`;
} }
} else { } else {
this.#rotation = this.#previousRotation; this.#rotation = this.#previousRotation;