1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Debugger - Added datamodel debugger

Shared UI - Added data model VMs
This commit is contained in:
Robert 2021-11-20 22:48:32 +01:00
parent e05de0d780
commit e4c1c99e27
39 changed files with 2737 additions and 54 deletions

View File

@ -14,6 +14,8 @@
<entry key="Artemis.UI.Avalonia/Screens/Sidebar/Views/SidebarProfileConfigurationView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Screens/SidebarView.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Artemis.UI.Avalonia/Views/MainWindow.axaml" value="Artemis.UI.Avalonia/Artemis.UI.Avalonia.csproj" />
<entry key="Avalonia/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugView.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
<entry key="Avalonia/Artemis.UI/Styles/Artemis.axaml" value="Avalonia/Artemis.UI.Linux/Artemis.UI.Linux.csproj" />
</map>
</option>
</component>

View File

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.Ninject;
using Artemis.Core.ScriptingProviders;
using Artemis.Storage;
@ -23,17 +21,19 @@ namespace Artemis.Core.Services
internal class CoreService : ICoreService
{
internal static IKernel? Kernel;
private readonly Stopwatch _frameStopWatch;
private readonly ILogger _logger;
private readonly PluginSetting<LogEventLevel> _loggingLevel;
private readonly IModuleService _moduleService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
private readonly IModuleService _moduleService;
private readonly IScriptingService _scriptingService;
private readonly IRgbService _rgbService;
private readonly IScriptingService _scriptingService;
private readonly List<Exception> _updateExceptions = new();
private int _frames;
private DateTime _lastExceptionLog;
private DateTime _lastFrameRateSample;
// ReSharper disable UnusedParameter.Local
public CoreService(IKernel kernel,
@ -151,6 +151,15 @@ namespace Artemis.Core.Services
{
_rgbService.CloseRender();
_frameStopWatch.Stop();
_frames++;
if ((DateTime.Now - _lastFrameRateSample).TotalSeconds >= 1)
{
FrameRate = _frames;
_frames = 0;
_lastFrameRateSample = DateTime.Now;
}
FrameTime = _frameStopWatch.Elapsed;
LogUpdateExceptions();
@ -181,6 +190,7 @@ namespace Artemis.Core.Services
Initialized?.Invoke(this, EventArgs.Empty);
}
public int FrameRate { get; private set; }
public TimeSpan FrameTime { get; private set; }
public bool ProfileRenderingDisabled { get; set; }
public List<string> StartupArguments { get; set; }

View File

@ -18,6 +18,11 @@ namespace Artemis.Core.Services
/// </summary>
TimeSpan FrameTime { get; }
/// <summary>
/// The amount of frames rendered each second
/// </summary>
public int FrameRate { get; }
/// <summary>
/// Gets or sets whether profiles are rendered each frame by calling their Render method
/// </summary>

View File

@ -579,9 +579,9 @@ namespace Artemis.Core.Services
{
if (plugin.IsEnabled)
throw new ArtemisCoreException("Cannot remove the settings of an enabled plugin");
_pluginRepository.RemoveSettings(plugin.Guid);
foreach (DeviceEntity deviceEntity in _deviceRepository.GetAll().Where(e => e.DeviceProvider == plugin.Guid.ToString()))
foreach (DeviceEntity deviceEntity in _deviceRepository.GetAll().Where(e => e.DeviceProvider == plugin.Guid.ToString()))
_deviceRepository.Remove(deviceEntity);
plugin.Settings?.ClearSettings();
@ -602,7 +602,10 @@ namespace Artemis.Core.Services
if (!saveState)
{
OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature));
throw new ArtemisCoreException("Cannot enable a feature that requires elevation without saving it's state.");
if (isAutoEnable)
_logger.Warning("Skipped auto-enabling plugin feature {feature} - {plugin} because it requires elevation but state isn't being saved", pluginFeature, pluginFeature.Plugin);
else
throw new ArtemisCoreException("Cannot enable a feature that requires elevation without saving it's state.");
}
pluginFeature.Entity.IsEnabled = true;

View File

@ -32,5 +32,15 @@ namespace Artemis.Core
return Enum.GetValues(t).Cast<Enum>().Select(e => (e, e.Humanize())).ToList();
}
/// <summary>
/// Humanizes the given enum value using the Humanizer library
/// </summary>
/// <param name="value">The enum value to humanize</param>
/// <returns>A humanized string describing the given value</returns>
public static string HumanizeValue(Enum value)
{
return value.Humanize();
}
}
}

View File

@ -174,6 +174,468 @@
<member name="M:Artemis.UI.Shared.Converters.SKColorToColorConverter.ConvertBack(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.Converters.TypeToStringConverter">
<summary>
Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
</summary>
</member>
<member name="M:Artemis.UI.Shared.Converters.TypeToStringConverter.Convert(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.Converters.TypeToStringConverter.ConvertBack(System.Object,System.Type,System.Object,System.Globalization.CultureInfo)">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel`1">
<summary>
Represents a <see cref="T:Artemis.Core.Modules.DataModel" /> display view model
</summary>
<typeparam name="T">The type of the data model</typeparam>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel`1.DisplayValue">
<summary>
Gets or sets value that the view model must display
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel`1.UpdateValue(System.Object)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel`1.OnDisplayValueUpdated">
<summary>
Occurs when the display value is updated
</summary>
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel">
<summary>
For internal use only, implement <see cref="T:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel`1" /> instead.
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel.PropertyDescription">
<summary>
Gets the property description of this value
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel.InternalGuard">
<summary>
Prevents this type being implemented directly, implement <see cref="T:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel`1" /> instead.
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelDisplayViewModel.UpdateValue(System.Object)">
<summary>
Updates the display value
</summary>
<param name="model">The value to set</param>
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1">
<summary>
Represents a <see cref="T:Artemis.Core.Modules.DataModel" /> input view model
</summary>
<typeparam name="T">The type of the data model</typeparam>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1.#ctor(Artemis.Core.Modules.DataModelPropertyAttribute,`0)">
<summary>
Creates a new instance of the <see cref="T:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1" /> class
</summary>
<param name="targetDescription">The description of the property this input VM is representing</param>
<param name="initialValue">The initial value to set the input value to</param>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1.InputValue">
<summary>
Gets or sets the value shown in the input
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1.TargetDescription">
<summary>
Gets the description of the property this input VM is representing
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1.Submit">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1.Cancel">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel">
<summary>
For internal use only, implement <see cref="T:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1" /> instead.
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel.InternalGuard">
<summary>
Prevents this type being implemented directly, implement <see cref="T:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel`1" /> instead.
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel.CompatibleConversionTypes">
<summary>
Gets the types this input view model can support through type conversion. This list is defined when registering the
view model.
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel.Submit">
<summary>
Submits the input value and removes this view model.
<para>This is called automatically when the user presses enter or clicks outside the view</para>
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel.Cancel">
<summary>
Discards changes to the input value and removes this view model.
<para>This is called automatically when the user presses escape</para>
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel.OnSubmit">
<summary>
Called before the current value is submitted
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.DataModelInputViewModel.OnCancel">
<summary>
Called before the current value is discarded
</summary>
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.DataModelVisualizationRegistration">
<summary>
Represents a layer brush registered through
<see cref="M:Artemis.UI.Shared.Services.IDataModelUIService.RegisterDataModelInput``1(Artemis.Core.Plugin,System.Collections.Generic.IReadOnlyCollection{System.Type})" /> or
<see cref="M:Artemis.UI.Shared.Services.IDataModelUIService.RegisterDataModelDisplay``1(Artemis.Core.Plugin)" />
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelVisualizationRegistration.RegistrationType">
<summary>
Gets the type of registration, either a display or an input
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelVisualizationRegistration.Plugin">
<summary>
Gets the plugin that registered the visualization
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelVisualizationRegistration.SupportedType">
<summary>
Gets the type supported by the visualization
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelVisualizationRegistration.ViewModelType">
<summary>
Gets the view model type of the visualization
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.DataModelVisualizationRegistration.CompatibleConversionTypes">
<summary>
Gets a read only collection of types this visualization can convert to and from
</summary>
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.RegistrationType">
<summary>
Represents a type of data model visualization registration
</summary>
</member>
<member name="F:Artemis.UI.Shared.DataModelVisualization.RegistrationType.Display">
<summary>
A visualization used for displaying values
</summary>
</member>
<member name="F:Artemis.UI.Shared.DataModelVisualization.RegistrationType.Input">
<summary>
A visualization used for inputting values
</summary>
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelEventViewModel">
<summary>
Represents a view model that visualizes an event data model property
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelEventViewModel.DisplayValueType">
<summary>
Gets the type of event arguments this event triggers and that must be displayed as children
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelEventViewModel.Update(Artemis.UI.Shared.Services.IDataModelUIService,Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelEventViewModel.GetCurrentValue">
<summary>
Always returns <see langword="null"/> for data model events
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelEventViewModel.ToString">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel">
<summary>
Represents a view model that wraps a regular <see cref="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel" /> but contained in
a <see cref="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel" />
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel.Index">
<summary>
Gets the index of the element within the list
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel.ListType">
<summary>
Gets the type of elements contained in the list
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel.DisplayValue">
<summary>
Gets the value of the property that is being visualized
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel.DisplayViewModel">
<summary>
Gets the view model that handles displaying the property
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel.DisplayPath">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel.Update(Artemis.UI.Shared.Services.IDataModelUIService,Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel.GetCurrentValue">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertiesViewModel.ToString">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertyViewModel">
<summary>
Represents a view model that visualizes a single data model property contained in a
<see cref="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel" />
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertyViewModel.Index">
<summary>
Gets the index of the element within the list
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertyViewModel.ListType">
<summary>
Gets the type of elements contained in the list
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertyViewModel.GetCurrentValue">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertyViewModel.Update(Artemis.UI.Shared.Services.IDataModelUIService,Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListPropertyViewModel.ToString">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel">
<summary>
Represents a view model that visualizes a list data model property
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel.List">
<summary>
Gets the instance of the list that is being visualized
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel.ListCount">
<summary>
Gets amount of elements in the list that is being visualized
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel.DisplayValueType">
<summary>
Gets the type of elements this list contains and that must be displayed as children
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel.CountDisplay">
<summary>
Gets a human readable display count
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel.ListChildren">
<summary>
Gets a list of child view models that visualize the elements in the list
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel.Update(Artemis.UI.Shared.Services.IDataModelUIService,Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelListViewModel.ToString">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel">
<summary>
Represents a view model that visualizes a class (POCO) data model property containing child properties
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel.DisplayValueType">
<summary>
Gets the type of the property that is being visualized
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel.DisplayValue">
<summary>
Gets the value of the property that is being visualized
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel.Update(Artemis.UI.Shared.Services.IDataModelUIService,Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel.GetCurrentValue">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel.ToString">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertyViewModel">
<summary>
Represents a view model that visualizes a single data model property contained in a
<see cref="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel" />
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertyViewModel.DisplayValue">
<summary>
Gets the value of the property that is being visualized
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertyViewModel.DisplayValueType">
<summary>
Gets the type of the property that is being visualized
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertyViewModel.DisplayViewModel">
<summary>
Gets the view model used to display the display value
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertyViewModel.Update(Artemis.UI.Shared.Services.IDataModelUIService,Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration)">
<inheritdoc />
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertyViewModel.ToString">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration">
<summary>
Represents a configuration to use while updating a <see cref="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel" />
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration.#ctor(System.Boolean)">
<summary>
Creates a new instance of the <see cref="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration" /> class
</summary>
<param name="createEventChildren">A boolean indicating whether or not event children should be created</param>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration.CreateEventChildren">
<summary>
Gets a boolean indicating whether or not event children should be created
</summary>
</member>
<member name="T:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel">
<summary>
Represents a base class for a view model that visualizes a part of the data model
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.IsRootViewModel">
<summary>
Gets a boolean indicating whether this view model is at the root of the data model
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.DataModelPath">
<summary>
Gets the data model path to the property this view model is visualizing
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.Path">
<summary>
Gets a string representation of the path backing this model
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.Depth">
<summary>
Gets the property depth of the view model
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.DataModel">
<summary>
Gets the data model backing this view model
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.PropertyDescription">
<summary>
Gets the property description of the property this view model is visualizing
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.Parent">
<summary>
Gets the parent of this view model
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.Children">
<summary>
Gets or sets an observable collection containing the children of this view model
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.IsMatchingFilteredTypes">
<summary>
Gets a boolean indicating whether the property being visualized matches the types last provided to
<see cref="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.ApplyTypeFilter(System.Boolean,System.Type[])" />
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.IsVisualizationExpanded">
<summary>
Gets or sets a boolean indicating whether the visualization is expanded, exposing the <see cref="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.Children" />
</summary>
</member>
<member name="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.DisplayPath">
<summary>
Gets a user-friendly representation of the <see cref="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.DataModelPath" />
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.Update(Artemis.UI.Shared.Services.IDataModelUIService,Artemis.UI.Shared.DataModelVisualization.Shared.DataModelUpdateConfiguration)">
<summary>
Updates the datamodel and if in an parent, any children
</summary>
<param name="dataModelUIService">The data model UI service used during update</param>
<param name="configuration">The configuration to apply while updating</param>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.GetCurrentValue">
<summary>
Gets the current value of the property being visualized
</summary>
<returns>The current value of the property being visualized</returns>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.ApplyTypeFilter(System.Boolean,System.Type[])">
<summary>
Determines whether the provided types match the type of the property being visualized and sets the result in
<see cref="P:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.IsMatchingFilteredTypes" />
</summary>
<param name="looseMatch">Whether the type may be a loose match, meaning it can be cast or converted</param>
<param name="filteredTypes">The types to filter</param>
</member>
<member name="E:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.UpdateRequested">
<summary>
Occurs when an update to the property this view model visualizes is requested
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.OnUpdateRequested">
<summary>
Invokes the <see cref="E:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.UpdateRequested" /> event
</summary>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.Dispose(System.Boolean)">
<summary>
Releases the unmanaged resources used by the object and optionally releases the managed resources.
</summary>
<param name="disposing">
<see langword="true" /> to release both managed and unmanaged resources;
<see langword="false" /> to release only unmanaged resources.
</param>
</member>
<member name="M:Artemis.UI.Shared.DataModelVisualization.Shared.DataModelVisualizationViewModel.Dispose">
<inheritdoc />
</member>
<member name="T:Artemis.UI.Shared.DefaultTypes.DataModel.Display.DefaultDataModelDisplayViewModel">
<summary>
Represents the default data model display view model that is used when no display viewmodel specific for the type
is registered
</summary>
</member>
<member name="T:Artemis.UI.Shared.Events.DataModelInputDynamicEventArgs">
<summary>
Provides data about selection events raised by <see cref="!:DataModelDynamicViewModel" />
@ -239,7 +701,7 @@
If applicable, the previous active profile element before the event was raised
</summary>
</member>
<member name="T:Artemis.UI.Shared.Exceptions.ArtemisSharedUIException">
<member name="T:Artemis.UI.Shared.ArtemisSharedUIException">
<summary>
Represents errors that occur within the Artemis Shared UI library
</summary>
@ -453,6 +915,104 @@
</summary>
<returns>The builder that can be used to configure the dialog</returns>
</member>
<member name="T:Artemis.UI.Shared.Services.IDataModelUIService">
<summary>
A service for UI related data model tasks
</summary>
</member>
<member name="P:Artemis.UI.Shared.Services.IDataModelUIService.RegisteredDataModelEditors">
<summary>
Gets a read-only list of all registered data model editors
</summary>
</member>
<member name="P:Artemis.UI.Shared.Services.IDataModelUIService.RegisteredDataModelDisplays">
<summary>
Gets a read-only list of all registered data model displays
</summary>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.GetMainDataModelVisualization">
<summary>
Creates a data model visualization view model for the main data model
</summary>
<returns>
A data model visualization view model containing all data model expansions and modules that expand the main
data model
</returns>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.GetPluginDataModelVisualization(System.Collections.Generic.List{Artemis.Core.Modules.Module},System.Boolean)">
<summary>
Creates a data model visualization view model for the data model of the provided plugin feature
</summary>
<param name="modules">The modules to create the data model visualization view model for</param>
<param name="includeMainDataModel">
Whether or not also to include the main data model (and therefore any modules marked
as <see cref="P:Artemis.Core.Modules.Module.IsAlwaysAvailable" />)
</param>
<returns>A data model visualization view model containing the data model of the provided feature</returns>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.UpdateModules(Artemis.UI.Shared.DataModelVisualization.Shared.DataModelPropertiesViewModel)">
<summary>
Updates the children of the provided main data model visualization, removing disabled children and adding newly
enabled children
</summary>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.RegisterDataModelInput``1(Artemis.Core.Plugin,System.Collections.Generic.IReadOnlyCollection{System.Type})">
<summary>
Registers a new data model editor
</summary>
<typeparam name="T">The type of the editor</typeparam>
<param name="plugin">The plugin this editor belongs to</param>
<param name="compatibleConversionTypes">A collection of extra types this editor supports</param>
<returns>A registration that can be used to remove the editor</returns>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.RegisterDataModelDisplay``1(Artemis.Core.Plugin)">
<summary>
Registers a new data model display
</summary>
<typeparam name="T">The type of the display</typeparam>
<param name="plugin">The plugin this display belongs to</param>
<returns>A registration that can be used to remove the display</returns>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.RemoveDataModelInput(Artemis.UI.Shared.DataModelVisualization.DataModelVisualizationRegistration)">
<summary>
Removes a data model editor
</summary>
<param name="registration">
The registration of the editor as returned by <see cref="M:Artemis.UI.Shared.Services.IDataModelUIService.RegisterDataModelInput``1(Artemis.Core.Plugin,System.Collections.Generic.IReadOnlyCollection{System.Type})" />
</param>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.RemoveDataModelDisplay(Artemis.UI.Shared.DataModelVisualization.DataModelVisualizationRegistration)">
<summary>
Removes a data model display
</summary>
<param name="registration">
The registration of the display as returned by <see cref="M:Artemis.UI.Shared.Services.IDataModelUIService.RegisterDataModelDisplay``1(Artemis.Core.Plugin)" />
</param>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.GetDataModelDisplayViewModel(System.Type,Artemis.Core.Modules.DataModelPropertyAttribute,System.Boolean)">
<summary>
Creates the most appropriate display view model for the provided <paramref name="propertyType" /> that can display
a value
</summary>
<param name="propertyType">The type of data model property to find a display view model for</param>
<param name="description">The description of the data model property</param>
<param name="fallBackToDefault">
If <see langword="true"></see>, a simple .ToString() display view model will be
returned if nothing else is found
</param>
<returns>The most appropriate display view model for the provided <paramref name="propertyType"></paramref></returns>
</member>
<member name="M:Artemis.UI.Shared.Services.IDataModelUIService.GetDataModelInputViewModel(System.Type,Artemis.Core.Modules.DataModelPropertyAttribute,System.Object,System.Action{System.Object,System.Boolean})">
<summary>
Creates the most appropriate input view model for the provided <paramref name="propertyType" /> that allows
inputting a value
</summary>
<param name="propertyType">The type of data model property to find a display view model for</param>
<param name="description">The description of the data model property</param>
<param name="initialValue">The initial value to show in the input</param>
<param name="updateCallback">A function to call whenever the input was updated (submitted or not)</param>
<returns>The most appropriate input view model for the provided <paramref name="propertyType" /></returns>
</member>
<member name="T:Artemis.UI.Shared.ViewModelBase">
<summary>
Represents the base class for Artemis view models

View File

@ -10,6 +10,11 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>C:\Repos\Artemis\src\Artemis.UI.Avalonia.Shared\Artemis.UI.Avalonia.Shared.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Remove="Artemis.UI.Avalonia.Shared.csproj.DotSettings" />
<None Remove="Artemis.UI.Shared.csproj.DotSettings" />
<None Remove="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
@ -20,6 +25,12 @@
<PackageReference Include="FluentAvaloniaUI" Version="1.1.5" />
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<Page Include="DefaultTypes\DataModel\Display\DefaultDataModelDisplayView.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Artemis.Core\Artemis.Core.csproj" />
</ItemGroup>

View File

@ -1,3 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cwindowservice/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -4,4 +4,4 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.ProfileConfigurationIcon">
</UserControl>
</UserControl>

View File

@ -0,0 +1,29 @@
using System;
using System.Globalization;
using Artemis.Core;
using Avalonia.Data.Converters;
namespace Artemis.UI.Shared.Converters
{
/// <summary>
/// Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
/// </summary>
public class TypeToStringConverter : IValueConverter
{
/// <inheritdoc />
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
bool humanizeProvided = bool.TryParse(parameter?.ToString(), out bool humanize);
if (value is Type type)
return type.GetDisplayName(humanizeProvided && humanize);
return value?.ToString();
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,73 @@
using System.Diagnostics.CodeAnalysis;
using Artemis.Core.Modules;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization
{
/// <summary>
/// Represents a <see cref="DataModel" /> display view model
/// </summary>
/// <typeparam name="T">The type of the data model</typeparam>
public abstract class DataModelDisplayViewModel<T> : DataModelDisplayViewModel
{
[AllowNull] private T _displayValue = default!;
/// <summary>
/// Gets or sets value that the view model must display
/// </summary>
[AllowNull]
public T DisplayValue
{
get => _displayValue;
set
{
if (Equals(value, _displayValue)) return;
this.RaiseAndSetIfChanged(ref _displayValue, value);
OnDisplayValueUpdated();
}
}
internal override object InternalGuard => new();
/// <inheritdoc />
public override void UpdateValue(object? model)
{
DisplayValue = model is T value ? value : default;
}
/// <summary>
/// Occurs when the display value is updated
/// </summary>
protected virtual void OnDisplayValueUpdated()
{
}
}
/// <summary>
/// For internal use only, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
/// </summary>
public abstract class DataModelDisplayViewModel : ViewModelBase
{
private DataModelPropertyAttribute? _propertyDescription;
/// <summary>
/// Gets the property description of this value
/// </summary>
public DataModelPropertyAttribute? PropertyDescription
{
get => _propertyDescription;
internal set => this.RaiseAndSetIfChanged(ref _propertyDescription, value);
}
/// <summary>
/// Prevents this type being implemented directly, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
/// </summary>
internal abstract object InternalGuard { get; }
/// <summary>
/// Updates the display value
/// </summary>
/// <param name="model">The value to set</param>
public abstract void UpdateValue(object? model);
}
}

View File

@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Artemis.Core.Modules;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization
{
/// <summary>
/// Represents a <see cref="DataModel" /> input view model
/// </summary>
/// <typeparam name="T">The type of the data model</typeparam>
public abstract class DataModelInputViewModel<T> : DataModelInputViewModel
{
private bool _closed;
[AllowNull] private T _inputValue = default!;
/// <summary>
/// Creates a new instance of the <see cref="DataModelInputViewModel{T}" /> class
/// </summary>
/// <param name="targetDescription">The description of the property this input VM is representing</param>
/// <param name="initialValue">The initial value to set the input value to</param>
protected DataModelInputViewModel(DataModelPropertyAttribute targetDescription, T initialValue)
{
TargetDescription = targetDescription;
InputValue = initialValue;
}
/// <summary>
/// Gets or sets the value shown in the input
/// </summary>
[AllowNull]
public T InputValue
{
get => _inputValue;
set => this.RaiseAndSetIfChanged(ref _inputValue, value);
}
/// <summary>
/// Gets the description of the property this input VM is representing
/// </summary>
public DataModelPropertyAttribute TargetDescription { get; }
internal override object InternalGuard { get; } = new();
/// <inheritdoc />
public sealed override void Submit()
{
if (_closed)
return;
_closed = true;
OnSubmit();
UpdateCallback(InputValue, true);
}
/// <inheritdoc />
public sealed override void Cancel()
{
if (_closed)
return;
_closed = true;
OnCancel();
UpdateCallback(InputValue, false);
}
}
/// <summary>
/// For internal use only, implement <see cref="DataModelInputViewModel{T}" /> instead.
/// </summary>
public abstract class DataModelInputViewModel : ReactiveObject
{
/// <summary>
/// Prevents this type being implemented directly, implement <see cref="DataModelInputViewModel{T}" /> instead.
/// </summary>
// ReSharper disable once UnusedMember.Global
internal abstract object InternalGuard { get; }
internal Action<object?, bool> UpdateCallback { get; set; } = null!; // Set right after construction
/// <summary>
/// Gets the types this input view model can support through type conversion. This list is defined when registering the
/// view model.
/// </summary>
internal IReadOnlyCollection<Type>? CompatibleConversionTypes { get; set; }
/// <summary>
/// Submits the input value and removes this view model.
/// <para>This is called automatically when the user presses enter or clicks outside the view</para>
/// </summary>
public abstract void Submit();
/// <summary>
/// Discards changes to the input value and removes this view model.
/// <para>This is called automatically when the user presses escape</para>
/// </summary>
public abstract void Cancel();
/// <summary>
/// Called before the current value is submitted
/// </summary>
protected virtual void OnSubmit()
{
}
/// <summary>
/// Called before the current value is discarded
/// </summary>
protected virtual void OnCancel()
{
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using Artemis.Core;
using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared.DataModelVisualization
{
/// <summary>
/// Represents a layer brush registered through
/// <see cref="IDataModelUIService.RegisterDataModelInput{T}" /> or
/// <see cref="IDataModelUIService.RegisterDataModelDisplay{T}" />
/// </summary>
public class DataModelVisualizationRegistration
{
private readonly IDataModelUIService _dataModelUIService;
internal DataModelVisualizationRegistration(IDataModelUIService dataModelUIService,
RegistrationType registrationType,
Plugin plugin,
Type supportedType,
Type viewModelType)
{
_dataModelUIService = dataModelUIService;
RegistrationType = registrationType;
Plugin = plugin;
SupportedType = supportedType;
ViewModelType = viewModelType;
if (Plugin != Constants.CorePlugin)
Plugin.Disabled += InstanceOnDisabled;
}
/// <summary>
/// Gets the type of registration, either a display or an input
/// </summary>
public RegistrationType RegistrationType { get; }
/// <summary>
/// Gets the plugin that registered the visualization
/// </summary>
public Plugin Plugin { get; }
/// <summary>
/// Gets the type supported by the visualization
/// </summary>
public Type SupportedType { get; }
/// <summary>
/// Gets the view model type of the visualization
/// </summary>
public Type ViewModelType { get; }
/// <summary>
/// Gets a read only collection of types this visualization can convert to and from
/// </summary>
public IReadOnlyCollection<Type>? CompatibleConversionTypes { get; internal set; }
internal void Unsubscribe()
{
if (Plugin != Constants.CorePlugin)
Plugin.Disabled -= InstanceOnDisabled;
}
private void InstanceOnDisabled(object? sender, EventArgs e)
{
if (RegistrationType == RegistrationType.Input)
_dataModelUIService.RemoveDataModelInput(this);
else if (RegistrationType == RegistrationType.Display)
_dataModelUIService.RemoveDataModelDisplay(this);
}
}
/// <summary>
/// Represents a type of data model visualization registration
/// </summary>
public enum RegistrationType
{
/// <summary>
/// A visualization used for displaying values
/// </summary>
Display,
/// <summary>
/// A visualization used for inputting values
/// </summary>
Input
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes an event data model property
/// </summary>
public class DataModelEventViewModel : DataModelVisualizationViewModel
{
private Type? _displayValueType;
internal DataModelEventViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath)
{
}
/// <summary>
/// Gets the type of event arguments this event triggers and that must be displayed as children
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
DisplayValueType = DataModelPath?.GetPropertyType();
if (configuration != null)
{
if (configuration.CreateEventChildren)
PopulateProperties(dataModelUIService, configuration);
else if (Children.Any())
Children.Clear();
}
// Only update children if the parent is expanded
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
return;
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
}
/// <summary>
/// Always returns <see langword="null"/> for data model events
/// </summary>
public override object? GetCurrentValue()
{
return null;
}
/// <inheritdoc />
public override string? ToString()
{
return DisplayPath ?? Path;
}
internal override int GetChildDepth()
{
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using System.Linq;
using Artemis.UI.Shared.Services;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that wraps a regular <see cref="DataModelPropertiesViewModel" /> but contained in
/// a <see cref="DataModelListViewModel" />
/// </summary>
public class DataModelListPropertiesViewModel : DataModelPropertiesViewModel
{
private object? _displayValue;
private int _index;
private Type? _listType;
internal DataModelListPropertiesViewModel(Type listType, string? name) : base(null, null, null)
{
ListType = listType;
}
/// <summary>
/// Gets the index of the element within the list
/// </summary>
public int Index
{
get => _index;
set => this.RaiseAndSetIfChanged(ref _index, value);
}
/// <summary>
/// Gets the type of elements contained in the list
/// </summary>
public Type? ListType
{
get => _listType;
set => this.RaiseAndSetIfChanged(ref _listType, value);
}
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public new object? DisplayValue
{
get => _displayValue;
set => this.RaiseAndSetIfChanged(ref _displayValue, value);
}
/// <summary>
/// Gets the view model that handles displaying the property
/// </summary>
public DataModelVisualizationViewModel? DisplayViewModel => Children.FirstOrDefault();
/// <inheritdoc />
public override string? DisplayPath => null;
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
PopulateProperties(dataModelUIService, configuration);
if (DisplayViewModel == null)
return;
if (IsVisualizationExpanded && !DisplayViewModel.IsVisualizationExpanded)
DisplayViewModel.IsVisualizationExpanded = IsVisualizationExpanded;
DisplayViewModel.Update(dataModelUIService, null);
}
/// <inheritdoc />
public override object? GetCurrentValue()
{
return DisplayValue;
}
/// <inheritdoc />
public override string ToString()
{
return $"[List item {Index}] {DisplayPath ?? Path}";
}
}
}

View File

@ -0,0 +1,71 @@
using System;
using Artemis.UI.Shared.Services;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes a single data model property contained in a
/// <see cref="DataModelListViewModel" />
/// </summary>
public class DataModelListPropertyViewModel : DataModelPropertyViewModel
{
private int _index;
private Type? _listType;
internal DataModelListPropertyViewModel(Type listType, DataModelDisplayViewModel displayViewModel, string? name) : base(null, null, null)
{
ListType = listType;
DisplayViewModel = displayViewModel;
}
internal DataModelListPropertyViewModel(Type listType, string? name) : base(null, null, null)
{
ListType = listType;
}
/// <summary>
/// Gets the index of the element within the list
/// </summary>
public int Index
{
get => _index;
internal set => this.RaiseAndSetIfChanged(ref _index, value);
}
/// <summary>
/// Gets the type of elements contained in the list
/// </summary>
public Type? ListType
{
get => _listType;
private set => this.RaiseAndSetIfChanged(ref _listType, value);
}
/// <inheritdoc />
public override object? GetCurrentValue()
{
return DisplayValue;
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
// Display value gets updated by parent, don't do anything if it is null
if (DisplayValue == null)
return;
if (DisplayViewModel == null)
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), PropertyDescription, true);
ListType = DisplayValue.GetType();
DisplayViewModel?.UpdateValue(DisplayValue);
}
/// <inheritdoc />
public override string ToString()
{
return $"[List item {Index}] {DisplayPath ?? Path} - {DisplayValue}";
}
}
}

View File

@ -0,0 +1,149 @@
using System;
using System.Collections;
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes a list data model property
/// </summary>
public class DataModelListViewModel : DataModelVisualizationViewModel
{
private string _countDisplay;
private Type? _displayValueType;
private IEnumerable? _list;
private ObservableCollection<DataModelVisualizationViewModel> _listChildren;
private int _listCount;
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath)
: base(dataModel, parent, dataModelPath)
{
_countDisplay = "0 items";
_listChildren = new ObservableCollection<DataModelVisualizationViewModel>();
}
/// <summary>
/// Gets the instance of the list that is being visualized
/// </summary>
public IEnumerable? List
{
get => _list;
private set => this.RaiseAndSetIfChanged(ref _list, value);
}
/// <summary>
/// Gets amount of elements in the list that is being visualized
/// </summary>
public int ListCount
{
get => _listCount;
private set => this.RaiseAndSetIfChanged(ref _listCount, value);
}
/// <summary>
/// Gets the type of elements this list contains and that must be displayed as children
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
}
/// <summary>
/// Gets a human readable display count
/// </summary>
public string CountDisplay
{
get => _countDisplay;
set => this.RaiseAndSetIfChanged(ref _countDisplay, value);
}
/// <summary>
/// Gets a list of child view models that visualize the elements in the list
/// </summary>
public ObservableCollection<DataModelVisualizationViewModel> ListChildren
{
get => _listChildren;
private set => this.RaiseAndSetIfChanged(ref _listChildren, value);
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
if (Parent != null && !Parent.IsVisualizationExpanded)
return;
List = GetCurrentValue() as IEnumerable;
DisplayValueType = List?.GetType();
if (List == null)
return;
int index = 0;
foreach (object item in List)
{
if (item == null)
continue;
DataModelVisualizationViewModel? child;
if (ListChildren.Count <= index)
{
child = CreateListChild(dataModelUIService, item.GetType(), DataModelPath?.GetPropertyDescription()?.ListItemName);
if (child == null)
continue;
ListChildren.Add(child);
}
else
{
child = ListChildren[index];
}
if (child is DataModelListPropertiesViewModel dataModelListClassViewModel)
{
dataModelListClassViewModel.DisplayValue = item;
dataModelListClassViewModel.Index = index;
}
else if (child is DataModelListPropertyViewModel dataModelListPropertyViewModel)
{
dataModelListPropertyViewModel.DisplayValue = item;
dataModelListPropertyViewModel.Index = index;
}
child.Update(dataModelUIService, configuration);
index++;
}
ListCount = index;
while (ListChildren.Count > ListCount)
ListChildren.RemoveAt(ListChildren.Count - 1);
CountDisplay = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}";
}
/// <inheritdoc />
public override string ToString()
{
return $"[List] {DisplayPath ?? Path} - {ListCount} item(s)";
}
private DataModelVisualizationViewModel? CreateListChild(IDataModelUIService dataModelUIService, Type listType, string? name)
{
// If a display VM was found, prefer to use that in any case
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription);
if (typeViewModel != null)
return new DataModelListPropertyViewModel(listType, typeViewModel, name);
// For primitives, create a property view model, it may be null that is fine
if (listType.IsPrimitive || listType.IsEnum || listType == typeof(string))
return new DataModelListPropertyViewModel(listType, name);
// For other value types create a child view model
if (listType.IsClass || listType.IsStruct())
return new DataModelListPropertiesViewModel(listType, name);
return null;
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes a class (POCO) data model property containing child properties
/// </summary>
public class DataModelPropertiesViewModel : DataModelVisualizationViewModel
{
private object? _displayValue;
private Type? _displayValueType;
internal DataModelPropertiesViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
: base(dataModel, parent, dataModelPath)
{
}
/// <summary>
/// Gets the type of the property that is being visualized
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
private set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
}
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public object? DisplayValue
{
get => _displayValue;
private set => this.RaiseAndSetIfChanged(ref _displayValue, value);
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
DisplayValueType = DataModelPath?.GetPropertyType();
// Only set a display value if ToString returns useful information and not just the type name
object? currentValue = GetCurrentValue();
if (currentValue != null && currentValue.ToString() != currentValue.GetType().ToString())
DisplayValue = currentValue.ToString();
else
DisplayValue = null;
// Always populate properties
PopulateProperties(dataModelUIService, configuration);
// Only update children if the parent is expanded
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
return;
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
}
/// <inheritdoc />
public override object? GetCurrentValue()
{
if (Parent == null || Parent.IsRootViewModel || IsRootViewModel)
return DataModel;
return base.GetCurrentValue();
}
/// <inheritdoc />
public override string? ToString()
{
return DisplayPath ?? Path;
}
internal override int GetChildDepth()
{
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a view model that visualizes a single data model property contained in a
/// <see cref="DataModelPropertiesViewModel" />
/// </summary>
public class DataModelPropertyViewModel : DataModelVisualizationViewModel
{
private object? _displayValue;
private Type? _displayValueType;
private DataModelDisplayViewModel? _displayViewModel;
internal DataModelPropertyViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
: base(dataModel, parent, dataModelPath)
{
}
/// <summary>
/// Gets the value of the property that is being visualized
/// </summary>
public object? DisplayValue
{
get => _displayValue;
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
}
/// <summary>
/// Gets the type of the property that is being visualized
/// </summary>
public Type? DisplayValueType
{
get => _displayValueType;
protected set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
}
/// <summary>
/// Gets the view model used to display the display value
/// </summary>
public DataModelDisplayViewModel? DisplayViewModel
{
get => _displayViewModel;
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
}
/// <inheritdoc />
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
{
if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel)
return;
if (DisplayViewModel == null)
{
Type? propertyType = DataModelPath?.GetPropertyType();
if (propertyType != null)
{
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription, true);
if (DisplayViewModel != null)
DisplayViewModel.PropertyDescription = DataModelPath?.GetPropertyDescription();
}
}
DisplayValue = GetCurrentValue();
DisplayValueType = DisplayValue != null ? DisplayValue.GetType() : DataModelPath?.GetPropertyType();
DisplayViewModel?.UpdateValue(DisplayValue);
}
/// <inheritdoc />
public override string ToString()
{
if (DisplayValueType != null)
return $"[{DisplayValueType.Name}] {DisplayPath ?? Path} - {DisplayValue}";
return $"{DisplayPath ?? Path} - {DisplayValue}";
}
}
}

View File

@ -0,0 +1,22 @@
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a configuration to use while updating a <see cref="DataModelVisualizationViewModel" />
/// </summary>
public class DataModelUpdateConfiguration
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelUpdateConfiguration" /> class
/// </summary>
/// <param name="createEventChildren">A boolean indicating whether or not event children should be created</param>
public DataModelUpdateConfiguration(bool createEventChildren)
{
CreateEventChildren = createEventChildren;
}
/// <summary>
/// Gets a boolean indicating whether or not event children should be created
/// </summary>
public bool CreateEventChildren { get; }
}
}

View File

@ -0,0 +1,373 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Services;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
{
/// <summary>
/// Represents a base class for a view model that visualizes a part of the data model
/// </summary>
public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposable
{
private const int MaxDepth = 4;
private ObservableCollection<DataModelVisualizationViewModel> _children;
private DataModel? _dataModel;
private bool _isMatchingFilteredTypes;
private bool _isVisualizationExpanded;
private DataModelVisualizationViewModel? _parent;
private DataModelPropertyAttribute? _propertyDescription;
private bool _populatedStaticChildren;
internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
{
_dataModel = dataModel;
_children = new ObservableCollection<DataModelVisualizationViewModel>();
_parent = parent;
DataModelPath = dataModelPath;
IsMatchingFilteredTypes = true;
if (parent == null)
IsRootViewModel = true;
else
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel?.DataModelDescription;
}
/// <summary>
/// Gets a boolean indicating whether this view model is at the root of the data model
/// </summary>
public bool IsRootViewModel { get; protected set; }
/// <summary>
/// Gets the data model path to the property this view model is visualizing
/// </summary>
public DataModelPath? DataModelPath { get; }
/// <summary>
/// Gets a string representation of the path backing this model
/// </summary>
public string? Path => DataModelPath?.Path;
/// <summary>
/// Gets the property depth of the view model
/// </summary>
public int Depth { get; private set; }
/// <summary>
/// Gets the data model backing this view model
/// </summary>
public DataModel? DataModel
{
get => _dataModel;
protected set => this.RaiseAndSetIfChanged(ref _dataModel, value);
}
/// <summary>
/// Gets the property description of the property this view model is visualizing
/// </summary>
public DataModelPropertyAttribute? PropertyDescription
{
get => _propertyDescription;
protected set => this.RaiseAndSetIfChanged(ref _propertyDescription, value);
}
/// <summary>
/// Gets the parent of this view model
/// </summary>
public DataModelVisualizationViewModel? Parent
{
get => _parent;
protected set => this.RaiseAndSetIfChanged(ref _parent, value);
}
/// <summary>
/// Gets or sets an observable collection containing the children of this view model
/// </summary>
public ObservableCollection<DataModelVisualizationViewModel> Children
{
get => _children;
set => this.RaiseAndSetIfChanged(ref _children, value);
}
/// <summary>
/// Gets a boolean indicating whether the property being visualized matches the types last provided to
/// <see cref="ApplyTypeFilter" />
/// </summary>
public bool IsMatchingFilteredTypes
{
get => _isMatchingFilteredTypes;
private set => this.RaiseAndSetIfChanged(ref _isMatchingFilteredTypes, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the visualization is expanded, exposing the <see cref="Children" />
/// </summary>
public bool IsVisualizationExpanded
{
get => _isVisualizationExpanded;
set
{
if (!this.RaiseAndSetIfChanged(ref _isVisualizationExpanded, value)) return;
RequestUpdate();
}
}
/// <summary>
/// Gets a user-friendly representation of the <see cref="DataModelPath" />
/// </summary>
public virtual string? DisplayPath => DataModelPath != null
? string.Join(" ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier))
: null;
/// <summary>
/// Updates the datamodel and if in an parent, any children
/// </summary>
/// <param name="dataModelUIService">The data model UI service used during update</param>
/// <param name="configuration">The configuration to apply while updating</param>
public abstract void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration);
/// <summary>
/// Gets the current value of the property being visualized
/// </summary>
/// <returns>The current value of the property being visualized</returns>
public virtual object? GetCurrentValue()
{
if (IsRootViewModel)
return null;
return DataModelPath?.GetValue();
}
/// <summary>
/// Determines whether the provided types match the type of the property being visualized and sets the result in
/// <see cref="IsMatchingFilteredTypes" />
/// </summary>
/// <param name="looseMatch">Whether the type may be a loose match, meaning it can be cast or converted</param>
/// <param name="filteredTypes">The types to filter</param>
public void ApplyTypeFilter(bool looseMatch, params Type[]? filteredTypes)
{
if (filteredTypes != null)
{
if (filteredTypes.All(t => t == null))
filteredTypes = null;
else
filteredTypes = filteredTypes.Where(t => t != null).ToArray();
}
// If the VM has children, its own type is not relevant
if (Children.Any())
{
foreach (DataModelVisualizationViewModel child in Children)
child?.ApplyTypeFilter(looseMatch, filteredTypes);
IsMatchingFilteredTypes = true;
return;
}
// If null is passed, clear the type filter
if (filteredTypes == null || filteredTypes.Length == 0)
{
IsMatchingFilteredTypes = true;
return;
}
// If the type couldn't be retrieved either way, assume false
Type? type = DataModelPath?.GetPropertyType();
if (type == null)
{
IsMatchingFilteredTypes = false;
return;
}
if (looseMatch)
IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) ||
t == typeof(Enum) && type.IsEnum ||
t == typeof(IEnumerable<>) && type.IsGenericEnumerable() ||
type.IsGenericType && t == type.GetGenericTypeDefinition());
else
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum);
}
internal virtual int GetChildDepth()
{
return 0;
}
internal void PopulateProperties(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? dataModelUpdateConfiguration)
{
if (IsRootViewModel && DataModel == null)
return;
Type? modelType = IsRootViewModel ? DataModel?.GetType() : DataModelPath?.GetPropertyType();
if (modelType == null)
throw new ArtemisSharedUIException("Failed to populate data model visualization properties, couldn't get a property type");
// Add missing static children only once, they're static after all
if (!_populatedStaticChildren)
{
foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken))
{
string childPath = AppendToPath(propertyInfo.Name);
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
continue;
if (propertyInfo.GetCustomAttribute<DataModelIgnoreAttribute>() != null)
continue;
MethodInfo? getMethod = propertyInfo.GetGetMethod();
if (getMethod == null || getMethod.GetParameters().Any())
continue;
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
if (child != null)
Children.Add(child);
}
_populatedStaticChildren = true;
}
// Remove static children that should be hidden
if (DataModel != null)
{
ReadOnlyCollection<PropertyInfo> hiddenProperties = DataModel.GetHiddenProperties();
foreach (PropertyInfo hiddenProperty in hiddenProperties)
{
string childPath = AppendToPath(hiddenProperty.Name);
DataModelVisualizationViewModel? toRemove = Children.FirstOrDefault(c => c.Path != null && c.Path == childPath);
if (toRemove != null)
Children.Remove(toRemove);
}
}
// Add missing dynamic children
object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue();
if (value is DataModel dataModel)
{
foreach (string key in dataModel.DynamicChildren.Keys.ToList())
{
string childPath = AppendToPath(key);
if (Children.Any(c => c.Path != null && c.Path.Equals(childPath)))
continue;
DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth());
if (child != null)
Children.Add(child);
}
}
// Remove dynamic children that have been removed from the data model
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList();
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in toRemoveDynamic)
Children.Remove(dataModelVisualizationViewModel);
}
private DataModelVisualizationViewModel? CreateChild(IDataModelUIService dataModelUIService, string path, int depth)
{
if (DataModel == null)
throw new ArtemisSharedUIException("Cannot create a data model visualization child VM for a parent without a data model");
if (depth > MaxDepth)
return null;
DataModelPath dataModelPath = new(DataModel, path);
if (!dataModelPath.IsValid)
return null;
PropertyInfo? propertyInfo = dataModelPath.GetPropertyInfo();
Type? propertyType = dataModelPath.GetPropertyType();
// Skip properties decorated with DataModelIgnore
if (propertyInfo != null && Attribute.IsDefined(propertyInfo, typeof(DataModelIgnoreAttribute)))
return null;
// Skip properties that are in the ignored properties list of the respective profile module/data model expansion
if (DataModel.GetHiddenProperties().Any(p => p.Equals(propertyInfo)))
return null;
if (propertyType == null)
return null;
// If a display VM was found, prefer to use that in any case
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription);
if (typeViewModel != null)
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {DisplayViewModel = typeViewModel, Depth = depth};
// For primitives, create a property view model, it may be null that is fine
if (propertyType.IsPrimitive || propertyType.IsEnum || propertyType == typeof(string) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {Depth = depth};
if (propertyType.IsGenericEnumerable())
return new DataModelListViewModel(DataModel, this, dataModelPath) {Depth = depth};
if (propertyType == typeof(DataModelEvent) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(DataModelEvent<>))
return new DataModelEventViewModel(DataModel, this, dataModelPath) {Depth = depth};
// For other value types create a child view model
if (propertyType.IsClass || propertyType.IsStruct())
return new DataModelPropertiesViewModel(DataModel, this, dataModelPath) {Depth = depth};
return null;
}
private string AppendToPath(string toAppend)
{
if (string.IsNullOrEmpty(Path))
return toAppend;
StringBuilder builder = new();
builder.Append(Path);
builder.Append(".");
builder.Append(toAppend);
return builder.ToString();
}
private void RequestUpdate()
{
Parent?.RequestUpdate();
OnUpdateRequested();
}
#region Events
/// <summary>
/// Occurs when an update to the property this view model visualizes is requested
/// </summary>
public event EventHandler? UpdateRequested;
/// <summary>
/// Invokes the <see cref="UpdateRequested" /> event
/// </summary>
protected virtual void OnUpdateRequested()
{
UpdateRequested?.Invoke(this, EventArgs.Empty);
}
#endregion
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
DataModelPath?.Dispose();
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Dispose(true);
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -0,0 +1,33 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.DefaultTypes.DataModel.Display.DefaultDataModelDisplayView">
<Grid ColumnDefinitions="*,Auto,Auto">
<!-- Prefix -->
<TextBlock Grid.Column="0"
Text="{Binding PropertyDescription.Prefix}"
IsVisible="{Binding PropertyDescription.Prefix, Converter={x:Static ObjectConverters.IsNotNull}}"
TextAlignment="Right"
Margin="0 0 5 0" />
<!-- Value -->
<TextBlock Grid.Column="1"
Text="{Binding DisplayValue, Mode=OneWay}"
HorizontalAlignment="Right"
IsVisible="{Binding ShowToString}"/>
<TextBlock Grid.Column="1"
Text="null"
FontFamily="Consolas"
HorizontalAlignment="Right"
Foreground="{DynamicResource MaterialDesignCheckBoxDisabled}"
IsVisible="{Binding ShowNull}"/>
<!-- Affix -->
<TextBlock Grid.Column="2"
Text="{Binding PropertyDescription.Affix}"
IsVisible="{Binding PropertyDescription.Affix, Converter={x:Static ObjectConverters.IsNotNull}}"
Margin="5 0 0 0" />
</Grid>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
{
public partial class DefaultDataModelDisplayView : UserControl
{
public DefaultDataModelDisplayView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,47 @@
<UserControl x:Class="Artemis.UI.Shared.DefaultTypes.DataModel.Display.DefaultDataModelDisplayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Shared.DefaultTypes.DataModel.Display"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:shared="clr-namespace:Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:DefaultDataModelDisplayViewModel}">
<UserControl.Resources>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Prefix -->
<TextBlock Grid.Column="0"
Text="{Binding PropertyDescription.Prefix}"
Visibility="{Binding PropertyDescription.Prefix, Converter={StaticResource NullToVisibilityConverter}}"
TextAlignment="Right"
Margin="0 0 5 0" />
<!-- Value -->
<TextBlock Grid.Column="1"
Text="{Binding DisplayValue, Mode=OneWay}"
HorizontalAlignment="Right"
Visibility="{Binding ShowToString, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<TextBlock Grid.Column="1"
Text="null"
FontFamily="Consolas"
HorizontalAlignment="Right"
Foreground="{DynamicResource MaterialDesignCheckBoxDisabled}"
Visibility="{Binding ShowNull, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<!-- Affix -->
<TextBlock Grid.Column="2"
Text="{Binding PropertyDescription.Affix}"
Visibility="{Binding PropertyDescription.Affix, Converter={StaticResource NullToVisibilityConverter}}"
Margin="5 0 0 0" />
</Grid>
</UserControl>

View File

@ -0,0 +1,43 @@
using System;
using Artemis.Core;
using Artemis.UI.Shared.DataModelVisualization;
using ReactiveUI;
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
{
/// <summary>
/// Represents the default data model display view model that is used when no display viewmodel specific for the type
/// is registered
/// </summary>
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<object>
{
private bool _showNull;
private bool _showToString;
internal DefaultDataModelDisplayViewModel()
{
ShowNull = true;
}
public bool ShowToString
{
get => _showToString;
private set => this.RaiseAndSetIfChanged(ref _showToString, value);
}
public bool ShowNull
{
get => _showNull;
set => this.RaiseAndSetIfChanged(ref _showNull, value);
}
protected override void OnDisplayValueUpdated()
{
if (DisplayValue is Enum enumDisplayValue)
DisplayValue = EnumUtilities.HumanizeValue(enumDisplayValue);
ShowToString = DisplayValue != null;
ShowNull = DisplayValue == null;
}
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace Artemis.UI.Shared.Exceptions
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents errors that occur within the Artemis Shared UI library

View File

@ -0,0 +1,251 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.Core.Services;
using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
using Ninject;
using Ninject.Parameters;
namespace Artemis.UI.Shared.Services
{
internal class DataModelUIService : IDataModelUIService
{
private readonly IDataModelService _dataModelService;
private readonly IKernel _kernel;
private readonly List<DataModelVisualizationRegistration> _registeredDataModelDisplays;
private readonly List<DataModelVisualizationRegistration> _registeredDataModelEditors;
public DataModelUIService(IDataModelService dataModelService, IKernel kernel)
{
_dataModelService = dataModelService;
_kernel = kernel;
_registeredDataModelEditors = new List<DataModelVisualizationRegistration>();
_registeredDataModelDisplays = new List<DataModelVisualizationRegistration>();
RegisteredDataModelEditors = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelEditors);
RegisteredDataModelDisplays = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelDisplays);
}
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
public DataModelPropertiesViewModel GetMainDataModelVisualization()
{
DataModelPropertiesViewModel viewModel = new(null, null, null);
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().Where(d => d.IsExpansion).OrderBy(d => d.DataModelDescription.Name))
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
// Update to populate children
viewModel.Update(this, null);
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
return viewModel;
}
public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization)
{
List<DataModelVisualizationViewModel> disabledChildren = mainDataModelVisualization.Children
.Where(d => d.DataModel != null && !d.DataModel.Module.IsEnabled)
.ToList();
foreach (DataModelVisualizationViewModel child in disabledChildren)
mainDataModelVisualization.Children.Remove(child);
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name))
{
if (mainDataModelVisualization.Children.All(c => c.DataModel != dataModelExpansion))
{
mainDataModelVisualization.Children.Add(
new DataModelPropertiesViewModel(dataModelExpansion, mainDataModelVisualization, new DataModelPath(dataModelExpansion))
);
}
}
mainDataModelVisualization.Update(this, null);
}
public DataModelPropertiesViewModel? GetPluginDataModelVisualization(List<Module> modules, bool includeMainDataModel)
{
DataModelPropertiesViewModel root;
// This will contain any modules that are always available
if (includeMainDataModel)
root = GetMainDataModelVisualization();
else
{
root = new DataModelPropertiesViewModel(null, null, null);
root.UpdateRequested += (sender, args) => root.Update(this, null);
}
foreach (Module module in modules)
{
DataModel? dataModel = _dataModelService.GetPluginDataModel(module);
if (dataModel == null)
continue;
root.Children.Add(new DataModelPropertiesViewModel(dataModel, root, new DataModelPath(dataModel)));
}
if (!root.Children.Any())
return null;
// Update to populate children
root.Update(this, null);
return root;
}
public DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type>? compatibleConversionTypes = null) where T : DataModelInputViewModel
{
compatibleConversionTypes ??= new List<Type>();
Type viewModelType = typeof(T);
lock (_registeredDataModelEditors)
{
Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0];
DataModelVisualizationRegistration? existing = _registeredDataModelEditors.FirstOrDefault(r => r.SupportedType == supportedType);
if (existing != null)
{
if (existing.Plugin != plugin)
throw new ArtemisSharedUIException($"Cannot register data model input for type {supportedType.Name} because an editor was already" +
$" registered by {existing.Plugin}");
return existing;
}
_kernel.Bind(viewModelType).ToSelf();
// Create the registration
DataModelVisualizationRegistration registration = new(this, RegistrationType.Input, plugin, supportedType, viewModelType)
{
// Apply the compatible conversion types to the registration
CompatibleConversionTypes = compatibleConversionTypes
};
_registeredDataModelEditors.Add(registration);
return registration;
}
}
public DataModelVisualizationRegistration RegisterDataModelDisplay<T>(Plugin plugin) where T : DataModelDisplayViewModel
{
Type viewModelType = typeof(T);
lock (_registeredDataModelDisplays)
{
Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0];
DataModelVisualizationRegistration? existing = _registeredDataModelDisplays.FirstOrDefault(r => r.SupportedType == supportedType);
if (existing != null)
{
if (existing.Plugin != plugin)
throw new ArtemisSharedUIException($"Cannot register data model display for type {supportedType.Name} because an editor was already" +
$" registered by {existing.Plugin}");
return existing;
}
_kernel.Bind(viewModelType).ToSelf();
DataModelVisualizationRegistration registration = new(this, RegistrationType.Display, plugin, supportedType, viewModelType);
_registeredDataModelDisplays.Add(registration);
return registration;
}
}
public void RemoveDataModelInput(DataModelVisualizationRegistration registration)
{
lock (_registeredDataModelEditors)
{
if (_registeredDataModelEditors.Contains(registration))
{
registration.Unsubscribe();
_registeredDataModelEditors.Remove(registration);
_kernel.Unbind(registration.ViewModelType);
}
}
}
public void RemoveDataModelDisplay(DataModelVisualizationRegistration registration)
{
lock (_registeredDataModelDisplays)
{
if (_registeredDataModelDisplays.Contains(registration))
{
registration.Unsubscribe();
_registeredDataModelDisplays.Remove(registration);
_kernel.Unbind(registration.ViewModelType);
}
}
}
public DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault)
{
lock (_registeredDataModelDisplays)
{
DataModelDisplayViewModel? result;
DataModelVisualizationRegistration? match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType);
if (match != null)
{
// If this ever happens something is likely wrong with the plugin unload detection
if (match.Plugin.Kernel == null)
throw new ArtemisSharedUIException("Cannot GetDataModelDisplayViewModel for a registration by an uninitialized plugin");
result = (DataModelDisplayViewModel) match.Plugin.Kernel.Get(match.ViewModelType);
}
else if (!fallBackToDefault)
result = null;
else
result = _kernel.Get<DefaultDataModelDisplayViewModel>();
if (result != null)
result.PropertyDescription = description;
return result;
}
}
public DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> updateCallback)
{
lock (_registeredDataModelEditors)
{
// Prefer a VM that natively supports the type
DataModelVisualizationRegistration? match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == propertyType);
// Fall back on a VM that supports the type through conversion
if (match == null)
match = _registeredDataModelEditors.FirstOrDefault(d => d.CompatibleConversionTypes != null && d.CompatibleConversionTypes.Contains(propertyType));
// Lastly try getting an enum VM if the provided type is an enum
if (match == null && propertyType.IsEnum)
match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == typeof(Enum));
if (match != null)
{
DataModelInputViewModel viewModel = InstantiateDataModelInputViewModel(match, description, initialValue);
viewModel.UpdateCallback = updateCallback;
return viewModel;
}
return null;
}
}
private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute? description, object? initialValue)
{
// This assumes the type can be converted, that has been checked when the VM was created
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
IParameter[] parameters =
{
new ConstructorArgument("targetDescription", description),
new ConstructorArgument("initialValue", initialValue)
};
// If this ever happens something is likely wrong with the plugin unload detection
if (registration.Plugin.Kernel == null)
throw new ArtemisSharedUIException("Cannot InstantiateDataModelInputViewModel for a registration by an uninitialized plugin");
DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Kernel.Get(registration.ViewModelType, parameters);
viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes;
return viewModel;
}
}
}

View File

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared.Services
{
/// <summary>
/// A service for UI related data model tasks
/// </summary>
public interface IDataModelUIService : IArtemisSharedUIService
{
/// <summary>
/// Gets a read-only list of all registered data model editors
/// </summary>
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
/// <summary>
/// Gets a read-only list of all registered data model displays
/// </summary>
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
/// <summary>
/// Creates a data model visualization view model for the main data model
/// </summary>
/// <returns>
/// A data model visualization view model containing all data model expansions and modules that expand the main
/// data model
/// </returns>
DataModelPropertiesViewModel GetMainDataModelVisualization();
/// <summary>
/// Creates a data model visualization view model for the data model of the provided plugin feature
/// </summary>
/// <param name="modules">The modules to create the data model visualization view model for</param>
/// <param name="includeMainDataModel">
/// Whether or not also to include the main data model (and therefore any modules marked
/// as <see cref="Module.IsAlwaysAvailable" />)
/// </param>
/// <returns>A data model visualization view model containing the data model of the provided feature</returns>
DataModelPropertiesViewModel? GetPluginDataModelVisualization(List<Module> modules, bool includeMainDataModel);
/// <summary>
/// Updates the children of the provided main data model visualization, removing disabled children and adding newly
/// enabled children
/// </summary>
void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization);
/// <summary>
/// Registers a new data model editor
/// </summary>
/// <typeparam name="T">The type of the editor</typeparam>
/// <param name="plugin">The plugin this editor belongs to</param>
/// <param name="compatibleConversionTypes">A collection of extra types this editor supports</param>
/// <returns>A registration that can be used to remove the editor</returns>
DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type> compatibleConversionTypes) where T : DataModelInputViewModel;
/// <summary>
/// Registers a new data model display
/// </summary>
/// <typeparam name="T">The type of the display</typeparam>
/// <param name="plugin">The plugin this display belongs to</param>
/// <returns>A registration that can be used to remove the display</returns>
DataModelVisualizationRegistration RegisterDataModelDisplay<T>(Plugin plugin) where T : DataModelDisplayViewModel;
/// <summary>
/// Removes a data model editor
/// </summary>
/// <param name="registration">
/// The registration of the editor as returned by <see cref="RegisterDataModelInput{T}" />
/// </param>
void RemoveDataModelInput(DataModelVisualizationRegistration registration);
/// <summary>
/// Removes a data model display
/// </summary>
/// <param name="registration">
/// The registration of the display as returned by <see cref="RegisterDataModelDisplay{T}" />
/// </param>
void RemoveDataModelDisplay(DataModelVisualizationRegistration registration);
/// <summary>
/// Creates the most appropriate display view model for the provided <paramref name="propertyType" /> that can display
/// a value
/// </summary>
/// <param name="propertyType">The type of data model property to find a display view model for</param>
/// <param name="description">The description of the data model property</param>
/// <param name="fallBackToDefault">
/// If <see langword="true"></see>, a simple .ToString() display view model will be
/// returned if nothing else is found
/// </param>
/// <returns>The most appropriate display view model for the provided <paramref name="propertyType"></paramref></returns>
DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault = false);
/// <summary>
/// Creates the most appropriate input view model for the provided <paramref name="propertyType" /> that allows
/// inputting a value
/// </summary>
/// <param name="propertyType">The type of data model property to find a display view model for</param>
/// <param name="description">The description of the data model property</param>
/// <param name="initialValue">The initial value to show in the input</param>
/// <param name="updateCallback">A function to call whenever the input was updated (submitted or not)</param>
/// <returns>The most appropriate input view model for the provided <paramref name="propertyType" /></returns>
DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> updateCallback);
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Artemis.UI.Shared.Exceptions;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia;

View File

@ -46,4 +46,9 @@
<Resource Include="Assets\Images\Logo\bow.ico" />
<Resource Include="Assets\Images\Logo\bow.svg" />
</ItemGroup>
<ItemGroup>
<Compile Update="Screens\Debugger\Tabs\Settings\DebugSettingsView.axaml.cs">
<DependentUpon>DebugSettingsView.axaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@ -3,6 +3,8 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:reactiveUi="http://reactiveui.net"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Debugger.DebugView"
Title="Artemis | Debugger"
@ -10,9 +12,59 @@
Height="800"
ExtendClientAreaToDecorationsHint="True">
<controls:NavigationView x:Name="Navigation">
</controls:NavigationView>
<Window.Styles>
<Style Selector="StackPanel.sidebar-stackpanel avalonia|MaterialIcon">
<Setter Property="Margin" Value="-7 0 0 0" />
</Style>
<Style Selector="StackPanel.sidebar-stackpanel TextBlock">
<Setter Property="Margin" Value="15 0 0 0" />
</Style>
</Window.Styles>
<controls:NavigationView x:Name="Navigation"
SelectedItem="{Binding SelectedItem}"
IsPaneToggleButtonVisible="False"
PaneDisplayMode="Left"
Margin="0 30 0 0">
<controls:NavigationView.MenuItems>
<controls:NavigationViewItem Tag="Rendering">
<controls:NavigationViewItem.Content>
<StackPanel Orientation="Horizontal" Classes="sidebar-stackpanel">
<avalonia:MaterialIcon Kind="Paint" />
<TextBlock>Rendering</TextBlock>
</StackPanel>
</controls:NavigationViewItem.Content>
</controls:NavigationViewItem>
<controls:NavigationViewItem Tag="DataModel">
<controls:NavigationViewItem.Content>
<StackPanel Orientation="Horizontal" Classes="sidebar-stackpanel">
<avalonia:MaterialIcon Kind="FormatListBulletedType" />
<TextBlock>Data Model</TextBlock>
</StackPanel>
</controls:NavigationViewItem.Content>
</controls:NavigationViewItem>
<controls:NavigationViewItem Tag="Performance">
<controls:NavigationViewItem.Content>
<StackPanel Orientation="Horizontal" Classes="sidebar-stackpanel">
<avalonia:MaterialIcon Kind="Gauge" />
<TextBlock>Performance</TextBlock>
</StackPanel>
</controls:NavigationViewItem.Content>
</controls:NavigationViewItem>
<controls:NavigationViewItem Tag="Logging">
<controls:NavigationViewItem.Content>
<StackPanel Orientation="Horizontal" Classes="sidebar-stackpanel">
<avalonia:MaterialIcon Kind="Text" />
<TextBlock>Logging</TextBlock>
</StackPanel>
</controls:NavigationViewItem.Content>
</controls:NavigationViewItem>
</controls:NavigationView.MenuItems>
<reactiveUi:RoutedViewHost Router="{Binding Router}" Padding="12">
<reactiveUi:RoutedViewHost.PageTransition>
<CrossFade Duration="0.1" />
</reactiveUi:RoutedViewHost.PageTransition>
</reactiveUi:RoutedViewHost>
</controls:NavigationView>
</Window>

View File

@ -4,6 +4,7 @@ using Artemis.UI.Screens.Debugger.Tabs.DataModel;
using Artemis.UI.Screens.Debugger.Tabs.Logs;
using Artemis.UI.Screens.Debugger.Tabs.Performance;
using Artemis.UI.Screens.Debugger.Tabs.Render;
using Artemis.UI.Screens.Debugger.Tabs.Settings;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using FluentAvalonia.UI.Controls;
@ -50,20 +51,23 @@ namespace Artemis.UI.Screens.Debugger
{
// Kind of a lame way to do this but it's so static idc
ConstructorArgument hostScreen = new("hostScreen", this);
switch ((string) item.Content)
switch (item.Tag as string)
{
case "Rendering":
Router.Navigate.Execute(_kernel.Get<RenderDebugViewModel>(hostScreen));
break;
case "Logs":
Router.Navigate.Execute(_kernel.Get<LogsDebugViewModel>(hostScreen));
break;
case "Data Model":
case "DataModel":
Router.Navigate.Execute(_kernel.Get<DataModelDebugViewModel>(hostScreen));
break;
case "Performance":
Router.Navigate.Execute(_kernel.Get<PerformanceDebugViewModel>(hostScreen));
break;
case "Logging":
Router.Navigate.Execute(_kernel.Get<LogsDebugViewModel>(hostScreen));
break;
case "Settings":
Router.Navigate.Execute(_kernel.Get<DebugSettingsViewModel>(hostScreen));
break;
}
}

View File

@ -2,7 +2,109 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared;assembly=Artemis.UI.Shared"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Debugger.Tabs.DataModel.DataModelDebugView">
<TextBlock>Data Model</TextBlock>
<UserControl.Resources>
<converters:TypeToStringConverter x:Key="TypeToStringConverter" />
</UserControl.Resources>
<StackPanel>
<TreeView Items="{Binding MainDataModel.Children}">
<TreeView.Styles>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
</Style>
</TreeView.Styles>
<TreeView.DataTemplates>
<TreeDataTemplate DataType="{x:Type dataModel:DataModelPropertiesViewModel}" ItemsSource="{Binding Children}">
<Grid ColumnDefinitions="Auto,Auto,*">
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
<TextBlock FontWeight="Bold">[</TextBlock>
<TextBlock FontWeight="Bold" Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
<TextBlock FontWeight="Bold">]</TextBlock>
</StackPanel>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
<TextBlock Grid.Column="2"
Text="{Binding DisplayValue}"
FontFamily="Consolas"
HorizontalAlignment="Right" />
</Grid>
</TreeDataTemplate>
<TreeDataTemplate DataType="{x:Type dataModel:DataModelListViewModel}" ItemsSource="{Binding ListChildren}">
<Grid ColumnDefinitions="Auto,Auto,*">
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
<TextBlock FontWeight="Bold">[</TextBlock>
<TextBlock FontWeight="Bold" Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
<TextBlock FontWeight="Bold">]</TextBlock>
</StackPanel>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
<TextBlock Grid.Column="2"
Text="{Binding CountDisplay, Mode=OneWay}"
FontFamily="Consolas"
HorizontalAlignment="Right" />
</Grid>
</TreeDataTemplate>
<TreeDataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
<Grid ColumnDefinitions="Auto,Auto,*">
<!-- Value description -->
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
<TextBlock FontWeight="Bold">[</TextBlock>
<TextBlock FontWeight="Bold" Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
<TextBlock FontWeight="Bold">]</TextBlock>
</StackPanel>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
<!-- Value display -->
<ContentControl Grid.Column="2" Content="{Binding DisplayViewModel}" FontFamily="Consolas" />
</Grid>
</TreeDataTemplate>
<TreeDataTemplate DataType="{x:Type dataModel:DataModelListPropertyViewModel}">
<Grid ColumnDefinitions="Auto,Auto,*">
<!-- Value description -->
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
<TextBlock FontWeight="Bold">[</TextBlock>
<TextBlock FontWeight="Bold" Text="{Binding ListType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
<TextBlock FontWeight="Bold">]</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="0 0 5 0">
<TextBlock>List item [</TextBlock>
<TextBlock Text="{Binding Index, Mode=OneWay}" />
<TextBlock>]</TextBlock>
</StackPanel>
<!-- Value display -->
<ContentControl Grid.Column="2" Content="{Binding DisplayViewModel}" FontFamily="Consolas" />
</Grid>
</TreeDataTemplate>
<TreeDataTemplate DataType="{x:Type dataModel:DataModelEventViewModel}" ItemsSource="{Binding Children}">
<Grid ColumnDefinitions="Auto,Auto,*">
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
<TextBlock FontWeight="Bold">[</TextBlock>
<TextBlock FontWeight="Bold" Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
<TextBlock FontWeight="Bold">]</TextBlock>
</StackPanel>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
<TextBlock Grid.Column="2"
Text="{Binding CountDisplay, Mode=OneWay}"
FontFamily="Consolas"
HorizontalAlignment="Right" />
</Grid>
</TreeDataTemplate>
<TreeDataTemplate DataType="{x:Type dataModel:DataModelListPropertiesViewModel}" ItemsSource="{Binding DisplayViewModel.Children}">
<StackPanel Orientation="Horizontal">
<TextBlock>List item [</TextBlock>
<TextBlock Text="{Binding Index, Mode=OneWay}" />
<TextBlock>]</TextBlock>
</StackPanel>
</TreeDataTemplate>
</TreeView.DataTemplates>
</TreeView>
</StackPanel>
</UserControl>

View File

@ -1,16 +1,147 @@
using Artemis.UI.Shared;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Timers;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Services;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Tabs.DataModel
{
public class DataModelDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
{
public DataModelDebugViewModel(IScreen hostScreen)
private readonly IDataModelUIService _dataModelUIService;
private readonly IPluginManagementService _pluginManagementService;
private readonly Timer _updateTimer;
private bool _isModuleFilterEnabled;
private DataModelPropertiesViewModel? _mainDataModel;
private string? _propertySearch;
private Module? _selectedModule;
private bool _slowUpdates;
public DataModelDebugViewModel(IScreen hostScreen, IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService)
{
_dataModelUIService = dataModelUIService;
_pluginManagementService = pluginManagementService;
_updateTimer = new Timer(25);
_updateTimer.Elapsed += UpdateTimerOnElapsed;
HostScreen = hostScreen;
Modules = new ObservableCollection<Module>();
this.WhenActivated(disposables =>
{
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x)
.Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature))
.DisposeWith(disposables);
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x)
.Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature))
.DisposeWith(disposables);
GetDataModel();
_updateTimer.Start();
Disposable.Create(() => _updateTimer.Stop()).DisposeWith(disposables);
});
}
public string UrlPathSegment => "data-model";
public IScreen HostScreen { get; }
public DataModelPropertiesViewModel? MainDataModel
{
get => _mainDataModel;
set => this.RaiseAndSetIfChanged(ref _mainDataModel, value);
}
public string? PropertySearch
{
get => _propertySearch;
set => this.RaiseAndSetIfChanged(ref _propertySearch, value);
}
public bool SlowUpdates
{
get => _slowUpdates;
set
{
this.RaiseAndSetIfChanged(ref _slowUpdates, value);
_updateTimer.Interval = _slowUpdates ? 500 : 25;
}
}
public ObservableCollection<Module> Modules { get; }
public Module? SelectedModule
{
get => _selectedModule;
set
{
this.RaiseAndSetIfChanged(ref _selectedModule, value);
GetDataModel();
}
}
public bool IsModuleFilterEnabled
{
get => _isModuleFilterEnabled;
set
{
this.RaiseAndSetIfChanged(ref _isModuleFilterEnabled, value);
if (!IsModuleFilterEnabled)
SelectedModule = null;
else
GetDataModel();
}
}
private void UpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
{
if (MainDataModel == null)
return;
lock (MainDataModel)
{
MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true));
}
}
private void PluginFeatureToggled(PluginFeature pluginFeature)
{
if (pluginFeature is Module)
PopulateModules();
}
private void GetDataModel()
{
MainDataModel = SelectedModule != null
? _dataModelUIService.GetPluginDataModelVisualization(new List<Module>() { SelectedModule }, false)
: _dataModelUIService.GetMainDataModelVisualization();
}
private void PopulateModules()
{
Modules.Clear();
Modules.AddRange(_pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).OrderBy(m => m.Info.Name));
if (MainDataModel == null)
return;
if (SelectedModule == null)
{
if (MainDataModel != null)
_dataModelUIService.UpdateModules(MainDataModel);
}
else if (!SelectedModule.IsEnabled)
SelectedModule = null;
}
}
}

View File

@ -4,13 +4,26 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Debugger.Tabs.Render.RenderDebugView">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock>Render</TextBlock>
<Image Source="{Binding CurrentFrame}" Grid.Row="1"></Image>
</Grid>
<StackPanel>
<TextBlock Classes="h3">Render</TextBlock>
<TextBlock TextWrapping="Wrap">
On this page you can view what Artemis renders to devices in real time. Artemis will overlay this image on your devices, taking the average of each pixel covering a LED, resulting the image appearing on your devices.
</TextBlock>
<TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10">
Please note that having this window open can have a performance impact on your system.
</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="FPS: " />
<TextBlock FontWeight="Bold" Text="{Binding CurrentFps}" />
<TextBlock Text=" at " />
<TextBlock Text="{Binding RenderWidth}" />
<TextBlock Text="x" />
<TextBlock Text="{Binding RenderHeight}" />
<TextBlock Text=" - Renderer: "></TextBlock>
<TextBlock Text="{Binding Renderer}"></TextBlock>
</StackPanel>
<Image Source="{Binding CurrentFrame}" />
</StackPanel>
</UserControl>

View File

@ -1,9 +1,15 @@
using System.IO;
using System.Diagnostics;
using System.IO;
using System.Reactive.Disposables;
using System.Reflection.Metadata.Ecma335;
using System.Timers;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using ReactiveUI;
using SkiaSharp;
@ -12,11 +18,9 @@ namespace Artemis.UI.Screens.Debugger.Tabs.Render
public class RenderDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
{
private readonly ICoreService _coreService;
private readonly Timer _fpsTimer;
private double _currentFps;
private SKImage? _currentFrame;
private int _frames;
private Bitmap? _currentFrame;
private string? _frameTargetPath;
private string _renderer;
private int _renderHeight;
@ -25,10 +29,7 @@ namespace Artemis.UI.Screens.Debugger.Tabs.Render
public RenderDebugViewModel(DebugViewModel hostScreen, ICoreService coreService)
{
HostScreen = hostScreen;
_coreService = coreService;
_fpsTimer = new Timer(1000);
_fpsTimer.Start();
this.WhenActivated(disposables =>
{
@ -37,7 +38,7 @@ namespace Artemis.UI.Screens.Debugger.Tabs.Render
});
}
public SKImage? CurrentFrame
public Bitmap? CurrentFrame
{
get => _currentFrame;
set => this.RaiseAndSetIfChanged(ref _currentFrame, value);
@ -69,21 +70,18 @@ namespace Artemis.UI.Screens.Debugger.Tabs.Render
private void HandleActivation()
{
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
_coreService.FrameRendered += CoreServiceOnFrameRendered;
_fpsTimer.Elapsed += FpsTimerOnElapsed;
}
private void HandleDeactivation()
{
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
_fpsTimer.Elapsed -= FpsTimerOnElapsed;
_fpsTimer.Dispose();
}
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
_frames++;
CurrentFps = _coreService.FrameRate;
using SKImage skImage = e.Texture.Surface.Snapshot();
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
@ -102,16 +100,14 @@ namespace Artemis.UI.Screens.Debugger.Tabs.Render
RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width;
CurrentFrame = e.Texture.Surface.Snapshot();
// TODO: This performs well enough but look into something else
using (SKData data = skImage.Encode(SKEncodedImageFormat.Png, 100))
{
CurrentFrame = new Bitmap(data.AsStream());
}
}
private void FpsTimerOnElapsed(object sender, ElapsedEventArgs e)
{
CurrentFps = _frames;
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
_frames = 0;
}
public string UrlPathSegment => "render";
public IScreen HostScreen { get; }

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Debugger.Tabs.Settings.DebugSettingsView">
<TextBlock>Settings</TextBlock>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Tabs.Settings
{
public class DebugSettingsView : ReactiveUserControl<DebugSettingsViewModel>
{
public DebugSettingsView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,16 @@
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.Debugger.Tabs.Settings
{
public class DebugSettingsViewModel : ActivatableViewModelBase, IRoutableViewModel
{
public DebugSettingsViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
}
public string UrlPathSegment => "logs";
public IScreen HostScreen { get; }
}
}