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