import React from "react"; import { observable, computed, reaction, action, makeObservable } from "mobx"; import { observer } from "mobx-react"; import { Rect } from "eez-studio-shared/geometry"; import { getProperty, PropertyProps } from "project-editor/core/object"; //////////////////////////////////////////////////////////////////////////////// export const PIN_TO_LEFT = 1; export const PIN_TO_RIGHT = 2; export const PIN_TO_TOP = 4; export const PIN_TO_BOTTOM = 8; export const FIX_WIDTH = 1; export const FIX_HEIGHT = 2; export interface IResizing { pinToEdge: number; fixSize: number; } //////////////////////////////////////////////////////////////////////////////// export function resizeWidget( rectWidgetOriginal: Rect, rectContainerOriginal: Rect, rectContainer: Rect, resizing?: IResizing ) { const pinToLeft = resizing && resizing.pinToEdge & PIN_TO_LEFT; const pinToRight = resizing && resizing.pinToEdge & PIN_TO_RIGHT; const pinToTop = resizing && resizing.pinToEdge & PIN_TO_TOP; const pinToBottom = resizing && resizing.pinToEdge & PIN_TO_BOTTOM; const fixedWidth = resizing && resizing.fixSize & FIX_WIDTH; const fixedHeight = resizing && resizing.fixSize & FIX_HEIGHT; let left = rectWidgetOriginal.left; let right = rectWidgetOriginal.left + rectWidgetOriginal.width; if (pinToLeft) { // left = left; } else { if (!fixedWidth) { left = (rectWidgetOriginal.left * rectContainer.width) / rectContainerOriginal.width; } } if (pinToRight) { right = rectContainer.width - (rectContainerOriginal.width - right); } else { if (!fixedWidth) { right = (right * rectContainer.width) / rectContainerOriginal.width; } } if (fixedWidth) { if (pinToLeft && !pinToRight) { right = left + rectWidgetOriginal.width; } else if (pinToRight && !pinToLeft) { left = right - rectWidgetOriginal.width; } else if (!pinToLeft && !pinToRight) { const center = ((rectWidgetOriginal.left + rectWidgetOriginal.width / 2) * rectContainer.width) / rectContainerOriginal.width; left = center - rectWidgetOriginal.width / 2; right = left + rectWidgetOriginal.width; } } let top = rectWidgetOriginal.top; let bottom = rectWidgetOriginal.top + rectWidgetOriginal.height; if (pinToTop) { //top = top; } else { if (!fixedHeight) { top = (rectWidgetOriginal.top * rectContainer.height) / rectContainerOriginal.height; } } if (pinToBottom) { bottom = rectContainer.height - (rectContainerOriginal.height - bottom); } else { if (!fixedHeight) { bottom = (bottom * rectContainer.height) / rectContainerOriginal.height; } } if (fixedHeight) { if (pinToTop && !pinToBottom) { bottom = top + rectWidgetOriginal.height; } else if (pinToBottom && !pinToTop) { top = bottom - rectWidgetOriginal.height; } else if (!pinToTop && !pinToBottom) { const center = ((rectWidgetOriginal.top + rectWidgetOriginal.height / 2) * rectContainer.height) / rectContainerOriginal.height; top = center - rectWidgetOriginal.height / 2; bottom = top + rectWidgetOriginal.height; } } return { left, top, width: right - left, height: bottom - top }; } //////////////////////////////////////////////////////////////////////////////// const X1 = 5.5; const Y1 = 0.5; const W = 80; const H = 60; const G1 = 4; const G2 = 20; const X2 = X1 + W + G2; const X3 = X2 + W + G2; const TH = 20; const LH = 10; const G3 = 4; const WL1 = 16; const HL1 = 10; const WL2 = 8; const HL2 = Math.max(W, H) / 2; const RFC = "#fff"; const RSC = "#ddd"; const LCD = "#DDD"; const LCE = "#999"; const LCA = "blue"; // preview rect colors const PRFC = "#eee"; const PRSC = "#ddd"; const PCRFC = "#fff"; const PCRSC = "#ddd"; const PWRFC = "#0f0"; const PWRSC = "#0f0"; const ANIMATION_OPEN_CLOSE_DURATION = 500; const ANIMATION_SUSTAIN_DURATION = 1000; export const ResizingProperty = observer( class ResizingProperty extends React.Component { constructor(props: any) { super(props); makeObservable(this, { resizing: computed, pinToEdge: computed, fixSize: computed, isFixWidth: computed, isFixHeight: computed, isPinToLeftAllowed: computed, isPinToRightAllowed: computed, isPinToTopAllowed: computed, isPinToBottomAllowed: computed, isFixWidthAllowed: computed, isFixHeightAllowed: computed, isPinToLeft: computed, isPinToRight: computed, isPinToTop: computed, isPinToBottom: computed, isFixAllAllowed: computed, widgetRect: observable, containerRect: observable, animationFrame: action.bound }); this.animateReactionDisposer = this.animateReaction(props); } updateAnimateReaction() { this.animateReactionDisposer(); this.animateReactionDisposer = this.animateReaction(this.props); } componentDidUpdate() { this.updateAnimateReaction(); } componentDidMount() { this.updateAnimateReaction(); } componentWillUnmount() { this.animateReactionDisposer(); } get resizing() { return getProperty( this.props.objects[0], this.props.propertyInfo.name ) as IResizing; } get pinToEdge() { return (this.resizing && this.resizing.pinToEdge) || 0; } get fixSize() { return (this.resizing && this.resizing.fixSize) || 0; } get isFixWidth() { return (this.fixSize & FIX_WIDTH) !== 0; } get isFixHeight() { return (this.fixSize & FIX_HEIGHT) !== 0; } get isPinToLeftAllowed() { return !(this.isPinToRight && this.isFixWidth); } get isPinToRightAllowed() { return !(this.isPinToLeft && this.isFixWidth); } get isPinToTopAllowed() { return !(this.isPinToBottom && this.isFixHeight); } get isPinToBottomAllowed() { return !(this.isPinToTop && this.isFixHeight); } get isFixWidthAllowed() { return !(this.isPinToLeft && this.isPinToRight); } get isFixHeightAllowed() { return !(this.isPinToTop && this.isPinToBottom); } get isPinToLeft() { return (this.pinToEdge & PIN_TO_LEFT) !== 0; } get isPinToRight() { return (this.pinToEdge & PIN_TO_RIGHT) !== 0; } get isPinToTop() { return (this.pinToEdge & PIN_TO_TOP) !== 0; } get isPinToBottom() { return (this.pinToEdge & PIN_TO_BOTTOM) !== 0; } togglePinToLeft = () => { if (!this.isPinToLeftAllowed) { return; } this.props.updateObject({ resizing: { pinToEdge: this.isPinToLeft ? this.pinToEdge & ~PIN_TO_LEFT : this.pinToEdge | PIN_TO_LEFT, fixSize: this.fixSize } }); }; togglePinToRight = () => { if (!this.isPinToRightAllowed) { return; } this.props.updateObject({ resizing: { pinToEdge: this.isPinToRight ? this.pinToEdge & ~PIN_TO_RIGHT : this.pinToEdge | PIN_TO_RIGHT, fixSize: this.fixSize } }); }; togglePinToTop = () => { if (!this.isPinToTopAllowed) { return; } this.props.updateObject({ resizing: { pinToEdge: this.isPinToTop ? this.pinToEdge & ~PIN_TO_TOP : this.pinToEdge | PIN_TO_TOP, fixSize: this.fixSize } }); }; togglePinToBottom = () => { if (!this.isPinToBottomAllowed) { return; } this.props.updateObject({ resizing: { pinToEdge: this.isPinToBottom ? this.pinToEdge & ~PIN_TO_BOTTOM : this.pinToEdge | PIN_TO_BOTTOM, fixSize: this.fixSize } }); }; togglePinToAll = () => { const isPinToAll = this.isPinToLeft && this.isPinToRight && this.isPinToTop && this.isPinToBottom; this.props.updateObject({ resizing: { pinToEdge: isPinToAll ? 0 : PIN_TO_LEFT | PIN_TO_RIGHT | PIN_TO_TOP | PIN_TO_BOTTOM, fixSize: isPinToAll ? this.fixSize : 0 } }); }; toggleFixWidth = () => { if (!this.isFixWidthAllowed) { return; } this.props.updateObject({ resizing: { pinToEdge: this.pinToEdge, fixSize: this.isFixWidth ? this.fixSize & ~FIX_WIDTH : this.fixSize | FIX_WIDTH } }); }; toggleFixHeight = () => { if (!this.isFixHeightAllowed) { return; } this.props.updateObject({ resizing: { pinToEdge: this.pinToEdge, fixSize: this.isFixHeight ? this.fixSize & ~FIX_HEIGHT : this.fixSize | FIX_HEIGHT } }); }; get isFixAllAllowed() { return this.isFixWidthAllowed && this.isFixHeightAllowed; } toggleFixAll = () => { if (!this.isFixAllAllowed) { return; } this.props.updateObject({ resizing: { pinToEdge: this.pinToEdge, fixSize: this.isFixWidth && this.isFixHeight ? 0 : FIX_WIDTH | FIX_HEIGHT } }); }; // make sure animation is shown if properties are changed animateReactionDisposer: any; originalContainerRect: Rect = { left: 5, top: 5, width: 20, height: 20 }; originalWidgetRect: Rect = { left: 6, top: 6, width: 8, height: 8 }; widgetRect: Rect = this.originalWidgetRect; containerRect: Rect = this.originalContainerRect; animationFrameHandle: any; finalWidgetRect: Rect; finalContainerRect: Rect; animationStart: number; animateReaction(props: any) { return reaction( () => { const resizing = props.object && (getProperty( props.object, props.propertyInfo.name ) as IResizing); return { pinToEdge: (resizing && resizing.pinToEdge) || 0, fixSize: (resizing && resizing.fixSize) || 0 }; }, () => this.startAnimation(true) ); } animationFrame() { this.animationFrameHandle = undefined; let t = (new Date().getTime() - this.animationStart) / ANIMATION_OPEN_CLOSE_DURATION; let done = false; const x = ANIMATION_SUSTAIN_DURATION / ANIMATION_OPEN_CLOSE_DURATION; if (t > 1 && t <= x + 1) { t = 1; } else if (t > x + 1) { t = x + 2 - t; if (t <= 0) { t = 0; done = true; } } this.widgetRect = { left: this.originalWidgetRect.left + t * (this.finalWidgetRect.left - this.originalWidgetRect.left), top: this.originalWidgetRect.top + t * (this.finalWidgetRect.top - this.originalWidgetRect.top), width: this.originalWidgetRect.width + t * (this.finalWidgetRect.width - this.originalWidgetRect.width), height: this.originalWidgetRect.height + t * (this.finalWidgetRect.height - this.originalWidgetRect.height) }; this.containerRect = { left: this.originalContainerRect.left + t * (this.finalContainerRect.left - this.originalContainerRect.left), top: this.originalContainerRect.top + t * (this.finalContainerRect.top - this.originalContainerRect.top), width: this.originalContainerRect.width + t * (this.finalContainerRect.width - this.originalContainerRect.width), height: this.originalContainerRect.height + t * (this.finalContainerRect.height - this.originalContainerRect.height) }; if (!done) { this.animationFrameHandle = requestAnimationFrame( this.animationFrame ); } } startAnimation = (interrupt: boolean) => { if (this.animationFrameHandle) { if (!interrupt) { return; } cancelAnimationFrame(this.animationFrameHandle); this.animationFrameHandle = undefined; } this.finalContainerRect = { left: this.originalContainerRect.left, top: this.originalContainerRect.top, width: W - 2 * this.originalContainerRect.left, height: H - 2 * this.originalContainerRect.top }; this.finalWidgetRect = resizeWidget( this.originalWidgetRect, this.originalContainerRect, this.finalContainerRect, this.resizing ); this.animationStart = new Date().getTime(); this.animationFrameHandle = requestAnimationFrame( this.animationFrame ); }; render() { const pinToLeft = ( ); const pinToRight = ( ); const pinToTop = ( ); const pinToBottom = ( ); const pinToEdge = ( Pin to edge {pinToLeft} {pinToRight} {pinToTop} {pinToBottom} ); //////////////////////////////////////////////////////////////////////////////// const fixWidth = ( ); const fixHeight = ( ); const fixSize = ( Fix size {fixWidth} {fixHeight} ); //////////////////////////////////////////////////////////////////////////////// const preview = ( this.startAnimation(false)}> Preview ); //////////////////////////////////////////////////////////////////////////////// return ( {pinToEdge} {fixSize} {preview} ); } } );