Skip to content
Merged
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
1 change: 1 addition & 0 deletions src-tauri/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ pub enum FadePosition {
Top,
Bottom,
None,
Both,
}

/// 이미지 맞춤 설정 (CSS object-fit과 동일)
Expand Down
32 changes: 29 additions & 3 deletions src/renderer/components/main/Modal/content/NoteSetting.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,20 @@ export default function NoteSetting({ onClose, settings, onSave }) {
const tabContentRef = useRef(null);
const [tabContentHeight, setTabContentHeight] = useState(null);
const [disableHeightTransition, setDisableHeightTransition] = useState(true);
const [isAnimating, setIsAnimating] = useState(false);

const updateTabContentHeight = useCallback(() => {
const element = tabContentRef.current;
if (!element) return;
const nextHeight = element.scrollHeight;
const nextHeight = element.offsetHeight;
setTabContentHeight((prev) => (prev === nextHeight ? prev : nextHeight));
}, []);

const fadeOptions = [
{ label: t("noteSetting.auto"), value: "auto" },
{ label: t("noteSetting.top"), value: "top" },
{ label: t("noteSetting.bottom"), value: "bottom" },
{ label: t("noteSetting.both"), value: "both" },
{ label: t("noteSetting.none"), value: "none" },
];

Expand Down Expand Up @@ -150,6 +152,20 @@ export default function NoteSetting({ onClose, settings, onSave }) {
return () => cancelAnimationFrame(rafId);
}, []);

// transitionend 미발화 시 (높이 동일, 트랜지션 비활성 등) 안전 해제
useEffect(() => {
if (!isAnimating) return;
const timer = setTimeout(() => setIsAnimating(false), 150);
return () => clearTimeout(timer);
}, [isAnimating]);

// overflow-hidden 제거 후 BFC 변경으로 인한 높이 차이 보정
useEffect(() => {
if (!isAnimating && !disableHeightTransition) {
requestAnimationFrame(() => updateTabContentHeight());
}
}, [isAnimating, disableHeightTransition, updateTabContentHeight]);

const handleSave = async () => {
const normalized = {
...settings,
Expand Down Expand Up @@ -331,15 +347,25 @@ export default function NoteSetting({ onClose, settings, onSave }) {
className="flex flex-col bg-[#1A191E] rounded-[13px] border-[1px] border-[#2A2A30] p-[20px]"
onClick={(e) => e.stopPropagation()}
>
<TabSwitch activeTab={activeTab} onTabChange={setActiveTab} />
<TabSwitch activeTab={activeTab} onTabChange={(tab) => {
if (tab !== activeTab) {
setIsAnimating(true);
setActiveTab(tab);
}
}} />

<div
className={`overflow-hidden ${
className={`${isAnimating ? "overflow-hidden" : ""} ${
disableHeightTransition ? "" : "transition-[height] duration-100 ease-in-out"
}`}
style={{
height: tabContentHeight !== null ? `${tabContentHeight}px` : "auto",
}}
onTransitionEnd={(e) => {
if (e.propertyName === "height") {
setIsAnimating(false);
}
}}
>
<div ref={tabContentRef}>
{activeTab === NOTE_TAB ? renderNoteTab() : renderAdvancedTab()}
Expand Down
6 changes: 2 additions & 4 deletions src/renderer/components/main/common/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const Dropdown: React.FC<DropdownProps> = ({
const [openUpward, setOpenUpward] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<HTMLDivElement>(null);

// 드롭다운 열릴 때 위치 계산
useEffect(() => {
Expand Down Expand Up @@ -97,9 +96,8 @@ const Dropdown: React.FC<DropdownProps> = ({
</svg>
</button>
{open && (
<div
ref={menuRef}
className={`absolute left-0 flex flex-col justify-center items-center p-[1px] bg-[#2A2A31] border-[1px] border-[#3A3944] rounded-[7px] z-20 overflow-hidden gap-[2px] max-h-[200px] overflow-y-auto ${fullWidth ? "right-0" : ""} ${
<div
className={`absolute left-0 flex flex-col justify-center items-center p-[1px] bg-[#2A2A31] border-[1px] border-[#3A3944] rounded-[7px] z-20 overflow-x-hidden overflow-y-auto gap-[2px] max-h-[200px] ${fullWidth ? "right-0" : ""} ${
openUpward ? "bottom-[25px]" : "top-[25px]"
}`}
>
Expand Down
46 changes: 20 additions & 26 deletions src/renderer/components/overlay/WebGLTracks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
SRGBColorSpace,
} from "three";
import { animationScheduler } from "../../utils/animationScheduler";
import { fadePositionToUniform } from "../../../types/noteSettings";

const MAX_NOTES = 2048; // 씬에서 동시에 렌더링할 수 있는 최대 노트 수

Expand Down Expand Up @@ -235,16 +236,19 @@ const fragmentShader = `
float trackRelativeY = gradientRatio;

float fadePosFlag = uFadePosition;
bool fadeDisabled = fadePosFlag > 2.5;
bool fadeDisabled = abs(fadePosFlag - 3.0) < 0.1;
bool fadeBoth = fadePosFlag > 3.5;
bool invertForFade = false;
if (!fadeDisabled && fadePosFlag < 0.5) {
invertForFade = (vReverse > 0.5);
} else if (!fadeDisabled && abs(fadePosFlag - 1.0) < 0.1) {
invertForFade = false;
} else if (!fadeDisabled) {
invertForFade = true;
if (!fadeDisabled && !fadeBoth) {
if (fadePosFlag < 0.5) {
invertForFade = (vReverse > 0.5);
} else if (abs(fadePosFlag - 1.0) < 0.1) {
invertForFade = false;
} else if (abs(fadePosFlag - 2.0) < 0.1) {
invertForFade = true;
}
}
if (!fadeDisabled && invertForFade) {
if (!fadeDisabled && !fadeBoth && invertForFade) {
trackRelativeY = 1.0 - trackRelativeY;
}

Expand All @@ -267,8 +271,12 @@ const fragmentShader = `
alpha *= smoothAlpha;
}

// 트랙 페이드 영역 적용 (상단 또는 하단)
if (!fadeDisabled && trackRelativeY < fadeRatio) {
// 트랙 페이드 영역 적용
if (fadeBoth) {
float topFade = clamp(trackRelativeY / fadeRatio, 0.0, 1.0);
float bottomFade = clamp((1.0 - trackRelativeY) / fadeRatio, 0.0, 1.0);
alpha *= min(topFade, bottomFade);
} else if (!fadeDisabled && trackRelativeY < fadeRatio) {
alpha *= clamp(trackRelativeY / fadeRatio, 0.0, 1.0);
}

Expand Down Expand Up @@ -350,16 +358,8 @@ export const WebGLTracks = memo(
uDelayEnabled: {
value: noteSettings.delayedNoteEnabled ? 1.0 : 0.0,
},
// fadePosition: 'auto' | 'top' | 'bottom' | 'none' -> 0 | 1 | 2 | 3
uFadePosition: {
value:
noteSettings.fadePosition === "top"
? 1.0
: noteSettings.fadePosition === "bottom"
? 2.0
: noteSettings.fadePosition === "none"
? 3.0
: 0.0,
value: fadePositionToUniform(noteSettings.fadePosition),
},
},
vertexShader,
Expand Down Expand Up @@ -700,13 +700,7 @@ export const WebGLTracks = memo(
materialRef.current.uniforms.uDelayEnabled.value =
noteSettings.delayedNoteEnabled ? 1.0 : 0.0;
materialRef.current.uniforms.uFadePosition.value =
noteSettings.fadePosition === "top"
? 1.0
: noteSettings.fadePosition === "bottom"
? 2.0
: noteSettings.fadePosition === "none"
? 3.0
: 0.0;
fadePositionToUniform(noteSettings.fadePosition);
}
}, [
noteSettings.speed,
Expand Down
43 changes: 19 additions & 24 deletions src/renderer/components/overlay/WebGLTracksOGL.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { memo, useEffect, useRef } from "react";
import { Renderer, Camera, Transform, Program, Geometry, Mesh } from "ogl";
import { animationScheduler } from "../../utils/animationScheduler";
import { fadePositionToUniform } from "../../../types/noteSettings";
import { MAX_NOTES } from "@stores/noteBuffer";
import { isMac } from "@utils/platform";

Expand Down Expand Up @@ -167,16 +168,19 @@ const fragmentShader = `
float trackRelativeY = gradientRatio;
float fadePosFlag = uFadePosition;
bool fadeDisabled = fadePosFlag > 2.5;
bool fadeDisabled = abs(fadePosFlag - 3.0) < 0.1;
bool fadeBoth = fadePosFlag > 3.5;
bool invertForFade = false;
if (!fadeDisabled && fadePosFlag < 0.5) {
invertForFade = (vReverse > 0.5);
} else if (!fadeDisabled && abs(fadePosFlag - 1.0) < 0.1) {
invertForFade = false;
} else if (!fadeDisabled) {
invertForFade = true;
if (!fadeDisabled && !fadeBoth) {
if (fadePosFlag < 0.5) {
invertForFade = (vReverse > 0.5);
} else if (abs(fadePosFlag - 1.0) < 0.1) {
invertForFade = false;
} else if (abs(fadePosFlag - 2.0) < 0.1) {
invertForFade = true;
}
}
if (!fadeDisabled && invertForFade) {
if (!fadeDisabled && !fadeBoth && invertForFade) {
trackRelativeY = 1.0 - trackRelativeY;
}
Expand All @@ -202,7 +206,11 @@ const fragmentShader = `
}
float fadeMask = 1.0;
if (!fadeDisabled && trackRelativeY < fadeRatio) {
if (fadeBoth) {
float topFade = clamp(trackRelativeY / fadeRatio, 0.0, 1.0);
float bottomFade = clamp((1.0 - trackRelativeY) / fadeRatio, 0.0, 1.0);
fadeMask = min(topFade, bottomFade);
} else if (!fadeDisabled && trackRelativeY < fadeRatio) {
fadeMask = clamp(trackRelativeY / fadeRatio, 0.0, 1.0);
}
bodyAlpha *= fadeMask;
Expand Down Expand Up @@ -435,14 +443,7 @@ export const WebGLTracksOGL = memo(
uTrackHeight: { value: noteSettings.trackHeight || 150 },
uReverse: { value: noteSettings.reverse ? 1.0 : 0.0 },
uFadePosition: {
value:
noteSettings.fadePosition === "top"
? 1.0
: noteSettings.fadePosition === "bottom"
? 2.0
: noteSettings.fadePosition === "none"
? 3.0
: 0.0,
value: fadePositionToUniform(noteSettings.fadePosition),
},
},
});
Expand Down Expand Up @@ -657,13 +658,7 @@ export const WebGLTracksOGL = memo(
uniforms.uTrackHeight.value = noteSettings.trackHeight || 150;
uniforms.uReverse.value = noteSettings.reverse ? 1.0 : 0.0;
uniforms.uFadePosition.value =
noteSettings.fadePosition === "top"
? 1.0
: noteSettings.fadePosition === "bottom"
? 2.0
: noteSettings.fadePosition === "none"
? 3.0
: 0.0;
fadePositionToUniform(noteSettings.fadePosition);
}, [noteSettings]);

return (
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"top": "Top",
"bottom": "Bottom",
"none": "None",
"both": "Full",
"reverseEffect": "Reverse Effect",
"save": "Apply",
"cancel": "Cancel"
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"top": "상단",
"bottom": "하단",
"none": "없음",
"both": "전체",
"reverseEffect": "리버스 효과",
"save": "저장",
"cancel": "취소"
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"top": "Сверху",
"bottom": "Снизу",
"none": "Нет",
"both": "Полный",
"reverseEffect": "Обратный эффект",
"save": "Применить",
"cancel": "Отмена"
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/zh-Hant.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"top": "頂部",
"bottom": "底部",
"none": "無",
"both": "全部",
"reverseEffect": "反向鍵雨",
"save": "應用",
"cancel": "取消"
Expand Down
1 change: 1 addition & 0 deletions src/renderer/locales/zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
"top": "顶部",
"bottom": "底部",
"none": "无",
"both": "全部",
"reverseEffect": "反向键雨",
"save": "应用",
"cancel": "取消"
Expand Down
14 changes: 14 additions & 0 deletions src/types/noteSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const fadePositionSchema = z.union([
z.literal("top"),
z.literal("bottom"),
z.literal("none"),
z.literal("both"),
]);

export const noteSettingsSchema = z.object({
Expand Down Expand Up @@ -58,6 +59,19 @@ export const NOTE_SETTINGS_DEFAULTS: NoteSettings = Object.freeze({
keyDisplayDelayMs: NOTE_SETTINGS_CONSTRAINTS.keyDisplayDelayMs.default,
});

/** fadePosition 문자열을 셰이더 uniform 값으로 변환 */
const FADE_POSITION_UNIFORM: Record<string, number> = {
auto: 0.0,
top: 1.0,
bottom: 2.0,
none: 3.0,
both: 4.0,
};

export function fadePositionToUniform(pos: string): number {
return FADE_POSITION_UNIFORM[pos] ?? 0.0;
}

export function normalizeNoteSettings(raw: unknown): NoteSettings {
const parsed = noteSettingsSchema.safeParse({
...NOTE_SETTINGS_DEFAULTS,
Expand Down