Profiles - Finished dispose implementation

Profiles - Added transition between active profiles
Core - Added startup animation
This commit is contained in:
Robert committed 2020-08-10 19:16:21 +02:00
1 parent c0bdd8cf26
commit d955bc8635
43 files changed
+1908 -361

No files matched your search

+31
View File
@@ -3,14 +3,17 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Artemis.Core.Events;
using Artemis.Core.Exceptions;
using Artemis.Core.JsonConverters;
using Artemis.Core.Models.Profile;
using Artemis.Core.Ninject;
using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services.Interfaces;
using Artemis.Core.Services.Storage.Interfaces;
using Artemis.Core.Utilities;
using Artemis.Storage;
using Newtonsoft.Json;
using RGB.NET.Core;
@@ -35,6 +38,7 @@ namespace Artemis.Core.Services
private readonly ISurfaceService _surfaceService;
private List<BaseDataModelExpansion> _dataModelExpansions;
private List<Module> _modules;
private IntroAnimation _introAnimation;
// ReSharper disable once UnusedParameter.Local - Storage migration service is injected early to ensure it runs before anything else
internal CoreService(ILogger logger, StorageMigrationService _, ISettingsService settingsService, IPluginService pluginService,
@@ -90,11 +94,38 @@ namespace Artemis.Core.Services
else
_logger.Information("Initialized without an active surface entity");
PlayIntroAnimation();
_profileService.ActivateLastActiveProfiles();
OnInitialized();
}
private void PlayIntroAnimation()
{
FrameRendering += DrawOverlay;
_introAnimation = new IntroAnimation(_logger, _profileService, _surfaceService);
// Draw a white overlay over the device
void DrawOverlay(object sender, FrameRenderingEventArgs args)
{
if (_introAnimation == null)
args.Canvas.Clear(new SKColor(0, 0, 0));
else
_introAnimation.Render(args.DeltaTime, args.Canvas, _rgbService.BitmapBrush.Bitmap.Info);
}
var introLength = _introAnimation.AnimationProfile.GetAllLayers().Max(l => l.TimelineLength);
// Stop rendering after the profile finishes (take 1 second extra in case of slow updates)
Task.Run(async () =>
{
await Task.Delay(introLength.Add(TimeSpan.FromSeconds(1)));
FrameRendering -= DrawOverlay;
_introAnimation.AnimationProfile?.Dispose();
_introAnimation = null;
});
}
public void SetMainWindowHandle(IntPtr handle)
{
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Artemis.Core.Events;
namespace Artemis.Core.Services.Interfaces
@@ -1,10 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Artemis.Core.Models.Profile;
using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Services.Interfaces;
namespace Artemis.Core.Services.Storage.Interfaces
{
/// <summary>
/// Provides access to profile storage and is responsible for activating default profiles
/// </summary>
public interface IProfileService : IArtemisService
{
/// <summary>
@@ -18,14 +22,14 @@ namespace Artemis.Core.Services.Storage.Interfaces
/// <param name="module">The profile module to create the profile for</param>
/// <param name="name">The name of the new profile</param>
/// <returns></returns>
ProfileDescriptor CreateProfile(ProfileModule module, string name);
ProfileDescriptor CreateProfileDescriptor(ProfileModule module, string name);
/// <summary>
/// Gets a descriptor for each profile stored for the given <see cref="ProfileModule" />
/// </summary>
/// <param name="module">The module to return profile descriptors for</param>
/// <returns></returns>
List<ProfileDescriptor> GetProfiles(ProfileModule module);
List<ProfileDescriptor> GetProfileDescriptors(ProfileModule module);
/// <summary>
/// Writes the profile to persistent storage
@@ -40,30 +44,53 @@ namespace Artemis.Core.Services.Storage.Interfaces
/// <param name="profile">The profile to delete</param>
void DeleteProfile(Profile profile);
/// <summary>
/// Permanently deletes the profile described by the provided profile descriptor
/// </summary>
/// <param name="profileDescriptor">The descriptor pointing to the profile to delete</param>
void DeleteProfile(ProfileDescriptor profileDescriptor);
/// <summary>
/// Activates the profile described in the given <see cref="ProfileDescriptor" /> with the currently active surface
/// </summary>
/// <param name="profileDescriptor">The descriptor describing the profile to activate</param>
Profile ActivateProfile(ProfileDescriptor profileDescriptor);
/// <summary>
/// Asynchronously activates the profile described in the given <see cref="ProfileDescriptor" /> with the currently
/// active surface using a fade animation
/// </summary>
/// <param name="profileDescriptor">The descriptor describing the profile to activate</param>
Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor);
/// <summary>
/// Clears the active profile on the given <see cref="ProfileModule" />
/// </summary>
/// <param name="module">The profile module to deactivate the active profile on</param>
void ClearActiveProfile(ProfileModule module);
/// <summary>
/// Asynchronously clears the active profile on the given <see cref="ProfileModule" /> using a fade animation
/// </summary>
/// <param name="module">The profile module to deactivate the active profile on</param>
Task ClearActiveProfileAnimated(ProfileModule module);
/// <summary>
/// Attempts to restore the profile to the state it had before the last <see cref="UpdateProfile" /> call.
/// </summary>
/// <param name="selectedProfile"></param>
/// <param name="module"></param>
bool UndoUpdateProfile(Profile selectedProfile, ProfileModule module);
/// <param name="profile"></param>
bool UndoUpdateProfile(Profile profile);
/// <summary>
/// Attempts to restore the profile to the state it had before the last <see cref="UndoUpdateProfile" /> call.
/// </summary>
/// <param name="selectedProfile"></param>
/// <param name="module"></param>
bool RedoUpdateProfile(Profile selectedProfile, ProfileModule module);
/// <param name="profile"></param>
bool RedoUpdateProfile(Profile profile);
/// <summary>
/// Prepares the profile for rendering. You should not need to call this, it is exposed for some niche usage in the core
/// </summary>
/// <param name="profile"></param>
void InstantiateProfile(Profile profile);
}
}
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core.Events;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Surface;
@@ -15,9 +16,6 @@ using Serilog;
namespace Artemis.Core.Services.Storage
{
/// <summary>
/// Provides access to profile storage and is responsible for activating default profiles
/// </summary>
public class ProfileService : IProfileService
{
private readonly ILogger _logger;
@@ -51,20 +49,13 @@ namespace Artemis.Core.Services.Storage
}
}
public List<ProfileDescriptor> GetProfiles(ProfileModule module)
public List<ProfileDescriptor> GetProfileDescriptors(ProfileModule module)
{
var profileEntities = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid);
return profileEntities.Select(e => new ProfileDescriptor(module, e)).ToList();
}
public ProfileDescriptor GetLastActiveProfile(ProfileModule module)
{
var moduleProfiles = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid);
var profileEntity = moduleProfiles.FirstOrDefault(p => p.IsActive) ?? moduleProfiles.FirstOrDefault();
return profileEntity == null ? null : new ProfileDescriptor(module, profileEntity);
}
public ProfileDescriptor CreateProfile(ProfileModule module, string name)
public ProfileDescriptor CreateProfileDescriptor(ProfileModule module, string name)
{
var profileEntity = new ProfileEntity {Id = Guid.NewGuid(), Name = name, PluginGuid = module.PluginInfo.Guid};
return new ProfileDescriptor(module, profileEntity);
@@ -72,31 +63,59 @@ namespace Artemis.Core.Services.Storage
public Profile ActivateProfile(ProfileDescriptor profileDescriptor)
{
if (profileDescriptor.ProfileModule.ActiveProfile.EntityId == profileDescriptor.Id)
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
return profileDescriptor.ProfileModule.ActiveProfile;
var profile = new Profile(profileDescriptor.ProfileModule, profileDescriptor.ProfileEntity);
InitializeLayerProperties(profile);
InstantiateLayers(profile);
InstantiateFolders(profile);
var profileEntity = _profileRepository.Get(profileDescriptor.Id);
var profile = new Profile(profileDescriptor.ProfileModule, profileEntity);
InstantiateProfile(profile);
profileDescriptor.ProfileModule.ChangeActiveProfile(profile, _surfaceService.ActiveSurface);
return profile;
}
public async Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor)
{
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
return profileDescriptor.ProfileModule.ActiveProfile;
var profileEntity = _profileRepository.Get(profileDescriptor.Id);
var profile = new Profile(profileDescriptor.ProfileModule, profileEntity);
InstantiateProfile(profile);
await profileDescriptor.ProfileModule.ChangeActiveProfileAnimated(profile, _surfaceService.ActiveSurface);
return profile;
}
public void ClearActiveProfile(ProfileModule module)
{
module.ChangeActiveProfile(null, _surfaceService.ActiveSurface);
}
public async Task ClearActiveProfileAnimated(ProfileModule module)
{
await module.ChangeActiveProfileAnimated(null, _surfaceService.ActiveSurface);
}
public void DeleteProfile(Profile profile)
{
_logger.Debug("Removing profile " + profile);
// If the given profile is currently active, disable it first (this also disposes it)
if (profile.Module.ActiveProfile == profile)
profile.Module.ChangeActiveProfile(null, _surfaceService.ActiveSurface);
else
profile.Dispose();
_profileRepository.Remove(profile.ProfileEntity);
}
public void DeleteProfile(ProfileDescriptor profileDescriptor)
{
var profileEntity = _profileRepository.Get(profileDescriptor.Id);
_profileRepository.Remove(profileEntity);
}
public void UpdateProfile(Profile profile, bool includeChildren)
{
_logger.Debug("Updating profile " + profile);
@@ -116,46 +135,73 @@ namespace Artemis.Core.Services.Storage
_profileRepository.Save(profile.ProfileEntity);
}
public bool UndoUpdateProfile(Profile profile, ProfileModule module)
public bool UndoUpdateProfile(Profile profile)
{
if (!profile.UndoStack.Any())
// Keep the profile from being rendered by locking it
lock (profile)
{
_logger.Debug("Undo profile update - Failed, undo stack empty");
return false;
}
if (!profile.UndoStack.Any())
{
_logger.Debug("Undo profile update - Failed, undo stack empty");
return false;
}
ActivateProfile(module, null);
var top = profile.UndoStack.Pop();
var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
profile.RedoStack.Push(memento);
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings);
profile.ApplyToProfile();
ActivateProfile(module, profile);
var top = profile.UndoStack.Pop();
var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
profile.RedoStack.Push(memento);
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings);
profile.ApplyToProfile();
InstantiateProfile(profile);
}
_logger.Debug("Undo profile update - Success");
return true;
}
public bool RedoUpdateProfile(Profile profile, ProfileModule module)
public bool RedoUpdateProfile(Profile profile)
{
if (!profile.RedoStack.Any())
// Keep the profile from being rendered by locking it
lock (profile)
{
_logger.Debug("Redo profile update - Failed, redo empty");
return false;
if (!profile.RedoStack.Any())
{
_logger.Debug("Redo profile update - Failed, redo empty");
return false;
}
var top = profile.RedoStack.Pop();
var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
profile.UndoStack.Push(memento);
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings);
profile.ApplyToProfile();
InstantiateProfile(profile);
_logger.Debug("Redo profile update - Success");
return true;
}
ActivateProfile(module, null);
var top = profile.RedoStack.Pop();
var memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
profile.UndoStack.Push(memento);
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings);
profile.ApplyToProfile();
ActivateProfile(module, profile);
_logger.Debug("Redo profile update - Success");
return true;
}
public ProfileDescriptor GetLastActiveProfile(ProfileModule module)
{
var moduleProfiles = _profileRepository.GetByPluginGuid(module.PluginInfo.Guid);
var profileEntity = moduleProfiles.FirstOrDefault(p => p.IsActive) ?? moduleProfiles.FirstOrDefault();
return profileEntity == null ? null : new ProfileDescriptor(module, profileEntity);
}
public void InstantiateProfile(Profile profile)
{
profile.PopulateLeds(_surfaceService.ActiveSurface);
InitializeLayerProperties(profile);
InstantiateLayers(profile);
InstantiateFolders(profile);
}
/// <summary>
/// Initializes the properties on the layers of the given profile
/// </summary>
/// <param name="profile"></param>
private void InitializeLayerProperties(Profile profile)
{
foreach (var layer in profile.GetAllLayers())
@@ -167,6 +213,9 @@ namespace Artemis.Core.Services.Storage
}
}
/// <summary>
/// Instantiates all plugin-related classes on the folders of the given profile
/// </summary>
private void InstantiateFolders(Profile profile)
{
foreach (var folder in profile.GetAllFolders())
@@ -182,6 +231,9 @@ namespace Artemis.Core.Services.Storage
}
}
/// <summary>
/// Instantiates all plugin-related classes on the layers of the given profile
/// </summary>
private void InstantiateLayers(Profile profile)
{
foreach (var layer in profile.GetAllLayers())
@@ -204,6 +256,10 @@ namespace Artemis.Core.Services.Storage
}
}
/// <summary>
/// Populates all missing LEDs on all currently active profiles
/// </summary>
/// <param name="surface"></param>
private void ActiveProfilesPopulateLeds(ArtemisSurface surface)
{
var profileModules = _pluginService.GetPluginsOfType<ProfileModule>();
@@ -211,6 +267,10 @@ namespace Artemis.Core.Services.Storage
profileModule.ActiveProfile.PopulateLeds(surface);
}
/// <summary>
/// Instantiates all missing plugin-related classes on the profile trees of all currently active profiles
/// </summary>
private void ActiveProfilesInstantiatePlugins()
{
var profileModules = _pluginService.GetPluginsOfType<ProfileModule>();
@@ -242,8 +302,8 @@ namespace Artemis.Core.Services.Storage
ActiveProfilesInstantiatePlugins();
else if (e.PluginInfo.Instance is ProfileModule profileModule)
{
var activeProfile = GetActiveProfile(profileModule);
ActivateProfile(profileModule, activeProfile);
var activeProfile = GetLastActiveProfile(profileModule);
ActivateProfile(activeProfile);
}
}