diff --git a/.idea/.idea.Nebula/.idea/avalonia.xml b/.idea/.idea.Nebula/.idea/avalonia.xml index feb38df..1aea4d7 100644 --- a/.idea/.idea.Nebula/.idea/avalonia.xml +++ b/.idea/.idea.Nebula/.idea/avalonia.xml @@ -12,6 +12,7 @@ + @@ -21,6 +22,7 @@ + diff --git a/Nebula.Launcher/LauncherConVar.cs b/Nebula.Launcher/LauncherConVar.cs index ebfb1c4..c40287f 100644 --- a/Nebula.Launcher/LauncherConVar.cs +++ b/Nebula.Launcher/LauncherConVar.cs @@ -26,8 +26,8 @@ public static class LauncherConVar ]); public static readonly ConVar Hub = ConVarBuilder.Build("launcher.hub.v2", [ - new ServerHubRecord("WizDen", "https://harpy.durenko.tatar/hub-api/api/servers", null), - new ServerHubRecord("AltHub","https://web.networkgamez.com/api/servers",null) + new ServerHubRecord("WizDen", "https://harpy.durenko.tatar/hub-api/api/servers"), + new ServerHubRecord("AltHub","https://web.networkgamez.com/api/servers") ]); public static readonly ConVar CurrentLang = ConVarBuilder.Build("launcher.language", "en-US"); diff --git a/Nebula.Launcher/Models/ListItemTemplate.cs b/Nebula.Launcher/Models/ListItemTemplate.cs index 71646da..ec87c98 100644 --- a/Nebula.Launcher/Models/ListItemTemplate.cs +++ b/Nebula.Launcher/Models/ListItemTemplate.cs @@ -8,5 +8,4 @@ public record ListItemTemplate(Type ModelType, string IconKey, string Label); public record ServerListTabTemplate(IServerListProvider ServerListProvider, string TabName); public record ServerHubRecord( [property:JsonPropertyName("name")] string Name, - [property:JsonPropertyName("url")] string MainUrl, - [property:JsonPropertyName("fallback")] string? Fallback); \ No newline at end of file + [property:JsonPropertyName("url")] string MainUrl); \ No newline at end of file diff --git a/Nebula.Launcher/Nebula.Launcher.csproj b/Nebula.Launcher/Nebula.Launcher.csproj index f6e067d..97005ba 100644 --- a/Nebula.Launcher/Nebula.Launcher.csproj +++ b/Nebula.Launcher/Nebula.Launcher.csproj @@ -78,4 +78,8 @@ + + + + diff --git a/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs b/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs index b2f3cc4..90a142d 100644 --- a/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs @@ -262,8 +262,7 @@ public sealed record ProfileAuthCredentials( string Password, string AuthServer, [property: JsonIgnore] ICommand OnSelect = default!, - [property: JsonIgnore] ICommand OnDelete = default! -); + [property: JsonIgnore] ICommand OnDelete = default!); public sealed record AuthServerCredentials( string Name, diff --git a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs index 9979e16..a8591c9 100644 --- a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs @@ -1,142 +1,349 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; using System.Linq; -using System.Runtime.CompilerServices; -using CommunityToolkit.Mvvm.ComponentModel; -using Microsoft.Extensions.DependencyInjection; -using Nebula.Launcher.Views.Config; +using System.Reflection; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Layout; using Nebula.Launcher.Views.Pages; +using Nebula.Shared; using Nebula.Shared.Services; -using BindingFlags = System.Reflection.BindingFlags; namespace Nebula.Launcher.ViewModels.Pages; [ViewModelRegister(typeof(ConfigurationView))] [ConstructGenerator] -public partial class ConfigurationViewModel : ViewModelBase, IConfigContext +public partial class ConfigurationViewModel : ViewModelBase { - public ObservableCollection ConfigurationVerbose { get; } = new(); + public ObservableCollection ConfigurationVerbose { get; } = new(); [GenerateProperty] private ConfigurationService ConfigurationService { get; } = default!; - [GenerateProperty] private IServiceProvider ServiceProvider { get; } = default!; + public List<(object, Type)> ConVarList = new(); - public void AddConfiguration(ConVar convar) where T1: ViewModelBase, IConfigurationVerbose + public void AddCvarConf(ConVar cvar) { - var configurationVerbose = ServiceProvider.GetService()!; - configurationVerbose.Context = new ConfigContext(convar, this); - configurationVerbose.InitializeConfig(); - ConfigurationVerbose.Add(configurationVerbose); + ConfigurationVerbose.Add( + ConfigControlHelper.GetConfigControl(cvar.Name, ConfigurationService.GetConfigValue(cvar))); + ConVarList.Add((cvar, cvar.Type)); } public void InvokeUpdateConfiguration() { - foreach (var verbose in ConfigurationVerbose) + for (int i = 0; i < ConfigurationVerbose.Count; i++) { - if(verbose is not IUpdateInvoker invoker) continue; - invoker.UpdateConfiguration(); + var conVarControl = ConfigurationVerbose[i]; + if(!conVarControl.Dirty) + continue; + + var conVar = ConVarList[i]; + var methodInfo = ConfigurationService.GetType().GetMethod("SetConfigValue")!.MakeGenericMethod(conVar.Item2); + methodInfo.Invoke(ConfigurationService, [conVar.Item1, conVarControl.GetValue()]); } } - protected override void InitialiseInDesignMode() - { - AddConfiguration(LauncherConVar.ILSpyUrl); + { + AddCvarConf(LauncherConVar.ILSpyUrl); + AddCvarConf(LauncherConVar.Hub); + AddCvarConf(LauncherConVar.AuthServers); + AddCvarConf(CurrentConVar.EngineManifestUrl); + AddCvarConf(CurrentConVar.RobustAssemblyName); + AddCvarConf(CurrentConVar.ManifestDownloadProtocolVersion); } protected override void Initialise() { InitialiseInDesignMode(); } +} - public void SetValue(ConVar conVar, T value) +public static class ConfigControlHelper{ + public static IConfigControl GetConfigControl(string name,object value) { - ConfigurationService.SetConfigValue(conVar, value); + switch (value) + { + case string stringValue: + return new StringUnitConfigControl(name, stringValue); + case int intValue: + return new IntUnitConfigControl(name, intValue); + case float floatValue: + return new FloatUnitConfigControl(name, floatValue); + } + + var valueType = value.GetType(); + + if (valueType.IsArray) + return new ArrayUnitConfigControl(name, value); + + return new ComplexUnitConfigControl(name, value); } - public T? GetValue(ConVar convar) + public static object? CreateDefaultValue(Type type) { - return ConfigurationService.GetConfigValue(convar); + if (type == typeof(string)) + return string.Empty; + if (type == typeof(int)) + return 0; + if (type == typeof(float)) + return 0f; + if(type.IsValueType) + return Activator.CreateInstance(type); + + var ctor = type.GetConstructors().First(); + var parameters = ctor.GetParameters() + .Select(p => CreateDefaultValue(p.ParameterType)) + .ToArray(); + + return ctor.Invoke(parameters); } } -public interface IConfigContext +public sealed class ComplexUnitConfigControl : StackPanel, IConfigControl { - public void SetValue(ConVar conVar, T value); - public T? GetValue(ConVar convar); -} - -public class ConfigContext : IConfigContext -{ - public ConfigContext(ConVar conVar, IConfigContext parent) - { - ConVar = conVar; - Parent = parent; - } - - public ConVar ConVar { get; } - public IConfigContext Parent { get; } - - public T? GetValue() - { - return GetValue(ConVar); - } - - public void SetValue(T? value) - { - SetValue(ConVar!, value); - } - - public void SetValue(ConVar conVar, T1 value) - { - Parent.SetValue(conVar, value); - } - - public T1? GetValue(ConVar convar) - { - return Parent.GetValue(convar); - } -} - -public interface IConfigurationVerbose -{ - public ConfigContext Context { get; set; } - public void InitializeConfig(); -} - -public interface IUpdateInvoker -{ - public void UpdateConfiguration(); -} - -[ViewModelRegister(typeof(StringConfigurationView))] -public partial class StringConfigurationViewModel : ViewModelBase , IConfigurationVerbose, IUpdateInvoker -{ - [ObservableProperty] private string _configText = string.Empty; - [ObservableProperty] private string? _configName = string.Empty; + private List<(PropertyInfo, IConfigControl)> _units = []; - private string _oldText = string.Empty; + private Type _objectType = typeof(object); - public required ConfigContext Context { get; set; } - public void InitializeConfig() + public string ConfigName { get; } + public bool Dirty => _units.Any(dirty => dirty.Item2.Dirty); + + public ComplexUnitConfigControl(string name, object obj) { - ConfigName = Context.ConVar.Name; - _oldText = Context.GetValue() ?? string.Empty; - ConfigText = _oldText; + Orientation = Orientation.Vertical; + Margin = new Thickness(5); + Spacing = 2f; + ConfigName = name; + SetValue(obj); } - public void UpdateConfiguration() + + public void SetValue(object value) { - if (_oldText == ConfigText) return; - Context.SetValue(ConfigText); + _units.Clear(); + Children.Clear(); + _objectType = value.GetType(); + + Children.Add(new Label() + { + Content = ConfigName + }); + + foreach (var propInfo in _objectType.GetProperties()) + { + if(propInfo.PropertyType.IsInterface) + continue; + + var propValue = propInfo.GetValue(value); + + var control = ConfigControlHelper.GetConfigControl(propInfo.Name, propValue); + + Children.Add(control as Control); + _units.Add((propInfo,control)); + } + } + + public object GetValue() + { + var obj = ConfigControlHelper.CreateDefaultValue(_objectType); + foreach (var (fieldInfo, configControl) in _units) + { + fieldInfo.SetValue(obj, configControl.GetValue()); + } + + return obj; + } +} + +public sealed class ArrayUnitConfigControl : StackPanel, IConfigControl +{ + private readonly List _itemControls = []; + private readonly StackPanel _itemsPanel = new StackPanel() { Orientation = Orientation.Vertical }; + private readonly Button _addButton = new Button() { Content = "Add Item" }; + private int oldCount; + private readonly Type _elementType; + + public string ConfigName { get; } + public bool Dirty => _itemControls.Any(dirty => dirty.Dirty) || _itemControls.Count != oldCount; + + public ArrayUnitConfigControl(string name, object value) + { + _elementType = value.GetType().GetElementType(); + + ConfigName = name; + Orientation = Orientation.Vertical; + Margin = new Thickness(5); + Spacing = 2f; + + Children.Add(new Label { Content = name }); + Children.Add(_itemsPanel); + Children.Add(_addButton); + + _addButton.Click += (_, _) => AddItem(ConfigControlHelper.CreateDefaultValue(_elementType)); + + SetValue(value); + oldCount = _itemControls.Count; + } + + private void AddItem(object value) + { + var itemPanel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; + var control = ConfigControlHelper.GetConfigControl(_itemControls.Count.ToString(), value); + var removeButton = new Button { Content = "Remove" }; + + removeButton.Click += (_, _) => + { + _itemControls.Remove(control); + _itemsPanel.Children.Remove(itemPanel); + }; + + itemPanel.Children.Add((Control)control); + itemPanel.Children.Add(removeButton); + + _itemsPanel.Children.Add(itemPanel); + _itemControls.Add(control); + } + + public void SetValue(object value) + { + _itemControls.Clear(); + _itemsPanel.Children.Clear(); + + if (value is IEnumerable list) + { + foreach (var item in list) + { + AddItem(item); + } + } + } + + public object GetValue() + { + return ConvertArray(_itemControls.Select(c => c.GetValue()).ToArray(), _elementType); } - protected override void InitialiseInDesignMode() + public static Array ConvertArray(Array sourceArray, Type targetType) { + int length = sourceArray.Length; + var newArray = Array.CreateInstance(targetType, length); + + for (int i = 0; i < length; i++) + { + var value = sourceArray.GetValue(i); + var converted = Convert.ChangeType(value, targetType); + newArray.SetValue(converted, i); + } + + return newArray; + } +} + +public abstract class UnitConfigControl : StackPanel, IConfigControl where T : notnull +{ + private readonly Label _nameLabel = new Label(); + private readonly TextBox _valueLabel = new TextBox(); + private string _originalValue; + + public string ConfigName { get; private set;} + + public bool Dirty + { + get + { + return _originalValue != ConfigValue; + } } - protected override void Initialise() + protected string? ConfigValue { + get => _valueLabel.Text; + set => _valueLabel.Text = value; } + + public UnitConfigControl(string name, T value) + { + ConfigName = name; + Orientation = Orientation.Horizontal; + Children.Add(_nameLabel); + Children.Add(_valueLabel); + + _nameLabel.Content = name; + _nameLabel.VerticalAlignment = VerticalAlignment.Center; + + SetConfValue(value); + _originalValue = ConfigValue; + } + + public abstract void SetConfValue(T value); + + public abstract T GetConfValue(); + + public void SetValue(object value) + { + SetConfValue((T)value); + } + + public object GetValue() + { + return GetConfValue()!; + } +} + +public sealed class StringUnitConfigControl(string name, string value) : UnitConfigControl(name, value) +{ + public override void SetConfValue(string value) + { + ConfigValue = value; + } + + public override string GetConfValue() + { + return ConfigValue ?? string.Empty; + } +} + +public sealed class IntUnitConfigControl(string name, int value) : UnitConfigControl(name, value) +{ + public override void SetConfValue(int value) + { + ConfigValue = value.ToString(); + } + + public override int GetConfValue() + { + Debug.Assert(ConfigValue != null, nameof(ConfigValue) + " != null"); + + return int.Parse(ConfigValue); + } +} + +public sealed class FloatUnitConfigControl(string name, float value) : UnitConfigControl(name, value) +{ + + public CultureInfo CultureInfo = CultureInfo.InvariantCulture; + + public override void SetConfValue(float value) + { + ConfigValue = value.ToString(CultureInfo); + } + + public override float GetConfValue() + { + Debug.Assert(ConfigValue != null, nameof(ConfigValue) + " != null"); + + return float.Parse(ConfigValue, CultureInfo); + } +} + + +public interface IConfigControl +{ + public string ConfigName { get; } + public bool Dirty {get;} + public abstract void SetValue(object value); + public abstract object GetValue(); } \ No newline at end of file diff --git a/Nebula.Launcher/Views/Config/StringConfigurationView.axaml b/Nebula.Launcher/Views/Config/StringConfigurationView.axaml deleted file mode 100644 index 8074c38..0000000 --- a/Nebula.Launcher/Views/Config/StringConfigurationView.axaml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - diff --git a/Nebula.Launcher/Views/Config/StringConfigurationView.axaml.cs b/Nebula.Launcher/Views/Config/StringConfigurationView.axaml.cs deleted file mode 100644 index 8f4570a..0000000 --- a/Nebula.Launcher/Views/Config/StringConfigurationView.axaml.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using Nebula.Launcher.ViewModels.Pages; - -namespace Nebula.Launcher.Views.Config; - -public partial class StringConfigurationView : UserControl -{ - public StringConfigurationView() - { - InitializeComponent(); - } - - public StringConfigurationView(StringConfigurationViewModel viewModel) - : this() - { - DataContext = viewModel; - } -} \ No newline at end of file diff --git a/Nebula.Launcher/Views/Pages/ConfigurationView.axaml b/Nebula.Launcher/Views/Pages/ConfigurationView.axaml index 4b7b84c..273f98a 100644 --- a/Nebula.Launcher/Views/Pages/ConfigurationView.axaml +++ b/Nebula.Launcher/Views/Pages/ConfigurationView.axaml @@ -10,31 +10,25 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/Nebula.sln.DotSettings.user b/Nebula.sln.DotSettings.user index b058021..4b0e9f0 100644 --- a/Nebula.sln.DotSettings.user +++ b/Nebula.sln.DotSettings.user @@ -8,6 +8,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -15,6 +16,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -23,9 +25,12 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded INFO \ No newline at end of file