From 63bf9cfcb2186c70bd7474dd75b0f22b47adb1d9 Mon Sep 17 00:00:00 2001 From: Denis Gladkiy Date: Fri, 24 Oct 2025 11:48:28 +0600 Subject: [PATCH 01/10] Tools selection merged into a popup. Signed-off-by: Denis Gladkiy --- .../src/components/DrawingBoardToolbar.svelte | 88 +++++++++++-------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/packages/presentation/src/components/DrawingBoardToolbar.svelte b/packages/presentation/src/components/DrawingBoardToolbar.svelte index 93ce265c0a5..c98f9b507a7 100644 --- a/packages/presentation/src/components/DrawingBoardToolbar.svelte +++ b/packages/presentation/src/components/DrawingBoardToolbar.svelte @@ -38,6 +38,18 @@ import { ColorsList, DrawingBoardColoringSetup } from '../drawingColors' import { Analytics } from '@hcengineering/analytics' + class ToolPresentation { + constructor (readonly label: IntlString, readonly icon: IconEdit, readonly tool: DrawingTool) { + } + } + + const tools: ToolPresentation[] = [ + new ToolPresentation(presentation.string.PenTool, IconEdit, 'pen'), + new ToolPresentation(presentation.string.EraserTool, IconEraser, 'erase'), + new ToolPresentation(presentation.string.PanTool, IconMove, 'pan'), + new ToolPresentation(presentation.string.TextTool, IconText, 'text') + ] + interface DrawingBoardToolbarEvents { undo: undefined redo: undefined @@ -58,6 +70,21 @@ } export let tool: DrawingTool = 'pen' + + function evaluateToolPresentation (tool: DrawingTool): ToolPresentation { + const found = tools.find(t => t.tool === tool) + if (found == null) { + return tools[0] + } + return found + } + + let toolPresentation: ToolPresentation = evaluateToolPresentation(tool) + + $: { + toolPresentation = evaluateToolPresentation(tool) + } + export let penColor: ColorMetaNameOrHex export let penWidth: number export let eraserWidth: number @@ -150,6 +177,25 @@ localStorage.setItem(storageKey.color, penColor) } + function showToolSelectionMenu (ev: MouseEvent): void { + const items: Array & { id: DrawingTool }> = [] + for (const toolPresentation of tools) { + if (toolPresentation.tool === 'pan' && !showPanTool) { + continue + } + items.push({ + id: toolPresentation.tool, + label: toolPresentation.label, + icon: toolPresentation.icon + }) + } + showPopup(SelectPopup, { value: items }, eventToHTMLElement(ev), (id: DrawingTool | undefined) => { + if (id != null) { + tool = id + } + }) + } + onMount(() => { try { const savedColors = localStorage.getItem(storageKey.colors) @@ -209,7 +255,6 @@ dispatch('redo') }} /> -
{#if tool === 'pen'} From da856bb007108837c927db25f780df1388daf98d Mon Sep 17 00:00:00 2001 From: Denis Gladkiy Date: Fri, 24 Oct 2025 13:30:38 +0600 Subject: [PATCH 03/10] Rectangle tool. Signed-off-by: Denis Gladkiy --- packages/presentation/lang/cs.json | 9 ++- packages/presentation/lang/de.json | 9 ++- packages/presentation/lang/en.json | 9 ++- packages/presentation/lang/es.json | 9 ++- packages/presentation/lang/fr.json | 9 ++- packages/presentation/lang/it.json | 9 ++- packages/presentation/lang/ja.json | 9 ++- packages/presentation/lang/pt.json | 9 ++- packages/presentation/lang/ru.json | 9 ++- packages/presentation/lang/tr.json | 9 ++- packages/presentation/lang/zh.json | 9 ++- .../src/components/DrawingBoardToolbar.svelte | 6 +- .../src/components/icons/Rectangle.svelte | 23 ++++++ packages/presentation/src/drawing.ts | 73 +++++++++++++++++-- packages/presentation/src/drawingCommand.ts | 9 ++- packages/presentation/src/plugin.ts | 1 + 16 files changed, 158 insertions(+), 53 deletions(-) create mode 100644 packages/presentation/src/components/icons/Rectangle.svelte diff --git a/packages/presentation/lang/cs.json b/packages/presentation/lang/cs.json index 47566f1a0d1..39e8f3c978d 100644 --- a/packages/presentation/lang/cs.json +++ b/packages/presentation/lang/cs.json @@ -49,10 +49,11 @@ "Undo": "Zpět", "Redo": "Znovu", "ClearCanvas": "Vyčistit plátno", - "PenTool": "Nástroj pero", - "EraserTool": "Nástroj guma", - "PanTool": "Nástroj posun", - "TextTool": "Nástroj text", + "PenTool": "Pero", + "EraserTool": "Guma", + "PanTool": "Posun", + "TextTool": "Text", + "RectangleTool": "Obdélník", "PaletteManagementMenu": "Spravovat barevné předvolby" }, "status": { diff --git a/packages/presentation/lang/de.json b/packages/presentation/lang/de.json index 61845272a53..df7883020df 100644 --- a/packages/presentation/lang/de.json +++ b/packages/presentation/lang/de.json @@ -49,10 +49,11 @@ "Undo": "Rückgängig", "Redo": "Wiederholen", "ClearCanvas": "Leinwand löschen", - "PenTool": "Stift-Werkzeug", - "EraserTool": "Radiergummi-Werkzeug", - "PanTool": "Verschieben-Werkzeug", - "TextTool": "Text-Werkzeug", + "PenTool": "Stift", + "EraserTool": "Radiergummi", + "PanTool": "Verschieben", + "TextTool": "Text", + "RectangleTool": "Rechteck", "PaletteManagementMenu": "Farbpresets verwalten" }, "status": { diff --git a/packages/presentation/lang/en.json b/packages/presentation/lang/en.json index 1089fb3329b..5aa31a52050 100644 --- a/packages/presentation/lang/en.json +++ b/packages/presentation/lang/en.json @@ -49,10 +49,11 @@ "Undo": "Undo", "Redo": "Redo", "ClearCanvas": "Clear canvas", - "PenTool": "Pen tool", - "EraserTool": "Eraser tool", - "PanTool": "Pan tool", - "TextTool": "Text tool", + "PenTool": "Pen", + "EraserTool": "Eraser", + "PanTool": "Pan", + "TextTool": "Text", + "RectangleTool": "Rectangle", "PaletteManagementMenu": "Manage color presets" }, "status": { diff --git a/packages/presentation/lang/es.json b/packages/presentation/lang/es.json index c424de8b882..63f52fd9510 100644 --- a/packages/presentation/lang/es.json +++ b/packages/presentation/lang/es.json @@ -49,10 +49,11 @@ "Undo": "Deshacer", "Redo": "Rehacer", "ClearCanvas": "Limpiar lienzo", - "PenTool": "Herramienta lápiz", - "EraserTool": "Herramienta borrador", - "PanTool": "Herramienta mover", - "TextTool": "Herramienta texto", + "PenTool": "Lápiz", + "EraserTool": "Borrador", + "PanTool": "Mover", + "TextTool": "Texto", + "RectangleTool": "Rectángulo", "PaletteManagementMenu": "Gestionar preajustes de color" }, "status": { diff --git a/packages/presentation/lang/fr.json b/packages/presentation/lang/fr.json index 506b4eb5ec6..91a15398184 100644 --- a/packages/presentation/lang/fr.json +++ b/packages/presentation/lang/fr.json @@ -49,10 +49,11 @@ "Undo": "Annuler", "Redo": "Rétablir", "ClearCanvas": "Effacer la toile", - "PenTool": "Outil stylo", - "EraserTool": "Outil gomme", - "PanTool": "Outil déplacement", - "TextTool": "Outil texte", + "PenTool": "Stylo", + "EraserTool": "Gomme", + "PanTool": "Déplacement", + "TextTool": "Texte", + "RectangleTool": "Rectangle", "PaletteManagementMenu": "Gérer les préréglages de couleur" }, "status": { diff --git a/packages/presentation/lang/it.json b/packages/presentation/lang/it.json index 2c58cde668d..3c088b9baa6 100644 --- a/packages/presentation/lang/it.json +++ b/packages/presentation/lang/it.json @@ -49,10 +49,11 @@ "Undo": "Annulla", "Redo": "Ripristina", "ClearCanvas": "Cancella la tela", - "PenTool": "Strumento penna", - "EraserTool": "Strumento gomma", - "PanTool": "Strumento sposta", - "TextTool": "Strumento testo", + "PenTool": "Penna", + "EraserTool": "Gomma", + "PanTool": "Sposta", + "TextTool": "Testo", + "RectangleTool": "Rettangolo", "PaletteManagementMenu": "Gestisci i preset di colore" }, "status": { diff --git a/packages/presentation/lang/ja.json b/packages/presentation/lang/ja.json index a594fa1598f..d24d6d4e98d 100644 --- a/packages/presentation/lang/ja.json +++ b/packages/presentation/lang/ja.json @@ -49,10 +49,11 @@ "Undo": "元に戻す", "Redo": "やり直し", "ClearCanvas": "キャンバスをクリア", - "PenTool": "ペンツール", - "EraserTool": "消しゴムツール", - "PanTool": "パンツール", - "TextTool": "テキストツール", + "PenTool": "ペン", + "EraserTool": "消しゴム", + "PanTool": "パン", + "TextTool": "テキスト", + "RectangleTool": "長方形", "PaletteManagementMenu": "カラープリセットを管理" }, "status": { diff --git a/packages/presentation/lang/pt.json b/packages/presentation/lang/pt.json index 42742573517..59cb737cbb6 100644 --- a/packages/presentation/lang/pt.json +++ b/packages/presentation/lang/pt.json @@ -49,10 +49,11 @@ "Undo": "Desfazer", "Redo": "Refazer", "ClearCanvas": "Limpar tela", - "PenTool": "Ferramenta caneta", - "EraserTool": "Ferramenta borracha", - "PanTool": "Ferramenta mover", - "TextTool": "Ferramenta texto", + "PenTool": "Caneta", + "EraserTool": "Borracha", + "PanTool": "Mover", + "TextTool": "Texto", + "RectangleTool": "Retângulo", "PaletteManagementMenu": "Gerenciar predefinições de cor" }, "status": { diff --git a/packages/presentation/lang/ru.json b/packages/presentation/lang/ru.json index 669638c81cf..f945f38cd12 100644 --- a/packages/presentation/lang/ru.json +++ b/packages/presentation/lang/ru.json @@ -49,10 +49,11 @@ "Undo": "Отменить", "Redo": "Повторить", "ClearCanvas": "Очистить холст", - "PenTool": "Инструмент перо", - "EraserTool": "Инструмент ластик", - "PanTool": "Инструмент перемещения", - "TextTool": "Инструмент текст", + "PenTool": "Перо", + "EraserTool": "Ластик", + "PanTool": "Перемещение", + "TextTool": "Текст", + "RectangleTool": "Прямоугольник", "PaletteManagementMenu": "Управление цветовыми пресетами" }, "status": { diff --git a/packages/presentation/lang/tr.json b/packages/presentation/lang/tr.json index 75b2c50701a..eae612719e4 100644 --- a/packages/presentation/lang/tr.json +++ b/packages/presentation/lang/tr.json @@ -49,10 +49,11 @@ "Undo": "Geri al", "Redo": "Yinele", "ClearCanvas": "Tuvali temizle", - "PenTool": "Kalem aracı", - "EraserTool": "Silgi aracı", - "PanTool": "Kaydırma aracı", - "TextTool": "Metin aracı", + "PenTool": "Kalem", + "EraserTool": "Silgi", + "PanTool": "Kaydırma", + "TextTool": "Metin", + "RectangleTool": "Dikdörtgen", "PaletteManagementMenu": "Renk önayarlarını yönet" }, "status": { diff --git a/packages/presentation/lang/zh.json b/packages/presentation/lang/zh.json index 152ad3f958e..47d8640009f 100644 --- a/packages/presentation/lang/zh.json +++ b/packages/presentation/lang/zh.json @@ -49,10 +49,11 @@ "Undo": "撤销", "Redo": "重做", "ClearCanvas": "清除画布", - "PenTool": "画笔工具", - "EraserTool": "橡皮擦工具", - "PanTool": "移动工具", - "TextTool": "文字工具", + "PenTool": "画笔", + "EraserTool": "橡皮擦", + "PanTool": "移动", + "TextTool": "文字", + "RectangleTool": "矩形", "PaletteManagementMenu": "管理颜色预设" }, "status": { diff --git a/packages/presentation/src/components/DrawingBoardToolbar.svelte b/packages/presentation/src/components/DrawingBoardToolbar.svelte index 16dc9211902..33c72767899 100644 --- a/packages/presentation/src/components/DrawingBoardToolbar.svelte +++ b/packages/presentation/src/components/DrawingBoardToolbar.svelte @@ -30,6 +30,7 @@ import IconEraser from './icons/Eraser.svelte' import IconMove from './icons/Move.svelte' import IconText from './icons/Text.svelte' + import IconRectangle from './icons/Rectangle.svelte' import { DrawingTool } from '../drawing' import presentation from '../plugin' import { ColorMetaName, ColorMetaNameOrHex } from '../drawingUtils' @@ -47,7 +48,8 @@ new ToolPresentation(presentation.string.PenTool, IconEdit, 'pen'), new ToolPresentation(presentation.string.EraserTool, IconEraser, 'erase'), new ToolPresentation(presentation.string.PanTool, IconMove, 'pan'), - new ToolPresentation(presentation.string.TextTool, IconText, 'text') + new ToolPresentation(presentation.string.TextTool, IconText, 'text'), + new ToolPresentation(presentation.string.RectangleTool, IconRectangle, 'shape-rectangle') ] interface DrawingBoardToolbarEvents { @@ -278,7 +280,7 @@
- {#if tool === 'pen'} + {#if tool === 'pen' || tool === 'shape-rectangle' } + + + + + + diff --git a/packages/presentation/src/drawing.ts b/packages/presentation/src/drawing.ts index 8573286654d..aaeafe3f4c0 100644 --- a/packages/presentation/src/drawing.ts +++ b/packages/presentation/src/drawing.ts @@ -31,7 +31,7 @@ import { offsetCanvasPoint, type ColorMetaNameOrHex } from './drawingUtils' -import { type DrawingCmd, type CommandUid, type DrawTextCmd, type DrawLineCmd, makeCommandUid } from './drawingCommand' +import { type DrawingCmd, type CommandUid, type DrawTextCmd, type DrawLineCmd, type DrawRectCmd, makeCommandUid } from './drawingCommand' import { type ColorsList, DrawingBoardColoringSetup, metaColorNameToHex } from './drawingColors' export interface DrawingData { @@ -71,7 +71,7 @@ export interface DrawingProps { toolChanged?: (tool: DrawingTool) => void } -export type DrawingTool = 'pen' | 'erase' | 'pan' | 'text' +export type DrawingTool = 'pen' | 'erase' | 'pan' | 'text' | 'shape-rectangle' const maxTextLength = 500 @@ -129,7 +129,7 @@ class DrawState { } isDrawingTool = (): boolean => { - return this.tool === 'pen' || this.tool === 'erase' + return this.tool === 'pen' || this.tool === 'erase' || this.tool === 'shape-rectangle' } translateCtx = (): void => { @@ -165,6 +165,8 @@ class DrawState { drawCommand = (cmd: DrawingCmd, currentTheme: ThemeVariantType): void => { if (cmd.type === 'text') { this.drawTextCommand(cmd as DrawTextCmd, currentTheme) + } else if (cmd.type === 'rectangle') { + this.drawRectCommand(cmd as DrawRectCmd, currentTheme) } else { this.drawLineCommand(cmd as DrawLineCmd, currentTheme) } @@ -206,6 +208,18 @@ class DrawState { this.ctx.restore() } + drawRectCommand = (cmd: DrawRectCmd, currentTheme: ThemeVariantType): void => { + this.ctx.save() + this.translateCtx() + this.ctx.beginPath() + this.ctx.strokeStyle = metaColorNameToHex(cmd.penColor, currentTheme, this.colors) + this.ctx.lineWidth = cmd.lineWidth + const width = cmd.end.x - cmd.start.x + const height = cmd.end.y - cmd.start.y + this.ctx.strokeRect(cmd.start.x, cmd.start.y, width, height) + this.ctx.restore() + } + isPointInText = (p: CanvasPoint, cmd: DrawTextCmd): boolean => { this.ctx.font = `${cmd.fontSize}px ${cmd.fontFace}` const lines = cmd.text.split('\n').map((l) => l.trim()) @@ -525,7 +539,12 @@ export function drawing ( toolCursor.style.top = `${parentRelativeLocation.y - cursorSize / 2}px` if (draw.on) { - if (Math.hypot(prevPos.x - scaledPoint.x, prevPos.y - scaledPoint.y) >= draw.minLineLength) { + if (draw.tool === 'shape-rectangle') { + requestAnimationFrame(() => { + replayCommands(currentCommands) + drawPreviewRectangle(scaledPoint) + }) + } else if (Math.hypot(prevPos.x - scaledPoint.x, prevPos.y - scaledPoint.y) >= draw.minLineLength) { draw.drawLine(scaledPoint, 'intermediate-point', props.getCurrentTheme()) prevPos = scaledPoint } @@ -557,8 +576,12 @@ export function drawing ( const scaledPoint = rescaleWithCss(p) if (draw.on) { if (!forcePan && draw.isDrawingTool()) { - draw.drawLine(scaledPoint, 'last-point', props.getCurrentTheme()) - storeLineCommand() + if (draw.tool === 'shape-rectangle') { + storeRectCommand(scaledPoint) + } else { + draw.drawLine(scaledPoint, 'last-point', props.getCurrentTheme()) + storeLineCommand() + } } else if (draw.tool === 'pan' || forcePan) { props.panned?.(draw.offset) } else if (draw.tool === 'text') { @@ -899,6 +922,44 @@ export function drawing ( } } + function drawPreviewRectangle (endPoint: MouseScaledPoint): void { + if (draw.points.length === 0) return + + const start = draw.points[0] + const end = draw.mouseToCanvasPoint(endPoint) + + draw.ctx.save() + draw.translateCtx() + draw.ctx.beginPath() + draw.ctx.strokeStyle = metaColorNameToHex(draw.penColor, props.getCurrentTheme(), colorsSetup) + draw.ctx.lineWidth = draw.penWidth + const width = end.x - start.x + const height = end.y - start.y + draw.ctx.strokeRect(start.x, start.y, width, height) + draw.ctx.restore() + } + + function storeRectCommand (endPoint: MouseScaledPoint): void { + if (draw.points.length > 0) { + const start = draw.points[0] + const end = draw.mouseToCanvasPoint(endPoint) + + const minRectSize = 2 + const nonDegenerateRect = Math.abs(end.x - start.x) > minRectSize || Math.abs(end.y - start.y) > 2 + if (nonDegenerateRect) { + const cmd: DrawRectCmd = { + id: makeCommandUid(), + type: 'rectangle', + lineWidth: draw.penWidth, + penColor: draw.penColor, + start, + end + } + props.cmdAdded?.(cmd) + } + } + } + function updateToolCursor (): void { if (readonly) { toolCursor.style.visibility = 'hidden' diff --git a/packages/presentation/src/drawingCommand.ts b/packages/presentation/src/drawingCommand.ts index 7f42fdb63d0..7b10ec1fd6e 100644 --- a/packages/presentation/src/drawingCommand.ts +++ b/packages/presentation/src/drawingCommand.ts @@ -20,7 +20,7 @@ export type CommandUid = string & { readonly __brand: 'CommandUid' } export interface DrawingCmd { id: CommandUid - type: 'line' | 'text' + type: 'line' | 'text' | 'rectangle' } export interface DrawTextCmd extends DrawingCmd { @@ -38,6 +38,13 @@ export interface DrawLineCmd extends DrawingCmd { points: CanvasPoint[] } +export interface DrawRectCmd extends DrawingCmd { + lineWidth: number + penColor: ColorMetaNameOrHex + start: CanvasPoint + end: CanvasPoint +} + export const makeCommandUid = (): CommandUid => { return (crypto?.randomUUID?.() ?? generateId()) as CommandUid } diff --git a/packages/presentation/src/plugin.ts b/packages/presentation/src/plugin.ts index fbe7fd98761..7c55e9f0600 100644 --- a/packages/presentation/src/plugin.ts +++ b/packages/presentation/src/plugin.ts @@ -155,6 +155,7 @@ export default plugin(presentationId, { EraserTool: '' as IntlString, PanTool: '' as IntlString, TextTool: '' as IntlString, + RectangleTool: '' as IntlString, PaletteManagementMenu: '' as IntlString }, extension: { From 355ee42d210ac7cd0610498d9895ab681963c9db Mon Sep 17 00:00:00 2001 From: Denis Gladkiy Date: Fri, 24 Oct 2025 14:18:44 +0600 Subject: [PATCH 04/10] Ellipse tool. Signed-off-by: Denis Gladkiy --- packages/presentation/lang/cs.json | 1 + packages/presentation/lang/de.json | 1 + packages/presentation/lang/en.json | 1 + packages/presentation/lang/es.json | 1 + packages/presentation/lang/fr.json | 1 + packages/presentation/lang/it.json | 1 + packages/presentation/lang/ja.json | 1 + packages/presentation/lang/pt.json | 1 + packages/presentation/lang/ru.json | 1 + packages/presentation/lang/tr.json | 1 + packages/presentation/lang/zh.json | 1 + .../src/components/DrawingBoardToolbar.svelte | 6 +- .../src/components/icons/Ellipse.svelte | 23 ++++ packages/presentation/src/drawing.ts | 112 ++++++++++++++---- packages/presentation/src/drawingCommand.ts | 9 +- packages/presentation/src/plugin.ts | 1 + 16 files changed, 139 insertions(+), 23 deletions(-) create mode 100644 packages/presentation/src/components/icons/Ellipse.svelte diff --git a/packages/presentation/lang/cs.json b/packages/presentation/lang/cs.json index 39e8f3c978d..306d88fc92c 100644 --- a/packages/presentation/lang/cs.json +++ b/packages/presentation/lang/cs.json @@ -54,6 +54,7 @@ "PanTool": "Posun", "TextTool": "Text", "RectangleTool": "Obdélník", + "EllipseTool": "Elipsa", "PaletteManagementMenu": "Spravovat barevné předvolby" }, "status": { diff --git a/packages/presentation/lang/de.json b/packages/presentation/lang/de.json index df7883020df..d9f99dc792f 100644 --- a/packages/presentation/lang/de.json +++ b/packages/presentation/lang/de.json @@ -54,6 +54,7 @@ "PanTool": "Verschieben", "TextTool": "Text", "RectangleTool": "Rechteck", + "EllipseTool": "Ellipse", "PaletteManagementMenu": "Farbpresets verwalten" }, "status": { diff --git a/packages/presentation/lang/en.json b/packages/presentation/lang/en.json index 5aa31a52050..d4aa4f93f9b 100644 --- a/packages/presentation/lang/en.json +++ b/packages/presentation/lang/en.json @@ -54,6 +54,7 @@ "PanTool": "Pan", "TextTool": "Text", "RectangleTool": "Rectangle", + "EllipseTool": "Ellipse", "PaletteManagementMenu": "Manage color presets" }, "status": { diff --git a/packages/presentation/lang/es.json b/packages/presentation/lang/es.json index 63f52fd9510..296721931ad 100644 --- a/packages/presentation/lang/es.json +++ b/packages/presentation/lang/es.json @@ -54,6 +54,7 @@ "PanTool": "Mover", "TextTool": "Texto", "RectangleTool": "Rectángulo", + "EllipseTool": "Elipse", "PaletteManagementMenu": "Gestionar preajustes de color" }, "status": { diff --git a/packages/presentation/lang/fr.json b/packages/presentation/lang/fr.json index 91a15398184..bdc7f1f34cf 100644 --- a/packages/presentation/lang/fr.json +++ b/packages/presentation/lang/fr.json @@ -54,6 +54,7 @@ "PanTool": "Déplacement", "TextTool": "Texte", "RectangleTool": "Rectangle", + "EllipseTool": "Ellipse", "PaletteManagementMenu": "Gérer les préréglages de couleur" }, "status": { diff --git a/packages/presentation/lang/it.json b/packages/presentation/lang/it.json index 3c088b9baa6..dce0258bb6a 100644 --- a/packages/presentation/lang/it.json +++ b/packages/presentation/lang/it.json @@ -54,6 +54,7 @@ "PanTool": "Sposta", "TextTool": "Testo", "RectangleTool": "Rettangolo", + "EllipseTool": "Ellisse", "PaletteManagementMenu": "Gestisci i preset di colore" }, "status": { diff --git a/packages/presentation/lang/ja.json b/packages/presentation/lang/ja.json index d24d6d4e98d..48c0677ca22 100644 --- a/packages/presentation/lang/ja.json +++ b/packages/presentation/lang/ja.json @@ -54,6 +54,7 @@ "PanTool": "パン", "TextTool": "テキスト", "RectangleTool": "長方形", + "EllipseTool": "楕円", "PaletteManagementMenu": "カラープリセットを管理" }, "status": { diff --git a/packages/presentation/lang/pt.json b/packages/presentation/lang/pt.json index 59cb737cbb6..6d9fc45c90e 100644 --- a/packages/presentation/lang/pt.json +++ b/packages/presentation/lang/pt.json @@ -54,6 +54,7 @@ "PanTool": "Mover", "TextTool": "Texto", "RectangleTool": "Retângulo", + "EllipseTool": "Elipse", "PaletteManagementMenu": "Gerenciar predefinições de cor" }, "status": { diff --git a/packages/presentation/lang/ru.json b/packages/presentation/lang/ru.json index f945f38cd12..3b4e654f972 100644 --- a/packages/presentation/lang/ru.json +++ b/packages/presentation/lang/ru.json @@ -54,6 +54,7 @@ "PanTool": "Перемещение", "TextTool": "Текст", "RectangleTool": "Прямоугольник", + "EllipseTool": "Эллипс", "PaletteManagementMenu": "Управление цветовыми пресетами" }, "status": { diff --git a/packages/presentation/lang/tr.json b/packages/presentation/lang/tr.json index eae612719e4..ea06af5d8e9 100644 --- a/packages/presentation/lang/tr.json +++ b/packages/presentation/lang/tr.json @@ -54,6 +54,7 @@ "PanTool": "Kaydırma", "TextTool": "Metin", "RectangleTool": "Dikdörtgen", + "EllipseTool": "Elips", "PaletteManagementMenu": "Renk önayarlarını yönet" }, "status": { diff --git a/packages/presentation/lang/zh.json b/packages/presentation/lang/zh.json index 47d8640009f..cd006095a43 100644 --- a/packages/presentation/lang/zh.json +++ b/packages/presentation/lang/zh.json @@ -54,6 +54,7 @@ "PanTool": "移动", "TextTool": "文字", "RectangleTool": "矩形", + "EllipseTool": "椭圆", "PaletteManagementMenu": "管理颜色预设" }, "status": { diff --git a/packages/presentation/src/components/DrawingBoardToolbar.svelte b/packages/presentation/src/components/DrawingBoardToolbar.svelte index 33c72767899..e3432183bb9 100644 --- a/packages/presentation/src/components/DrawingBoardToolbar.svelte +++ b/packages/presentation/src/components/DrawingBoardToolbar.svelte @@ -31,6 +31,7 @@ import IconMove from './icons/Move.svelte' import IconText from './icons/Text.svelte' import IconRectangle from './icons/Rectangle.svelte' + import IconEllipse from './icons/Ellipse.svelte' import { DrawingTool } from '../drawing' import presentation from '../plugin' import { ColorMetaName, ColorMetaNameOrHex } from '../drawingUtils' @@ -49,7 +50,8 @@ new ToolPresentation(presentation.string.EraserTool, IconEraser, 'erase'), new ToolPresentation(presentation.string.PanTool, IconMove, 'pan'), new ToolPresentation(presentation.string.TextTool, IconText, 'text'), - new ToolPresentation(presentation.string.RectangleTool, IconRectangle, 'shape-rectangle') + new ToolPresentation(presentation.string.RectangleTool, IconRectangle, 'shape-rectangle'), + new ToolPresentation(presentation.string.EllipseTool, IconEllipse, 'shape-ellipse') ] interface DrawingBoardToolbarEvents { @@ -59,6 +61,7 @@ } const dispatch = createEventDispatcher() + const maxColors = 8 const minColors = 0 const defaultColor: ColorMetaName = 'alpha' @@ -263,7 +266,6 @@ showTooltip={{ label: presentation.string.ClearCanvas }} noFocus on:click={() => { - tool = 'pen' dispatch('clear') }} /> diff --git a/packages/presentation/src/components/icons/Ellipse.svelte b/packages/presentation/src/components/icons/Ellipse.svelte new file mode 100644 index 00000000000..51c2af3c343 --- /dev/null +++ b/packages/presentation/src/components/icons/Ellipse.svelte @@ -0,0 +1,23 @@ + + + + + + + diff --git a/packages/presentation/src/drawing.ts b/packages/presentation/src/drawing.ts index aaeafe3f4c0..7f9b66cb9a0 100644 --- a/packages/presentation/src/drawing.ts +++ b/packages/presentation/src/drawing.ts @@ -31,7 +31,7 @@ import { offsetCanvasPoint, type ColorMetaNameOrHex } from './drawingUtils' -import { type DrawingCmd, type CommandUid, type DrawTextCmd, type DrawLineCmd, type DrawRectCmd, makeCommandUid } from './drawingCommand' +import { type DrawingCmd, type CommandUid, type DrawTextCmd, type DrawLineCmd, type DrawRectCmd, type DrawEllipseCmd, makeCommandUid } from './drawingCommand' import { type ColorsList, DrawingBoardColoringSetup, metaColorNameToHex } from './drawingColors' export interface DrawingData { @@ -71,7 +71,7 @@ export interface DrawingProps { toolChanged?: (tool: DrawingTool) => void } -export type DrawingTool = 'pen' | 'erase' | 'pan' | 'text' | 'shape-rectangle' +export type DrawingTool = 'pen' | 'erase' | 'pan' | 'text' | 'shape-rectangle' | 'shape-ellipse' const maxTextLength = 500 @@ -129,7 +129,7 @@ class DrawState { } isDrawingTool = (): boolean => { - return this.tool === 'pen' || this.tool === 'erase' || this.tool === 'shape-rectangle' + return this.tool === 'pen' || this.tool === 'erase' || this.tool === 'shape-rectangle' || this.tool === 'shape-ellipse' } translateCtx = (): void => { @@ -167,6 +167,8 @@ class DrawState { this.drawTextCommand(cmd as DrawTextCmd, currentTheme) } else if (cmd.type === 'rectangle') { this.drawRectCommand(cmd as DrawRectCmd, currentTheme) + } else if (cmd.type === 'ellipse') { + this.drawEllipseCommand(cmd as DrawEllipseCmd, currentTheme) } else { this.drawLineCommand(cmd as DrawLineCmd, currentTheme) } @@ -220,6 +222,21 @@ class DrawState { this.ctx.restore() } + drawEllipseCommand = (cmd: DrawEllipseCmd, currentTheme: ThemeVariantType): void => { + this.ctx.save() + this.translateCtx() + this.ctx.beginPath() + this.ctx.strokeStyle = metaColorNameToHex(cmd.penColor, currentTheme, this.colors) + this.ctx.lineWidth = cmd.lineWidth + const centerX = (cmd.start.x + cmd.end.x) / 2 + const centerY = (cmd.start.y + cmd.end.y) / 2 + const radiusX = Math.abs(cmd.end.x - cmd.start.x) / 2 + const radiusY = Math.abs(cmd.end.y - cmd.start.y) / 2 + this.ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2) + this.ctx.stroke() + this.ctx.restore() + } + isPointInText = (p: CanvasPoint, cmd: DrawTextCmd): boolean => { this.ctx.font = `${cmd.fontSize}px ${cmd.fontFace}` const lines = cmd.text.split('\n').map((l) => l.trim()) @@ -544,6 +561,11 @@ export function drawing ( replayCommands(currentCommands) drawPreviewRectangle(scaledPoint) }) + } else if (draw.tool === 'shape-ellipse') { + requestAnimationFrame(() => { + replayCommands(currentCommands) + drawPreviewEllipse(scaledPoint) + }) } else if (Math.hypot(prevPos.x - scaledPoint.x, prevPos.y - scaledPoint.y) >= draw.minLineLength) { draw.drawLine(scaledPoint, 'intermediate-point', props.getCurrentTheme()) prevPos = scaledPoint @@ -578,6 +600,8 @@ export function drawing ( if (!forcePan && draw.isDrawingTool()) { if (draw.tool === 'shape-rectangle') { storeRectCommand(scaledPoint) + } else if (draw.tool === 'shape-ellipse') { + storeEllipseCommand(scaledPoint) } else { draw.drawLine(scaledPoint, 'last-point', props.getCurrentTheme()) storeLineCommand() @@ -908,22 +932,25 @@ export function drawing ( } function storeLineCommand (): void { - if (draw.points.length > 0) { - const erasing = draw.tool === 'erase' - const cmd: DrawLineCmd = { - id: makeCommandUid(), - type: 'line', - lineWidth: erasing ? draw.eraserWidth : draw.penWidth, - erasing, - penColor: draw.penColor, - points: draw.points - } - props.cmdAdded?.(cmd) + if (draw.points.length === 0) { + return } + const erasing = draw.tool === 'erase' + const cmd: DrawLineCmd = { + id: makeCommandUid(), + type: 'line', + lineWidth: erasing ? draw.eraserWidth : draw.penWidth, + erasing, + penColor: draw.penColor, + points: draw.points + } + props.cmdAdded?.(cmd) } function drawPreviewRectangle (endPoint: MouseScaledPoint): void { - if (draw.points.length === 0) return + if (draw.points.length === 0) { + return + } const start = draw.points[0] const end = draw.mouseToCanvasPoint(endPoint) @@ -940,16 +967,61 @@ export function drawing ( } function storeRectCommand (endPoint: MouseScaledPoint): void { + if (draw.points.length === 0) { + return + } + + const start = draw.points[0] + const end = draw.mouseToCanvasPoint(endPoint) + + const minRectSize = 2 + const nonDegenerateRect = Math.abs(end.x - start.x) > minRectSize || Math.abs(end.y - start.y) > 2 + if (nonDegenerateRect) { + const cmd: DrawRectCmd = { + id: makeCommandUid(), + type: 'rectangle', + lineWidth: draw.penWidth, + penColor: draw.penColor, + start, + end + } + props.cmdAdded?.(cmd) + } + } + + function drawPreviewEllipse (endPoint: MouseScaledPoint): void { + if (draw.points.length === 0) { + return + } + + const start = draw.points[0] + const end = draw.mouseToCanvasPoint(endPoint) + + draw.ctx.save() + draw.translateCtx() + draw.ctx.beginPath() + draw.ctx.strokeStyle = metaColorNameToHex(draw.penColor, props.getCurrentTheme(), colorsSetup) + draw.ctx.lineWidth = draw.penWidth + const centerX = (start.x + end.x) / 2 + const centerY = (start.y + end.y) / 2 + const radiusX = Math.abs(end.x - start.x) / 2 + const radiusY = Math.abs(end.y - start.y) / 2 + draw.ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2) + draw.ctx.stroke() + draw.ctx.restore() + } + + function storeEllipseCommand (endPoint: MouseScaledPoint): void { if (draw.points.length > 0) { const start = draw.points[0] const end = draw.mouseToCanvasPoint(endPoint) - const minRectSize = 2 - const nonDegenerateRect = Math.abs(end.x - start.x) > minRectSize || Math.abs(end.y - start.y) > 2 - if (nonDegenerateRect) { - const cmd: DrawRectCmd = { + const minSize = 2 + const nonDegenerate = Math.abs(end.x - start.x) > minSize || Math.abs(end.y - start.y) > minSize + if (nonDegenerate) { + const cmd: DrawEllipseCmd = { id: makeCommandUid(), - type: 'rectangle', + type: 'ellipse', lineWidth: draw.penWidth, penColor: draw.penColor, start, diff --git a/packages/presentation/src/drawingCommand.ts b/packages/presentation/src/drawingCommand.ts index 7b10ec1fd6e..babef1600f6 100644 --- a/packages/presentation/src/drawingCommand.ts +++ b/packages/presentation/src/drawingCommand.ts @@ -20,7 +20,7 @@ export type CommandUid = string & { readonly __brand: 'CommandUid' } export interface DrawingCmd { id: CommandUid - type: 'line' | 'text' | 'rectangle' + type: 'line' | 'text' | 'rectangle' | 'ellipse' } export interface DrawTextCmd extends DrawingCmd { @@ -45,6 +45,13 @@ export interface DrawRectCmd extends DrawingCmd { end: CanvasPoint } +export interface DrawEllipseCmd extends DrawingCmd { + lineWidth: number + penColor: ColorMetaNameOrHex + start: CanvasPoint + end: CanvasPoint +} + export const makeCommandUid = (): CommandUid => { return (crypto?.randomUUID?.() ?? generateId()) as CommandUid } diff --git a/packages/presentation/src/plugin.ts b/packages/presentation/src/plugin.ts index 7c55e9f0600..86f4b99b84b 100644 --- a/packages/presentation/src/plugin.ts +++ b/packages/presentation/src/plugin.ts @@ -156,6 +156,7 @@ export default plugin(presentationId, { PanTool: '' as IntlString, TextTool: '' as IntlString, RectangleTool: '' as IntlString, + EllipseTool: '' as IntlString, PaletteManagementMenu: '' as IntlString }, extension: { From 66210372dc25c975254b576270220bb6af0d3108 Mon Sep 17 00:00:00 2001 From: Denis Gladkiy Date: Fri, 24 Oct 2025 14:35:50 +0600 Subject: [PATCH 05/10] Line tool. Signed-off-by: Denis Gladkiy --- packages/presentation/lang/cs.json | 1 + packages/presentation/lang/de.json | 1 + packages/presentation/lang/en.json | 1 + packages/presentation/lang/es.json | 1 + packages/presentation/lang/fr.json | 1 + packages/presentation/lang/it.json | 1 + packages/presentation/lang/ja.json | 1 + packages/presentation/lang/pt.json | 1 + packages/presentation/lang/ru.json | 1 + packages/presentation/lang/tr.json | 1 + packages/presentation/lang/zh.json | 1 + .../src/components/DrawingBoardToolbar.svelte | 20 +++--- .../src/components/icons/Eraser.svelte | 15 ++++ .../src/components/icons/Line.svelte | 23 ++++++ .../src/components/icons/Move.svelte | 15 ++++ .../src/components/icons/Text.svelte | 15 ++++ packages/presentation/src/drawing.ts | 70 ++++++++++++++++++- packages/presentation/src/drawingCommand.ts | 9 ++- packages/presentation/src/plugin.ts | 1 + 19 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 packages/presentation/src/components/icons/Line.svelte diff --git a/packages/presentation/lang/cs.json b/packages/presentation/lang/cs.json index 306d88fc92c..83ff9d63001 100644 --- a/packages/presentation/lang/cs.json +++ b/packages/presentation/lang/cs.json @@ -53,6 +53,7 @@ "EraserTool": "Guma", "PanTool": "Posun", "TextTool": "Text", + "LineTool": "Čára", "RectangleTool": "Obdélník", "EllipseTool": "Elipsa", "PaletteManagementMenu": "Spravovat barevné předvolby" diff --git a/packages/presentation/lang/de.json b/packages/presentation/lang/de.json index d9f99dc792f..d9ea8df19fc 100644 --- a/packages/presentation/lang/de.json +++ b/packages/presentation/lang/de.json @@ -53,6 +53,7 @@ "EraserTool": "Radiergummi", "PanTool": "Verschieben", "TextTool": "Text", + "LineTool": "Linie", "RectangleTool": "Rechteck", "EllipseTool": "Ellipse", "PaletteManagementMenu": "Farbpresets verwalten" diff --git a/packages/presentation/lang/en.json b/packages/presentation/lang/en.json index d4aa4f93f9b..5bc374d2afe 100644 --- a/packages/presentation/lang/en.json +++ b/packages/presentation/lang/en.json @@ -53,6 +53,7 @@ "EraserTool": "Eraser", "PanTool": "Pan", "TextTool": "Text", + "LineTool": "Line", "RectangleTool": "Rectangle", "EllipseTool": "Ellipse", "PaletteManagementMenu": "Manage color presets" diff --git a/packages/presentation/lang/es.json b/packages/presentation/lang/es.json index 296721931ad..9381530fae9 100644 --- a/packages/presentation/lang/es.json +++ b/packages/presentation/lang/es.json @@ -53,6 +53,7 @@ "EraserTool": "Borrador", "PanTool": "Mover", "TextTool": "Texto", + "LineTool": "Línea", "RectangleTool": "Rectángulo", "EllipseTool": "Elipse", "PaletteManagementMenu": "Gestionar preajustes de color" diff --git a/packages/presentation/lang/fr.json b/packages/presentation/lang/fr.json index bdc7f1f34cf..ca016bba899 100644 --- a/packages/presentation/lang/fr.json +++ b/packages/presentation/lang/fr.json @@ -53,6 +53,7 @@ "EraserTool": "Gomme", "PanTool": "Déplacement", "TextTool": "Texte", + "LineTool": "Ligne", "RectangleTool": "Rectangle", "EllipseTool": "Ellipse", "PaletteManagementMenu": "Gérer les préréglages de couleur" diff --git a/packages/presentation/lang/it.json b/packages/presentation/lang/it.json index dce0258bb6a..8331b58e276 100644 --- a/packages/presentation/lang/it.json +++ b/packages/presentation/lang/it.json @@ -53,6 +53,7 @@ "EraserTool": "Gomma", "PanTool": "Sposta", "TextTool": "Testo", + "LineTool": "Linea", "RectangleTool": "Rettangolo", "EllipseTool": "Ellisse", "PaletteManagementMenu": "Gestisci i preset di colore" diff --git a/packages/presentation/lang/ja.json b/packages/presentation/lang/ja.json index 48c0677ca22..55de4e0de76 100644 --- a/packages/presentation/lang/ja.json +++ b/packages/presentation/lang/ja.json @@ -53,6 +53,7 @@ "EraserTool": "消しゴム", "PanTool": "パン", "TextTool": "テキスト", + "LineTool": "直線", "RectangleTool": "長方形", "EllipseTool": "楕円", "PaletteManagementMenu": "カラープリセットを管理" diff --git a/packages/presentation/lang/pt.json b/packages/presentation/lang/pt.json index 6d9fc45c90e..3fbb5c8d633 100644 --- a/packages/presentation/lang/pt.json +++ b/packages/presentation/lang/pt.json @@ -53,6 +53,7 @@ "EraserTool": "Borracha", "PanTool": "Mover", "TextTool": "Texto", + "LineTool": "Linha", "RectangleTool": "Retângulo", "EllipseTool": "Elipse", "PaletteManagementMenu": "Gerenciar predefinições de cor" diff --git a/packages/presentation/lang/ru.json b/packages/presentation/lang/ru.json index 3b4e654f972..5bda323ec9d 100644 --- a/packages/presentation/lang/ru.json +++ b/packages/presentation/lang/ru.json @@ -53,6 +53,7 @@ "EraserTool": "Ластик", "PanTool": "Перемещение", "TextTool": "Текст", + "LineTool": "Линия", "RectangleTool": "Прямоугольник", "EllipseTool": "Эллипс", "PaletteManagementMenu": "Управление цветовыми пресетами" diff --git a/packages/presentation/lang/tr.json b/packages/presentation/lang/tr.json index ea06af5d8e9..4fd772345ea 100644 --- a/packages/presentation/lang/tr.json +++ b/packages/presentation/lang/tr.json @@ -53,6 +53,7 @@ "EraserTool": "Silgi", "PanTool": "Kaydırma", "TextTool": "Metin", + "LineTool": "Çizgi", "RectangleTool": "Dikdörtgen", "EllipseTool": "Elips", "PaletteManagementMenu": "Renk önayarlarını yönet" diff --git a/packages/presentation/lang/zh.json b/packages/presentation/lang/zh.json index cd006095a43..6b110070861 100644 --- a/packages/presentation/lang/zh.json +++ b/packages/presentation/lang/zh.json @@ -53,6 +53,7 @@ "EraserTool": "橡皮擦", "PanTool": "移动", "TextTool": "文字", + "LineTool": "直线", "RectangleTool": "矩形", "EllipseTool": "椭圆", "PaletteManagementMenu": "管理颜色预设" diff --git a/packages/presentation/src/components/DrawingBoardToolbar.svelte b/packages/presentation/src/components/DrawingBoardToolbar.svelte index e3432183bb9..6dedf7764d1 100644 --- a/packages/presentation/src/components/DrawingBoardToolbar.svelte +++ b/packages/presentation/src/components/DrawingBoardToolbar.svelte @@ -32,6 +32,7 @@ import IconText from './icons/Text.svelte' import IconRectangle from './icons/Rectangle.svelte' import IconEllipse from './icons/Ellipse.svelte' + import IconLine from './icons/Line.svelte' import { DrawingTool } from '../drawing' import presentation from '../plugin' import { ColorMetaName, ColorMetaNameOrHex } from '../drawingUtils' @@ -50,6 +51,7 @@ new ToolPresentation(presentation.string.EraserTool, IconEraser, 'erase'), new ToolPresentation(presentation.string.PanTool, IconMove, 'pan'), new ToolPresentation(presentation.string.TextTool, IconText, 'text'), + new ToolPresentation(presentation.string.LineTool, IconLine, 'shape-line'), new ToolPresentation(presentation.string.RectangleTool, IconRectangle, 'shape-rectangle'), new ToolPresentation(presentation.string.EllipseTool, IconEllipse, 'shape-ellipse') ] @@ -260,15 +262,6 @@ dispatch('redo') }} /> -
+