From e5350d7497953c5f26498fb80a53727533519332 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 4 Jan 2025 19:04:41 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=93=9D=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\200\345\217\221\350\277\233\347\250\213\345\233\276.json" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/docs-pg/ProjectGraph\345\274\200\345\217\221\350\277\233\347\250\213\345\233\276.json" "b/docs-pg/ProjectGraph\345\274\200\345\217\221\350\277\233\347\250\213\345\233\276.json" index 5e1d6e64..080200e5 100644 --- "a/docs-pg/ProjectGraph\345\274\200\345\217\221\350\277\233\347\250\213\345\233\276.json" +++ "b/docs-pg/ProjectGraph\345\274\200\345\217\221\350\277\233\347\250\213\345\233\276.json" @@ -1411,7 +1411,7 @@ "text": "解决win10系统无法打开地球仪的问题", "uuid": "7bde46b8-bc89-47fb-a375-8c3b87880015", "details": "", - "color": [0, 0, 0, 0], + "color": [22, 163, 74, 1], "type": "core:text_node" }, { @@ -1420,7 +1420,7 @@ "text": "@较真小猫", "uuid": "284d3026-36da-436e-b656-1e0eb7b45fd3", "details": "", - "color": [0, 0, 0, 0], + "color": [22, 163, 74, 1], "type": "core:text_node" }, { From 265f1a4972c8a8c0da01423ec46dc6d59ae9d09f Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 4 Jan 2025 21:07:44 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=92=84=20=E5=B0=86=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=94=B9=E4=B8=BA=E5=8F=B3=E4=BE=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/controller/concrete/utilsControl.tsx | 8 ++ src/pages/_details_edit_side_panel.tsx | 75 +++++++++++++++++++ src/pages/index.tsx | 2 + 3 files changed, 85 insertions(+) create mode 100644 src/pages/_details_edit_side_panel.tsx diff --git a/src/core/controller/concrete/utilsControl.tsx b/src/core/controller/concrete/utilsControl.tsx index bc8c4d57..f3fa3aef 100644 --- a/src/core/controller/concrete/utilsControl.tsx +++ b/src/core/controller/concrete/utilsControl.tsx @@ -49,10 +49,18 @@ export function editNode(clickedNode: TextNode) { * 一个全局对象,用于编辑节点的钩子函数 */ export const editTextNodeHookGlobal = { + /** + * 编辑节点的钩子函数,用于开始编辑,弹窗触发 + * @param _ + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars hookFunctionStart(_: Entity) { // 在外部将被修改 }, + /** + * 编辑节点的钩子函数,用于结束编辑,弹窗关闭 + * @param _ + */ // eslint-disable-next-line @typescript-eslint/no-unused-vars hookFunctionEnd(_: Entity) { // 在外部将被修改 diff --git a/src/pages/_details_edit_side_panel.tsx b/src/pages/_details_edit_side_panel.tsx new file mode 100644 index 00000000..612e2bd0 --- /dev/null +++ b/src/pages/_details_edit_side_panel.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import Button from "../components/ui/Button"; +import Input from "../components/ui/Input"; +import { editTextNodeHookGlobal } from "../core/controller/concrete/utilsControl"; +import { Controller } from "../core/controller/Controller"; +import { Entity } from "../core/stageObject/StageObject"; +import { cn } from "../utils/cn"; + +export default function DetailsEditSidePanel() { + const [inputCurrentDetails, setInputCurrentDetails] = React.useState(""); + const [isNodeTextEditing, setIsNodeTextEditing] = React.useState(false); + const [clickedNode, setClickedNode] = React.useState(); + const setInputCurrentDetailsHandler = (value: string) => { + setInputCurrentDetails(value); + }; + const handleConfirmDetailsEdit = () => { + setIsNodeTextEditing(false); + if (clickedNode) { + editTextNodeHookGlobal.hookFunctionEnd(clickedNode); + } else { + console.warn("没有点击节点"); + } + }; + const handleCancelDetailsEdit = () => { + setIsNodeTextEditing(false); + Controller.isCameraLocked = false; + if (clickedNode) { + clickedNode.isEditingDetails = false; + } + }; + editTextNodeHookGlobal.hookFunctionStart = (entity: Entity) => { + setInputCurrentDetails(entity.details); + setClickedNode(entity); + setIsNodeTextEditing(true); + }; + editTextNodeHookGlobal.hookFunctionEnd = (entity: Entity) => { + entity.changeDetails(inputCurrentDetails); + Controller.isCameraLocked = false; + entity.isEditingDetails = false; + }; + + return ( + <> + { +
+ {/* 顶部空白 */} +
+ + +
+ + +
+ {/* 底部空白 */} +
+
+ } + + ); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 7adde4d1..a4d3cd66 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -10,6 +10,7 @@ import DetailsEditPanel from "./_details_edit_panel"; import HintText from "./_hint_text"; import SearchingNodePanel from "./_searching_node_panel"; import Toolbar from "./_toolbar"; +import DetailsEditSidePanel from "./_details_edit_side_panel"; export default function Home() { const canvasRef: React.RefObject = useRef(null); @@ -95,6 +96,7 @@ export default function Home() { + {/* TODO: 下面这个写法有点奇怪 rgba值太长了 */}
Date: Sat, 4 Jan 2025 22:34:20 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=9A=B8=20=E5=AE=8C=E5=96=84=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/MouseLocation.tsx | 16 ++++++ src/core/algorithm/numberFunctions.tsx | 19 +++++++ .../concrete/ControllerNodeEdit.tsx | 16 ++++++ .../EntityDetailsButtonRenderer.tsx | 52 +++++++++++++++++++ .../entityRenderer/EntityRenderer.tsx | 3 ++ src/core/stageObject/StageObject.tsx | 13 +++++ 6 files changed, 119 insertions(+) create mode 100644 src/core/render/canvas2d/entityRenderer/EntityDetailsButtonRenderer.tsx diff --git a/src/core/MouseLocation.tsx b/src/core/MouseLocation.tsx index 79118585..7f452e75 100644 --- a/src/core/MouseLocation.tsx +++ b/src/core/MouseLocation.tsx @@ -1,3 +1,5 @@ +import { Vector } from "./dataStruct/Vector"; + export namespace MouseLocation { export let x: number = 0; export let y: number = 0; @@ -6,6 +8,20 @@ export namespace MouseLocation { window.addEventListener("mousemove", (event) => { x = event.clientX; y = event.clientY; + + // 维护一个Vector对象 + vectorObject.x = x; + vectorObject.y = y; }); } + + const vectorObject = new Vector(x, y); + + /** + * 返回的时视野坐标系中的鼠标位置 + * @returns + */ + export function vector(): Vector { + return vectorObject; + } } diff --git a/src/core/algorithm/numberFunctions.tsx b/src/core/algorithm/numberFunctions.tsx index 518820b0..05b93e4a 100644 --- a/src/core/algorithm/numberFunctions.tsx +++ b/src/core/algorithm/numberFunctions.tsx @@ -16,4 +16,23 @@ export namespace NumberFunctions { ): boolean { return Math.abs(number1 - number2) <= tolerance; } + + /** + * 此函数用于放在循环函数中,生成一个周期震荡的数字 + * @param maxValue 震荡的最大值 + * @param minValue 震荡的最小值 + * @param cycleTime 周期时间,单位为秒 + */ + export function sinNumberByTime( + maxValue: number, + minValue: number, + cycleTime: number, + ) { + const t = performance.now() / 1000; + return ( + Math.sin(((t % cycleTime) * (Math.PI * 2)) / cycleTime) * + (maxValue - minValue) + + minValue + ); + } } diff --git a/src/core/controller/concrete/ControllerNodeEdit.tsx b/src/core/controller/concrete/ControllerNodeEdit.tsx index a98fee65..218aad2b 100644 --- a/src/core/controller/concrete/ControllerNodeEdit.tsx +++ b/src/core/controller/concrete/ControllerNodeEdit.tsx @@ -36,6 +36,22 @@ ControllerNodeEdit.mouseDoubleClick = (event: MouseEvent) => { } }; +ControllerNodeEdit.mouseup = (event: MouseEvent) => { + if (event.button !== 0) { + return; + } + + const pressLocation = Renderer.transformView2World( + new Vector(event.clientX, event.clientY), + ); + for (const entity of StageManager.getEntities()) { + if (entity.isMouseInDetailsButton(pressLocation)) { + editNodeDetails(entity); + return; + } + } +}; + ControllerNodeEdit.mousemove = (event: MouseEvent) => { /** * 如果一直显示详细信息,则不显示鼠标悬停效果 diff --git a/src/core/render/canvas2d/entityRenderer/EntityDetailsButtonRenderer.tsx b/src/core/render/canvas2d/entityRenderer/EntityDetailsButtonRenderer.tsx new file mode 100644 index 00000000..37e68914 --- /dev/null +++ b/src/core/render/canvas2d/entityRenderer/EntityDetailsButtonRenderer.tsx @@ -0,0 +1,52 @@ +import { MouseLocation } from "../../../MouseLocation"; +import { NumberFunctions } from "../../../algorithm/numberFunctions"; +import { Vector } from "../../../dataStruct/Vector"; +import { Camera } from "../../../stage/Camera"; +import { Entity } from "../../../stageObject/StageObject"; +import { StageStyleManager } from "../../../stageStyle/StageStyleManager"; +import { RenderUtils } from "../RenderUtils"; +import { Renderer } from "../renderer"; +/** + * 仅仅渲染一个节点右上角的按钮 + */ +export function EntityDetailsButtonRenderer(entity: Entity) { + if (!entity.details) { + return; + } + // RenderUtils.renderRect( + // entity.detailsButtonRectangle().transformWorld2View(), + // StageStyleManager.currentStyle.DetailsDebugTextColor, + // StageStyleManager.currentStyle.DetailsDebugTextColor, + // 2 * Camera.currentScale, + // Renderer.NODE_ROUNDED_RADIUS * Camera.currentScale, + // ); + let isMouseHovering = false; + // 鼠标悬浮在按钮上提示文字 + if ( + entity + .detailsButtonRectangle() + .isPointIn(Renderer.transformView2World(MouseLocation.vector())) + ) { + isMouseHovering = true; + if (!entity.isEditingDetails) + // 鼠标悬浮在这上面 + RenderUtils.renderText( + "点击展开或关闭节点注释详情", + Renderer.transformWorld2View( + entity.detailsButtonRectangle().topCenter.subtract(new Vector(0, 12)), + ), + 12 * Camera.currentScale, + StageStyleManager.currentStyle.DetailsDebugTextColor, + ); + } + RenderUtils.renderText( + entity.isEditingDetails ? "✏️" : "📃", + Renderer.transformWorld2View(entity.detailsButtonRectangle().leftTop), + isMouseHovering ? getFontSizeByTime() : 20 * Camera.currentScale, + ); +} + +function getFontSizeByTime() { + const r = NumberFunctions.sinNumberByTime(19, 21, 0.25); + return r * Camera.currentScale; +} diff --git a/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx b/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx index 08cb2e2d..7e49f672 100644 --- a/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx +++ b/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx @@ -14,6 +14,7 @@ import { StageStyleManager } from "../../../stageStyle/StageStyleManager"; import { ImageNode } from "../../../stageObject/entity/ImageNode"; import { ImageRenderer } from "../ImageRenderer"; import { Entity } from "../../../stageObject/StageObject"; +import { EntityDetailsButtonRenderer } from "./EntityDetailsButtonRenderer"; /** * 处理节点相关的绘制 @@ -41,6 +42,8 @@ export namespace EntityRenderer { } else if (entity instanceof ImageNode) { renderImageNode(entity); } + // details右上角小按钮 + EntityDetailsButtonRenderer(entity); } function renderSection(section: Section) { if (section.isHiddenBySectionCollapse) { diff --git a/src/core/stageObject/StageObject.tsx b/src/core/stageObject/StageObject.tsx index 9c3df826..b9e2eed9 100644 --- a/src/core/stageObject/StageObject.tsx +++ b/src/core/stageObject/StageObject.tsx @@ -1,3 +1,4 @@ +import { Rectangle } from "../dataStruct/shape/Rectangle"; import { Vector } from "../dataStruct/Vector"; import { StageManager } from "../stage/stageManager/StageManager"; import { CollisionBox } from "./collisionBox/collisionBox"; @@ -48,6 +49,18 @@ export abstract class Entity extends StageObject { changeDetails(details: string) { this.details = details; } + + public detailsButtonRectangle(): Rectangle { + const thisRectangle = this.collisionBox.getRectangle(); + return new Rectangle( + thisRectangle.rightTop.subtract(new Vector(20, 20)), + new Vector(20, 20), + ); + } + public isMouseInDetailsButton(mouseWorldLocation: Vector): boolean { + return this.detailsButtonRectangle().isPointIn(mouseWorldLocation); + } + /** * 由于自身位置的移动,递归的更新所有父级Section的位置和大小 */ From 798160dd66cde65543d37f6efc9fd852f5d9753f Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 4 Jan 2025 22:37:46 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=93=9D=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/_details_edit_panel.tsx | 4 ++++ src/pages/index.tsx | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/_details_edit_panel.tsx b/src/pages/_details_edit_panel.tsx index 6ac31433..c368c583 100644 --- a/src/pages/_details_edit_panel.tsx +++ b/src/pages/_details_edit_panel.tsx @@ -7,6 +7,10 @@ import { Renderer } from "../core/render/canvas2d/renderer"; import { Camera } from "../core/stage/Camera"; import { Entity } from "../core/stageObject/StageObject"; +/** + * 2025年1月4日,这个打算被侧边栏取代 ——littlefean + * @returns + */ export default function DetailsEditPanel() { const [inputCurrentDetails, setInputCurrentDetails] = React.useState(""); const [isNodeTextEditing, setIsNodeTextEditing] = React.useState(false); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a4d3cd66..8e9d516c 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -6,7 +6,7 @@ import { Canvas } from "../core/stage/Canvas"; import { Stage } from "../core/stage/Stage"; import { StageStyleManager } from "../core/stageStyle/StageStyleManager"; import { Dialog } from "../utils/dialog"; -import DetailsEditPanel from "./_details_edit_panel"; +// import DetailsEditPanel from "./_details_edit_panel"; import HintText from "./_hint_text"; import SearchingNodePanel from "./_searching_node_panel"; import Toolbar from "./_toolbar"; @@ -95,7 +95,8 @@ export default function Home() { <> - + {/* 这个打算被取代 */} + {/* */} {/* TODO: 下面这个写法有点奇怪 rgba值太长了 */} From a2aeb922d401a8aac16f807ded9f57643ef8ae76 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 4 Jan 2025 23:12:02 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E5=B1=95?= =?UTF-8?q?=E5=BC=80=E5=92=8C=E6=94=B6=E7=BC=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/Input.tsx | 5 +++- src/pages/_details_edit_side_panel.tsx | 32 ++++++++++++++++---------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx index 5a7d8115..b6b67ca3 100644 --- a/src/components/ui/Input.tsx +++ b/src/components/ui/Input.tsx @@ -9,6 +9,7 @@ type InputProps = { multiline?: boolean; onChange?: (value: T extends true ? number : string) => void; // 使用条件类型来定义 onChange 的参数 number?: T; + enableFocusOpacity?: boolean; [key: string]: any; }; @@ -20,6 +21,7 @@ export default function Input({ placeholder = "", number = false as T, multiline = false, + enableFocusOpacity = true, ...props }: React.PropsWithChildren>) { const handleChange = ( @@ -37,7 +39,8 @@ export default function Input({ { + setIsFullScreen(!isFullScreen); + }; return ( <> {
{/* 顶部空白 */}
- +
+ + {isFullScreen ? : } + + + + {/* 取消,关闭 */} + +
-
- - -
{/* 底部空白 */}
From 7b1ad09103d906c95c14602a36df7cdd4d504971 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 4 Jan 2025 23:14:04 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D=E5=A4=9A?= =?UTF-8?q?=E8=A1=8C=E8=BE=93=E5=85=A5=E6=A1=86=E9=80=8F=E6=98=8Ebug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/Input.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx index b6b67ca3..af187b0f 100644 --- a/src/components/ui/Input.tsx +++ b/src/components/ui/Input.tsx @@ -39,8 +39,8 @@ export default function Input({