feat: add configurable operator appearance and layout

This commit is contained in:
2026-07-23 11:14:10 +09:00
parent cf117de144
commit f43483533a
34 changed files with 5942 additions and 740 deletions

View File

@@ -14,20 +14,20 @@ public sealed partial class MainWindow
private LegacyOperatorSettingsMessageKind _operatorSettingsMessageKind =
LegacyOperatorSettingsMessageKind.Neutral;
private LegacyOperatorFolderSnapshot _designFolderSnapshot = new(
"앱 기본 디자인 폴더",
"앱 기본",
IsCustom: false,
IsValid: false,
"확인 중");
private LegacyOperatorFolderSnapshot _resourceFolderSnapshot = new(
"앱 기본 설정 폴더",
"앱 기본",
IsCustom: false,
IsValid: false,
"확인 중");
private LegacyOperatorFolderSnapshot _backgroundFolderSnapshot = new(
"장면 폴더 기준 자동 탐색",
"자동",
IsCustom: false,
IsValid: true,
"필요할 때 선택");
"사용 가능");
private void InitializeOperatorSettingsStatus(string? warningMessage)
{
@@ -58,9 +58,17 @@ public sealed partial class MainWindow
{
NavigationExpanded = navigation.Expanded
},
navigation.Expanded
? "왼쪽 메뉴를 펼쳐서 시작하도록 저장했습니다."
: "왼쪽 메뉴를 접어서 시작하도록 저장했습니다.");
"저장됨");
break;
case LegacySetOperatorAppearanceIntent appearance:
SaveOperatorSettings(
_operatorSettings with
{
ColorTheme = appearance.ColorTheme,
ViewMode = appearance.ViewMode,
StartWorkspace = appearance.StartWorkspace
},
"저장됨");
break;
default:
SetOperatorSettingsMessage(
@@ -90,9 +98,6 @@ public sealed partial class MainWindow
var selected = await picker.PickSingleFolderAsync();
if (selected is null)
{
SetOperatorSettingsMessage(
"폴더 선택을 취소했습니다.",
LegacyOperatorSettingsMessageKind.Information);
return;
}
@@ -137,11 +142,7 @@ public sealed partial class MainWindow
},
_ => throw new ArgumentOutOfRangeException(nameof(kind))
};
var savedMessage = kind == LegacyOperatorFolderKind.Design &&
validated.MissingOptionalExternalAssetCount > 0
? $"디자인 폴더를 저장했습니다. 외부 영상 {validated.MissingOptionalExternalAssetCount}개가 없어 관련 장면만 제한됩니다. 앱을 다시 시작하면 적용됩니다."
: $"{OperatorFolderLabel(kind)} 폴더를 저장했습니다. 앱을 다시 시작하면 적용됩니다.";
SaveOperatorSettings(next, savedMessage);
SaveOperatorSettings(next, "저장됨 · 재시작 필요");
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
@@ -175,7 +176,7 @@ public sealed partial class MainWindow
};
SaveOperatorSettings(
next,
$"{OperatorFolderLabel(kind)} 폴더를 기본값으로 되돌렸습니다. 앱을 다시 시작하면 적용됩니다.");
"초기화됨 · 재시작 필요");
}
private void SaveOperatorSettings(
@@ -252,31 +253,27 @@ public sealed partial class MainWindow
_designFolderSnapshot = CreateOperatorFolderSnapshot(
LegacyOperatorSettingsFolderKind.Scene,
_operatorSettings.SceneDirectory,
"앱 기본 디자인 폴더",
"활성 장면·기본 자산 확인됨");
"앱 기본값");
_resourceFolderSnapshot = CreateOperatorFolderSnapshot(
LegacyOperatorSettingsFolderKind.Resource,
_operatorSettings.ResourceDirectory,
"앱 기본 설정 폴더",
"UI 설정 검증됨");
"앱 기본값");
_backgroundFolderSnapshot = _operatorSettings.BackgroundDirectory is null
? new LegacyOperatorFolderSnapshot(
"장면 폴더 기준 자동 탐색",
"자동",
IsCustom: false,
IsValid: true,
"필요할 때 선택")
"사용 가능")
: CreateOperatorFolderSnapshot(
LegacyOperatorSettingsFolderKind.Background,
_operatorSettings.BackgroundDirectory,
"장면 폴더 기준 자동 탐색",
"배경 폴더 확인됨");
"자동");
}
private static LegacyOperatorFolderSnapshot CreateOperatorFolderSnapshot(
LegacyOperatorSettingsFolderKind kind,
string? configuredPath,
string defaultDisplay,
string validStatus)
string defaultDisplay)
{
if (configuredPath is null)
{
@@ -307,7 +304,7 @@ public sealed partial class MainWindow
defaultDisplay,
IsCustom: false,
IsValid: true,
FolderStatus(kind, defaultFolder, validStatus));
"사용 가능");
}
var validation = LegacyOperatorFolderValidator.Validate(kind, configuredPath);
@@ -320,32 +317,22 @@ public sealed partial class MainWindow
"폴더 확인 필요");
}
var status = kind == LegacyOperatorSettingsFolderKind.Resource &&
validation.Folder.DatabaseIniDetected
? "UI·DB 설정 검증됨"
: FolderStatus(kind, validation.Folder, validStatus);
return new LegacyOperatorFolderSnapshot(
validation.Folder.AbbreviatedPath,
IsCustom: true,
IsValid: true,
status);
"사용 가능");
}
private static string FolderStatus(
LegacyOperatorSettingsFolderKind kind,
LegacyOperatorValidatedFolder folder,
string validStatus) =>
kind == LegacyOperatorSettingsFolderKind.Scene &&
folder.MissingOptionalExternalAssetCount > 0
? $"핵심 자산 확인됨 · 외부 영상 {folder.MissingOptionalExternalAssetCount}개 제외"
: validStatus;
private LegacyOperatorSettingsSnapshot CreateOperatorSettingsSnapshot() => new(
Volatile.Read(ref _operatorSettingsRevision),
_designFolderSnapshot,
_resourceFolderSnapshot,
_backgroundFolderSnapshot,
_operatorSettings.NavigationExpanded,
_operatorSettings.ColorTheme,
_operatorSettings.ViewMode,
_operatorSettings.StartWorkspace,
RestartRequired: !OperatorFolderSettingsEqual(
_operatorSettings,
_appliedOperatorSettings),
@@ -408,11 +395,4 @@ public sealed partial class MainWindow
_ => throw new ArgumentOutOfRangeException(nameof(kind))
};
private static string OperatorFolderLabel(LegacyOperatorFolderKind kind) => kind switch
{
LegacyOperatorFolderKind.Design => "디자인",
LegacyOperatorFolderKind.Resource => "설정",
LegacyOperatorFolderKind.Background => "운영 배경",
_ => throw new ArgumentOutOfRangeException(nameof(kind))
};
}