Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom animation #199

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
949a671
Fixed PercentageProfile
JasonBSteele Sep 7, 2021
6d5eb1b
Added CreateEffectWindow and opened it from MainWindow
JasonBSteele Sep 8, 2021
de42ade
Created FrameUserControl
JasonBSteele Sep 8, 2021
f06f619
Add and use the CustomEffect and Frames models
JasonBSteele Sep 10, 2021
7cb2323
Added color picker
JasonBSteele Sep 12, 2021
32cd086
Build the pallete of used colors
JasonBSteele Sep 13, 2021
4ad4474
Created panel to Brush mapping and passed it to the panel layout
JasonBSteele Sep 13, 2021
c026e39
Fix missing black brush
JasonBSteele Sep 13, 2021
484e557
Add Transition time and Play button
JasonBSteele Sep 14, 2021
fcccf4d
Added API calls for writing a custom effects
JasonBSteele Sep 14, 2021
c7253d1
Added the CustomEffectCommandBuilder
JasonBSteele Sep 15, 2021
dfff735
Added lock to the CustomEffectCommandBuilder
JasonBSteele Sep 15, 2021
db33de8
tidying
JasonBSteele Sep 15, 2021
7b12074
Called the API!
JasonBSteele Sep 16, 2021
f7c0e24
Save to Device and cosmetics
JasonBSteele Sep 17, 2021
35b2585
Use a ListBox for the Frames instead
JasonBSteele Sep 19, 2021
9c7ddcf
minor bug and remove deleted code
JasonBSteele Sep 20, 2021
5e63c8e
Tidying
JasonBSteele Sep 21, 2021
26c0e2f
Added english text for the NL resource strings
JasonBSteele Sep 21, 2021
1a5e6d3
PR feedback changes
JasonBSteele Sep 25, 2021
01aa821
Change the Play button to a stop button after it is clicked
JasonBSteele Oct 7, 2021
e87ed76
Added comment for commented out code reason
JasonBSteele Oct 8, 2021
07c131f
Remove currently selected frame rather than last
JasonBSteele Oct 8, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion Winleafs.Api/Endpoints/EffectsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public void SetSelectedEffect(string effectName)
SendRequest(BaseUrl, Method.PUT, body: new { select = effectName});
}


/// <inheritdoc />
public Task<Effect> GetEffectDetailsAsync(string effectName)
{
Expand All @@ -78,6 +77,18 @@ public Effect GetEffectDetails(string effectName)
return SendRequest<Effect>(BaseUrl, Method.PUT, CreateWriteEffectCommand(effectName));
}

/// <inheritdoc />
public void WriteCustomEffectCommand(CustomEffectCommand customEffectCommand)
{
SendRequest(BaseUrl, Method.PUT, body: CreateWriteAnimationCommand(customEffectCommand));
}

/// <inheritdoc />
public async Task WriteCustomEffectCommandAsync(CustomEffectCommand customEffectCommand)
{
await SendRequestAsync(BaseUrl, Method.PUT, body: CreateWriteAnimationCommand(customEffectCommand));
}

private static object CreateWriteEffectCommand(string effectName)
{
return new
Expand All @@ -89,5 +100,13 @@ private static object CreateWriteEffectCommand(string effectName)
}
};
}

private static object CreateWriteAnimationCommand(CustomEffectCommand customAnimationCommand)
{
return new
{
write = customAnimationCommand
};
}
}
}
10 changes: 10 additions & 0 deletions Winleafs.Api/Endpoints/Interfaces/IEffectsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,15 @@ public interface IEffectsEndpoint

/// <inheritdoc cref="GetEffectDetailsAsync"/>
Effect GetEffectDetails(string effectName);

/// <summary>
/// Send a command for a custom effect.
/// </summary>
/// <param name="customAnimationCommand">The custom effect command to be sent.</param>
/// <returns>The details about the effect.</returns>
Task WriteCustomEffectCommandAsync(CustomEffectCommand customEffectCommand);

/// <inheritdoc cref="WriteCustomEffectCommandAsync"/>
void WriteCustomEffectCommand(CustomEffectCommand customEffectCommand);
}
}
5 changes: 5 additions & 0 deletions Winleafs.Api/Endpoints/NanoleafEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Newtonsoft.Json;
using NLog;
using RestSharp;
using RestSharp.Serializers.NewtonsoftJson;

namespace Winleafs.Api.Endpoints
{
Expand Down Expand Up @@ -60,13 +61,15 @@ protected async Task<object> SendRequestAsync(string endpoint, Method method,
Type returnType = null, object body = null, bool disableLogging = false)
{
var restClient = new RestClient(Client.BaseUri);

var request = new RestRequest(GetUrlForRequest(endpoint), method)
{
Timeout = Timeout
};

if (body != null)
{
restClient.UseNewtonsoftJson();
JasonBSteele marked this conversation as resolved.
Show resolved Hide resolved
request.AddJsonBody(body);
}

Expand Down Expand Up @@ -97,13 +100,15 @@ protected async Task<object> SendRequestAsync(string endpoint, Method method,
protected object SendRequest(string endpoint, Method method, Type returnType = null, object body = null, bool disableLogging = false)
{
var restClient = new RestClient(Client.BaseUri);

var request = new RestRequest(GetUrlForRequest(endpoint), method)
{
Timeout = Timeout //Set timeout to 2 seconds
};

if (body != null)
{
restClient.UseNewtonsoftJson();
request.AddJsonBody(body);
}

Expand Down
1 change: 1 addition & 0 deletions Winleafs.Api/Winleafs.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<PackageReference Include="NLog" Version="4.7.10" />
<PackageReference Include="Polly" Version="7.2.2" />
<PackageReference Include="RestSharp" Version="106.12.0" />
<PackageReference Include="RestSharp.Serializers.NewtonsoftJson" Version="106.12.0" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions Winleafs.Models/Models/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class Device

public PercentageProfile PercentageProfile { get;set;}

public CustomEffect CustomEffect { get; set; }

public ScreenMirrorAlgorithm ScreenMirrorAlgorithm { get; set; }

public ScreenMirrorFlip ScreenMirrorFlip { get; set; }
Expand Down
33 changes: 33 additions & 0 deletions Winleafs.Models/Models/Effects/CustomEffectCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Winleafs.Models.Models.Effects
{
public class CustomEffectCommand
{
[JsonProperty("command")]
public string Command { get; set; } // add, update, display

[JsonProperty("version")]
public string Version { get; } = "1.0";

[JsonProperty("animName")]
public string AnimName { get; set; }

[JsonProperty("animType")]
public string AnimType { get; set; } //custom, static
JasonBSteele marked this conversation as resolved.
Show resolved Hide resolved

[JsonProperty("animData")]
public string AnimData { get; set; }

[JsonProperty("loop")]
public bool Loop { get; set; }

[JsonProperty("palette")]
public IList<Palette> Palette { get; set; } = new List<Palette>();
}
}
30 changes: 30 additions & 0 deletions Winleafs.Models/Models/Layouts/CustomEffect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;

namespace Winleafs.Models.Models.Layouts
{
/// <summary>
/// A user created Custom Effect which specifies a static pattern or animation
/// </summary>
public class CustomEffect
{
/// <summary>
/// True if this is just one Frame to set each panel's color
/// </summary>
public bool IsStatic { get; set; }

/// <summary>
/// True if the animation is played repeatedly
/// </summary>
public bool IsLoop { get; set; }

/// <summary>
/// A series of frames each specifying the color for each panel
/// </summary>
public IList<Frame> Frames { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

Personally prefer using either IEnumerable<T> or just List<T>, don't see a point in using IList<T>

Copy link
Author

Choose a reason for hiding this comment

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

So IMHO IList<T> is preferable to List<T> so we're not forcing the specific implementation of List. IEnumerable may be okay if I'm not using any IList methods. Sometimes you can end up with LINQ having to provide functionality that is already there, an example is Count. IList provides a property for Count, IEnumerable doesn't, so if you need to count the items you have to get LINQ to iterate over the IEnumerable for you using Count() rather than just using the Count property that would be there if it were an IList.

The same is true for Any(), rather than Count > 0. This isn't as bad as Count() as Any() just needs to read one item from the IEnumerable, but it is still less efficient than the Count property.

I'll see what I break if I change to IEnumerable and get back to you.

Copy link
Author

@JasonBSteele JasonBSteele Sep 25, 2021

Choose a reason for hiding this comment

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

There is a lot of use of indexing, so it has to be List or IList. Personally I prefer IList as this means if we ever wanted to swap the implementation from List to a similar collection that still implements IList we would know it would definitely not break any existing code.


public CustomEffect()
{
Frames = new List<Frame>();
}
}
}
14 changes: 14 additions & 0 deletions Winleafs.Models/Models/Layouts/Frame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace Winleafs.Models.Models.Layouts
{
public class Frame
{
public IDictionary<int, uint> PanelColors { get; set; }

public Frame()
{
PanelColors = new Dictionary<int, uint>();
}
}
}
17 changes: 17 additions & 0 deletions Winleafs.Models/Models/Layouts/FrameListItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Winleafs.Models.Models.Layouts;

namespace Winleafs.Models.Models.Layouts
{
public class FrameListItem
{
public FrameListItem(Frame frame, string name)
{
Frame = frame;
Name = name;
}

public string Name { get; set; }
public Frame Frame { get; set; }
}
}

134 changes: 134 additions & 0 deletions Winleafs.Wpf/Api/Effects/CustomEffectCommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Winleafs.Models.Models.Effects;
using Winleafs.Models.Models.Layouts;
using Winleafs.Wpf.Helpers;

namespace Winleafs.Wpf.Api.Effects
{
/// <summary>
/// Used to build a CustomEffectCommand to send to the API
/// </summary>
public class CustomEffectCommandBuilder
{
private CustomEffect _customEffect;
JasonBSteele marked this conversation as resolved.
Show resolved Hide resolved
private int _transitionTime;

public CustomEffectCommandBuilder(CustomEffect customEffect)
{
_customEffect = customEffect;
}

public CustomEffectCommand BuildAddCommand(float transitionTime, string Name)
JasonBSteele marked this conversation as resolved.
Show resolved Hide resolved
{
//If a name has been passed the custom effect will be added to the device
var customEffectCommand = new CustomEffectCommand();
customEffectCommand.Command = "add";
customEffectCommand.AnimName = Name;
_transitionTime = (int)Math.Floor(transitionTime * 10);
JasonBSteele marked this conversation as resolved.
Show resolved Hide resolved

BuildBody(customEffectCommand);

return customEffectCommand;
}

public CustomEffectCommand BuildDisplayCommand(float transitionTime)
{
//If no name has been passed the custom effect will just be dispayed on the device
var customEffectCommand = new CustomEffectCommand();
customEffectCommand.Command = "display";
_transitionTime = (int)Math.Floor(transitionTime * 10);

BuildBody(customEffectCommand);

return customEffectCommand;
}

private void BuildBody(CustomEffectCommand customEffectCommand)
{

JasonBSteele marked this conversation as resolved.
Show resolved Hide resolved
if (_customEffect.IsStatic)
{
customEffectCommand.AnimType = "static";
}
else
{
customEffectCommand.AnimType = "custom";
customEffectCommand.Loop = _customEffect.IsLoop;
}

var animData = new StringBuilder();
animData.Append(_customEffect.Frames[0].PanelColors.Count);

foreach (var panelId in _customEffect.Frames[0].PanelColors.Keys)
{
animData.Append(BuildPanelAnimData(panelId));
}

customEffectCommand.AnimData = animData.ToString();

//Set pallete so used colurs are displayed in apps (docs say max of 20)
var rgbs = _customEffect.Frames.SelectMany(f => f.PanelColors.Values).Distinct().Take(20);
foreach(var rgb in rgbs)
{
customEffectCommand.Palette.Add(ColorFormatConverter.ToPalette(rgb));
}
}

private string BuildPanelAnimData(int panelId)
{
//Example taken from docs. This method returns one of the panel rows
//(semicolons and newlines added for clarity only):
//numPanels;
//panelId0; numFrames0; RGBWT01; RGBWT02; ... RGBWT0n(0);
//panelId1; numFrames1; RGBWT11; RGBWT12; ... RGBWT1n(1); ... ...
//panelIdN; numFramesN; RGBWTN1; RGBWTN2; ... RGBWTNn(N);
//RGB=color, W=0, Txx is transition time is tenths of a second

var sb = new StringBuilder();
uint? prevRgb = null;

var totalFrames = 0;
var sameColorFrameCount = 0;

foreach (var rgb in _customEffect.Frames.Select(f => f.PanelColors[panelId]))
{
if (prevRgb == null || rgb != prevRgb.Value)
{
if (prevRgb != null && sameColorFrameCount > 0)
{
sb.AppendFormat(" {0} {1} {2} 0 {3}",
(prevRgb >> 16) & 255,
JasonBSteele marked this conversation as resolved.
Show resolved Hide resolved
(prevRgb >> 8) & 255,
prevRgb & 255,
sameColorFrameCount * _transitionTime);

totalFrames++;
}

sb.AppendFormat(" {0} {1} {2} 0 {3}",
(rgb >> 16) & 255,
(rgb >> 8) & 255,
rgb & 255,
_transitionTime);

sameColorFrameCount = 0;
totalFrames++;
}
else
{
sameColorFrameCount++;
}

prevRgb = rgb;
}

//Prepend panelId numframes
return string.Format(" {0} {1}{2}", panelId, totalFrames, sb);
}

}
}
2 changes: 1 addition & 1 deletion Winleafs.Wpf/Api/Effects/CustomEffectsCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public CustomEffectsCollection(Orchestrator orchestrator)

var customColorEffects = UserSettings.Settings.CustomEffects;

if (customColorEffects != null && customColorEffects.Any())
if (customColorEffects != null && customColorEffects.Count > 0)
{
foreach (var customColorEffect in customColorEffects)
{
Expand Down
Loading