From 36eed6cc3a3387a1f7755a848aea4e19e2645e14 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Thu, 9 Jan 2020 19:55:52 -0500
Subject: some stuff that ive played around with today. mobile interface, five
 fingers, other stuff

---
 src/client/views/InkingControl.tsx | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

(limited to 'src/client/views/InkingControl.tsx')

diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index e33f193b8..243123352 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -4,19 +4,18 @@ import { Doc } from "../../new_fields/Doc";
 import { InkTool } from "../../new_fields/InkField";
 import { List } from "../../new_fields/List";
 import { listSpec } from "../../new_fields/Schema";
-import { Cast, NumCast, StrCast } from "../../new_fields/Types";
+import { Cast, NumCast, StrCast, FieldValue } from "../../new_fields/Types";
 import { Utils } from "../../Utils";
 import { Scripting } from "../util/Scripting";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
 
-
 export class InkingControl {
     @observable static Instance: InkingControl;
-    @observable private _selectedTool: InkTool = InkTool.None;
-    @observable private _selectedColor: string = "rgb(244, 67, 54)";
-    @observable private _selectedWidth: string = "5";
+    @computed private get _selectedTool(): InkTool { return FieldValue(NumCast(CurrentUserUtils.UserDocument.inkTool)) ?? InkTool.None; }
+    @computed private get _selectedColor(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkColor)) ?? "rgb(244, 67, 54)"; }
+    @computed private get _selectedWidth(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkWidth)) ?? "5"; }
     @observable public _open: boolean = false;
 
     constructor() {
@@ -24,7 +23,8 @@ export class InkingControl {
     }
 
     switchTool = action((tool: InkTool): void => {
-        this._selectedTool = tool;
+        // this._selectedTool = tool;
+        CurrentUserUtils.UserDocument.inkTool = tool;
     });
     decimalToHexString(number: number) {
         if (number < 0) {
@@ -36,7 +36,7 @@ export class InkingControl {
 
     @undoBatch
     switchColor = action((color: ColorState): void => {
-        this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
+        CurrentUserUtils.UserDocument.inkColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
 
         if (InkingControl.Instance.selectedTool === InkTool.None) {
             const selected = SelectionManager.SelectedDocuments();
@@ -99,7 +99,8 @@ export class InkingControl {
     });
     @action
     switchWidth = (width: string): void => {
-        this._selectedWidth = width;
+        // this._selectedWidth = width;
+        CurrentUserUtils.UserDocument.inkWidth = width;
     }
 
     @computed
@@ -114,7 +115,8 @@ export class InkingControl {
 
     @action
     updateSelectedColor(value: string) {
-        this._selectedColor = value;
+        // this._selectedColor = value;
+        CurrentUserUtils.UserDocument.inkColor = value;
     }
 
     @computed
@@ -127,6 +129,7 @@ Scripting.addGlobal(function activatePen(pen: any, width: any, color: any) { Ink
 Scripting.addGlobal(function activateBrush(pen: any, width: any, color: any) { InkingControl.Instance.switchTool(pen ? InkTool.Highlighter : InkTool.None); InkingControl.Instance.switchWidth(width); InkingControl.Instance.updateSelectedColor(color); });
 Scripting.addGlobal(function activateEraser(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Eraser : InkTool.None); });
 Scripting.addGlobal(function activateScrubber(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Scrubber : InkTool.None); });
+Scripting.addGlobal(function activateStamp(pen: any) { return InkingControl.Instance.switchTool(pen ? InkTool.Stamp : InkTool.None); });
 Scripting.addGlobal(function deactivateInk() { return InkingControl.Instance.switchTool(InkTool.None); });
 Scripting.addGlobal(function setInkWidth(width: any) { return InkingControl.Instance.switchWidth(width); });
 Scripting.addGlobal(function setInkColor(color: any) { return InkingControl.Instance.updateSelectedColor(color); });
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From d86b4db095379d473820271868e8f7cd5830d502 Mon Sep 17 00:00:00 2001
From: Stanley Yip <stanley_yip@brown.edu>
Date: Mon, 13 Jan 2020 15:25:07 -0500
Subject: palette events are now on gestureOverlay, but cffv and dv is still
 capturing one and two finger events, which makes it impossible for the
 gestureoverlay to capture 5 fingers spread across different targets

---
 src/client/documents/Documents.ts                  |  3 +
 src/client/util/InteractionUtils.ts                | 10 ++-
 src/client/views/GestureOverlay.tsx                | 64 ++++++++++++++-
 src/client/views/InkingControl.tsx                 |  5 +-
 src/client/views/Palette.scss                      | 10 ++-
 src/client/views/Palette.tsx                       | 44 +++++++++-
 src/client/views/Touchable.tsx                     | 21 +++--
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 94 +++++++++++-----------
 src/client/views/nodes/DocumentView.tsx            | 24 +++++-
 src/new_fields/documentSchemas.ts                  |  2 +
 .../authentication/models/current_user_utils.ts    | 23 ++++++
 11 files changed, 224 insertions(+), 76 deletions(-)

(limited to 'src/client/views/InkingControl.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1853be529..64abd4f57 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -103,6 +103,8 @@ export interface DocumentOptions {
     ischecked?: ScriptField; // returns whether a font icon box is checked
     activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts)
     onClick?: ScriptField;
+    onPointerDown?: ScriptField;
+    onPointerUp?: ScriptField;
     dragFactory?: Doc; // document to create when dragging with a suitable onDragStart script
     onDragStart?: ScriptField; //script to execute at start of drag operation --  e.g., when a "creator" button is dragged this script generates a different document to drop
     icon?: string;
@@ -116,6 +118,7 @@ export interface DocumentOptions {
     color?: string;
     limitHeight?: number; // maximum height for newly created (eg, from pasting) text documents
     // [key: string]: Opt<Field>;
+    pointerHack?: boolean; // for buttons, allows onClick handler to fire onPointerDown
 }
 
 class EmptyBox {
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
index 2e4e8c7ca..76b43da3c 100644
--- a/src/client/util/InteractionUtils.ts
+++ b/src/client/util/InteractionUtils.ts
@@ -8,12 +8,14 @@ export namespace InteractionUtils {
     const REACT_POINTER_PEN_BUTTON = 0;
     const ERASER_BUTTON = 5;
 
-    export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>): React.Touch[] {
+    export function GetMyTargetTouches(e: TouchEvent | React.TouchEvent, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
         const myTouches = new Array<React.Touch>();
         for (let i = 0; i < e.targetTouches.length; i++) {
-            const pt = e.targetTouches.item(i);
+            const pt: any = e.targetTouches.item(i);
             if (pt && prevPoints.has(pt.identifier)) {
-                myTouches.push(pt);
+                if (ignorePen || (pt.radiusX > 1 && pt.radiusY > 1)) {
+                    myTouches.push(pt);
+                }
             }
         }
         return myTouches;
@@ -23,7 +25,7 @@ export namespace InteractionUtils {
         switch (type) {
             // pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
             case PENTYPE:
-                return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? POINTER_PEN_BUTTON : REACT_POINTER_PEN_BUTTON);
+                return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
             case ERASERTYPE:
                 return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
             default:
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 848927912..830c06f1f 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { Touchable } from "./Touchable";
 import { observer } from "mobx-react";
 import "./GestureOverlay.scss"
-import { computed, observable, action } from "mobx";
+import { computed, observable, action, runInAction } from "mobx";
 import { CreatePolyline } from "./InkingStroke";
 import { GestureUtils } from "../../pen-gestures/GestureUtils";
 import { InteractionUtils } from "../util/InteractionUtils";
@@ -12,14 +12,22 @@ import { Doc } from "../../new_fields/Doc";
 import { LinkManager } from "../util/LinkManager";
 import { DocUtils } from "../documents/Documents";
 import { undoBatch } from "../util/UndoManager";
+import { Scripting } from "../util/Scripting";
+import { FieldValue, Cast } from "../../new_fields/Types";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import Palette from "./Palette";
 
 @observer
 export default class GestureOverlay extends Touchable {
     static Instance: GestureOverlay;
 
     @observable private _points: { X: number, Y: number }[] = [];
+    @observable private _palette?: JSX.Element;
+    @observable public Color: string = "rgb(244, 67, 54)";
+    @observable public Width: number = 5;
 
     private _d1: Doc | undefined;
+    private thumbIdentifier?: number;
 
     constructor(props: Readonly<{}>) {
         super(props);
@@ -27,6 +35,47 @@ export default class GestureOverlay extends Touchable {
         GestureOverlay.Instance = this;
     }
 
+    @action
+    handleHandDown = (e: React.TouchEvent) => {
+        const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+        const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+        this.thumbIdentifier = thumb?.identifier;
+        const others = fingers.filter(f => f !== thumb);
+        const minX = Math.min(...others.map(f => f.clientX));
+        const minY = Math.min(...others.map(f => f.clientY));
+        // const t = this.getTransform().transformPoint(minX, minY);
+        // const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
+
+        const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+        if (thumbDoc) {
+            this._palette = <Palette x={minX} y={minY} thumb={[thumb.clientX, thumb.clientY]} thumbDoc={thumbDoc} />;
+        }
+
+        document.removeEventListener("touchmove", this.onTouch);
+        document.removeEventListener("touchmove", this.handleHandMove);
+        document.addEventListener("touchmove", this.handleHandMove);
+        document.removeEventListener("touchend", this.handleHandUp);
+        document.addEventListener("touchend", this.handleHandUp);
+    }
+
+    @action
+    handleHandMove = (e: TouchEvent) => {
+        for (let i = 0; i < e.changedTouches.length; i++) {
+            const pt = e.changedTouches.item(i);
+            if (pt?.identifier === this.thumbIdentifier) {
+            }
+        }
+    }
+
+    @action
+    handleHandUp = (e: TouchEvent) => {
+        this.onTouchEnd(e);
+        if (this.prevPoints.size < 3) {
+            this._palette = undefined;
+            document.removeEventListener("touchend", this.handleHandUp);
+        }
+    }
+
     @action
     onPointerDown = (e: React.PointerEvent) => {
         if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
@@ -82,6 +131,8 @@ export default class GestureOverlay extends Touchable {
         return actionPerformed;
     }
 
+
+
     @action
     onPointerUp = (e: PointerEvent) => {
         if (this._points.length > 1) {
@@ -157,16 +208,21 @@ export default class GestureOverlay extends Touchable {
 
         return (
             <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: "none", position: "absolute", zIndex: 30000 }}>
-                {CreatePolyline(this._points, B.left, B.top)}
+                {CreatePolyline(this._points, B.left, B.top, this.Color, this.Width)}
             </svg>
         );
     }
 
     render() {
         return (
-            <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown}>
+            <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart}>
                 {this.props.children}
+                {this._palette}
                 {this.currentStroke}
             </div>);
     }
-}
\ No newline at end of file
+}
+
+Scripting.addGlobal("GestureOverlay", GestureOverlay);
+Scripting.addGlobal(function setPen(width: any, color: any) { runInAction(() => { GestureOverlay.Instance.Color = color; GestureOverlay.Instance.Width = width; }); });
+Scripting.addGlobal(function resetPen() { runInAction(() => { GestureOverlay.Instance.Color = "rgb(244, 67, 54)"; GestureOverlay.Instance.Width = 5; }); });
\ No newline at end of file
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 243123352..be07a9024 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -10,12 +10,13 @@ import { Scripting } from "../util/Scripting";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch, UndoManager } from "../util/UndoManager";
 import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import GestureOverlay from "./GestureOverlay";
 
 export class InkingControl {
     @observable static Instance: InkingControl;
     @computed private get _selectedTool(): InkTool { return FieldValue(NumCast(CurrentUserUtils.UserDocument.inkTool)) ?? InkTool.None; }
-    @computed private get _selectedColor(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkColor)) ?? "rgb(244, 67, 54)"; }
-    @computed private get _selectedWidth(): string { return FieldValue(StrCast(CurrentUserUtils.UserDocument.inkWidth)) ?? "5"; }
+    @computed private get _selectedColor(): string { return GestureOverlay.Instance.Color ?? FieldValue(StrCast(CurrentUserUtils.UserDocument.inkColor)) ?? "rgb(244, 67, 54)"; }
+    @computed private get _selectedWidth(): string { return GestureOverlay.Instance.Width?.toString() ?? FieldValue(StrCast(CurrentUserUtils.UserDocument.inkWidth)) ?? "5"; }
     @observable public _open: boolean = false;
 
     constructor() {
diff --git a/src/client/views/Palette.scss b/src/client/views/Palette.scss
index 60004c81f..2626774cb 100644
--- a/src/client/views/Palette.scss
+++ b/src/client/views/Palette.scss
@@ -4,15 +4,17 @@
         height: 300px;
         touch-action: pan-x;
         overflow: scroll;
+        position: absolute;
 
         .palette-thumbContent {
             width: 100%;
             height: 100%;
+        }
 
-            .palette-button {
-                width: 100px;
-                height: 100px;
-            }
+        .palette-button {
+            width: 100px;
+            height: 100px;
+            background: blue;
         }
     }
 }
\ No newline at end of file
diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx
index 390b7e2c2..3649cccfe 100644
--- a/src/client/views/Palette.tsx
+++ b/src/client/views/Palette.tsx
@@ -1,22 +1,60 @@
 import * as React from "react";
 import "./Palette.scss";
 import { PointData } from "../../new_fields/InkField";
+import { Doc } from "../../new_fields/Doc";
+import { Docs } from "../documents/Documents";
+import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
+import { List } from "../../new_fields/List";
+import { DocumentView } from "./nodes/DocumentView";
+import { emptyPath, returnFalse, emptyFunction, returnOne, returnEmptyString, returnTrue } from "../../Utils";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { Transform } from "../util/Transform";
+import { computed, action } from "mobx";
+import { FieldValue, Cast } from "../../new_fields/Types";
+import { observer } from "mobx-react";
+import { DocumentContentsView } from "./nodes/DocumentContentsView";
+import { CollectionStackingView } from "./collections/CollectionStackingView";
+import { CollectionView } from "./collections/CollectionView";
 
 export interface PaletteProps {
     x: number;
     y: number;
     thumb: number[];
+    thumbDoc: Doc;
 }
 
+@observer
 export default class Palette extends React.Component<PaletteProps> {
     render() {
         return (
             <div className="palette-container" style={{ transform: `translate(${this.props.x}px, ${this.props.y}px)` }}>
                 <div className="palette-thumb" style={{ transform: `translate(${this.props.thumb[0] - this.props.x}px, ${this.props.thumb[1] - this.props.y}px)` }}>
                     <div className="palette-thumbContent">
-                        <div className="palette-button" style={{ background: "green" }} onPointerDown={() => console.log("hi")}>1</div>
-                        <div className="palette-button" style={{ background: "red" }}>2</div>
-                        <div className="palette-button" style={{ background: "blue" }}>3</div>
+                        <DocumentView
+                            Document={this.props.thumbDoc}
+                            DataDoc={undefined}
+                            LibraryPath={emptyPath}
+                            addDocument={undefined}
+                            addDocTab={returnFalse}
+                            pinToPres={emptyFunction}
+                            removeDocument={undefined}
+                            ruleProvider={undefined}
+                            onClick={undefined}
+                            ScreenToLocalTransform={Transform.Identity}
+                            ContentScaling={returnOne}
+                            PanelWidth={() => window.screen.width}
+                            PanelHeight={() => window.screen.height}
+                            renderDepth={0}
+                            focus={emptyFunction}
+                            backgroundColor={returnEmptyString}
+                            parentActive={returnTrue}
+                            whenActiveChanged={emptyFunction}
+                            bringToFront={emptyFunction}
+                            ContainingCollectionView={undefined}
+                            ContainingCollectionDoc={undefined}
+                            zoomToScale={emptyFunction}
+                            getScale={returnOne}>
+                        </DocumentView>
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
index 3eb66ff72..24ea801a0 100644
--- a/src/client/views/Touchable.tsx
+++ b/src/client/views/Touchable.tsx
@@ -21,15 +21,26 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
      */
     @action
     protected onTouchStart = (e: React.TouchEvent): void => {
+        const actualPts: React.Touch[] = [];
         for (let i = 0; i < e.targetTouches.length; i++) {
             const pt: any = e.targetTouches.item(i);
+            actualPts.push(pt);
             // pen is also a touch, but with a radius of 0.5 (at least with the surface pens)
             // and this seems to be the only way of differentiating pen and touch on touch events
-            if (pt.radiusX > 0.5 && pt.radiusY > 0.5) {
+            if (pt.radiusX > 1 && pt.radiusY > 1) {
                 this.prevPoints.set(pt.identifier, pt);
             }
         }
 
+        const ptsToDelete: number[] = [];
+        this.prevPoints.forEach(pt => {
+            if (!actualPts.includes(pt)) {
+                ptsToDelete.push(pt.identifier);
+            }
+        });
+
+        ptsToDelete.forEach(pt => this.prevPoints.delete(pt));
+
         if (this.prevPoints.size) {
             switch (this.prevPoints.size) {
                 case 1:
@@ -57,13 +68,12 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
     */
     @action
     protected onTouch = (e: TouchEvent): void => {
-        const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+        const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
 
         // if we're not actually moving a lot, don't consider it as dragging yet
         if (!InteractionUtils.IsDragging(this.prevPoints, myTouches, 5) && !this._touchDrag) return;
         this._touchDrag = true;
         if (this.holdTimer) {
-            console.log("clear");
             clearTimeout(this.holdTimer);
             this.holdTimer = undefined;
         }
@@ -99,7 +109,6 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
             }
         }
         if (this.holdTimer) {
-            console.log("clear");
             clearTimeout(this.holdTimer);
             this.holdTimer = undefined;
         }
@@ -152,7 +161,7 @@ export abstract class Touchable<T = {}> extends React.Component<T> {
     }
 
     handleHandDown = (e: React.TouchEvent) => {
-        e.stopPropagation();
-        e.preventDefault();
+        // e.stopPropagation();
+        // e.preventDefault();
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d7cccc036..84945c6e6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,7 +9,7 @@ import { Id } from "../../../../new_fields/FieldSymbols";
 import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { BoolCast, Cast, DateCast, NumCast, StrCast, FieldValue } from "../../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
 import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
 import { DocServer } from "../../../DocServer";
@@ -468,7 +468,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     handle1PointerMove = (e: TouchEvent) => {
         // panning a workspace
         if (!e.cancelBubble) {
-            const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+            const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
             const pt = myTouches[0];
             if (pt) {
                 if (InkingControl.Instance.selectedTool === InkTool.None) {
@@ -490,7 +490,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     handle2PointersMove = (e: TouchEvent) => {
         // pinch zooming
         if (!e.cancelBubble) {
-            const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+            const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
             const pt1 = myTouches[0];
             const pt2 = myTouches[1];
 
@@ -849,51 +849,47 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     private thumbIdentifier?: number;
-    private hand?: React.Touch[];
 
-    @action
-    handleHandDown = (e: React.TouchEvent) => {
-        const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
-        this.hand = fingers;
-        const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
-        this.thumbIdentifier = thumb?.identifier;
-        const others = fingers.filter(f => f !== thumb);
-        const minX = Math.min(...others.map(f => f.clientX));
-        const minY = Math.min(...others.map(f => f.clientY));
-        const t = this.getTransform().transformPoint(minX, minY);
-        const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
-        this._palette = <Palette x={t[0]} y={t[1]} thumb={th} />;
-
-        document.removeEventListener("touchmove", this.onTouch);
-        document.removeEventListener("touchmove", this.handleHandMove);
-        document.addEventListener("touchmove", this.handleHandMove);
-        document.removeEventListener("touchend", this.handleHandUp);
-        document.addEventListener("touchend", this.handleHandUp);
-    }
-
-    @action
-    handleHandMove = (e: TouchEvent) => {
-        for (let i = 0; i < e.changedTouches.length; i++) {
-            const pt = e.changedTouches.item(i);
-            if (pt?.identifier === this.thumbIdentifier) {
-            }
-        }
-    }
-
-    @action
-    handleHandUp = (e: TouchEvent) => {
-        console.log(e.changedTouches.length);
-        this.onTouchEnd(e);
-        if (this.prevPoints.size < 3) {
-            if (this.hand) {
-                for (const h of this.hand) {
-                    this.prevPoints.has(h.identifier) && this.prevPoints.delete(h.identifier);
-                }
-            }
-            this._palette = undefined;
-            document.removeEventListener("touchend", this.handleHandUp);
-        }
-    }
+    // @action
+    // handleHandDown = (e: React.TouchEvent) => {
+    //     const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
+    //     const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
+    //     this.thumbIdentifier = thumb?.identifier;
+    //     const others = fingers.filter(f => f !== thumb);
+    //     const minX = Math.min(...others.map(f => f.clientX));
+    //     const minY = Math.min(...others.map(f => f.clientY));
+    //     const t = this.getTransform().transformPoint(minX, minY);
+    //     const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
+
+    //     const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
+    //     if (thumbDoc) {
+    //         this._palette = <Palette x={t[0]} y={t[1]} thumb={th} thumbDoc={thumbDoc} />;
+    //     }
+
+    //     document.removeEventListener("touchmove", this.onTouch);
+    //     document.removeEventListener("touchmove", this.handleHandMove);
+    //     document.addEventListener("touchmove", this.handleHandMove);
+    //     document.removeEventListener("touchend", this.handleHandUp);
+    //     document.addEventListener("touchend", this.handleHandUp);
+    // }
+
+    // @action
+    // handleHandMove = (e: TouchEvent) => {
+    //     for (let i = 0; i < e.changedTouches.length; i++) {
+    //         const pt = e.changedTouches.item(i);
+    //         if (pt?.identifier === this.thumbIdentifier) {
+    //         }
+    //     }
+    // }
+
+    // @action
+    // handleHandUp = (e: TouchEvent) => {
+    //     this.onTouchEnd(e);
+    //     if (this.prevPoints.size < 3) {
+    //         this._palette = undefined;
+    //         document.removeEventListener("touchend", this.handleHandUp);
+    //     }
+    // }
 
     onContextMenu = (e: React.MouseEvent) => {
         const layoutItems: ContextMenuProps[] = [];
@@ -958,12 +954,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ];
     }
 
-    @observable private _palette?: JSX.Element;
+    // @observable private _palette?: JSX.Element;
 
     children = () => {
         const eles: JSX.Element[] = [];
         this.extensionDoc && (eles.push(...this.childViews()));
-        this._palette && (eles.push(this._palette));
+        // this._palette && (eles.push(this._palette));
         // this.currentStroke && (eles.push(this.currentStroke));
         eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
         return eles;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 415d0e2ef..bdec94eb3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -59,6 +59,8 @@ export interface DocumentViewProps {
     LibraryPath: Doc[];
     fitToBox?: boolean;
     onClick?: ScriptField;
+    onPointerDown?: ScriptField;
+    onPointerUp?: ScriptField;
     dragDivName?: string;
     addDocument?: (doc: Doc) => boolean;
     removeDocument?: (doc: Doc) => boolean;
@@ -105,6 +107,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; }
     @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; }
     @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; }
+    @computed get onPointerDownHandler() { return this.props.onPointerDown ? this.props.onPointerDown : this.Document.onPointerDown; }
+    @computed get onPointerUpHandler() { return this.props.onPointerUp ? this.props.onPointerUp : this.Document.onPointerUp; }
 
     @action
     componentDidMount() {
@@ -180,7 +184,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    onClick = async (e: React.MouseEvent) => {
+    onClick = async (e: React.MouseEvent | React.PointerEvent) => {
         if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
             e.stopPropagation();
@@ -240,8 +244,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     handle1PointerDown = (e: React.TouchEvent) => {
+        if (this.Document.onPointerDown) return;
         if (!e.nativeEvent.cancelBubble) {
-            const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+            const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
             this._downX = touch.clientX;
             this._downY = touch.clientY;
             this._hitTemplateDrag = false;
@@ -265,7 +270,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             document.removeEventListener("touchmove", this.onTouch);
         }
         else if (!e.cancelBubble && (SelectionManager.IsSelected(this, true) || this.props.parentActive(true) || this.Document.onDragStart || this.Document.onClick) && !this.Document.lockedPosition && !this.Document.inOverlay) {
-            const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints)[0];
+            const touch = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true)[0];
             if (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3) {
                 if (!e.altKey && (!this.topMost || this.Document.onDragStart || this.Document.onClick)) {
                     document.removeEventListener("touchmove", this.onTouch);
@@ -293,7 +298,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     @action
     handle2PointersMove = (e: TouchEvent) => {
-        const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints);
+        const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
         const pt1 = myTouches[0];
         const pt2 = myTouches[1];
         const oldPoint1 = this.prevPoints.get(pt1.identifier);
@@ -363,6 +368,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerDown = (e: React.PointerEvent): void => {
+        if (this.onPointerDownHandler && this.onPointerDownHandler.script) {
+            this.onPointerDownHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            document.addEventListener("pointerup", this.onPointerUp);
+            return;
+        }
         // console.log(e.button)
         // console.log(e.nativeEvent)
         // continue if the event hasn't been canceled AND we are using a moues or this is has an onClick or onDragStart function (meaning it is a button document)
@@ -412,6 +423,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     onPointerUp = (e: PointerEvent): void => {
+        if (this.onPointerUpHandler && this.onPointerUpHandler.script && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+            this.onPointerUpHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log);
+            document.removeEventListener("pointerup", this.onPointerUp);
+            return;
+        }
         document.removeEventListener("pointermove", this.onPointerMove);
         document.removeEventListener("pointerup", this.onPointerUp);
         this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index 21e69fbed..1cba3cba9 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -20,6 +20,8 @@ export const documentSchema = createSchema({
     dropAction: "string",       // override specifying what should happen when this document is dropped (can be "alias" or "copy")
     removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped
     onClick: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
+    onPointerDown: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
+    onPointerUp: ScriptField,       // script to run when document is clicked (can be overriden by an onClick prop)
     onDragStart: ScriptField,   // script to run when document is dragged (without being selected).  the script should return the Doc to be dropped.
     dragFactory: Doc,           // the document that serves as the "template" for the onDragStart script.  ie, to drag out copies of the dragFactory document.
     ignoreAspect: "boolean",    // whether aspect ratio should be ignored when laying out or manipulating the document
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 71dd34e68..2ade6f102 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -110,6 +110,29 @@ export class CurrentUserUtils {
         }));
     }
 
+    static setupThumbButtons(doc: Doc) {
+        const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [
+            { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activePen.pen,  this)`, activePen: doc },
+            { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activePen.pen, this)`, activePen: doc },
+        ];
+        return docProtoData.map(data => Docs.Create.FontIconDocument({
+            nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.pointerDown ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick,
+            onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined,
+            onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined,
+            ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activePen: data.activePen, pointerHack: true,
+            backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory,
+        }));
+    }
+
+    static setupThumbDoc(userDoc: Doc) {
+        if (!userDoc.thumbDoc) {
+            return Docs.Create.MasonryDocument(CurrentUserUtils.setupThumbButtons(userDoc), {
+                width: 300, columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5
+            });
+        }
+        return userDoc.thumbDoc;
+    }
+
     static setupMobileDoc(userDoc: Doc) {
         return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), {
             columnWidth: 100, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", autoHeight: true, yMargin: 5
-- 
cgit v1.2.3-70-g09d2

