Skip to content

Commit cc09e50

Browse files
committed
Merge branch 'main' into minor-fixes
2 parents 9d53f55 + 07476df commit cc09e50

File tree

16 files changed

+685
-127
lines changed

16 files changed

+685
-127
lines changed

H2MLauncher.UI/Constants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,8 @@ public static class Constants
4747
public const string BackgroundImageBlurRadiusKey = "BackgroundImageBlurRadius";
4848
public const string BackgroundVideoSourceKey = "BackgroundVideoSource";
4949
public const string CurrentThemeDirectoryKey = "CurrentThemeDirectory";
50+
51+
public const string EmbeddedThemesRelativePath = "Themes";
52+
public const string EmbeddedThemesAbsolutePath = $"pack://application:,,,/{EmbeddedThemesRelativePath}";
5053
}
5154
}

H2MLauncher.UI/Dialog/Views/CustomizationDialogView.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@
297297
<!-- Kebab menu button -->
298298
<ToggleButton x:Name="KebabMenuButton" Content="" Margin="0,5,5,5" Width="0"
299299
FontSize="15" Style="{DynamicResource LinkToggleButtonStyle}"
300-
Visibility="{Binding IsDefault, Converter={StaticResource InverseBoolToVisibilityConverter}, ConverterParameter={x:Static Visibility.Hidden}}">
300+
Visibility="{Binding IsInternal, Converter={StaticResource InverseBoolToVisibilityConverter}, ConverterParameter={x:Static Visibility.Hidden}}">
301301
</ToggleButton>
302302

303303
<!-- Kebab menu popup -->

H2MLauncher.UI/Dialog/Views/SettingsDialogView.xaml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,7 @@
2626
</Style.Triggers>
2727
</Style>
2828

29-
<Style TargetType="{x:Type TextBox}">
30-
<Style.Setters>
31-
<Setter Property="Foreground" Value="{DynamicResource TextLight}"/>
32-
<Setter Property="Background" Value="{DynamicResource TextBoxBackgroundDark}"/>
33-
<Setter Property="CaretBrush" Value="{DynamicResource TextLight}"/>
34-
</Style.Setters>
35-
</Style>
29+
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource TransparentDarkTextBoxStyle}"/>
3630

3731
<Style TargetType="ToolTip">
3832
<Style.Resources>
@@ -83,7 +77,7 @@
8377
<TextBlock Text="📂" Padding="0,0,0,2" FontSize="13"/>
8478
</Button>
8579
<TextBox Text="{Binding MwrLocation}" BorderThickness="0" IsReadOnly="True"
86-
FocusVisualStyle="{x:Null}" Foreground="{DynamicResource TextLight}" Margin="0,3" Background="Transparent">
80+
FocusVisualStyle="{x:Null}" Foreground="{DynamicResource TextBoxForegroundDark}" Margin="0,3" Background="Transparent">
8781
<TextBox.Style>
8882
<Style TargetType="TextBox">
8983
<Style.Triggers>

H2MLauncher.UI/GlobalResources.xaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153

154154
<!-- TextBox Brushes -->
155155
<SolidColorBrush x:Key="TextBoxBackgroundDark" Color="{DynamicResource ColorBlack}" Opacity="0.2"/>
156+
<SolidColorBrush x:Key="TextBoxForegroundDark" Color="{DynamicResource ColorLight}"/>
156157
<SolidColorBrush x:Key="TextBoxBorderBrush" Color="{DynamicResource ColorNeutral400}"/>
157158

158159
<!-- Transparent Item Brushes -->
@@ -990,6 +991,13 @@
990991
</Style>
991992

992993

994+
<Style x:Key="ServerTabControlStyle" TargetType="TabControl">
995+
<Setter Property="Background" Value="Transparent"/>
996+
<Setter Property="BorderThickness" Value="1"/>
997+
<Setter Property="BorderBrush" Value="{DynamicResource ControlBorderBrush}"/>
998+
</Style>
999+
1000+
9931001
<SolidColorBrush x:Key="ServerDataGridHeaderBackground" Color="{DynamicResource ColorBlack}" Opacity="0.9"/>
9941002

9951003
<Style x:Key="ServerDataGridStyle" TargetType="DataGrid">
@@ -1047,4 +1055,13 @@
10471055
</Trigger>
10481056
</Style.Triggers>
10491057
</Style>
1058+
1059+
<!-- Dark TextBox used in dialogs on dark background -->
1060+
<Style x:Key="TransparentDarkTextBoxStyle" TargetType="{x:Type TextBox}">
1061+
<Style.Setters>
1062+
<Setter Property="Foreground" Value="{DynamicResource TextBoxForegroundDark}"/>
1063+
<Setter Property="Background" Value="{DynamicResource TextBoxBackgroundDark}"/>
1064+
<Setter Property="CaretBrush" Value="{DynamicResource TextBoxForegroundDark}"/>
1065+
</Style.Setters>
1066+
</Style>
10501067
</ResourceDictionary>

H2MLauncher.UI/H2MLauncher.UI.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,16 @@
2525
<None Remove="Assets\h2m.png" />
2626
<None Remove="Assets\hmw.png" />
2727
<None Remove="Assets\Icon.ico" />
28+
<None Remove="Themes\Nyan\back.jpg" />
29+
<None Remove="Themes\Nyan\icon.jpg" />
30+
<None Remove="Themes\Nyan\metadata.json" />
31+
<None Remove="Themes\Nyan\nian-cat.gif" />
32+
<None Remove="Themes\Nyan\nyan-cat-cat.gif" />
2833
</ItemGroup>
2934

3035
<ItemGroup>
3136
<Page Remove="GlobalResources.xaml" />
37+
<Page Remove="Themes\Nyan\Nyan.xaml" />
3238
</ItemGroup>
3339

3440
<ItemGroup>
@@ -96,6 +102,12 @@
96102
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
97103
</Resource>
98104
<Resource Include="GlobalResources.xaml" />
105+
<Resource Include="Themes\Nyan\back.jpg" />
106+
<Resource Include="Themes\Nyan\icon.jpg" />
107+
<Resource Include="Themes\Nyan\metadata.json" />
108+
<Resource Include="Themes\Nyan\nian-cat.gif" />
109+
<Resource Include="Themes\Nyan\nyan-cat-cat.gif" />
110+
<Resource Include="Themes\Nyan\Nyan.xaml" />
99111
</ItemGroup>
100112

101113
<ItemGroup>

H2MLauncher.UI/MainWindow.xaml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,7 @@
314314

315315
<!-- Server tabs -->
316316
<TabControl ItemsSource="{Binding ServerTabs}" SelectedItem="{Binding SelectedTab}"
317-
Grid.RowSpan="2" Margin="0,10,0,5" BorderThickness="1"
318-
BorderBrush="{DynamicResource ControlBorderBrush}" Background="Transparent" >
317+
Grid.RowSpan="2" Margin="0,10,0,5" Style="{DynamicResource ServerTabControlStyle}" >
319318
<TabControl.ItemTemplate>
320319
<DataTemplate DataType="{x:Type vm:IServerTabViewModel}">
321320
<TextBlock Text="{Binding TabName}"/>
@@ -458,18 +457,19 @@
458457
<DataGridTemplateColumn Header="Favourite" MinWidth="69" SortMemberPath="IsFavorite" CanUserResize="False">
459458
<DataGridTemplateColumn.CellTemplate>
460459
<DataTemplate>
461-
<Button Content="{Binding IsFavorite, Converter={StaticResource BooleanToStarConverter}}"
462-
Command="{Binding DataContext.ToggleFavouriteCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
463-
CommandParameter="{Binding}"
464-
HorizontalAlignment="Center"
465-
VerticalAlignment="Center"
466-
FontSize="16"
467-
Background="Transparent"
468-
BorderThickness="0"
469-
Margin="5,0,0,0"
470-
Focusable="False"
471-
IsTabStop="False"
472-
Cursor="Hand"/>
460+
<Button Style="{StaticResource LinkButtonStyle}"
461+
Content="{Binding IsFavorite, Converter={StaticResource BooleanToStarConverter}}"
462+
Command="{Binding DataContext.ToggleFavouriteCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
463+
CommandParameter="{Binding}"
464+
HorizontalAlignment="Center"
465+
VerticalAlignment="Center"
466+
FontSize="16"
467+
Background="Transparent"
468+
BorderThickness="0"
469+
Margin="5,0,0,0"
470+
Focusable="False"
471+
IsTabStop="False"
472+
Cursor="Hand"/>
473473
</DataTemplate>
474474
</DataGridTemplateColumn.CellTemplate>
475475
</DataGridTemplateColumn>

H2MLauncher.UI/Services/CustomizationManager.cs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
namespace H2MLauncher.UI.Services;
1616

17+
1718
public partial class CustomizationManager : ObservableObject
1819
{
1920
internal const string DefaultResourceDirectory = "pack://application:,,,/H2MLauncher.UI;component";
@@ -93,7 +94,9 @@ public async Task LoadInitialValues()
9394
if (customizationSettings?.Themes is not null &&
9495
customizationSettings.Themes.Count > 0)
9596
{
96-
if (!File.Exists(customizationSettings.Themes[0]))
97+
string selectedThemeFile = customizationSettings.Themes[0];
98+
bool isInternal = selectedThemeFile.StartsWith(Constants.EmbeddedThemesAbsolutePath);
99+
if (isInternal ? !Uri.TryCreate(selectedThemeFile, UriKind.Absolute, out Uri? themeResourceUri) : !File.Exists(selectedThemeFile))
97100
{
98101
UpdateCustomizationSettings(settings => settings with
99102
{
@@ -102,8 +105,8 @@ public async Task LoadInitialValues()
102105
}
103106
else
104107
{
105-
LoadTheme(customizationSettings.Themes[0]);
106-
}
108+
LoadTheme(customizationSettings.Themes[0]);
109+
}
107110
}
108111

109112
HotReloadThemes = customizationSettings?.HotReloadThemes ?? false;
@@ -248,15 +251,19 @@ public bool LoadTheme(string xamlPath)
248251
{
249252
string themeDirectory = Path.GetDirectoryName(xamlPath)! + '\\';
250253

254+
bool isEmbedded = xamlPath.StartsWith("pack://application");
255+
251256
// load resource dictionary
252-
using FileStream stream = new(xamlPath, FileMode.Open, FileAccess.Read);
257+
using Stream stream = isEmbedded
258+
? Application.GetResourceStream(new Uri(xamlPath)).Stream
259+
: new FileStream(xamlPath, FileMode.Open, FileAccess.Read);
253260

254261
ParserContext parserContext = new()
255262
{
256263
// Critical: this makes relative URIs work!
257-
BaseUri = new Uri(themeDirectory, UriKind.Absolute)
264+
BaseUri = new Uri(isEmbedded ? xamlPath.Substring(0, xamlPath.LastIndexOf('/') + 1) : themeDirectory, UriKind.Absolute)
258265
};
259-
266+
260267
ResourceDictionary resourceDictionary = (ResourceDictionary)XamlReader.Load(stream, parserContext);
261268

262269
// remove old one
@@ -295,19 +302,20 @@ public void ResetTheme()
295302
if (Application.Current.Resources.MergedDictionaries.Count > 1)
296303
{
297304
Application.Current.Resources.MergedDictionaries.RemoveAt(1);
305+
}
298306

299-
// reset resource directory
300-
Application.Current.Resources.Remove(Constants.CurrentThemeDirectoryKey);
301-
CurrentThemeDirectory = DefaultResourceDirectory;
302-
CurrentThemeFile = null;
307+
// reset resource directory
308+
Application.Current.Resources.Remove(Constants.CurrentThemeDirectoryKey);
309+
CurrentThemeDirectory = DefaultResourceDirectory;
310+
CurrentThemeFile = null;
303311

304-
UpdateCustomizationSettings(settings => settings with
305-
{
306-
Themes = []
307-
});
312+
UpdateCustomizationSettings(settings => settings with
313+
{
314+
Themes = []
315+
});
316+
317+
ThemeLoadingError = false;
308318

309-
ThemeLoadingError = false;
310-
}
311319
}
312320

313321
partial void OnBackgroundBlurChanged(double value)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Collections;
2+
using System.IO;
3+
using System.Reflection;
4+
using System.Resources;
5+
using System.Windows;
6+
7+
namespace H2MLauncher.UI.Services;
8+
9+
public static class ResourceHelper
10+
{
11+
/// <summary>
12+
/// Gets a list of resource names under a specific folder (case-insensitive).
13+
/// </summary>
14+
/// <param name="folder">The folder path within the resources (e.g., "Images" or "Data/TextFiles").</param>
15+
/// <returns>An array of resource names (relative to the folder).</returns>
16+
public static List<string> GetResourcesUnder(string folder)
17+
{
18+
folder = folder.ToLower().TrimEnd('/') + "/"; // Ensure consistent folder format
19+
Assembly assembly = Assembly.GetCallingAssembly(); // Or Assembly.GetEntryAssembly() for the main executable
20+
string resourcesName = assembly.GetName().Name + ".g.resources";
21+
22+
using Stream? stream = assembly.GetManifestResourceStream(resourcesName);
23+
if (stream is null)
24+
{
25+
// This can happen if there are no 'Resource' files in the assembly,
26+
// or if the assembly name is different from what's expected.
27+
return [];
28+
}
29+
30+
using ResourceReader resourceReader = new(stream);
31+
List<string> resourceNames = [];
32+
foreach (DictionaryEntry entry in resourceReader)
33+
{
34+
string? resourceKey = entry.Key.ToString()?.ToLower();
35+
if (resourceKey is not null && resourceKey.StartsWith(folder))
36+
{
37+
resourceNames.Add(resourceKey.Substring(folder.Length));
38+
}
39+
}
40+
return resourceNames;
41+
}
42+
43+
/// <summary>
44+
/// Gets a Stream for a specific WPF resource file using a Pack URI.
45+
/// </summary>
46+
/// <param name="relativePath">The relative path to the resource within your project (e.g., "Images/myImage.png").</param>
47+
/// <returns>A Stream for the resource, or null if not found.</returns>
48+
public static Stream? GetWpfResourceStream(string relativePath)
49+
{
50+
try
51+
{
52+
// Construct a Pack URI. The "///" indicates the current assembly.
53+
// Adjust if the resource is in a different assembly.
54+
Uri uri = new Uri($"pack://application:,,,/{relativePath.Replace('\\', '/')}", UriKind.Absolute);
55+
return Application.GetResourceStream(uri).Stream;
56+
}
57+
catch
58+
{
59+
return null;
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)