Skip to content

Commit

Permalink
Feat/side details (#241)
Browse files Browse the repository at this point in the history
  • Loading branch information
Littlefean authored Jan 4, 2025
2 parents 0ba2e4e + 7b1ad09 commit c6b672e
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 5 deletions.
4 changes: 2 additions & 2 deletions docs-pg/ProjectGraph开发进程图.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
{
Expand All @@ -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"
},
{
Expand Down
5 changes: 4 additions & 1 deletion src/components/ui/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type InputProps<T extends boolean = false> = {
multiline?: boolean;
onChange?: (value: T extends true ? number : string) => void; // 使用条件类型来定义 onChange 的参数
number?: T;
enableFocusOpacity?: boolean;
[key: string]: any;
};

Expand All @@ -20,6 +21,7 @@ export default function Input<T extends boolean = false>({
placeholder = "",
number = false as T,
multiline = false,
enableFocusOpacity = true,
...props
}: React.PropsWithChildren<InputProps<T>>) {
const handleChange = (
Expand All @@ -37,7 +39,8 @@ export default function Input<T extends boolean = false>({
<Box
as={multiline ? "textarea" : "input"}
className={cn(
"px-3 py-2 outline-none hover:opacity-80 focus:opacity-80",
"px-3 py-2 outline-none",
enableFocusOpacity && "hover:opacity-80 focus:opacity-80",
className,
)}
value={value}
Expand Down
16 changes: 16 additions & 0 deletions src/core/MouseLocation.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Vector } from "./dataStruct/Vector";

export namespace MouseLocation {
export let x: number = 0;
export let y: number = 0;
Expand All @@ -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;
}
}
19 changes: 19 additions & 0 deletions src/core/algorithm/numberFunctions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
}
}
16 changes: 16 additions & 0 deletions src/core/controller/concrete/ControllerNodeEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
/**
* 如果一直显示详细信息,则不显示鼠标悬停效果
Expand Down
8 changes: 8 additions & 0 deletions src/core/controller/concrete/utilsControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
// 在外部将被修改
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
3 changes: 3 additions & 0 deletions src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
* 处理节点相关的绘制
Expand Down Expand Up @@ -41,6 +42,8 @@ export namespace EntityRenderer {
} else if (entity instanceof ImageNode) {
renderImageNode(entity);
}
// details右上角小按钮
EntityDetailsButtonRenderer(entity);
}
function renderSection(section: Section) {
if (section.isHiddenBySectionCollapse) {
Expand Down
13 changes: 13 additions & 0 deletions src/core/stageObject/StageObject.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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的位置和大小
*/
Expand Down
4 changes: 4 additions & 0 deletions src/pages/_details_edit_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
83 changes: 83 additions & 0 deletions src/pages/_details_edit_side_panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
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";
import IconButton from "../components/ui/IconButton";
import { ArrowLeftFromLine, ArrowRightFromLine } from "lucide-react";

export default function DetailsEditSidePanel() {
const [inputCurrentDetails, setInputCurrentDetails] = React.useState("");
const [isNodeTextEditing, setIsNodeTextEditing] = React.useState(false);
const [clickedNode, setClickedNode] = React.useState<Entity>();
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;
};

const [isFullScreen, setIsFullScreen] = React.useState(false);
// 将整个面板侧向伸展成全屏或缩小到原来的尺寸
const switchPanelSize = () => {
setIsFullScreen(!isFullScreen);
};
return (
<>
{
<div
className={cn(
"fixed top-0 z-50 flex h-full flex-col transition-all",
isFullScreen ? "right-0 w-full" : "-right-96 w-96",
isNodeTextEditing && "right-0",
)}
>
{/* 顶部空白 */}
<div className="h-16" />
<div className="flex gap-2">
<IconButton onClick={switchPanelSize}>
{isFullScreen ? <ArrowRightFromLine /> : <ArrowLeftFromLine />}
</IconButton>
<Button className="flex-1">编辑模式</Button>
<Button onClick={handleConfirmDetailsEdit}>确认修改</Button>
{/* 取消,关闭 */}
<Button onClick={handleCancelDetailsEdit}>取消修改</Button>
</div>
<Input
multiline
onChange={setInputCurrentDetailsHandler}
value={inputCurrentDetails}
className="my-2 flex-1"
enableFocusOpacity={false}
/>
{/* 底部空白 */}
<div className="h-8" />
</div>
}
</>
);
}
7 changes: 5 additions & 2 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ 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";
import DetailsEditSidePanel from "./_details_edit_side_panel";

export default function Home() {
const canvasRef: React.RefObject<HTMLCanvasElement | null> = useRef(null);
Expand Down Expand Up @@ -94,7 +95,9 @@ export default function Home() {
<>
<Toolbar />
<SearchingNodePanel />
<DetailsEditPanel />
{/* 这个打算被取代 */}
{/* <DetailsEditPanel /> */}
<DetailsEditSidePanel />
<HintText />
{/* TODO: 下面这个写法有点奇怪 rgba值太长了 */}
<div
Expand Down

0 comments on commit c6b672e

Please sign in to comment.