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
2 changes: 1 addition & 1 deletion OpenUtau.Core/Util/PathManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public PathManager() {
public string LogsPath => Path.Combine(DataPath, "Logs");
public string LogFilePath => Path.Combine(DataPath, "Logs", "log.txt");
public string PrefsFilePath => Path.Combine(DataPath, "prefs.json");
public string ThemeFilePath => Path.Combine(DataPath, "theme.yaml");
public string ThemesPath => Path.Combine(DataPath, "Themes");
public string NotePresetsFilePath => Path.Combine(DataPath, "notepresets.json");
public string BackupsPath => Path.Combine(DataPath, "Backups");

Expand Down
2 changes: 1 addition & 1 deletion OpenUtau.Core/Util/Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public class SerializablePreferences {
public int? PlaybackDeviceIndex;
public bool ShowPrefs = true;
public bool ShowTips = true;
public int Theme;
public string ThemeName = "Light";
public bool PenPlusDefault = false;
public int DegreeStyle;
public bool UseTrackColor = false;
Expand Down
1 change: 1 addition & 0 deletions OpenUtau/App.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
<StyleInclude Source="/Styles/Styles.axaml"/>
</Application.Styles>
<NativeMenu.Menu>
Expand Down
48 changes: 27 additions & 21 deletions OpenUtau/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,30 +92,36 @@ public static void SetTheme() {
if (Current == null) {
return;
}
var light = (IResourceProvider)Current.Resources["themes-light"]!;
var dark = (IResourceProvider)Current.Resources["themes-dark"]!;
var custom = (IResourceProvider)Current.Resources["themes-custom"]!;
Current.Resources.MergedDictionaries.Remove(light);
Current.Resources.MergedDictionaries.Remove(dark);
Current.Resources.MergedDictionaries.Remove(custom);
if (Core.Util.Preferences.Default.Theme == 0) {
Current.Resources.MergedDictionaries.Add(light);
Current.RequestedThemeVariant = ThemeVariant.Light;
}
if (Core.Util.Preferences.Default.Theme == 1) {
Current.Resources.MergedDictionaries.Add(dark);
Current.RequestedThemeVariant = ThemeVariant.Dark;
}
if (Core.Util.Preferences.Default.Theme == 2) {
Current.Resources.MergedDictionaries.Add(custom);
CustomTheme.ApplyTheme();
if (CustomTheme.Default.IsDarkMode == true) {
Current.RequestedThemeVariant = ThemeVariant.Dark;
} else {
var light = (IResourceDictionary) Current.Resources["themes-light"]!;
var dark = (IResourceDictionary) Current.Resources["themes-dark"]!;
var custom = (IResourceDictionary) Current.Resources["themes-custom"]!;
switch (Core.Util.Preferences.Default.ThemeName) {
case "Light":
ApplyTheme(light);
Current.RequestedThemeVariant = ThemeVariant.Light;
}
break;
case "Dark":
ApplyTheme(dark);
Current.RequestedThemeVariant = ThemeVariant.Dark;
break;
default:
ApplyTheme(custom);
CustomTheme.ApplyTheme(Core.Util.Preferences.Default.ThemeName);
if (CustomTheme.Default.IsDarkMode == true) {
Current.RequestedThemeVariant = ThemeVariant.Dark;
} else {
Current.RequestedThemeVariant = ThemeVariant.Light;
}
break;
}
ThemeManager.LoadTheme();
}

private static void ApplyTheme(IResourceDictionary resDict) {
var res = Current?.Resources;
foreach (var item in resDict) {
res![item.Key] = item.Value;
}
}
}
}
142 changes: 85 additions & 57 deletions OpenUtau/Colors/CustomTheme.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,80 +1,108 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Avalonia;
using Avalonia.Media;
using OpenUtau.Core;
using OpenUtau.Core.Util;
using Serilog;

namespace OpenUtau.Colors;
public class CustomTheme {
public static Dictionary<string, string> Themes = [];
public static ThemeYaml Default;

static CustomTheme() {
Load();
if (Default == null) {
Default = new ThemeYaml();
}
Default = new ThemeYaml();
ListThemes();
}

public static void Load() {
if (File.Exists(PathManager.Inst.ThemeFilePath)) {
Default = Yaml.DefaultDeserializer.Deserialize<ThemeYaml>(File.ReadAllText(PathManager.Inst.ThemeFilePath,
Encoding.UTF8));
} else {
Save();
public static void Load(string themeName) {
if (!string.IsNullOrEmpty(themeName) && Themes.TryGetValue(themeName, out var themePath) && File.Exists(themePath)) {
try {
Default = Yaml.DefaultDeserializer.Deserialize<ThemeYaml>(File.ReadAllText(themePath, Encoding.UTF8));
return;
} catch (Exception e) {
Log.Error(e, $"Failed to parse yaml in {themePath}");
}
}
}

public static void Save() {
PathManager path = new PathManager();
Preferences.Default.ThemeName = "Light";
Default = new ThemeYaml();
Directory.CreateDirectory(path.DataPath);
File.WriteAllText(path.ThemeFilePath, Yaml.DefaultSerializer.Serialize(Default), Encoding.UTF8);
}

public static void ApplyTheme() {
Load();
public static void ListThemes() {
Themes.Clear();
Directory.CreateDirectory(PathManager.Inst.ThemesPath);
foreach (var item in Directory.EnumerateFiles(PathManager.Inst.ThemesPath, "*.yaml")) {
try {
string baseName = Yaml.DefaultDeserializer.Deserialize<ThemeYaml>(File.ReadAllText(item, Encoding.UTF8)).Name;
string themeName = baseName;
int dupIter = 1;
while (Themes.ContainsKey(themeName)) {
themeName = $"{baseName} ({dupIter})";
dupIter++;
}
Themes.Add(themeName, item);
} catch (Exception e) {
Log.Error(e, $"Failed to parse yaml in {item}");
}
}
}

public static void ApplyTheme(string themeName) {
Load(themeName);

if (Application.Current != null) {
Application.Current.Resources["IsDarkMode"] = Default.IsDarkMode;
Application.Current.Resources["BackgroundColor"] = Color.Parse($"{Default.BackgroundColor}");
Application.Current.Resources["BackgroundColorPointerOver"] = Color.Parse($"{Default.BackgroundColorPointerOver}");
Application.Current.Resources["BackgroundColorPressed"] = Color.Parse($"{Default.BackgroundColorPressed}");
Application.Current.Resources["BackgroundColorDisabled"] = Color.Parse($"{Default.BackgroundColorDisabled}");

Application.Current.Resources["ForegroundColor"] = Color.Parse($"{Default.ForegroundColor}");
Application.Current.Resources["ForegroundColorPointerOver"] = Color.Parse($"{Default.ForegroundColorPointerOver}");
Application.Current.Resources["ForegroundColorPressed"] = Color.Parse($"{Default.ForegroundColorPressed}");
Application.Current.Resources["ForegroundColorDisabled"] = Color.Parse($"{Default.ForegroundColorDisabled}");

Application.Current.Resources["BorderColor"] = Color.Parse($"{Default.BorderColor}");
Application.Current.Resources["BorderColorPointerOver"] = Color.Parse($"{Default.BorderColorPointerOver}");

Application.Current.Resources["SystemAccentColor"] = Color.Parse($"{Default.SystemAccentColor}");
Application.Current.Resources["SystemAccentColorLight1"] = Color.Parse($"{Default.SystemAccentColorLight1}");
Application.Current.Resources["SystemAccentColorDark1"] = Color.Parse($"{Default.SystemAccentColorDark1}");

Application.Current.Resources["NeutralAccentColor"] = Color.Parse($"{Default.NeutralAccentColor}");
Application.Current.Resources["NeutralAccentColorPointerOver"] = Color.Parse($"{Default.NeutralAccentColorPointerOver}");
Application.Current.Resources["AccentColor1"] = Color.Parse($"{Default.AccentColor1}");
Application.Current.Resources["AccentColor2"] = Color.Parse($"{Default.AccentColor2}");
Application.Current.Resources["AccentColor3"] = Color.Parse($"{Default.AccentColor3}");

Application.Current.Resources["TickLineColor"] = Color.Parse($"{Default.TickLineColor}");
Application.Current.Resources["BarNumberColor"] = Color.Parse($"{Default.BarNumberColor}");
Application.Current.Resources["FinalPitchColor"] = Color.Parse($"{Default.FinalPitchColor}");
Application.Current.Resources["TrackBackgroundAltColor"] = Color.Parse($"{Default.TrackBackgroundAltColor}");

Application.Current.Resources["WhiteKeyColorLeft"] = Color.Parse($"{Default.WhiteKeyColorLeft}");
Application.Current.Resources["WhiteKeyColorRight"] = Color.Parse($"{Default.WhiteKeyColorRight}");
Application.Current.Resources["WhiteKeyNameColor"] = Color.Parse($"{Default.WhiteKeyNameColor}");

Application.Current.Resources["CenterKeyColorLeft"] = Color.Parse($"{Default.CenterKeyColorLeft}");
Application.Current.Resources["CenterKeyColorRight"] = Color.Parse($"{Default.CenterKeyColorRight}");
Application.Current.Resources["CenterKeyNameColor"] = Color.Parse($"{Default.CenterKeyNameColor}");

Application.Current.Resources["BlackKeyColorLeft"] = Color.Parse($"{Default.BlackKeyColorLeft}");
Application.Current.Resources["BlackKeyColorRight"] = Color.Parse($"{Default.BlackKeyColorRight}");
Application.Current.Resources["BlackKeyNameColor"] = Color.Parse($"{Default.BlackKeyNameColor}");
SetResourceColor("BackgroundColor", Default.BackgroundColor);
SetResourceColor("BackgroundColorPointerOver", Default.BackgroundColorPointerOver);
SetResourceColor("BackgroundColorPressed", Default.BackgroundColorPressed);
SetResourceColor("BackgroundColorDisabled", Default.BackgroundColorDisabled);

SetResourceColor("ForegroundColor", Default.ForegroundColor);
SetResourceColor("ForegroundColorPointerOver", Default.ForegroundColorPointerOver);
SetResourceColor("ForegroundColorPressed", Default.ForegroundColorPressed);
SetResourceColor("ForegroundColorDisabled", Default.ForegroundColorDisabled);

SetResourceColor("BorderColor", Default.BorderColor);
SetResourceColor("BorderColorPointerOver", Default.BorderColorPointerOver);

SetResourceColor("SystemAccentColor", Default.SystemAccentColor);
SetResourceColor("SystemAccentColorLight1", Default.SystemAccentColorLight1);
SetResourceColor("SystemAccentColorDark1", Default.SystemAccentColorDark1);

SetResourceColor("NeutralAccentColor", Default.NeutralAccentColor);
SetResourceColor("NeutralAccentColorPointerOver", Default.NeutralAccentColorPointerOver);
SetResourceColor("AccentColor1", Default.AccentColor1);
SetResourceColor("AccentColor2", Default.AccentColor2);
SetResourceColor("AccentColor3", Default.AccentColor3);

SetResourceColor("TickLineColor", Default.TickLineColor);
SetResourceColor("BarNumberColor", Default.BarNumberColor);
SetResourceColor("FinalPitchColor", Default.FinalPitchColor);
SetResourceColor("TrackBackgroundAltColor", Default.TrackBackgroundAltColor);

SetResourceColor("WhiteKeyColorLeft", Default.WhiteKeyColorLeft);
SetResourceColor("WhiteKeyColorRight", Default.WhiteKeyColorRight);
SetResourceColor("WhiteKeyNameColor", Default.WhiteKeyNameColor);

SetResourceColor("CenterKeyColorLeft", Default.CenterKeyColorLeft);
SetResourceColor("CenterKeyColorRight", Default.CenterKeyColorRight);
SetResourceColor("CenterKeyNameColor", Default.CenterKeyNameColor);

SetResourceColor("BlackKeyColorLeft", Default.BlackKeyColorLeft);
SetResourceColor("BlackKeyColorRight", Default.BlackKeyColorRight);
SetResourceColor("BlackKeyNameColor", Default.BlackKeyNameColor);
}
}

private static void SetResourceColor(string res, string colorStr) {
if (Color.TryParse(colorStr, out var color)) {
Application.Current!.Resources[res] = color;
} else {
Log.Error($"Failed to parse color \"{colorStr}\" in {Default.Name} custom theme");
}
}

Expand Down
1 change: 1 addition & 0 deletions OpenUtau/OpenUtau.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<PackageReference Include="ReactiveUI.Fody" Version="19.5.41" />
<PackageReference Include="Avalonia" Version="11.2.4" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.4" />
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.2.4" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.4" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.4" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.4" />
Expand Down
10 changes: 10 additions & 0 deletions OpenUtau/Strings/Strings.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,12 @@ Warning: this option removes custom presets.</system:String>
<system:String x:Key="prefs.appearance.theme">Theme</system:String>
<system:String x:Key="prefs.appearance.theme.dark">Dark</system:String>
<system:String x:Key="prefs.appearance.theme.light">Light</system:String>
<system:String x:Key="prefs.appearance.customtheme.edit.title">Edit Custom Theme</system:String>
<system:String x:Key="prefs.appearance.customtheme.delete.title">Delete Custom Theme</system:String>
<system:String x:Key="prefs.appearance.customtheme.delete.message">Are you sure you want to delete this theme?</system:String>
<system:String x:Key="prefs.appearance.customtheme.create.title">Create Custom Theme</system:String>
<system:String x:Key="prefs.appearance.customtheme.create.prompt">Enter Theme Name</system:String>
<system:String x:Key="prefs.appearance.customtheme.create.empty">Theme name cannot be empty.</system:String>
<system:String x:Key="prefs.appearance.trackcolor">Use track color in UI</system:String>
<system:String x:Key="prefs.cache">Cache</system:String>
<system:String x:Key="prefs.cache.clearonquit">Clear cache on quit</system:String>
Expand Down Expand Up @@ -695,4 +701,8 @@ General
<system:String x:Key="welcome.recent">Recent</system:String>
<system:String x:Key="welcome.start">Start</system:String>
<system:String x:Key="welcome.template">Template</system:String>

<system:String x:Key="themeeditor.title">Theme Editor</system:String>
<system:String x:Key="themeeditor.save">Save</system:String>
<system:String x:Key="themeeditor.cancel">Cancel</system:String>
</ResourceDictionary>
9 changes: 9 additions & 0 deletions OpenUtau/Styles/Styles.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -469,4 +469,13 @@
<Setter Property="Margin" Value="7,0,0,0"/>
</Style>
</Style>

<Style Selector="ColorPicker TextBox">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="CornerRadius" Value="0,4,4,0"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Margin" Value="0"/>
</Style>

</Styles>
5 changes: 5 additions & 0 deletions OpenUtau/ThemeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ class ThemeManager {
new TrackColor("Purple2", "#7B1FA2", "#4A148C", "#AB47BC", "#D5A3DE"),
};

public static List<string> GetAvailableThemes() {
Colors.CustomTheme.ListThemes();
return ["Light", "Dark", ..Colors.CustomTheme.Themes.Select(v => v.Key)];
}

public static void LoadTheme() {
if (Application.Current == null) {
return;
Expand Down
28 changes: 20 additions & 8 deletions OpenUtau/ViewModels/PreferencesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@ public int SafeMaxThreadCount {
[Reactive] public GpuInfo OnnxGpu { get; set; }

// Appearance
[Reactive] public int Theme { get; set; }
[Reactive] public string CustomName { get; set; } = Colors.CustomTheme.Default.Name;
[Reactive] public string ThemeName { get; set; }
[Reactive] public int DegreeStyle { get; set; }
[Reactive] public bool UseTrackColor { get; set; }
[Reactive] public bool ShowPortrait { get; set; }
[Reactive] public bool ShowIcon { get; set; }
[Reactive] public bool ShowGhostNotes { get; set; }
[Reactive] public bool ThemeEditable { get; set; }
public List<string> ThemeItems => ThemeManager.GetAvailableThemes();
public bool IsThemeEditorOpen => Views.ThemeEditorWindow.IsOpen;

// UTAU
public List<string> DefaultRendererOptions { get; set; }
Expand Down Expand Up @@ -170,7 +172,7 @@ public PreferencesViewModel() {
DiffSingerTensorCache = Preferences.Default.DiffSingerTensorCache;
DiffSingerLangCodeHide = Preferences.Default.DiffSingerLangCodeHide;
SkipRenderingMutedTracks = Preferences.Default.SkipRenderingMutedTracks;
Theme = Preferences.Default.Theme;
ThemeName = Preferences.Default.ThemeName;
PenPlusDefault = Preferences.Default.PenPlusDefault;
DegreeStyle = Preferences.Default.DegreeStyle;
UseTrackColor = Preferences.Default.UseTrackColor;
Expand All @@ -186,6 +188,9 @@ public PreferencesViewModel() {
RememberVsqx = Preferences.Default.RememberVsqx;
ClearCacheOnQuit = Preferences.Default.ClearCacheOnQuit;

MessageBus.Current.Listen<ThemeEditorStateChangedEvent>()
.Subscribe(_ => this.RaisePropertyChanged(nameof(IsThemeEditorOpen)));

this.WhenAnyValue(vm => vm.AudioOutputDevice)
.WhereNotNull()
.SubscribeOn(RxApp.MainThreadScheduler)
Expand Down Expand Up @@ -249,11 +254,14 @@ public PreferencesViewModel() {
Preferences.Default.SortingOrder = so?.Name ?? null;
Preferences.Save();
});
this.WhenAnyValue(vm => vm.Theme)
.Subscribe(theme => {
Preferences.Default.Theme = theme;
Preferences.Save();
App.SetTheme();
this.WhenAnyValue(vm => vm.ThemeName)
.Subscribe(themeName => {
ThemeEditable = themeName != "Light" && themeName != "Dark";
if (!IsThemeEditorOpen) {
Preferences.Default.ThemeName = themeName;
Preferences.Save();
App.SetTheme();
}
});
this.WhenAnyValue(vm => vm.DegreeStyle)
.Subscribe(degreeStyle => {
Expand Down Expand Up @@ -427,5 +435,9 @@ public void SetWinePath(string path) {
ToolsManager.Inst.Initialize();
this.RaisePropertyChanged(nameof(WinePath));
}

public void RefreshThemes() {
this.RaisePropertyChanged(nameof(ThemeItems));
}
}
}
Loading
Loading