From 30ac99479fa6c214faed83033465aa88cea89436 Mon Sep 17 00:00:00 2001 From: DiTus Date: Sat, 21 Mar 2026 23:41:05 +0100 Subject: [PATCH] feat: Implement settings panel for vertical and horizontal lines - Add default settings (color, width, style, opacity) for horizontal_line and vertical_line - Enable settings panel to open on double-click for horizontal/vertical lines - Update rendering to support full styling (color, width, style, opacity) with visual selection handles - Show/hide Text tab based on drawing type (trend_line/ray/rectangle only) - Add proper drag handling to move entire horizontal/vertical lines --- index.html | 2 +- js/ui/drawing-tools.js | 155 ++++++++++++++++++++++++++++++++++------- 2 files changed, 130 insertions(+), 27 deletions(-) diff --git a/index.html b/index.html index d888172..adae0ba 100644 --- a/index.html +++ b/index.html @@ -151,7 +151,7 @@
- +
diff --git a/js/ui/drawing-tools.js b/js/ui/drawing-tools.js index 1e74bda..f347206 100644 --- a/js/ui/drawing-tools.js +++ b/js/ui/drawing-tools.js @@ -30,6 +30,18 @@ export class DrawingManager { italic: false, alignVert: 'top', alignHorz: 'left' + }, + horizontal_line: { + color: '#2962ff', + width: 2, + style: 0, + opacity: 100 + }, + vertical_line: { + color: '#2962ff', + width: 2, + style: 0, + opacity: 100 } }; @@ -55,7 +67,7 @@ export class DrawingManager { if (hit) { this.selectedDrawing = hit.drawing; // Only open panel for supported types - if (['trend_line', 'ray', 'rectangle'].includes(hit.drawing.type)) { + if (['trend_line', 'ray', 'rectangle', 'horizontal_line', 'vertical_line'].includes(hit.drawing.type)) { this.toggleSettingsPanel(true); } this.update(); @@ -337,11 +349,11 @@ export class DrawingManager { return; } - const defs = this.defaults.trend_line; - const color = defs.color; + const color = this.defaults.trend_line.color; const p1 = { time: pos.time, price: pos.price }; const p2 = { time: pos.time, price: pos.price }; + const defs = this.defaults.trend_line; if (this.activeTool === 'trend_line' || this.activeTool === 'ray' || this.activeTool === 'rectangle') { this.currentDrawing = { type: this.activeTool, p1, p2, @@ -358,10 +370,26 @@ export class DrawingManager { alignHorz: defs.alignHorz }; } else if (this.activeTool === 'horizontal_line') { - this.drawings.push({ type: 'horizontal_line', price: pos.price, color }); + const defs = this.defaults.horizontal_line; + this.drawings.push({ + type: 'horizontal_line', + price: pos.price, + color: defs.color, + width: defs.width, + style: defs.style, + opacity: defs.opacity + }); this.setTool(null); } else if (this.activeTool === 'vertical_line') { - this.drawings.push({ type: 'vertical_line', time: pos.time, color }); + const defs = this.defaults.vertical_line; + this.drawings.push({ + type: 'vertical_line', + time: pos.time, + color: defs.color, + width: defs.width, + style: defs.style, + opacity: defs.opacity + }); this.setTool(null); } else if (this.activeTool === 'fib_retracement') { this.currentDrawing = { type: 'fib_retracement', p1, p2, color: '#fb8c00' }; @@ -526,10 +554,10 @@ export class DrawingManager { } } else if (d.price !== undefined && d.type === 'horizontal_line') { const dy = this.series.priceToCoordinate(d.price); - if (dy !== null && Math.abs(y - dy) < threshold) return { drawing: d, part: 'price' }; + if (dy !== null && Math.abs(y - dy) < threshold) return { drawing: d, part: 'all' }; } else if (d.time !== undefined && d.type === 'vertical_line') { const dx = this.timeToX(d.time); - if (dx !== null && Math.abs(x - dx) < threshold) return { drawing: d, part: 'time' }; + if (dx !== null && Math.abs(x - dx) < threshold) return { drawing: d, part: 'all' }; } else if (d.time !== undefined && d.price !== undefined) { const dx = this.timeToX(d.time); const dy = this.series.priceToCoordinate(d.price); @@ -752,18 +780,44 @@ export class DrawingManager { } else if (d.type === 'horizontal_line') { const y = series.priceToCoordinate(d.price); if (y !== null) { + ctx.save(); + ctx.strokeStyle = d.opacity !== undefined ? this.hexToRgba(d.color, d.opacity) : d.color; + ctx.lineWidth = d.width || 2; + if (d.style === 1) ctx.setLineDash([10, 5]); + else if (d.style === 2) ctx.setLineDash([2, 2]); + else ctx.setLineDash([]); + if (isSelected) { ctx.shadowBlur = 5; ctx.shadowColor = d.color; } ctx.beginPath(); ctx.moveTo(-1000, y); ctx.lineTo(scope.mediaSize.width + 1000, y); ctx.stroke(); + if (isSelected) { + ctx.setLineDash([]); + ctx.fillStyle = '#ffffff'; + ctx.beginPath(); ctx.arc(0, y, 5, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); + } + ctx.restore(); } } else if (d.type === 'vertical_line') { const x = this.timeToX(d.time); if (x !== null) { + ctx.save(); + ctx.strokeStyle = d.opacity !== undefined ? this.hexToRgba(d.color, d.opacity) : d.color; + ctx.lineWidth = d.width || 2; + if (d.style === 1) ctx.setLineDash([10, 5]); + else if (d.style === 2) ctx.setLineDash([2, 2]); + else ctx.setLineDash([]); + if (isSelected) { ctx.shadowBlur = 5; ctx.shadowColor = d.color; } ctx.beginPath(); ctx.moveTo(x, -1000); ctx.lineTo(x, scope.mediaSize.height + 1000); ctx.stroke(); + if (isSelected) { + ctx.setLineDash([]); + ctx.fillStyle = '#ffffff'; + ctx.beginPath(); ctx.arc(x, 0, 5, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); + } + ctx.restore(); } } else if (d.type === 'fib_retracement') { const x1 = this.timeToX(d.p1.time); @@ -1017,8 +1071,17 @@ export class DrawingManager { window.switchTLTab = (tab) => { this.activeTLTab = tab; + const isLineWithText = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type); + document.getElementById('tlTabStyle').className = tab === 'style' ? 'flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium' : 'flex-1 py-2 text-center text-gray-400 hover:text-white'; - document.getElementById('tlTabText').className = tab === 'text' ? 'flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium' : 'flex-1 py-2 text-center text-gray-400 hover:text-white'; + + const textTab = document.getElementById('tlTabText'); + if (isLineWithText) { + textTab.className = tab === 'text' ? 'flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium' : 'flex-1 py-2 text-center text-gray-400 hover:text-white'; + textTab.style.display = 'block'; + } else { + textTab.style.display = 'none'; + } document.getElementById('tlContentStyle').className = tab === 'style' ? 'block' : 'hidden'; document.getElementById('tlContentText').className = tab === 'text' ? 'block' : 'hidden'; @@ -1032,11 +1095,11 @@ export class DrawingManager { window.setTLThickness = (width) => this.applySettings('width', width); window.setTLStyle = (style) => this.applySettings('style', style); window.toggleTLBold = () => { - const current = this.selectedDrawing ? this.selectedDrawing.bold : this.defaults.trend_line.bold; + const current = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type) ? this.selectedDrawing.bold : this.defaults.trend_line.bold; this.applySettings('bold', !current); }; window.toggleTLItalic = () => { - const current = this.selectedDrawing ? this.selectedDrawing.italic : this.defaults.trend_line.italic; + const current = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type) ? this.selectedDrawing.italic : this.defaults.trend_line.italic; this.applySettings('italic', !current); }; @@ -1129,6 +1192,14 @@ export class DrawingManager { if (show) { panel.classList.remove('hidden'); + const isLineWithText = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type); + if (!isLineWithText) { + this.activeTLTab = 'style'; + document.getElementById('tlTabStyle').className = 'flex-1 py-2 text-center text-blue-500 border-b-2 border-blue-500 font-medium'; + document.getElementById('tlTabText').className = 'flex-1 py-2 text-center text-gray-400 hover:text-white'; + document.getElementById('tlContentStyle').className = 'block'; + document.getElementById('tlContentText').className = 'hidden'; + } this.updateSettingsPanelUI(); } else { panel.classList.add('hidden'); @@ -1136,7 +1207,7 @@ export class DrawingManager { } updateSettingsPanelUI() { - const settings = this.selectedDrawing || this.defaults.trend_line; + const settings = this.selectedDrawing || this.defaults[this.selectedDrawing ? this.selectedDrawing.type : 'trend_line']; document.querySelectorAll('#tlColorGrid .color-swatch').forEach(el => { if (el.style.backgroundColor === settings.color) el.classList.add('active'); @@ -1154,28 +1225,60 @@ export class DrawingManager { btn.setAttribute('data-active', parseInt(btn.dataset.style) === settings.style); }); - document.querySelectorAll('#tlTextColorGrid .color-swatch').forEach(el => { - if (el.style.backgroundColor === settings.textColor) el.classList.add('active'); - else el.classList.remove('active'); - }); - - if (document.getElementById('tlTextColorBtn')) { - document.getElementById('tlTextColorBtn').style.backgroundColor = settings.textColor || settings.color; + const isLineWithText = this.selectedDrawing && ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type); + + const textColorBtn = document.getElementById('tlTextColorBtn'); + const textPicker = document.getElementById('tlTextColorPicker'); + const fontInput = document.getElementById('tlFontSize'); + const textInput = document.getElementById('tlTextInput'); + const alignVert = document.getElementById('tlAlignVert'); + const alignHorz = document.getElementById('tlAlignHorz'); + const boldBtn = document.getElementById('tlBoldBtn'); + const italicBtn = document.getElementById('tlItalicBtn'); + + if (isLineWithText) { + if (textColorBtn && textPicker) { + textColorBtn.style.backgroundColor = settings.textColor || settings.color; + textPicker.classList.remove('hidden'); + } + fontInput.value = settings.fontSize || 14; + textInput.value = settings.text || ''; + boldBtn.setAttribute('data-active', !!settings.bold); + italicBtn.setAttribute('data-active', !!settings.italic); + alignVert.value = settings.alignVert || 'top'; + alignHorz.value = settings.alignHorz || 'left'; + } else { + if (textPicker) textPicker.classList.add('hidden'); } - document.getElementById('tlFontSize').value = settings.fontSize || 14; - document.getElementById('tlTextInput').value = settings.text || ''; - document.getElementById('tlBoldBtn').setAttribute('data-active', !!settings.bold); - document.getElementById('tlItalicBtn').setAttribute('data-active', !!settings.italic); - document.getElementById('tlAlignVert').value = settings.alignVert || 'top'; - document.getElementById('tlAlignHorz').value = settings.alignHorz || 'left'; } applySettings(key, value) { if (this.selectedDrawing) { - this.selectedDrawing[key] = value; + if (key === 'bold' || key === 'italic' || key === 'fontSize' || key === 'text' || key === 'textColor' || key === 'alignVert' || key === 'alignHorz') { + const isLineWithText = ['trend_line', 'ray', 'rectangle'].includes(this.selectedDrawing.type); + if (!isLineWithText) return; + this.selectedDrawing[key] = value; + } else { + this.selectedDrawing[key] = value; + } this.update(); } else { - this.defaults.trend_line[key] = value; + const drawingType = this.selectedDrawing ? this.selectedDrawing.type : 'trend_line'; + if (key === 'bold' || key === 'italic' || key === 'fontSize' || key === 'text' || key === 'textColor' || key === 'alignVert' || key === 'alignHorz') { + if (drawingType === 'trend_line' || drawingType === 'ray' || drawingType === 'rectangle') { + this.defaults.trend_line[key] = value; + } + } else { + if (drawingType === 'trend_line' || drawingType === 'ray' || drawingType === 'rectangle') { + this.defaults.trend_line[key] = value; + } else if (drawingType === 'horizontal_line') { + this.defaults.horizontal_line[key] = value; + } else if (drawingType === 'vertical_line') { + this.defaults.vertical_line[key] = value; + } else { + this.defaults.trend_line[key] = value; + } + } } this.updateSettingsPanelUI(); }