Skip to content

Commit eff67dd

Browse files
committed
feat(themes): New Theme Selector
1 parent e614c88 commit eff67dd

5 files changed

Lines changed: 121 additions & 112 deletions

File tree

src/App.axaml.cs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using System;
22
using System.IO;
3+
using System.Linq;
34
using System.Net.Http;
45
using System.Text.Json;
56
using System.Threading.Tasks;
6-
77
using Avalonia;
88
using Avalonia.Controls;
99
using Avalonia.Controls.ApplicationLifetimes;
@@ -14,12 +14,14 @@
1414
using Avalonia.Platform;
1515
using Avalonia.Styling;
1616
using Avalonia.Threading;
17+
using SourceGit.Models;
1718

1819
namespace SourceGit
1920
{
2021
public partial class App : Application
2122
{
2223
#region App Entry Point
24+
2325
[STAThread]
2426
public static void Main(string[] args)
2527
{
@@ -56,10 +58,7 @@ public static AppBuilder BuildAvaloniaApp()
5658
builder.UsePlatformDetect();
5759
builder.LogToTrace();
5860
builder.WithInterFont();
59-
builder.With(new FontManagerOptions()
60-
{
61-
DefaultFamilyName = "fonts:Inter#Inter"
62-
});
61+
builder.With(new FontManagerOptions() { DefaultFamilyName = "fonts:Inter#Inter" });
6362
builder.ConfigureFonts(manager =>
6463
{
6564
var monospace = new EmbeddedFontCollection(
@@ -71,9 +70,11 @@ public static AppBuilder BuildAvaloniaApp()
7170
Native.OS.SetupApp(builder);
7271
return builder;
7372
}
73+
7474
#endregion
7575

7676
#region Utility Functions
77+
7778
public static async Task<bool> AskConfirmAsync(string message, Models.ConfirmButtonType buttonType = Models.ConfirmButtonType.OkCancel)
7879
{
7980
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
@@ -117,17 +118,21 @@ app.Resources[finalLocaleKey] is not ResourceDictionary targetLocale ||
117118
app._activeLocale = targetLocale;
118119
}
119120

120-
public static void SetTheme(string theme, string themeOverridesFile)
121+
public static void SetTheme(ThemeOverrides theme)
121122
{
122123
if (Current is not App app)
123124
return;
124125

125-
if (theme.Equals("Light", StringComparison.OrdinalIgnoreCase))
126+
string themeOverridesFile = string.Empty;
127+
128+
if (theme.Name.Equals("Light", StringComparison.OrdinalIgnoreCase))
126129
app.RequestedThemeVariant = ThemeVariant.Light;
127-
else if (theme.Equals("Dark", StringComparison.OrdinalIgnoreCase))
130+
else if (theme.Name.Equals("Dark", StringComparison.OrdinalIgnoreCase))
128131
app.RequestedThemeVariant = ThemeVariant.Dark;
129-
else
132+
else if (theme.Name.Equals("Default", StringComparison.OrdinalIgnoreCase))
130133
app.RequestedThemeVariant = ThemeVariant.Default;
134+
else
135+
themeOverridesFile = theme.FilePath;
131136

132137
if (app._themeOverrides != null)
133138
{
@@ -234,16 +239,18 @@ public static void Quit(int exitCode)
234239
else
235240
Environment.Exit(exitCode);
236241
}
242+
237243
#endregion
238244

239245
#region Overrides
246+
240247
public override void Initialize()
241248
{
242249
AvaloniaXamlLoader.Load(this);
243250

244251
var pref = ViewModels.Preferences.Instance;
245252
SetLocale(pref.Locale);
246-
SetTheme(pref.Theme, pref.ThemeOverrides);
253+
SetTheme(pref.Themes.FirstOrDefault(m => m.Name == pref.Theme) ?? new ThemeOverrides("Default"));
247254
SetFonts(pref.DefaultFontFamily, pref.MonospaceFontFamily);
248255
}
249256

@@ -276,9 +283,11 @@ public override void OnFrameworkInitializationCompleted()
276283
TryLaunchAsNormal(desktop);
277284
}
278285
}
286+
279287
#endregion
280288

281289
#region Launch Ways
290+
282291
private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
283292
{
284293
exitCode = -1;
@@ -362,17 +371,11 @@ private bool TryLaunchAsFileHistoryViewer(IClassicDesktopStyleApplicationLifetim
362371
var relativePath = Path.GetRelativePath(repo, fullPath).Replace('\\', '/');
363372
if (File.Exists(fullPath))
364373
{
365-
desktop.MainWindow = new Views.FileHistories()
366-
{
367-
DataContext = new ViewModels.FileHistories(repo, relativePath)
368-
};
374+
desktop.MainWindow = new Views.FileHistories() { DataContext = new ViewModels.FileHistories(repo, relativePath) };
369375
}
370376
else if (Directory.Exists(fullPath))
371377
{
372-
desktop.MainWindow = new Views.DirHistories()
373-
{
374-
DataContext = new ViewModels.DirHistories(repo, relativePath.TrimEnd('/'))
375-
};
378+
desktop.MainWindow = new Views.DirHistories() { DataContext = new ViewModels.DirHistories(repo, relativePath.TrimEnd('/')) };
376379
}
377380
else
378381
{
@@ -410,10 +413,7 @@ private bool TryLaunchAsBlameViewer(IClassicDesktopStyleApplicationLifetime desk
410413
}
411414

412415
var relFile = Path.GetRelativePath(repo, file);
413-
var viewer = new Views.Blame()
414-
{
415-
DataContext = new ViewModels.Blame(repo, relFile, head)
416-
};
416+
var viewer = new Views.Blame() { DataContext = new ViewModels.Blame(repo, relFile, head) };
417417
desktop.MainWindow = viewer;
418418
return true;
419419
}
@@ -532,9 +532,11 @@ private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
532532
Check4Update();
533533
#endif
534534
}
535+
535536
#endregion
536537

537538
#region Check for Updates
539+
538540
private void Check4Update(bool manually = false)
539541
{
540542
if (_launcher != null)
@@ -595,6 +597,7 @@ private void ShowSelfUpdateResult(object data)
595597
// Ignore exceptions.
596598
}
597599
}
600+
598601
#endregion
599602

600603
private Models.IpcChannel _ipcChannel = null;

src/Models/ThemeOverrides.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,23 @@ namespace SourceGit.Models
55
{
66
public class ThemeOverrides
77
{
8+
public ThemeOverrides()
9+
{
10+
}
11+
12+
public ThemeOverrides(string name)
13+
{
14+
Name = name;
15+
}
16+
17+
public string Version { get; set; }
18+
public string Name { get; set; }
19+
public string Author { get; set; }
20+
public string Url { get; set; }
821
public Dictionary<string, Color> BasicColors { get; set; } = new Dictionary<string, Color>();
922
public double GraphPenThickness { get; set; } = 2;
1023
public double OpacityForNotMergedCommits { get; set; } = 0.5;
1124
public List<Color> GraphColors { get; set; } = new List<Color>();
25+
public string FilePath { get; set; }
1226
}
1327
}

src/ViewModels/Preferences.cs

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
34
using System.IO;
5+
using System.Linq;
46
using System.Text.Json;
57
using System.Text.Json.Serialization;
68
using System.Threading.Tasks;
79
using Avalonia.Collections;
810
using CommunityToolkit.Mvvm.ComponentModel;
11+
using SourceGit.Models;
912

1013
namespace SourceGit.ViewModels
1114
{
@@ -41,26 +44,33 @@ public string Locale
4144
}
4245
}
4346

44-
public string Theme
47+
[JsonIgnore]
48+
public List<ThemeOverrides> Themes
4549
{
46-
get => _theme;
50+
get => _themes;
4751
set
4852
{
49-
if (SetProperty(ref _theme, value) && !_isLoading)
50-
App.SetTheme(_theme, _themeOverrides);
53+
SetProperty(ref _themes, value);
5154
}
5255
}
5356

54-
public string ThemeOverrides
57+
[JsonIgnore]
58+
public ThemeOverrides SelectedTheme
5559
{
56-
get => _themeOverrides;
60+
get
61+
{
62+
return Themes.FirstOrDefault(m => m.Name == Theme) ?? new ThemeOverrides("Default");
63+
}
5764
set
5865
{
59-
if (SetProperty(ref _themeOverrides, value) && !_isLoading)
60-
App.SetTheme(_theme, value);
66+
if (SetProperty(ref _selectedTheme, value) && !_isLoading)
67+
App.SetTheme(_selectedTheme);
68+
Theme = _selectedTheme.Name;
6169
}
6270
}
6371

72+
public string Theme { get; set; }
73+
6474
public string DefaultFontFamily
6575
{
6676
get => _defaultFontFamily;
@@ -566,10 +576,7 @@ public RepositoryNode FindOrAddNodeByRepositoryPath(string repo, RepositoryNode
566576
{
567577
node = new RepositoryNode()
568578
{
569-
Id = normalized,
570-
Name = Path.GetFileName(normalized),
571-
Bookmark = 0,
572-
IsRepository = true,
579+
Id = normalized, Name = Path.GetFileName(normalized), Bookmark = 0, IsRepository = true,
573580
};
574581

575582
AddNode(node, parent, save);
@@ -649,6 +656,25 @@ public void Save()
649656

650657
private static Preferences Load()
651658
{
659+
try
660+
{
661+
var themesPath = Path.Combine(Native.OS.DataDir, "themes");
662+
var themeFiles = Directory.GetFiles(themesPath, "theme.json", SearchOption.AllDirectories);
663+
foreach (var file in themeFiles)
664+
{
665+
using var stream = File.OpenRead(file);
666+
var theme = JsonSerializer.Deserialize(stream, JsonCodeGen.Default.ThemeOverrides);
667+
if (theme == null)
668+
continue;
669+
theme.FilePath = file;
670+
_themes.Add(theme);
671+
}
672+
}
673+
catch
674+
{
675+
// ignored
676+
}
677+
652678
var path = Path.Combine(Native.OS.DataDir, "preference.json");
653679
if (!File.Exists(path))
654680
return new Preferences();
@@ -791,7 +817,8 @@ private bool RemoveInvalidRepositoriesRecursive(List<RepositoryNode> collection)
791817
private bool _isLoading = true;
792818
private bool _isReadonly = true;
793819
private string _locale = "en_US";
794-
private string _theme = "Default";
820+
private ThemeOverrides _selectedTheme = new("Default");
821+
private static List<ThemeOverrides> _themes = new() { new ThemeOverrides("Default"), new ThemeOverrides("Dark"), new ThemeOverrides("Light") };
795822
private string _themeOverrides = string.Empty;
796823
private string _defaultFontFamily = string.Empty;
797824
private string _monospaceFontFamily = string.Empty;

src/Views/Preferences.axaml

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -193,19 +193,31 @@
193193
Text="{DynamicResource Text.Preferences.Appearance.Theme}"
194194
HorizontalAlignment="Right"
195195
Margin="0,0,16,0"/>
196-
<ComboBox Grid.Row="0" Grid.Column="1"
197-
MinHeight="28"
198-
Padding="8,0"
199-
HorizontalAlignment="Stretch"
200-
DisplayMemberBinding="{Binding Key, x:DataType=ThemeVariant}"
201-
SelectedItem="{Binding Theme, Mode=TwoWay, Converter={x:Static c:StringConverters.ToTheme}}">
202-
<ComboBox.Items>
203-
<ThemeVariant>Default</ThemeVariant>
204-
<ThemeVariant>Dark</ThemeVariant>
205-
<ThemeVariant>Light</ThemeVariant>
206-
</ComboBox.Items>
207-
</ComboBox>
208-
196+
<Grid Grid.Row="0" Grid.Column="1">
197+
<Grid.ColumnDefinitions>
198+
<ColumnDefinition Width="*" />
199+
<ColumnDefinition Width="28" />
200+
<ColumnDefinition Width="28" />
201+
</Grid.ColumnDefinitions>
202+
<ComboBox Grid.Column="0" Grid.Row="0" MinHeight="28"
203+
HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"
204+
Padding="8,0"
205+
ItemsSource="{Binding Themes, Mode=OneWay}"
206+
SelectedItem="{Binding SelectedTheme, Mode=TwoWay}">
207+
<ComboBox.ItemTemplate>
208+
<DataTemplate x:DataType="{x:Type m:ThemeOverrides}">
209+
<TextBlock Text="{Binding Name, Mode=OneWay}"/>
210+
</DataTemplate>
211+
</ComboBox.ItemTemplate>
212+
</ComboBox>
213+
<Button Grid.Column="1" Grid.Row="0" Classes="icon_button" Width="28" Height="28" Click="OpenThemeRepository">
214+
<Path Width="14" Height="14" Data="{StaticResource Icons.Remotes}" Fill="{DynamicResource Brush.FG1}"/>
215+
</Button>
216+
<Button Grid.Column="2" Grid.Row="0" Classes="icon_button" Width="28" Height="28" Click="OpenThemeFolder">
217+
<Path Width="14" Height="14" Data="{StaticResource Icons.Folder.Open}" Fill="{DynamicResource Brush.FG1}"/>
218+
</Button>
219+
</Grid>
220+
209221
<TextBlock Grid.Row="1" Grid.Column="0"
210222
Text="{DynamicResource Text.Preferences.Appearance.DefaultFont}"
211223
HorizontalAlignment="Right"
@@ -270,27 +282,6 @@
270282
Value="{Binding EditorTabWidth, Mode=TwoWay}"/>
271283
</Grid>
272284

273-
<TextBlock Grid.Row="5" Grid.Column="0"
274-
Text="{DynamicResource Text.Preferences.Appearance.ThemeOverrides}"
275-
HorizontalAlignment="Right"
276-
Margin="0,0,16,0"/>
277-
<TextBox Grid.Row="5" Grid.Column="1"
278-
Height="28"
279-
CornerRadius="3"
280-
Text="{Binding ThemeOverrides, Mode=TwoWay}">
281-
<TextBox.InnerRightContent>
282-
<StackPanel Orientation="Horizontal">
283-
<Button Classes="icon_button" Width="28" Height="28" Click="SelectThemeOverrideFile">
284-
<Path Width="16" Height="16" Data="{StaticResource Icons.Folder.Open}" Margin="0,2,0,0" Fill="{DynamicResource Brush.FG1}"/>
285-
</Button>
286-
287-
<Button Classes="icon_button" Width="28" Height="28" Click="OpenThemeRepository">
288-
<Path Width="14" Height="14" Data="{StaticResource Icons.Remotes}" Fill="{DynamicResource Brush.FG1}"/>
289-
</Button>
290-
</StackPanel>
291-
</TextBox.InnerRightContent>
292-
</TextBox>
293-
294285
<CheckBox Grid.Row="6" Grid.Column="1"
295286
Height="32"
296287
Content="{DynamicResource Text.Preferences.Appearance.UseFixedTabWidth}"

0 commit comments

Comments
 (0)