Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
17 changes: 17 additions & 0 deletions Race Element.UI/App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:themes="clr-namespace:Avalonia.Themes.Neumorphism;assembly=Avalonia.Themes.Neumorphism"
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
x:Class="RaceElement.UI.App"
xmlns:local="using:RaceElement.UI"
RequestedThemeVariant="Light">

<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>

<Application.Styles>
<themes:NeumorphismTheme BaseTheme="Light" PrimaryColor="LightBlue" SecondaryColor="Lime" />
<StyleInclude Source="avares://Material.Icons.Avalonia/MaterialIconStyles.axaml" />
</Application.Styles>
</Application>
35 changes: 35 additions & 0 deletions Race Element.UI/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Avalonia;
using Avalonia.Markup.Xaml;
using Avalonia.Controls.ApplicationLifetimes;
using RaceElement.UI.ViewModels;
using RaceElement.UI.Views;

namespace RaceElement.UI;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainWindow
{
DataContext = new MainWindowViewModel()
};
}

base.OnFrameworkInitializationCompleted();
}

}
Binary file added Race Element.UI/Assets/Banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Race Element.UI/Assets/acc-list-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Race Element.UI/Assets/acc.ico
Binary file not shown.
Binary file added Race Element.UI/Assets/acevo-list-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Race Element.UI/Assets/acevo.ico
Binary file not shown.
Binary file added Race Element.UI/Assets/iracing-list-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Race Element.UI/Assets/iracing.ico
Binary file not shown.
Binary file added Race Element.UI/Assets/lmu-list-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Race Element.UI/Assets/lmu.ico
Binary file not shown.
Binary file added Race Element.UI/Assets/raceelement.ico
Binary file not shown.
24 changes: 24 additions & 0 deletions Race Element.UI/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using Avalonia;
using Avalonia.Styling;
using Avalonia.ReactiveUI;

namespace RaceElement.UI;

internal sealed class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);

// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace()
.UseReactiveUI();
}
43 changes: 43 additions & 0 deletions Race Element.UI/RaceElement.UI.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>

<ItemGroup>
<Folder Include="Models\" />
<AvaloniaResource Include="Assets\**" />
</ItemGroup>

<ItemGroup>
<AvaloniaResource Remove="Assets\race element icon 2.ico" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.6" />
<PackageReference Include="Avalonia.Desktop" Version="11.2.6" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.6" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.6" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.6">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.6" />
<PackageReference Include="Material.Icons.Avalonia" Version="2.3.1" />
<PackageReference Include="Neumorphism.Avalonia" Version="0.11.2" />
</ItemGroup>

<ItemGroup>
<Compile Update="Views\MainTopMenuView.axaml.cs">
<DependentUpon>MainTopMenuView.axaml</DependentUpon>
</Compile>
<Compile Update="Views\Pages\OverlaySettingPages\ControlButtonsPanelView.axaml.cs">
<DependentUpon>ControlButtonsPanelView.axaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>
113 changes: 113 additions & 0 deletions Race Element.UI/Services/GameSelectionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Create a new file: Race Element.UI/Services/GameSelectionService.cs
using ReactiveUI;
using System;
using System.Diagnostics;

namespace RaceElement.UI.Services;

public enum SupportedGame
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
IRacing,
ACC,
ACEvo,
LMU
}

/// <summary>
/// Provides application-wide access to the currently selected game
/// </summary>
public class GameSelectionService : ReactiveObject
{
private static GameSelectionService? _instance;

private string _selectedGame = "acc";

// Constructor is private to enforce singleton pattern
private GameSelectionService() { }

/// <summary>
/// Gets the currently selected game
/// </summary>
public string SelectedGame => _selectedGame;

public static GameSelectionService Instance => _instance ??= new GameSelectionService();
// Helper methods to check for specific games
public bool IsIRacing => SelectedGame == "iracing";
public bool IsACC => SelectedGame == "acc";
public bool IsACEvo => SelectedGame == "acevo";
public bool IsLMU => SelectedGame == "lmu";

/// <summary>
/// Updates the selected game (internal - only used by MainTopMenuViewModel)
/// </summary>
internal void UpdateSelectedGame(string game)
{
this.RaiseAndSetIfChanged(ref _selectedGame, game);
}

/// <summary>
/// Updates the selected game and performs any game-specific initialization logic
/// </summary>
/// <param name="selection">The game identifier</param>
/// <returns>True if the selection was valid and processed, false otherwise</returns>
internal bool SelectGame(string selection)
{
string game = selection.ToLower();

// Skip if same game selected
if (_selectedGame == game) return false;

// Update the selected game
this.RaiseAndSetIfChanged(ref _selectedGame, game);

// Perform game-specific initialization
InitializeGameSpecificSettings(game);

return true;
}

/// <summary>
/// Initializes game-specific settings and configurations
/// </summary>
private void InitializeGameSpecificSettings(string game)
{
switch (game)
{
case "iracing":
// iRacing-specific initialization
InitializeIRacingSettings();
break;

case "acc":
// ACC-specific initialization
InitializeACCSettings();
break;

case "acevo":
// AC Evo-specific initialization
InitializeACEvoSettings();
break;

case "lmu":
// LMU-specific initialization
InitializeLMUSettings();
break;
}
}

private void InitializeIRacingSettings()
{
}

private void InitializeACCSettings()
{
}

private void InitializeACEvoSettings()
{
}

private void InitializeLMUSettings()
{
}
}
38 changes: 38 additions & 0 deletions Race Element.UI/ViewLocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using RaceElement.UI.ViewModels;
using Avalonia.Controls;
using Avalonia.Controls.Templates;

namespace RaceElement.UI;

public class ViewLocator : IDataTemplate
{
public Control? Build(object? param)
{
if (param is null)
return null;

var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);

if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}

var pageName = param.GetType().FullName!.Replace("ViewModel", "PageView", StringComparison.Ordinal);
pageName =pageName.Replace("PageViews", "Views.Pages", StringComparison.Ordinal);
var pageType = Type.GetType(pageName);

if (pageType != null)
{
return (Control)Activator.CreateInstance(pageType)!;
}
return new TextBlock { Text = "Not Found: " + name + " or " + pageName};
}

public bool Match(object? data)
{
return data is ViewModelBase;
}
}
7 changes: 7 additions & 0 deletions Race Element.UI/ViewModels/DataViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using ReactiveUI;

namespace RaceElement.UI.ViewModels;

public class DataViewModel : ViewModelBase
{
}
6 changes: 6 additions & 0 deletions Race Element.UI/ViewModels/LiveriesViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using ReactiveUI;

namespace RaceElement.UI.ViewModels;
public class LiveriesViewModel : ViewModelBase
{
}
104 changes: 104 additions & 0 deletions Race Element.UI/ViewModels/MainTopMenuViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls;
using ReactiveUI;
using System;
using System.Reactive;
using System.Diagnostics;
using RaceElement.UI.Services;

namespace RaceElement.UI.ViewModels;
public class MainTopMenuViewModel : ViewModelBase
{
#region PRIVATE FIELDS

// Reference to the main window view model for Navigation, DI can be used to access
private MainWindowViewModel? _mainWindowViewModel;
// Default page
private string _currentPageName = "Overlays";
// Track the current game icon source
private string _currentGameIconSource;
#endregion

#region CONSTRUCTORS
public MainTopMenuViewModel()
{
// Initialize navigation commands
NavigateToOverlaysPageCommand = ReactiveCommand.Create(() => { _mainWindowViewModel?.NavigateTo(new OverlaysViewModel()); CurrentPageName = "Overlays"; });
NavigateToDataPageCommand = ReactiveCommand.Create(() => { _mainWindowViewModel?.NavigateTo(new DataViewModel()); CurrentPageName = "Data"; });
NavigateToSetupsPageCommand = ReactiveCommand.Create(() => { _mainWindowViewModel?.NavigateTo(new SetupsViewModel()); CurrentPageName = "Setups"; });
NavigateToLiveriesPageCommand = ReactiveCommand.Create(() => { _mainWindowViewModel?.NavigateTo(new LiveriesViewModel()); CurrentPageName = "Liveries"; });
NavigateToToolsPageCommand = ReactiveCommand.Create(() => { _mainWindowViewModel?.NavigateTo(new ToolsViewModel()); CurrentPageName = "Tools"; });

// Initialize GameSelectionCommand
GameSelectionCommand = ReactiveCommand.Create<string>(HandleGameSelection);
}
#endregion

#region PUBIC PROPERTIES
public string CurrentPageName
{
get => _currentPageName;
private set => this.RaiseAndSetIfChanged(ref _currentPageName, value);
}

public string SelectedGame
{
get => GameSelectionService.Instance.SelectedGame;
private set => GameSelectionService.Instance.UpdateSelectedGame(value);
}

public string CurrentGameIconSource => $"avares://RaceElement.UI/Assets/{SelectedGame}.ico";
#endregion

#region PUBLIC COMMANDS/METHODS
public bool IsCurrentPage(ViewModelBase page)
{
return _mainWindowViewModel?.CurrentPage == page;
}

// Navigation commands
public ReactiveCommand<Unit, Unit> NavigateToOverlaysPageCommand { get; }
public ReactiveCommand<Unit, Unit> NavigateToDataPageCommand { get; }
public ReactiveCommand<Unit, Unit> NavigateToSetupsPageCommand { get; }
public ReactiveCommand<Unit, Unit> NavigateToLiveriesPageCommand { get; }
public ReactiveCommand<Unit, Unit> NavigateToToolsPageCommand { get; }

// Command for game selection from dropdown
public ReactiveCommand<string, Unit> GameSelectionCommand { get; }

// Command to close the application
public static ReactiveCommand<Unit, Unit> CloseApplicationCommand => ReactiveCommand.Create(() => { Environment.Exit(0); });

// Command to minimize the application
public static ReactiveCommand<Unit, Unit> MinimizeApplicationCommand => ReactiveCommand.Create(() =>
{
if (Avalonia.Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
desktopLifetime.MainWindow.WindowState = WindowState.Minimized;
}
});
#endregion

#region INTERNAL METHODS
internal void SetMainWindowViewModel(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
}
#endregion

#region PRIVATE METHODS
/// <summary>
/// Handles the game selection from the dropdown menu.
/// </summary>
/// <param name="selection"></param>
private void HandleGameSelection(string selection)
{
// Let the service handle game selection and initialization
if (GameSelectionService.Instance.SelectGame(selection))
{
// Update UI only if the game actually changed
this.RaisePropertyChanged(nameof(CurrentGameIconSource));
}
}
#endregion
}
Loading