Skip to content
2 changes: 2 additions & 0 deletions app/src/core/Project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { WorldRenderUtils } from "@/core/render/canvas2d/utilsRenderer/Worl
import type { InputElement } from "@/core/render/domElement/inputElement";
import type { AutoLayoutFastTree } from "@/core/service/controlService/autoLayoutEngine/autoLayoutFastTreeMode";
import type { AutoLayout } from "@/core/service/controlService/autoLayoutEngine/mainTick";
import type { ForceDirectedLayout } from "@/core/service/controlService/autoLayoutEngine/forceDirectedLayout";
import type { ControllerUtils } from "@/core/service/controlService/controller/concrete/utilsControl";
import type { Controller } from "@/core/service/controlService/controller/Controller";
import type { KeyboardOnlyEngine } from "@/core/service/controlService/keyboardOnlyEngine/keyboardOnlyEngine";
Expand Down Expand Up @@ -530,6 +531,7 @@ declare module "./Project" {
copyEngine: CopyEngine;
autoLayout: AutoLayout;
autoLayoutFastTree: AutoLayoutFastTree;
forceDirectedLayout: ForceDirectedLayout;
layoutManager: LayoutManager;
autoAlign: AutoAlign;
mouseInteraction: MouseInteraction;
Expand Down
2 changes: 2 additions & 0 deletions app/src/core/loadAllServices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { WorldRenderUtils } from "@/core/render/canvas2d/utilsRenderer/WorldRend
import { InputElement } from "@/core/render/domElement/inputElement";
import { AutoLayoutFastTree } from "@/core/service/controlService/autoLayoutEngine/autoLayoutFastTreeMode";
import { AutoLayout } from "@/core/service/controlService/autoLayoutEngine/mainTick";
import { ForceDirectedLayout } from "@/core/service/controlService/autoLayoutEngine/forceDirectedLayout";
import { ControllerUtils } from "@/core/service/controlService/controller/concrete/utilsControl";
import { Controller } from "@/core/service/controlService/controller/Controller";
import { KeyboardOnlyEngine } from "@/core/service/controlService/keyboardOnlyEngine/keyboardOnlyEngine";
Expand Down Expand Up @@ -115,6 +116,7 @@ export function loadAllServicesBeforeInit(project: Project): void {
project.loadService(CopyEngine);
project.loadService(AutoLayout);
project.loadService(AutoLayoutFastTree);
project.loadService(ForceDirectedLayout);
project.loadService(LayoutManager);
project.loadService(AutoAlign);
project.loadService(MouseInteraction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Project, service } from "@/core/Project";
import { Renderer } from "@/core/render/canvas2d/renderer";
import { Settings } from "@/core/service/Settings";
import { Section } from "@/core/stage/stageObject/entity/Section";
import { getTextSize } from "@/utils/font";
import { getTextSize, textToTextArray } from "@/utils/font";
import { Color, colorInvert, mixColors, Vector } from "@graphif/data-structures";
import { CubicBezierCurve, Rectangle } from "@graphif/shapes";

Expand Down Expand Up @@ -93,6 +93,44 @@ export class SectionRenderer {
}
}

private renderCaption(section: Section) {
const borderWidth = 2 * this.project.camera.currentScale;
const rect = section.rectangle;

this.project.shapeRenderer.renderRect(
new Rectangle(
this.project.renderer.transformWorld2View(rect.location),
rect.size.multiply(this.project.camera.currentScale),
),
Color.Transparent,
this.project.stageStyleManager.currentStyle.StageObjectBorder,
borderWidth,
Renderer.NODE_ROUNDED_RADIUS * this.project.camera.currentScale,
);

if (section.text !== "" && this.project.camera.currentScale > 0.065 && !section.isEditingTitle) {
const padding = 10;
const lineHeight = 1.2;
const limitWidth = rect.size.x - padding * 2;
const lines = textToTextArray(section.text, Renderer.FONT_SIZE, limitWidth);
const captionHeight = lines.length * Renderer.FONT_SIZE * lineHeight + padding * 2;
const captionLocation = new Vector(
rect.location.x + padding,
rect.location.y + rect.size.y - captionHeight + padding,
);
this.project.textRenderer.renderMultiLineText(
section.text,
this.project.renderer.transformWorld2View(captionLocation),
Renderer.FONT_SIZE * this.project.camera.currentScale,
limitWidth * this.project.camera.currentScale,
section.color.a === 1
? colorInvert(section.color)
: colorInvert(this.project.stageStyleManager.currentStyle.Background),
lineHeight,
);
}
}

renderBackgroundColor(section: Section) {
if (Settings.sectionBackgroundFillMode === "titleOnly") {
// 只填充顶部标题条(不透明),标题为空时跳过
Expand Down Expand Up @@ -216,42 +254,16 @@ export class SectionRenderer {
this.project.textRenderer.renderText(section.text, leftTopFontViewLocation, fontSize, textColor);
}

// private getFontSizeBySectionSize(section: Section): Vector {
// // 使用getTextSize获取准确的文本尺寸
// const baseFontSize = 100;
// const measuredSize = getTextSize(section.text, baseFontSize);
// const ratio = measuredSize.x / measuredSize.y;
// const sectionRatio = section.rectangle.size.x / section.rectangle.size.y;

// // 计算最大可用字体高度
// let fontHeight;
// const paddingRatio = 0.9; // 增加边距比例,确保文字不会贴边
// if (sectionRatio < ratio) {
// // 宽度受限
// fontHeight = (section.rectangle.size.x / ratio) * paddingRatio;
// } else {
// // 高度受限
// fontHeight = section.rectangle.size.y * paddingRatio;
// }

// // 确保字体大小合理
// const minFontSize = 8;
// const maxFontSize = Math.max(section.rectangle.size.x, section.rectangle.size.y) * 0.8; // 限制最大字体
// fontHeight = Math.max(minFontSize, Math.min(fontHeight, maxFontSize));

// return new Vector(ratio * fontHeight, fontHeight);
// }

render(section: Section): void {
if (section.isHiddenBySectionCollapse) {
return;
}

if (section.isCollapsed) {
// 折叠状态
this.renderCollapsed(section);
} else if (section.mode === "caption") {
this.renderCaption(section);
} else {
// 非折叠状态
this.renderNoCollapse(section);
}

Expand Down
13 changes: 12 additions & 1 deletion app/src/core/service/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LazyStore } from "@tauri-apps/plugin-store";
import { LazyStore } from "@tauri-apps/plugin-store";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import z from "zod";
Expand Down Expand Up @@ -57,6 +57,14 @@ export const settingsSchema = z.object({
compatibilityMode: z.boolean().default(false),
isEnableEntityCollision: z.boolean().default(false),
isEnableSectionCollision: z.boolean().default(false),
isEnableForceDirected: z.boolean().default(false),
forceDirectedLinkDistance: z.number().min(50).max(500).default(200),
forceDirectedLinkStrength: z.number().min(0.001).max(0.1).multipleOf(0.001).default(0.01),
forceDirectedCollisionStrength: z.number().min(0.01).max(1).multipleOf(0.01).default(0.5),
forceDirectedVelocityDecay: z.number().min(0.1).max(0.99).multipleOf(0.01).default(0.6),
forceDirectedMaxMovePerFrame: z.number().int().min(10).max(200).default(50),
forceDirectedConvergenceThreshold: z.number().min(0.01).max(10).multipleOf(0.01).default(0.5),
forceDirectedMinDistance: z.number().int().min(5).max(100).default(30),
autoNamerTemplate: z.string().default("..."),
autoNamerSectionTemplate: z.string().default("Section_{{i}}"),
autoNamerDetailsTemplate: z.string().default(""),
Expand Down Expand Up @@ -374,6 +382,7 @@ export const settingsSchema = z.object({
{ type: "item", id: "openTextNodeByContentExternal", label: "将内容视为路径并打开", icon: "ExternalLink" },
{ type: "item", id: "folderSection", icon: "Package" },
{ type: "item", id: "toggleSectionLock", label: "锁定/解锁 section 框", icon: "Lock" },
{ type: "item", id: "toggleSectionMode", label: "转为解说框/分组框", icon: "Repeat2" },
{ type: "item", id: "refreshReferenceBlockNode", label: "刷新引用块", icon: "RefreshCcwDot" },
{ type: "item", id: "goToReferenceBlockSource", label: "进入该引用块所在的源头位置", icon: "CornerUpRight" },
{ type: "item", id: "switchEdgeToUndirectedEdge", label: "转换为无向边", icon: "Spline" },
Expand Down Expand Up @@ -447,6 +456,8 @@ export const settingsSchema = z.object({
{ type: "item", id: "setSelectedImageAsBackground", label: "转化为背景图片", icon: "Images" },
{ type: "item", id: "unsetSelectedImageAsBackground", label: "取消背景化", icon: "SquareSquare" },
{ type: "item", id: "saveSelectedImagesToProjectDirectory", label: "另存图片到当前prg所在目录下", icon: "Save" },
{ type: "item", id: "wrapImageInCaptionSection", label: "将图片包裹到说明框中", icon: "ImagePlus" },
{ type: "item", id: "toggleForceDirected", label: "开启/关闭力导向", icon: "Network" },
]),
disabledExtensions: z.array(z.string()).default([]),
extensionSettings: z.record(z.record(z.unknown())).default({}),
Expand Down
16 changes: 16 additions & 0 deletions app/src/core/service/SettingsIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ChevronUp,
CircleDot,
Crosshair,
Cpu,
Database,
Delete,
FileStack,
Expand All @@ -25,9 +26,11 @@ import {
ImageMinus,
ImageUpscale,
Keyboard,
KeyRound,
Languages,
Layers,
Lightbulb,
Link,
MessageSquareText,
LineSquiggle,
ListCheck,
Expand Down Expand Up @@ -85,6 +88,7 @@ import {
MouseRight,
MouseLeft,
LoaderPinwheel,
Network,
Circle,
} from "lucide-react";

Expand Down Expand Up @@ -162,6 +166,14 @@ export const settingsIcons = {
antialiasing: Calculator,
compatibilityMode: Turtle,
isEnableEntityCollision: Ungroup,
isEnableForceDirected: Network,
forceDirectedLinkDistance: MoveHorizontal,
forceDirectedLinkStrength: Spline,
forceDirectedCollisionStrength: Ungroup,
forceDirectedVelocityDecay: TrendingUpDown,
forceDirectedMaxMovePerFrame: Move,
forceDirectedConvergenceThreshold: ScanEye,
forceDirectedMinDistance: Minus,
language: Languages,
showTipsOnUI: AppWindow,
useNativeTitleBar: AppWindowMac,
Expand Down Expand Up @@ -218,4 +230,8 @@ export const settingsIcons = {
enableAutoEdgeWidth: Minus,
showKeyBindHint: Lightbulb,
showEditModeHint: MessageSquareText,
aiApiBaseUrl: Link,
aiApiKey: KeyRound,
aiModel: Cpu,
aiShowTokenCount: Tally4,
};
Loading