From 37be2477782b0b372c16d46df3f9634ebf3677fe Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Wed, 21 Sep 2022 01:43:03 -0400 Subject: restyled document decorations and changed novice mode to exclude `data` in treeView --- src/client/views/DocumentDecorations.scss | 189 ++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 65 deletions(-) (limited to 'src/client/views/DocumentDecorations.scss') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index b490278c3..2e8d31478 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -22,6 +22,129 @@ $resizeHandler: 8px; grid-template-columns: $resizeHandler 1fr $resizeHandler; pointer-events: none; + .documentDecorations-topbar { + display: flex; + grid-column-start: 1; + grid-column-end: 4; + flex-direction: row; + gap: 2px; + + + .documentDecorations-openButton { + display: flex; + align-items: center; + justify-content: center; + background: #3ce312; + border: solid 1.5px #0a620a; + color: #3ce312; + transition: 0.1s ease; + font-size: 11px; + opacity: 1; + pointer-events: all; + width: 20px; + height: 20px; + min-width: 20px; + border-radius: 100%; + cursor: pointer; + + &:hover { + color: #02600d; + } + } + + .documentDecorations-closeButton { + display: flex; + align-items: center; + justify-content: center; + background: #fb9d75; + border: solid 1.5px #a94442; + color: #fb9d75; + transition: 0.1s ease; + opacity: 1; + pointer-events: all; + width: 20px; + height: 20px; + min-width: 20px; + border-radius: 100%; + cursor: pointer; + + &:hover { + color: #a94442; + } + + > svg { + margin: 0; + } + } + + .documentDecorations-minimizeButton { + display: flex; + align-items: center; + justify-content: center; + background: #ffdd00; + border: solid 1.5px #a94442; + color: #ffdd00; + transition: 0.1s ease; + font-size: 11px; + opacity: 1; + grid-column: 2; + pointer-events: all; + width: 20px; + height: 20px; + min-width: 20px; + border-radius: 100%; + cursor: pointer; + + &:hover { + color: #a94442; + } + + > svg { + margin: 0; + } + } + + .documentDecorations-title-Dark, + .documentDecorations-title { + opacity: 1; + width: calc(100% - 60px); // = margin-left + margin-right + grid-column: 3; + pointer-events: auto; + overflow: hidden; + text-align: center; + display: flex; + height: 20px; + border-radius: 8px; + outline: none; + border: none; + + .documentDecorations-titleSpan, + .documentDecorations-titleSpan-Dark { + width: 100%; + border-radius: 8px; + background: $light-gray; + display: inline-block; + cursor: move; + } + .documentDecorations-titleSpan-Dark { + background: hsla(0, 0%, 0%, 0.412); + } + } + + .documentDecorations-title-Dark { + color: white; + background: black; + } + + .documentDecorations-titleBackground { + background: $light-gray; + border-radius: 8px; + width: 100%; + height: 100%; + position: absolute; + } + } + .documentDecorations-centerCont { grid-column: 2; background: none; @@ -225,76 +348,12 @@ $resizeHandler: 8px; cursor: ew-resize; } - .documentDecorations-title-Dark, - .documentDecorations-title { - opacity: 1; - width: calc(100% - 8px); // = margin-left + margin-right - grid-column: 2; - grid-column-end: 2; - pointer-events: auto; - overflow: hidden; - text-align: center; - display: flex; - margin-left: 6px; // closeButton width (14) - leftColumn width (8) - margin-right: 2px; - height: 20px; - position: absolute; - border-radius: 8px; - background: rgba(159, 159, 159, 0.1); - - .documentDecorations-titleSpan, - .documentDecorations-titleSpan-Dark { - width: 100%; - border-radius: 8px; - background: #ffffffa0; - position: absolute; - display: inline-block; - cursor: move; - } - .documentDecorations-titleSpan-Dark { - background: hsla(0, 0%, 0%, 0.412); - } - } - .documentDecorations-title-Dark { - color: white; - background: black; - } - - .documentDecorations-titleBackground { - background: #ffffffcf; - border-radius: 8px; - width: 100%; - height: 100%; - position: absolute; - } - .focus-visible { margin-left: 0px; } } -.documentDecorations-openButton { - display: flex; - align-items: center; - opacity: 1; - grid-column-start: 3; - pointer-events: all; - cursor: pointer; -} -.documentDecorations-closeButton { - display: flex; - align-items: center; - opacity: 1; - grid-column: 1; - pointer-events: all; - width: 14px; - cursor: pointer; - - > svg { - margin: 0; - } -} .documentDecorations-background { background: lightblue; @@ -329,7 +388,7 @@ $resizeHandler: 8px; justify-content: center; align-items: center; gap: 5px; - background: $medium-gray; + background: $light-gray; } .linkButtonWrapper { -- cgit v1.2.3-70-g09d2 From b772fb95bb2f980f91da44d1442f52c4fb0b6eac Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 17 Oct 2022 19:07:32 -0400 Subject: added rotation around an arbitrary point to doc decos. fixed show title for func plots and for shared docs in novice mode. --- src/Utils.ts | 4 + src/client/util/DragManager.ts | 2 +- src/client/views/DocumentDecorations.scss | 56 +++++++------ src/client/views/DocumentDecorations.tsx | 135 ++++++++++++++++++++++-------- src/client/views/StyleProvider.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 77 +++++++++-------- 6 files changed, 175 insertions(+), 101 deletions(-) (limited to 'src/client/views/DocumentDecorations.scss') diff --git a/src/Utils.ts b/src/Utils.ts index 6fc00040f..bf1f72774 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -243,6 +243,10 @@ export namespace Utils { return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]]; } + export function rotPt(x: number, y: number, radAng: number) { + return { x: x * Math.cos(radAng) - y * Math.sin(radAng), y: x * Math.sin(radAng) + y * Math.cos(radAng) }; + } + function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) { if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 }; const atob = { x: bx - ax, y: by - ay }; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 664933de0..160aba294 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -360,7 +360,7 @@ export namespace DragManager { let useDim = false; if (ele?.parentElement?.parentElement?.parentElement?.className === 'collectionFreeFormDocumentView-container') { ele = ele.parentElement.parentElement.parentElement; - const rotStr = ele.style.transform.replace(/.*rotate\(([-0-9.]*)deg\).*/, '$1'); + const rotStr = ele.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1'); if (rotStr) rot = Number(rotStr); } else { useDim = true; diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 2e8d31478..e915fc88d 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -8,12 +8,38 @@ $resizeHandler: 8px; .documentDecorations { position: absolute; z-index: 2000; + + // Rotation handler + .documentDecorations-rotation { + border-radius: 100%; + height: 30; + width: 30; + right: -17; + top: calc(50% - 15px); + position: absolute; + pointer-events: all; + cursor: pointer; + background: white; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + font-size: 30px; + } + .documentDecorations-rotationCenter { + position: absolute; + width: 6px; + height: 6px; + pointer-events: all; + background: green; + border-radius: 50%; + } } .documentDecorations-Dark { background: dimgray; } + .documentDecorations-container { - z-index: $docDecorations-zindex; position: absolute; top: 0; left: 0; @@ -29,7 +55,6 @@ $resizeHandler: 8px; flex-direction: row; gap: 2px; - .documentDecorations-openButton { display: flex; align-items: center; @@ -51,7 +76,7 @@ $resizeHandler: 8px; color: #02600d; } } - + .documentDecorations-closeButton { display: flex; align-items: center; @@ -71,7 +96,7 @@ $resizeHandler: 8px; &:hover { color: #a94442; } - + > svg { margin: 0; } @@ -98,7 +123,7 @@ $resizeHandler: 8px; &:hover { color: #a94442; } - + > svg { margin: 0; } @@ -207,25 +232,6 @@ $resizeHandler: 8px; grid-column: 3; } - // Rotation handler - .documentDecorations-rotation { - border-radius: 100%; - height: 30; - width: 30; - right: -10; - top: 50%; - z-index: 1000000; - position: absolute; - pointer-events: all; - cursor: pointer; - background: white; - display: flex; - justify-content: center; - align-items: center; - text-align: center; - font-size: 30px; - } - // Border radius handler .documentDecorations-borderRadius { position: absolute; @@ -353,8 +359,6 @@ $resizeHandler: 8px; } } - - .documentDecorations-background { background: lightblue; position: absolute; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8b8c32642..8f66eaca7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -12,7 +12,7 @@ import { InkField } from '../../fields/InkField'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl } from '../../fields/util'; -import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils'; +import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; @@ -30,7 +30,6 @@ import { LightboxView } from './LightboxView'; import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; -import { VideoBox } from './nodes/VideoBox'; import React = require('react'); @observer @@ -311,49 +310,83 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P e => {} // clickEvent ); }; + @action + onRotateCenterDown = (e: React.PointerEvent): void => { + this._isRotating = true; + const seldocview = SelectionManager.Views()[0]; + setupMoveUpEvents( + this, + e, + action((e: PointerEvent, down: number[], delta: number[]) => { + const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(this.rotCenter[0] + delta[0], this.rotCenter[1] + delta[1]); + const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; + const final = Utils.rotPt(newlocenter[0], newlocenter[1], -(NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI); + seldocview.rootDoc.rotateCenterX = final.x / NumCast(seldocview.layoutDoc._width); + seldocview.rootDoc.rotateCenterY = final.y / NumCast(seldocview.layoutDoc._height); + + return false; + }), // moveEvent + action(() => {}), // upEvent + action((e, doubleTap) => { + if (doubleTap) { + seldocview.rootDoc.rotateCenterX = 0.5; + seldocview.rootDoc.rotateCenterY = 0.5; + } + }) + ); + }; @action onRotateDown = (e: React.PointerEvent): void => { this._isRotating = true; + const rcScreen = { X: this.rotCenter[0], Y: this.rotCenter[1] }; const rotateUndo = UndoManager.StartBatch('rotatedown'); const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke); const centerPoint = { X: (this.Bounds.x + this.Bounds.r) / 2, Y: (this.Bounds.y + this.Bounds.b) / 2 }; + const infos = new Map(); + SelectionManager.Views().forEach(dv => { + const accumRot = (NumCast(dv.rootDoc._jitterRotation) / 180) * Math.PI; + const localRotCtr = dv.props.ScreenToLocalTransform().transformPoint(rcScreen.X, rcScreen.Y); + const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.rootDoc.width) / 2, localRotCtr[1] - NumCast(dv.rootDoc.height) / 2]; + const startRotCtr = Utils.rotPt(localRotCtrOffset[0], localRotCtrOffset[1], -accumRot); + const unrotatedDocPos = { x: NumCast(dv.rootDoc.x) + localRotCtrOffset[0] - startRotCtr.x, y: NumCast(dv.rootDoc.y) + localRotCtrOffset[1] - startRotCtr.y }; + infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot }); + }); + const infoRot = (angle: number, isAbs = false) => { + SelectionManager.Views().forEach( + action(dv => { + const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.rootDoc)!; + const endRotCtr = Utils.rotPt(startRotCtr.x, startRotCtr.y, isAbs ? angle : accumRot + angle); + infos.set(dv.rootDoc, { unrotatedDocPos, startRotCtr, accumRot: isAbs ? angle : accumRot + angle }); + dv.rootDoc.x = infos.get(dv.rootDoc)!.unrotatedDocPos.x - (endRotCtr.x - startRotCtr.x); + dv.rootDoc.y = infos.get(dv.rootDoc)!.unrotatedDocPos.y - (endRotCtr.y - startRotCtr.y); + dv.rootDoc._jitterRotation = ((isAbs ? 0 : NumCast(dv.rootDoc._jitterRotation)) + (angle * 180) / Math.PI) % 360; // Rotation between -360 and 360 + }) + ); + }; setupMoveUpEvents( this, e, (e: PointerEvent, down: number[], delta: number[]) => { const previousPoint = { X: e.clientX, Y: e.clientY }; const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] }; - const angle = InkStrokeProperties.angleChange(previousPoint, movedPoint, centerPoint); + const deltaAng = InkStrokeProperties.angleChange(movedPoint, previousPoint, rcScreen); if (selectedInk.length) { - angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, centerPoint); + deltaAng && InkStrokeProperties.Instance.rotateInk(selectedInk, -deltaAng, centerPoint); } else { - SelectionManager.Views().forEach(dv => { - const oldRotation = NumCast(dv.rootDoc._jitterRotation); - // Rotation between -360 and 360 - let newRotation = (oldRotation - (angle * 180) / Math.PI) % 360; - - const diff = Math.round(newRotation / 45) - newRotation / 45; - if (diff < 0.05) { - console.log('show lines'); - } - dv.rootDoc._jitterRotation = newRotation; - }); + infoRot(deltaAng); } return false; }, // moveEvent action(() => { - SelectionManager.Views().forEach(dv => { - const oldRotation = NumCast(dv.rootDoc._jitterRotation); - const diff = Math.round(oldRotation / 45) - oldRotation / 45; - if (diff < 0.05) { - let newRotation = Math.round(oldRotation / 45) * 45; - dv.rootDoc._jitterRotation = newRotation; - } - }); + const dv = SelectionManager.Views()[0]; + const oldRotation = NumCast(dv.rootDoc._jitterRotation); + const diff = oldRotation - Math.round(oldRotation / 45) * 45; + if (Math.abs(diff) < 5) { + infoRot(((Math.round(oldRotation / 45) * 45) / 180) * Math.PI, true); + } this._isRotating = false; rotateUndo?.end(); - UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); }), // upEvent emptyFunction ); @@ -603,8 +636,26 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === 'layout_icon'); } + @observable _rotCenter = [0, 0]; + @computed get rotCenter() { + if (SelectionManager.Views().length) { + const seldocview = SelectionManager.Views()[0]; + const loccenter = Utils.rotPt( + NumCast(seldocview.rootDoc.rotateCenterX) * NumCast(seldocview.layoutDoc._width), + NumCast(seldocview.rootDoc.rotateCenterY) * NumCast(seldocview.layoutDoc._height), + (NumCast(seldocview.rootDoc._jitterRotation) / 180) * Math.PI + ); + return seldocview.props + .ScreenToLocalTransform() + .inverse() + .transformPoint(loccenter.x + NumCast(seldocview.layoutDoc._width) / 2, loccenter.y + NumCast(seldocview.layoutDoc._height) / 2); + } + return this._rotCenter; + } + render() { - const bounds = this.Bounds; + const { b, c, r, x, y } = this.Bounds; + const bounds = { b, c, r, x, y }; const seldocview = SelectionManager.Views().slice(-1)[0]; if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return null; @@ -695,8 +746,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
)} - - {useRotation && ( -
e.preventDefault()}> - } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> -
- )} - {useRounding && (
)}
+ + {useRotation && ( + <> +
+
e.preventDefault()}> + } isCircle={true} hoverStyle={'lighten'} backgroundColor={Colors.DARK_GRAY} color={Colors.LIGHT_GRAY} /> +
+
+
e.preventDefault()} + /> + + )}
)}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 8b256923a..aadcd7169 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -131,7 +131,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt users.user.email === this.dataDoc.author)?.sharingDoc.userColor, Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : 'rgba(0,0,0,0.4)' ); - const titleView = - !showTitle || Doc.noviceMode ? null : ( -
- field.trim()) - .map(field => targetDoc[field]?.toString()) - .join('\\')} - display={'block'} - fontSize={10} - GetValue={() => (showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle)} - SetValue={undoBatch((input: string) => { - if (input?.startsWith('#')) { - if (this.props.showTitle) { - this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined; - } else { - Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : 'creationDate'; - } + const titleView = !showTitle ? null : ( +
+ field.trim()) + .map(field => targetDoc[field]?.toString()) + .join('\\')} + display={'block'} + fontSize={10} + GetValue={() => (showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle)} + SetValue={undoBatch((input: string) => { + if (input?.startsWith('#')) { + if (this.props.showTitle) { + this.rootDoc._showTitle = input?.substring(1) ? input.substring(1) : undefined; } else { - var value = input.replace(new RegExp(showTitle + '='), '') as string | number; - if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value); - if (showTitle.includes('Date') || showTitle === 'author') return true; - Doc.SetInPlace(targetDoc, showTitle, value, true); + Doc.UserDoc().showTitle = input?.substring(1) ? input.substring(1) : 'creationDate'; } - return true; - })} - /> -
- ); + } else { + var value = input.replace(new RegExp(showTitle + '='), '') as string | number; + if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value); + if (showTitle.includes('Date') || showTitle === 'author') return true; + Doc.SetInPlace(targetDoc, showTitle, value, true); + } + return true; + })} + /> +
+ ); return this.props.hideTitle || (!showTitle && !showCaption) ? ( this.contents ) : ( -- cgit v1.2.3-70-g09d2 From 880023cb520f82fa1bfd4740450a18e86e817b0f Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 18 Oct 2022 13:00:55 -0400 Subject: starting to fix resizing rotated docs. --- src/client/views/DocumentDecorations.scss | 2 +- src/client/views/DocumentDecorations.tsx | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src/client/views/DocumentDecorations.scss') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index e915fc88d..5c8dd36cc 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -14,7 +14,7 @@ $resizeHandler: 8px; border-radius: 100%; height: 30; width: 30; - right: -17; + right: -30; top: calc(50% - 15px); position: absolute; pointer-events: all; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d79cf014d..9881ef9f1 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -579,10 +579,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } else doc._height = actualdH; } } else { + const rotCtr = [NumCast(doc._width) / 2, NumCast(doc._height) / 2]; + const tlRotated = Utils.rotPt(-rotCtr[0], -rotCtr[1], (NumCast(doc._jitterRotation) / 180) * Math.PI); + const maxHeight = doc.nativHeightUnfrozen || !nheight ? 0 : Math.max(nheight, NumCast(doc.scrollHeight, NumCast(doc[docView.LayoutFieldKey + '-scrollHeight']))) * docView.NativeDimScaling(); dH && (doc._height = actualdH > maxHeight && maxHeight ? maxHeight : actualdH); dW && (doc._width = actualdW); dH && (doc._autoHeight = false); + + const rotCtr2 = [NumCast(doc._width) / 2, NumCast(doc._height) / 2]; + const tlRotated2 = Utils.rotPt(-rotCtr2[0], -rotCtr2[1], (NumCast(doc._jitterRotation) / 180) * Math.PI); + doc.x = NumCast(doc.x) + tlRotated.x + rotCtr[0] - (tlRotated2.x + rotCtr2[0]); // doc shifts by amount topleft moves because rotation is about center of doc + doc.y = NumCast(doc.y) + tlRotated.y + rotCtr[1] - (tlRotated2.y + rotCtr2[1]); } doc.x = (doc.x || 0) + dX * (actualdW - docwidth); doc.y = (doc.y || 0) + (dragBottom ? 0 : dY * (actualdH - docheight)); -- cgit v1.2.3-70-g09d2 From a81ab2e6f75681bc4fb3a7b49d2056144e396b94 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 19 Oct 2022 12:38:19 -0400 Subject: fixed erasing straight line stroke segments caused by intersections with other strokes. cleaned up more with gestures. changed docView lock icon to be part of docDecorations. --- src/client/views/DocumentDecorations.scss | 28 ++++++- src/client/views/DocumentDecorations.tsx | 41 +++++++++- src/client/views/GestureOverlay.tsx | 21 +++--- src/client/views/StyleProvider.scss | 18 ++--- src/client/views/StyleProvider.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 58 ++++++-------- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 5 +- src/client/views/nodes/button/FontIconBox.tsx | 20 ++--- src/pen-gestures/GestureUtils.ts | 2 - src/pen-gestures/ndollar.ts | 88 +++++++++------------- 11 files changed, 148 insertions(+), 137 deletions(-) (limited to 'src/client/views/DocumentDecorations.scss') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 5c8dd36cc..4e0b061a6 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -1,7 +1,7 @@ @import 'global/globalCssVariables'; $linkGap: 3px; -$headerHeight: 20px; +$headerHeight: 25px; $resizeHandler: 8px; .documentDecorations-Dark, @@ -54,6 +54,8 @@ $resizeHandler: 8px; grid-column-end: 4; flex-direction: row; gap: 2px; + pointer-events: all; + cursor: move; .documentDecorations-openButton { display: flex; @@ -236,8 +238,8 @@ $resizeHandler: 8px; .documentDecorations-borderRadius { position: absolute; border-radius: 100%; - left: 3px; - top: 23px; + left: 7px; + top: 27px; background: $medium-gray; height: 10; width: 10; @@ -245,6 +247,21 @@ $resizeHandler: 8px; cursor: nwse-resize; } + .documentDecorations-lock { + position: absolute; + background: black; + right: 11; + top: 30px; + color: gray; + height: 14; + width: 14; + pointer-events: all; + margin: auto; + display: flex; + align-items: center; + flex-direction: column; + } + .documentDecorations-rotationPath { position: absolute; width: 100%; @@ -260,6 +277,7 @@ $resizeHandler: 8px; cursor: nwse-resize; background: unset; opacity: 1; + transform: scale(2); } .documentDecorations-topLeftResizer { @@ -314,6 +332,7 @@ $resizeHandler: 8px; .documentDecorations-topLeftResizer:hover, .documentDecorations-bottomRightResizer:hover { opacity: 1; + background: black; } .documentDecorations-bottomRightResizer { @@ -325,6 +344,7 @@ $resizeHandler: 8px; cursor: nesw-resize; background: unset; opacity: 1; + transform: scale(2); } .documentDecorations-topRightResizer { @@ -339,7 +359,6 @@ $resizeHandler: 8px; .documentDecorations-topRightResizer:hover, .documentDecorations-bottomLeftResizer:hover { - cursor: nesw-resize; background: black; opacity: 1; } @@ -392,6 +411,7 @@ $resizeHandler: 8px; justify-content: center; align-items: center; gap: 5px; + top: 4px; background: $light-gray; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9881ef9f1..6cf7df357 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -140,6 +140,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } }; + @action onContainerDown = (e: React.PointerEvent): void => { + setupMoveUpEvents( + this, + e, + e => this.onBackgroundMove(true, e), + e => {}, + emptyFunction + ); + }; + @action onTitleDown = (e: React.PointerEvent): void => { setupMoveUpEvents( this, @@ -311,6 +321,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); }; + @action + onLockDown = (e: React.PointerEvent): void => { + // Call util move event function + setupMoveUpEvents( + this, // target + e, // pointerEvent + returnFalse, // moveEvent + emptyFunction, // upEvent + e => { + UndoManager.RunInBatch( + () => + SelectionManager.Views().map(dv => { + dv.rootDoc._lockedPosition = !dv.rootDoc._lockedPosition; + dv.rootDoc._pointerEvents = dv.rootDoc._lockedPosition ? 'none' : undefined; + }), + 'toggleBackground' + ); + } // clickEvent + ); + }; + setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => { const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]); const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; @@ -791,7 +822,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px', }}> -
+
{hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} {hideResizers ?
: titleArea} @@ -817,21 +848,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P key="rad" style={{ background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`, - left: `${radiusHandleLocation + 3}`, - top: `${radiusHandleLocation + 23}`, + transform: `translate(${radiusHandleLocation}px, ${radiusHandleLocation}px)`, }} className={`documentDecorations-borderRadius`} onPointerDown={this.onRadiusDown} onContextMenu={e => e.preventDefault()} /> )} +
e.preventDefault()}> + +
{hideDocumentButtonBar ? null : (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 23b03bc50..362aa3c86 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,7 +1,7 @@ import React = require('react'); import * as fitCurve from 'fit-curve'; import { action, computed, observable, runInAction, trace } from 'mobx'; -import { Doc } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; import { InkData, InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; @@ -49,7 +49,7 @@ interface GestureOverlayProps { export class GestureOverlay extends Touchable { static Instance: GestureOverlay; - @observable public InkShape: string = ''; + @observable public InkShape: Opt; @observable public SavedColor?: string; @observable public SavedWidth?: number; @observable public Tool: ToolglassTools = ToolglassTools.None; @@ -644,8 +644,6 @@ export class GestureOverlay extends Touchable { if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - //push first points to so interactionUtil knows pointer is up - this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); const initialPoint = this._points[0]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; @@ -685,10 +683,10 @@ export class GestureOverlay extends Touchable { //if any of the shape is activated in the CollectionFreeFormViewChrome else if (this.InkShape) { this.makeBezierPolygon(this.InkShape, false); - this.dispatchGesture(GestureUtils.Gestures.Stroke); + this.dispatchGesture(this.InkShape); this._points.length = 0; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { - this.InkShape = ''; + this.InkShape = undefined; Doc.ActiveTool = InkTool.None; } } @@ -703,12 +701,13 @@ export class GestureOverlay extends Touchable { case GestureUtils.Gestures.Rectangle: case GestureUtils.Gestures.Circle: this.makeBezierPolygon(result.Name, true); - case GestureUtils.Gestures.StartBracket: - case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture(result.Name); break; case GestureUtils.Gestures.Line: - actionPerformed = this.handleLineGesture(); + if (!(actionPerformed = this.handleLineGesture())) { + this.makeBezierPolygon(result.Name, true); + actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Stroke); + } break; case GestureUtils.Gestures.Scribble: console.log('scribble'); @@ -972,7 +971,7 @@ export class GestureOverlay extends Touchable { ActiveDash(), 1, 1, - this.InkShape, + this.InkShape ?? '', 'none', 1.0, false @@ -999,7 +998,7 @@ export class GestureOverlay extends Touchable { ActiveDash(), 1, 1, - this.InkShape, + this.InkShape ?? '', 'none', 1.0, false diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss index 8929954c8..b1c97164a 100644 --- a/src/client/views/StyleProvider.scss +++ b/src/client/views/StyleProvider.scss @@ -1,11 +1,11 @@ .styleProvider-lock { - font-size: 12px; - width: 20; - height: 20; - position: absolute; - right: -25; - top: -5; - background: transparent; + font-size: 10; + width: 15; + height: 15; + position: absolute; + right: -0; + top: 0; + background: black; pointer-events: all; opacity: 0.3; display: flex; @@ -15,7 +15,7 @@ cursor: default; } .styleProvider-lock:hover { - opacity:1; + opacity: 1; } .styleProvider-treeView-icon, @@ -26,4 +26,4 @@ .styleProvider-treeView-icon { opacity: 0; -} \ No newline at end of file +} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index aadcd7169..a5a886f42 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -276,7 +276,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 && ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index daf69d4f6..983cf4f9b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -568,6 +568,8 @@ export class CollectionFreeFormView extends CollectionSubView { switch (ge.gesture) { + case GestureUtils.Gestures.Line: + break; default: case GestureUtils.Gestures.Circle: case GestureUtils.Gestures.Rectangle: @@ -602,33 +604,6 @@ export class CollectionFreeFormView extends CollectionSubView p.X)), Math.min(...ge.points.map(p => p.Y))); - const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); - const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] }; - const bWidth = bounds.r - bounds.x; - const bHeight = bounds.b - bounds.y; - const sel = this.getActiveDocuments().filter(doc => { - const l = NumCast(doc.x); - const r = l + doc[WidthSym](); - const t = NumCast(doc.y); - const b = t + doc[HeightSym](); - const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t); - if (pass) { - doc.x = l - bounds.x - bWidth / 2; - doc.y = t - bounds.y - bHeight / 2; - } - return pass; - }); - this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); - sel.forEach(d => this.props.removeDocument?.(d)); - e.stopPropagation(); - break; - case GestureUtils.Gestures.StartBracket: - const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y))); - this._inkToTextStartX = start[0]; - this._inkToTextStartY = start[1]; - break; - case GestureUtils.Gestures.EndBracket: if (this._inkToTextStartX && this._inkToTextStartY) { const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color); @@ -805,15 +780,15 @@ export class CollectionFreeFormView extends CollectionSubView ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve } @@ -862,6 +837,15 @@ export class CollectionFreeFormView extends CollectionSubView { + if ((otherCurve as any)._linear) { + return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); + } + return curve.intersects(otherCurve); + }; + /** * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all * ink strokes in the current collection. @@ -886,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView ({ x: p.X, y: p.Y }))); - curve.intersects(otherCurve).forEach((val: string | number, i: number) => { + this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1c48d47e9..040e03150 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1082,7 +1082,7 @@ export class DocumentViewInternal extends DocComponent )} {audioView} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 959c641a8..461d6984d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast } from '../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -77,10 +77,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'), { fireImmediately: true, delay: 1000 } ); + const layoutDoc = this.layoutDoc; this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { - if (true || !this.layoutDoc._height) { + if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) { this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 6eaf3c31a..fd0c0d141 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -771,36 +771,26 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); } -/** INK - * setActiveTool - * setStrokeWidth - * setStrokeColor - **/ - -ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) { +ScriptingGlobals.add(function setActiveTool(tool: InkTool | GestureUtils.Gestures, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); if (checkResult) { return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent'; } - if ([GestureUtils.Gestures.Circle, GestureUtils.Gestures.Rectangle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Triangle].includes(tool as any)) { + if (Object.values(GestureUtils.Gestures).includes(tool as any)) { if (GestureOverlay.Instance.InkShape === tool) { Doc.ActiveTool = InkTool.None; - GestureOverlay.Instance.InkShape = InkTool.None; + GestureOverlay.Instance.InkShape = undefined; } else { Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool; + GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; } } else if (tool) { // pen or eraser if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { Doc.ActiveTool = InkTool.None; - } else if (tool == InkTool.Write) { - // console.log("write mode selected - create groupDoc here!", tool) - Doc.ActiveTool = tool; - GestureOverlay.Instance.InkShape = ''; } else { Doc.ActiveTool = tool as any; - GestureOverlay.Instance.InkShape = ''; + GestureOverlay.Instance.InkShape = undefined; } } else { Doc.ActiveTool = InkTool.None; diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 2d3b1fdb8..41917aac9 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -19,8 +19,6 @@ export namespace GestureUtils { export enum Gestures { Line = 'line', - StartBracket = 'startbracket', - EndBracket = 'endbracket', Stroke = 'stroke', Scribble = 'scribble', Text = 'text', diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index b10a9da17..3ee9506cb 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -145,7 +145,7 @@ export class Result { // // NDollarRecognizer constants // -const NumMultistrokes = 6; +let NumMultistrokes = 0; const NumPoints = 96; const SquareSize = 250.0; const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35) @@ -162,73 +162,59 @@ const AngleSimilarityThreshold = Deg2Rad(30.0); // NDollarRecognizer class // export class NDollarRecognizer { - public Multistrokes: Multistroke[]; + public Multistrokes: Multistroke[] = []; - /** - * @IMPORTANT - IF YOU'RE ADDING A NEW GESTURE, BE SURE TO INCREMENT THE NumMultiStrokes CONST RIGHT ABOVE THIS CLASS. - */ constructor( useBoundedRotationInvariance: boolean // constructor ) { // // one predefined multistroke for each multistroke type // - this.Multistrokes = new Array(NumMultistrokes); - this.Multistrokes[0] = new Multistroke( - GestureUtils.Gestures.Rectangle, - useBoundedRotationInvariance, - new Array( + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Rectangle, + useBoundedRotationInvariance, new Array( - new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), - new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), - new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), - new Point(106, 146), //new Point(80, 150), new Point(50, 146), - new Point(30, 143) + new Array( + new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), + new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), + new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), + new Point(106, 146), //new Point(80, 150), new Point(50, 146), + new Point(30, 143) + ) ) ) ); - this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))); - this.Multistrokes[2] = new Multistroke( - GestureUtils.Gestures.StartBracket, - useBoundedRotationInvariance, - new Array( - // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150)) - new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150)) + this.Multistrokes.push(new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347))))); + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Triangle, // equilateral + useBoundedRotationInvariance, + new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) ) ); - this.Multistrokes[3] = new Multistroke( - GestureUtils.Gestures.EndBracket, - useBoundedRotationInvariance, - new Array( - // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152)) - // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150)) - new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100)) - ) - ); - this.Multistrokes[4] = new Multistroke( - GestureUtils.Gestures.Triangle, // equilateral - useBoundedRotationInvariance, - new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) - ); - this.Multistrokes[5] = new Multistroke( - GestureUtils.Gestures.Circle, - useBoundedRotationInvariance, - new Array( + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Circle, + useBoundedRotationInvariance, new Array( - new Point(200, 250), - new Point(240, 230), - new Point(248, 210), - new Point(248, 190), - new Point(240, 170), - new Point(200, 150), - new Point(160, 170), - new Point(151, 190), - new Point(151, 210), - new Point(160, 230), - new Point(201, 250) + new Array( + new Point(200, 250), + new Point(240, 230), + new Point(248, 210), + new Point(248, 190), + new Point(240, 170), + new Point(200, 150), + new Point(160, 170), + new Point(151, 190), + new Point(151, 210), + new Point(160, 230), + new Point(201, 250) + ) ) ) ); + NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes // // PREDEFINED STROKES // -- cgit v1.2.3-70-g09d2