Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 60 additions & 53 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default function App() {
const [isClassroomMode, setIsClassroomMode] = useAtom(isClassroomModeAtom);
const [showQuickSettingsToolbar, setShowQuickSettingsToolbar] = useState(Settings.showQuickSettingsToolbar);
const [windowBackgroundAlpha, setWindowBackgroundAlpha] = useState(Settings.windowBackgroundAlpha);
const [uiScalePercent, setUiScalePercent] = useState(Settings.uiScalePercent);

const contextMenuTriggerRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -112,6 +113,12 @@ export default function App() {
setWindowBackgroundAlpha(value);
});

// 初始化 UI 缩放(只缩放 UI 组件层,不缩放 Canvas 画布)
setUiScalePercent(Settings.uiScalePercent);
const unwatchUiScale = Settings.watch("uiScalePercent", (value) => {
setUiScalePercent(value);
});

// 恢复窗口位置大小
restoreStateCurrent(StateFlags.SIZE | StateFlags.POSITION | StateFlags.MAXIMIZED);

Expand Down Expand Up @@ -158,6 +165,7 @@ export default function App() {
// 清理全局快捷键资源
unwatchShowQuickSettingsToolbar();
unwatchWindowBackgroundAlpha();
unwatchUiScale();
globalShortcutManager.dispose();
};
}, []);
Expand Down Expand Up @@ -324,6 +332,9 @@ export default function App() {
[closeTab],
);

const zoomStyle = uiScalePercent !== 100 ? { zoom: `${uiScalePercent / 100}` } as React.CSSProperties : undefined;
const zoomClass = "relative z-10 flex h-full w-full flex-col gap-2 pointer-events-none [&>*]:pointer-events-auto";

return (
<>
{/* 这是一个底层的 div,用于在拖拽改变窗口大小时填充背景,防止窗口出现透明闪烁 */}
Expand All @@ -332,36 +343,7 @@ export default function App() {
className="relative flex h-full w-full flex-col overflow-clip rounded-lg sm:gap-2 sm:p-2"
onContextMenu={(e) => e.preventDefault()}
>
{/* 菜单 | 标签页 | ...移动窗口区域... | 窗口控制按钮 */}
<div
className={cn(
"z-10 flex h-4 items-center transition-all hover:opacity-100 sm:h-9 sm:gap-2",
isClassroomMode && "opacity-0",
)}
>
<div
className="hover:bg-primary/25 h-full min-w-6 cursor-grab transition-colors active:cursor-grabbing sm:hidden"
data-tauri-drag-region
/>
{isMac && <WindowButtons />}
<GlobalMenu />
<div
className="hover:bg-primary/25 h-full flex-1 cursor-grab transition-colors hover:*:opacity-100 active:cursor-grabbing sm:rounded-sm sm:hover:border"
data-tauri-drag-region
/>
<ThemeModeSwitch />
{!isMac && <WindowButtons />}
</div>

<ProjectTabs
tabs={tabs}
activeTab={activeTab}
onTabClick={handleTabClick}
onTabClose={handleTabClose}
isClassroomMode={isClassroomMode}
/>

{/* content */}
{/* Canvas content - NOT zoomed */}
{tabs.map((p) => (
<div
key={p instanceof Project ? p.uri.toString() : p.constructor.name}
Expand All @@ -371,36 +353,61 @@ export default function App() {
</div>
))}

{/* 没有项目处于打开状态时,显示欢迎页面 */}
{tabs.length === 0 && (
<div className="absolute inset-0 overflow-hidden *:h-full *:w-full">
<Welcome />
{/* Zoomed UI layer - 只缩放主窗口的 UI 组件,不缩放 Canvas 画布 */}
<div style={zoomStyle} className={zoomClass}>
{/* 菜单 | 标签页 | ...移动窗口区域... | 窗口控制按钮 */}
<div
className={cn(
"z-10 flex h-4 items-center transition-all hover:opacity-100 sm:h-9 sm:gap-2",
isClassroomMode && "opacity-0",
)}
>
<div
className="hover:bg-primary/25 h-full min-w-6 cursor-grab transition-colors active:cursor-grabbing sm:hidden"
data-tauri-drag-region
/>
{isMac && <WindowButtons />}
<GlobalMenu />
<div
className="hover:bg-primary/25 h-full flex-1 cursor-grab transition-colors hover:*:opacity-100 active:cursor-grabbing sm:rounded-sm sm:hover:border"
data-tauri-drag-region
/>
<ThemeModeSwitch />
{!isMac && <WindowButtons />}
</div>
)}

{/* 右键菜单 */}
<ContextMenu>
<ContextMenuTrigger>
<div ref={contextMenuTriggerRef} />
</ContextMenuTrigger>
<MyContextMenuContent />
</ContextMenu>

{/* ======= */}
{/* <ErrorHandler /> */}
<ProjectTabs
tabs={tabs}
activeTab={activeTab}
onTabClick={handleTabClick}
onTabClose={handleTabClose}
isClassroomMode={isClassroomMode}
/>

{/* <PGCanvas /> */}
{/* 没有项目处于打开状态时,显示欢迎页面 */}
{tabs.length === 0 && (
<div className="absolute inset-0 overflow-hidden *:h-full *:w-full">
<Welcome />
</div>
)}

{/* <FloatingOutlet />
<RenderSubWindows /> */}
{/* 右键菜单 */}
<ContextMenu>
<ContextMenuTrigger>
<div ref={contextMenuTriggerRef} />
</ContextMenuTrigger>
<MyContextMenuContent />
</ContextMenu>

<RenderSubWindows />
{/* 底部工具栏 */}
{activeTab && <ToolbarContent />}

{/* 底部工具栏 */}
{activeTab && <ToolbarContent />}
{/* 右侧工具栏 */}
{activeTab && showQuickSettingsToolbar && <RightToolbar />}
</div>

{/* 右侧工具栏 */}
{activeTab && showQuickSettingsToolbar && <RightToolbar />}
{/* NOT zoomed - 使用固定/全屏定位的组件,缩放会破坏它们的位置计算 */}
<RenderSubWindows />

{/* 右上角关闭的触发角 */}
{isWindows && (
Expand Down
13 changes: 9 additions & 4 deletions app/src/core/service/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,22 @@ export const settingsSchema = z.object({
enableDragAutoAlign: z.boolean().default(false),
reverseTreeMoveMode: z.boolean().default(false),
mouseWheelMode: z
.union([z.literal("zoom"), z.literal("move"), z.literal("moveX"), z.literal("none")])
.union([z.literal("zoom"), z.literal("move"), z.literal("moveX"), z.literal("none"), z.literal("zoomUI")])
.default("zoom"),
mouseWheelModeReverse: z.boolean().default(false),
mouseWheelWithShiftMode: z
.union([z.literal("zoom"), z.literal("move"), z.literal("moveX"), z.literal("none")])
.union([z.literal("zoom"), z.literal("move"), z.literal("moveX"), z.literal("none"), z.literal("zoomUI")])
.default("moveX"),
mouseWheelWithShiftModeReverse: z.boolean().default(false),
mouseWheelWithCtrlMode: z
.union([z.literal("zoom"), z.literal("move"), z.literal("moveX"), z.literal("none")])
.union([z.literal("zoom"), z.literal("move"), z.literal("moveX"), z.literal("none"), z.literal("zoomUI")])
.default("none"),
mouseWheelWithCtrlModeReverse: z.boolean().default(false),
mouseWheelWithAltMode: z
.union([z.literal("zoom"), z.literal("move"), z.literal("moveX"), z.literal("none")])
.union([z.literal("zoom"), z.literal("move"), z.literal("moveX"), z.literal("none"), z.literal("zoomUI")])
.default("none"),
mouseWheelWithAltModeReverse: z.boolean().default(false),
uiScalePercent: z.number().min(25).max(200).default(100),
doubleClickMiddleMouseButton: z.union([z.literal("adjustCamera"), z.literal("none")]).default("adjustCamera"),
doubleClickMiddleMouseButtonOnEntity: z.union([z.literal("openUrl"), z.literal("none")]).default("openUrl"),
mouseSideWheelMode: z
Expand Down
1 change: 1 addition & 0 deletions app/src/core/service/SettingsIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const settingsIcons = {
mouseLeftMode: MouseLeft,
enableDragAutoAlign: AlignStartVertical,
reverseTreeMoveMode: Move,
uiScalePercent: Scaling,
mouseWheelMode: LoaderPinwheel,
mouseWheelWithShiftMode: LoaderPinwheel,
mouseWheelWithCtrlMode: LoaderPinwheel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ export class ControllerCameraClass extends ControllerClass {
* @param event
* @returns
*/
private zoomUIMethod(event: WheelEvent, overrideDeltaY?: number) {
const deltaY = overrideDeltaY ?? event.deltaY;
const step = 25;
const current = Settings.uiScalePercent;
if (deltaY > 0) {
Settings.uiScalePercent = Math.max(25, current - step);
} else if (deltaY < 0) {
Settings.uiScalePercent = Math.min(200, current + step);
}
}

private mousewheelFunction(event: WheelEvent) {
// 获取触发滚轮的鼠标位置
const mouseLocation = new Vector(event.clientX, event.clientY);
Expand All @@ -261,48 +272,60 @@ export class ControllerCameraClass extends ControllerClass {
this.project.camera.targetLocationByScale = worldLocation;

if (this.project.controller.pressingKeySet.has("shift")) {
const overrideDeltaY = Settings.mouseWheelWithShiftModeReverse ? -event.deltaY : undefined;
if (Settings.mouseWheelWithShiftMode === "zoom") {
this.zoomCameraByMouseWheel(event);
this.zoomCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithShiftMode === "move") {
this.moveYCameraByMouseWheel(event);
this.moveYCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithShiftMode === "moveX") {
this.moveXCameraByMouseWheel(event);
this.moveXCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithShiftMode === "none") {
return;
} else if (Settings.mouseWheelWithShiftMode === "zoomUI") {
this.zoomUIMethod(event, overrideDeltaY);
}
} else if (
this.project.controller.pressingKeySet.has("control") ||
this.project.controller.pressingKeySet.has("meta")
) {
// 不要在节点上滚动
const overrideDeltaY = Settings.mouseWheelWithCtrlModeReverse ? -event.deltaY : undefined;
if (Settings.mouseWheelWithCtrlMode === "zoom") {
this.zoomCameraByMouseWheel(event);
this.zoomCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithCtrlMode === "move") {
this.moveYCameraByMouseWheel(event);
this.moveYCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithCtrlMode === "moveX") {
this.moveXCameraByMouseWheel(event);
this.moveXCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithCtrlMode === "none") {
return;
} else if (Settings.mouseWheelWithCtrlMode === "zoomUI") {
this.zoomUIMethod(event, overrideDeltaY);
}
} else if (this.project.controller.pressingKeySet.has("alt")) {
const overrideDeltaY = Settings.mouseWheelWithAltModeReverse ? -event.deltaY : undefined;
if (Settings.mouseWheelWithAltMode === "zoom") {
this.zoomCameraByMouseWheel(event);
this.zoomCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithAltMode === "move") {
this.moveYCameraByMouseWheel(event);
this.moveYCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithAltMode === "moveX") {
this.moveXCameraByMouseWheel(event);
this.moveXCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelWithAltMode === "none") {
return;
} else if (Settings.mouseWheelWithAltMode === "zoomUI") {
this.zoomUIMethod(event, overrideDeltaY);
}
} else {
const overrideDeltaY = Settings.mouseWheelModeReverse ? -event.deltaY : undefined;
if (Settings.mouseWheelMode === "zoom") {
this.zoomCameraByMouseWheel(event);
this.zoomCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelMode === "move") {
this.moveYCameraByMouseWheel(event);
this.moveYCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelMode === "moveX") {
this.moveXCameraByMouseWheel(event);
this.moveXCameraByMouseWheel(event, overrideDeltaY);
} else if (Settings.mouseWheelMode === "none") {
return;
} else if (Settings.mouseWheelMode === "zoomUI") {
this.zoomUIMethod(event, overrideDeltaY);
}
}

Expand Down Expand Up @@ -366,44 +389,45 @@ export class ControllerCameraClass extends ControllerClass {
this.project.camera.location = this.project.camera.location.add(diffLocation);
}

private zoomCameraByMouseWheel(event: WheelEvent) {
private zoomCameraByMouseWheel(event: WheelEvent, overrideDeltaY?: number) {
const deltaY = overrideDeltaY ?? event.deltaY;
if (isMac) {
// mac电脑滚动一格滚轮会触发很多次事件。这个列表里是每个事件的deltaY
// [7, 7, 7, 7, 6, 7, 7, 6, 5, 5, 4, 4, 3, 3, 3, 2, 2, 1, 1, 1, 1, 1]
if (Settings.macMouseWheelIsSmoothed) {
// 盲猜是开了平滑滚动了
const deltaY = event.deltaY;
this.project.camera.targetScale *= 1 + deltaY / 500;
} else {
// 如果没有开平滑滚动
if (event.deltaY > 0) {
if (deltaY > 0) {
this.project.camera.targetScale *= 0.8;
this.project.effects.addEffect(MouseTipFeedbackEffect.default("shrink"));
} else if (event.deltaY < 0) {
} else if (deltaY < 0) {
this.project.camera.targetScale *= 1.2;
this.project.effects.addEffect(MouseTipFeedbackEffect.default("expand"));
}
}
} else {
if (event.deltaY > 0) {
if (deltaY > 0) {
this.project.camera.targetScale *= 0.8;
this.project.effects.addEffect(MouseTipFeedbackEffect.default("shrink"));
} else if (event.deltaY < 0) {
} else if (deltaY < 0) {
this.project.camera.targetScale *= 1.2;
this.project.effects.addEffect(MouseTipFeedbackEffect.default("expand"));
}
}
}

private moveYCameraByMouseWheel(event: WheelEvent) {
private moveYCameraByMouseWheel(event: WheelEvent, overrideDeltaY?: number) {
const deltaY = overrideDeltaY ?? event.deltaY;
this.project.camera.bombMove(
this.project.camera.location.add(
new Vector(0, (Settings.moveAmplitude * event.deltaY * 0.5) / this.project.camera.currentScale),
new Vector(0, (Settings.moveAmplitude * deltaY * 0.5) / this.project.camera.currentScale),
),
);
if (event.deltaY > 0) {
if (deltaY > 0) {
this.project.effects.addEffect(MouseTipFeedbackEffect.default("moveDown"));
} else if (event.deltaY < 0) {
} else if (deltaY < 0) {
this.project.effects.addEffect(MouseTipFeedbackEffect.default("moveUp"));
}
}
Expand Down Expand Up @@ -479,15 +503,16 @@ export class ControllerCameraClass extends ControllerClass {
}
}

private moveXCameraByMouseWheel(event: WheelEvent) {
private moveXCameraByMouseWheel(event: WheelEvent, overrideDeltaY?: number) {
const deltaY = overrideDeltaY ?? event.deltaY;
this.project.camera.bombMove(
this.project.camera.location.add(
new Vector((Settings.moveAmplitude * event.deltaY * 0.5) / this.project.camera.currentScale, 0),
new Vector((Settings.moveAmplitude * deltaY * 0.5) / this.project.camera.currentScale, 0),
),
);
if (event.deltaY > 0) {
if (deltaY > 0) {
this.project.effects.addEffect(MouseTipFeedbackEffect.default("moveRight"));
} else if (event.deltaY < 0) {
} else if (deltaY < 0) {
this.project.effects.addEffect(MouseTipFeedbackEffect.default("moveLeft"));
}
}
Expand Down
Loading