From 4c5267d2796fa5ae44388bb5e9108393cd655812 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 12 Feb 2024 01:14:33 -0400 Subject: [PATCH] smooth ladder dragging! --- src/charts/chart.js | 185 ++++++++++++-------------- src/charts/shape.js | 68 +++++++--- src/components/Vault.vue | 2 +- src/components/chart/LimitBuilder.vue | 55 +++----- src/misc.js | 4 +- 5 files changed, 155 insertions(+), 159 deletions(-) diff --git a/src/charts/chart.js b/src/charts/chart.js index d92dc0e..c8786da 100644 --- a/src/charts/chart.js +++ b/src/charts/chart.js @@ -1,28 +1,20 @@ import {useChartOrderStore} from "@/orderbuild.js"; import {invokeCallbacks, prototype} from "@/common.js"; -import datafeed from "./datafeed.js"; export let widget = null export let chart = null export let crosshairPoint = null const subscribeEvents = [ - 'activeChartChanged', 'add_compare', 'chart_load_requested', 'chart_loaded', 'compare_add', 'drawing', - 'drawing_event', 'edit_object_dialog', 'indicators_dialog', 'layout_about_to_be_changed', 'layout_changed', - 'load_study_template', 'mouse_down', 'mouse_up', 'onAutoSaveNeeded', 'onMarkClick', 'onPlusClick', - 'onScreenshotReady', 'onSelectedLineToolChanged', 'onTick', 'onTimescaleMarkClick', 'panes_height_changed', - 'panes_order_changed', 'redo', 'reset_scales', 'series_event', 'series_properties_changed', 'study', 'study_event', - 'study_properties_changed', 'toggle_header', 'toggle_sidebar', 'undo', 'undo_redo_state_changed', + 'toggle_sidebar', 'indicators_dialog', 'toggle_header', 'edit_object_dialog', 'chart_load_requested', + 'chart_loaded', 'mouse_down', 'mouse_up', 'drawing', 'study', 'undo', 'redo', 'undo_redo_state_changed', + 'reset_scales', 'compare_add', 'add_compare', 'load_study_template', 'onTick', 'onAutoSaveNeeded', + 'onScreenshotReady', 'onMarkClick', 'onPlusClick', 'onTimescaleMarkClick', 'onSelectedLineToolChanged', + 'layout_about_to_be_changed', 'layout_changed', 'activeChartChanged', 'series_event', 'study_event', + 'drawing_event', 'study_properties_changed', 'series_properties_changed', 'panes_height_changed', + 'panes_order_changed', ] -function mouseDown() { - // console.log('mouseDown') - // console.log('selection', chart.selection().allSources()) -} - -function crosshairMoved({time,price}) { - console.log('crosshair', time, price) -} export function initWidget(el) { widget = window.tvWidget = new TradingView.widget({ @@ -37,17 +29,19 @@ export function initWidget(el) { // datafeed: datafeed, // use this for ohlc locale: "en", disabled_features: [], - enabled_features: [], + enabled_features: ['saveload_separate_drawings_storage'], drawings_access: {type: 'white', tools: [],}, // show no tools }); + + // debug dump all events + // for( const event of subscribeEvents ) + // widget.subscribe(event, ()=>console.log('event', event, arguments)) + widget.subscribe('drawing_event', handleDrawingEvent) widget.subscribe('onSelectedLineToolChanged', onSelectedLineToolChanged) - widget.onChartReady(initChart) - widget.subscribe('mouse_down', mouseDown) - - // for( const event of subscribeEvents ) - // widget.subscribe(event, ()=>console.log(event, arguments)) + widget.subscribe('mouse_up', mouseUp) + widget.onChartReady(initChart) } @@ -55,41 +49,26 @@ export function initWidget(el) { function initChart() { console.log('init chart') chart = widget.activeChart() - chart.crossHairMoved().subscribe(null, (point)=>{ - crosshairPoint=point - const co = useChartOrderStore() - invokeCallbacks(co.drawingCallbacks, 'onRedraw') - }) + chart.crossHairMoved().subscribe(null, (point)=>setTimeout(()=>handleCrosshairMovement(point),0) ) useChartOrderStore().chartReady = true } // noinspection JSUnusedLocalSymbols export const ShapeCallback = { - onDraw: () => { - }, // start drawing a new shape for the builder - onRedraw: () => { - }, // the mouse moved while in drawing mode - onUndraw: () => { - }, // drawing was canceled by clicking on a different tool - onAddPoint: () => { - }, // the user clicked a point while drawing (that point is added to the points list) - onCreate: (shapeId, points, props) => { - }, // the user has finished creating all the control points. drawing mode is exited and the initial shape is created. - onPoints: (shapeId, points) => { - }, // the control points of an existing shape were changed - onProps: (shapeId, props) => { - }, // the display properties of an existing shape were changed - onMove: (shapeId, points) => { - }, // the entire shape was moved. todo same as onPoints? - onHide: (shapeId, props) => { - }, - onShow: (shapeId, props) => { - }, - onClick: (shapeId) => { - }, // the shape was selected - onDelete: (shapeId) => { - }, + onDraw: () => {}, // start drawing a new shape for the builder + onRedraw: () => {}, // the mouse moved while in drawing mode + onUndraw: () => {}, // drawing was canceled by clicking on a different tool + onAddPoint: () => {}, // the user clicked a point while drawing (that point is added to the points list) + onCreate: (shapeId, points, props) => {}, // the user has finished creating all the control points. drawing mode is exited and the initial shape is created. + onPoints: (shapeId, points) => {}, // the control points of an existing shape were changed + onProps: (shapeId, props) => {}, // the display properties of an existing shape were changed + onMove: (shapeId, points) => {}, // the entire shape was moved without dragging control points + onDrag: (shapeId, points) => {}, // the shape is being dragged: this gets called on every mouse movement update during a drag + onHide: (shapeId, props) => {}, + onShow: (shapeId, props) => {}, + onClick: (shapeId) => {}, // the shape was selected + onDelete: (shapeId) => {}, } @@ -103,6 +82,7 @@ export const VerboseCallback = prototype(ShapeCallback, { onPoints: (shapeId, points)=>{console.log('onPoints')}, onProps: (shapeId, props)=>{console.log('onProps')}, onMove: (shapeId, points)=>{console.log('onMove')}, + onDrag: (shapeId, points) => {console.log('onDrag')}, onHide: (shapeId, props)=>{console.log('onHide')}, onShow: (shapeId, props)=>{console.log('onShow')}, onClick: (shapeId)=>{console.log('onClick')}, @@ -110,23 +90,6 @@ export const VerboseCallback = prototype(ShapeCallback, { }) - -export const BuilderUpdateCallback = prototype(ShapeCallback,{ - onCreate(shapeId, points, props) { - this.builder.shapes[this.tag] = shapeId; - }, - onPoints(shapeId, points) { - this.builder.points[this.tag] = points; - }, - onProps(shapeId, props) { - this.builder.props[this.tag] = props; - }, - onMove(shapeId, points) { - this.builder.points[this.tag] = points; - }, -}) - - export function drawShape(shapeType, ...callbacks) { // puts the chart into a line-drawing mode for a new shape console.log('drawShape', callbacks, shapeType.name, shapeType.code) @@ -155,7 +118,7 @@ export function createShape(shapeType, points, options={}, ...callbacks) { shapeId = chart.createMultipointShape(points, options) if( callbacks.length ) shapeCallbacks[shapeId] = callbacks - console.log('created shape', shapeId) + console.log(`created ${shapeType.name}`, shapeId) const props = chart.getShapeById(shapeId).getProperties() invokeCallbacks(callbacks, 'onCreate', shapeId, points, props) return shapeId @@ -171,33 +134,6 @@ export function cancelDrawing() { } -export function builderUpdater(builder, tag='a') { - return prototype(BuilderUpdateCallback, {builder,tag}) -} - - -export function builderShape(builder, tag, shapeType, ...callbacks) { - const updater = builderUpdater(builder, tag) - console.log('updater', updater) - drawShape(shapeType, updater, ...callbacks) -} - - -export function setPoints( shapeId, points ) { - if( points.time || points.price ) - points = [points] - console.log('setting points', shapeId, points) - let shape - try { - shape = chart.getShapeById(shapeId) - } - catch (e) { - return - } - shape.setPoints(points) -} - - const shapeCallbacks = {} function onSelectedLineToolChanged() { @@ -207,12 +143,65 @@ function onSelectedLineToolChanged() { cancelDrawing(); } +export let dragging = false +function mouseDown() { + // console.log('mouseDown') + dragging = true +} + +function mouseUp() { + // console.log('mouseUp') + dragging = false + draggingShapeIds = [] +} + + +export let draggingShapeIds = [] + + +function handleCrosshairMovement(point) { + crosshairPoint = point + const co = useChartOrderStore() + if (co.drawing) + invokeCallbacks(co.drawingCallbacks, 'onRedraw') + else if (dragging) { + draggingShapeIds = chart.selection().allSources(); + // console.log('dragging selected', draggingShapeIds) + for (const shapeId of draggingShapeIds) { + const shape = chart.getShapeById(shapeId); + const points = shape.getPoints(); + // console.log(`dragging ${shapeId}`, shape, points1, points2, points3) + // invokeCallbacks(shapeCallbacks[shapeId], 'onDrag', shapeId, points) + // console.log('calling onPoints') + invokeCallbacks(shapeCallbacks[shapeId], 'onPoints', shapeId, points) + } + } + else if (draggingShapeIds.length > 0) { + draggingShapeIds = [] + } +} + + +let drawingEventQueue = [] + function handleDrawingEvent(id, event) { - setTimeout(()=>doHandleDrawingEvent(id,event), 0) + // it's important to decouple our actions from the TV thread. we must wait until the TV loop is completed + // before interacting with the chart + if (drawingEventQueue.length===0) + setTimeout(drawingEventWorker,0) + drawingEventQueue.push([id,event]) +} + + +function drawingEventWorker() { + const queue = drawingEventQueue + drawingEventQueue = [] + for (const [id,event] of queue) + doHandleDrawingEvent(id,event) } function doHandleDrawingEvent(id, event) { - console.log('drawing event', arguments) + // console.log('drawing event', arguments) const shape = event === 'remove' ? null : chart.getShapeById(id); if (event === 'create') { const co = useChartOrderStore(); @@ -267,6 +256,6 @@ function doHandleDrawingEvent(id, event) { export function deleteShapeId(id) { if( id in shapeCallbacks ) delete shapeCallbacks[id] - console.log('removing entity', id) + // console.log('removing entity', id) chart.removeEntity(id) } diff --git a/src/charts/shape.js b/src/charts/shape.js index 65b1721..07fdd6f 100644 --- a/src/charts/shape.js +++ b/src/charts/shape.js @@ -1,7 +1,7 @@ // noinspection JSPotentiallyInvalidUsageOfThis import {invokeCallback} from "@/common.js"; -import {chart, createShape, deleteShapeId, drawShape} from "@/charts/chart.js"; +import {chart, createShape, deleteShapeId, dragging, draggingShapeIds, drawShape} from "@/charts/chart.js"; // @@ -69,7 +69,6 @@ class Shape { // programatically create the shape using the current this.points const points = this.pointsFromModel() if( points && points.length ) { - console.log(`create ${this.type.name}`) this.doCreate(points) } } @@ -89,8 +88,12 @@ class Shape { } + tvShape() { + return this.id === null ? null : chart.getShapeById(this.id); + } + + setModel(model) { - console.log('setModel', model, this.id) for( const [k,v] of Object.entries(model)) this.model[k] = v this.setPoints(this.pointsFromModel()); @@ -99,23 +102,32 @@ class Shape { setPoints(points) { - // invokeCallback(this, 'onPoints', points) if (points !== null && points.length) { if (this.id === null) this.doCreate(points) else { - this.pointsLock++ - chart.getShapeById(this.id).setPoints(points) + const s = this.tvShape(); + if (!dragging) { + this.pointsLock++ + s.setPoints(points) + } + else { + // quiet setPoints doesn't disturb tool editing mode + const i = s._pointsConverter.apiPointsToDataSource(points) + // s._model.startChangingLinetool(this._source) + s._model.changeLinePoints(s._source, i) + // s._model.endChangingLinetool(!0) + s._source.createServerPoints() + } } } } setProps(props) { - // invokeCallback(this, 'onProps', props) if(this.id) { this.propsLock++ - chart.getShapeById(this.id).setProperties(props) + this.tvShape().setProperties(props) } } @@ -126,6 +138,11 @@ class Shape { } + beingDragged() { + return draggingShapeIds.indexOf(this.id) !== -1 + } + + delete() { // console.log('shape.delete', this.id) if (this.id === null) return @@ -165,6 +182,7 @@ class Shape { onPoints(points) {} // the control points of an existing shape were changed onProps(props) {} // the display properties of an existing shape were changed onMove(points) {} // the shape was moved by dragging a drawing element not the control point + onDrag(points) {} onHide(props) {} onShow(props) {} onClick() {} // the shape was selected @@ -201,17 +219,14 @@ class ShapeTVCallbacks { } onPoints(shapeId, points) { - console.log(`shapetvcb ${shapeId} onPoints`, points, this.shape.lock) this.shape.points = points if( this.shape.pointsLock ) { this.shape.pointsLock-- return } - setTimeout(()=>{ - this.shape.onPoints(points) - this.shape.pointsToModel() - this.shape.onModel(this.shape.model) - }, 0) + this.shape.onPoints(points) + this.shape.pointsToModel() + this.shape.onModel(this.shape.model) } onProps(shapeId, props) { @@ -220,11 +235,9 @@ class ShapeTVCallbacks { this.shape.propsLock-- return } - setTimeout(()=>{ - this.shape.onProps(props) - this.shape.propsToModel() - this.shape.onModel(this.shape.model) - }, 0) + this.shape.onProps(props) + this.shape.propsToModel() + this.shape.onModel(this.shape.model) } onDraw() { @@ -252,6 +265,11 @@ class ShapeTVCallbacks { invokeCallback(this.shape, 'onMove',points) } + onDrag(_shapeId, points) { + if( this.shape.lock ) return + invokeCallback(this.shape, 'onDrag', points) + } + onHide(_shapeId, props) { if( this.shape.lock ) return invokeCallback(this.shape, 'onHide',props) @@ -283,12 +301,10 @@ export class HLine extends Shape { pointsFromModel() { if (this.model.price === null) return null const time = this.points !== null && this.points.length > 0 ? this.points[0].time : 0 - console.log(`pointsFromModel ${this.id}`, this.model.price) return [{time:time, price:this.model.price}] } pointsToModel() { - console.log(`pointsToModel ${this.id}`, this.points[0].price) this.model.price = this.points[0].price } @@ -298,5 +314,15 @@ export class HLine extends Shape { propsToModel() {this.model.color=this.props.linecolor} + + onDrag(points) { + const s = this.tvShape(); + console.log('shape', s) + console.log('currentMovingPoint', s._source.currentMovingPoint()) + console.log('startMovingPoint', s._source.startMovingPoint()) + console.log('isBeingEdited', s._source.isBeingEdited()) + console.log('state', s._source.state()) + } + } diff --git a/src/components/Vault.vue b/src/components/Vault.vue index dc5c24f..56af3dc 100644 --- a/src/components/Vault.vue +++ b/src/components/Vault.vue @@ -117,7 +117,7 @@ function checkVault() { ensureVault() } -setTimeout(checkVault, 1) +setTimeout(checkVault, 0) diff --git a/src/components/chart/LimitBuilder.vue b/src/components/chart/LimitBuilder.vue index 8e45090..ac21b2c 100644 --- a/src/components/chart/LimitBuilder.vue +++ b/src/components/chart/LimitBuilder.vue @@ -1,4 +1,5 @@