Add a UI to set panel animations.
This is driven by the config tool, which needs to stay running.
This commit is contained in:
parent
e5e75e0106
commit
135e3c5401
@ -1,3 +1,45 @@
|
||||
// Handle playing GIF animations from inside SMXConfig.
|
||||
//
|
||||
// This can load two GIF animations, one for when panels are released
|
||||
// and one for when they're pressed, and play them automatically on the
|
||||
// pad in the background. Applications that control lights can do more
|
||||
// sophisticated things with the lights, but this gives an easy way for
|
||||
// people to create simple animations.
|
||||
//
|
||||
// If you're implementing the SDK in a game, you don't need this and should
|
||||
// use SMX.h instead.
|
||||
//
|
||||
// An animation is a single GIF with animations for all panels, in the
|
||||
// following layout:
|
||||
//
|
||||
// 0000|1111|2222
|
||||
// 0000|1111|2222
|
||||
// 0000|1111|2222
|
||||
// 0000|1111|2222
|
||||
// --------------
|
||||
// 3333|4444|5555
|
||||
// 3333|4444|5555
|
||||
// 3333|4444|5555
|
||||
// 3333|4444|5555
|
||||
// --------------
|
||||
// 6666|7777|8888
|
||||
// 6666|7777|8888
|
||||
// 6666|7777|8888
|
||||
// 6666|7777|8888
|
||||
// x-------------
|
||||
//
|
||||
// The - | regions are ignored and are only there to space out the animation
|
||||
// to make it easier to view.
|
||||
//
|
||||
// The extra bottom row is a flag row and should normally be black. The first
|
||||
// pixel (bottom-left) optionally marks a loop frame. By default, the animation
|
||||
// plays all the way through and then loops back to the beginning. If the loop
|
||||
// frame pixel is white, it marks a frame to loop to instead of the beginning.
|
||||
// This allows pressed animations to have a separate lead-in and loop.
|
||||
//
|
||||
// Each animation is for a single pad. You can load the same animation for both
|
||||
// pads or use different ones.
|
||||
|
||||
#include "SMXPanelAnimation.h"
|
||||
#include "SMXManager.h"
|
||||
#include "SMXDevice.h"
|
||||
@ -185,37 +227,33 @@ namespace
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Given a 23x24 graphic frame and a panel number, return an array of 25 colors, containing
|
||||
// The X,Y positions of each possible panel.
|
||||
vector<pair<int,int>> graphic_positions = {
|
||||
{ 0,0 },
|
||||
{ 1,0 },
|
||||
{ 2,0 },
|
||||
{ 0,1 },
|
||||
{ 1,1 },
|
||||
{ 2,1 },
|
||||
{ 0,2 },
|
||||
{ 1,2 },
|
||||
{ 2,2 },
|
||||
};
|
||||
|
||||
// Given a 14x15 graphic frame and a panel number, return an array of 16 colors, containing
|
||||
// each light in the order it's sent to the master controller.
|
||||
void ConvertToPanelGraphic(const SMXGif::GIFImage &src, vector<SMXGif::Color> &dst, int panel)
|
||||
{
|
||||
vector<pair<int,int>> graphic_positions = {
|
||||
{ 0,0 },
|
||||
{ 1,0 },
|
||||
{ 2,0 },
|
||||
{ 0,1 },
|
||||
{ 1,1 },
|
||||
{ 2,1 },
|
||||
{ 0,2 },
|
||||
{ 1,2 },
|
||||
{ 2,2 },
|
||||
};
|
||||
|
||||
dst.clear();
|
||||
|
||||
// The top-left corner for this panel:
|
||||
int x = graphic_positions[panel].first * 8;
|
||||
int y = graphic_positions[panel].second * 8;
|
||||
int x = graphic_positions[panel].first * 5;
|
||||
int y = graphic_positions[panel].second * 5;
|
||||
|
||||
// Add the 4x4 grid first.
|
||||
// Add the 4x4 grid.
|
||||
for(int dy = 0; dy < 4; ++dy)
|
||||
for(int dx = 0; dx < 4; ++dx)
|
||||
dst.push_back(src.get(x+dx*2, y+dy*2));
|
||||
|
||||
// Add the 3x3 grid.
|
||||
for(int dy = 0; dy < 3; ++dy)
|
||||
for(int dx = 0; dx < 3; ++dx)
|
||||
dst.push_back(src.get(x+dx*2+1, y+dy*2+1));
|
||||
dst.push_back(src.get(x+dx, y+dy));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,11 @@ namespace smx_config
|
||||
SMX_Internal_OpenConsole();
|
||||
|
||||
CurrentSMXDevice.singleton = new CurrentSMXDevice();
|
||||
|
||||
// Load animations, and tell the SDK to handle auto-lighting as long as
|
||||
// we're running.
|
||||
Helpers.LoadSavedPanelAnimations();
|
||||
SMX.SMX.LightsAnimation_SetAuto(true);
|
||||
}
|
||||
|
||||
private void UnhandledExceptionEventHandler(object sender, UnhandledExceptionEventArgs e)
|
||||
|
@ -2,7 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Resources;
|
||||
using System.Windows.Threading;
|
||||
using SMXJSON;
|
||||
|
||||
@ -227,6 +229,34 @@ namespace smx_config
|
||||
v = M;
|
||||
}
|
||||
|
||||
// Return our settings directory, creating it if it doesn't exist.
|
||||
public static string GetSettingsDirectory()
|
||||
{
|
||||
string result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "/StepManiaX/";
|
||||
System.IO.Directory.CreateDirectory(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static byte[] ReadFileFromSettings(string filename)
|
||||
{
|
||||
string outputFilename = GetSettingsDirectory() + filename;
|
||||
try {
|
||||
return System.IO.File.ReadAllBytes(outputFilename);
|
||||
} catch {
|
||||
// If the file doesn't exist or can't be read for some other reason, just
|
||||
// return null.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveFileToSettings(string filename, byte[] data)
|
||||
{
|
||||
string outputFilename = GetSettingsDirectory() + filename;
|
||||
string directory = System.IO.Path.GetDirectoryName(outputFilename);
|
||||
System.IO.Directory.CreateDirectory(directory);
|
||||
System.IO.File.WriteAllBytes(outputFilename, data);
|
||||
}
|
||||
|
||||
// Read path. If an error is encountered, return "".
|
||||
public static string ReadFile(string path)
|
||||
{
|
||||
@ -238,6 +268,70 @@ namespace smx_config
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Read path. If an error is encountered, return null.
|
||||
public static byte[] ReadBinaryFile(string path)
|
||||
{
|
||||
try {
|
||||
return System.IO.File.ReadAllBytes(path);
|
||||
}
|
||||
catch(System.IO.IOException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<SMX.SMX.LightsType, string> LightsTypeNames = new Dictionary<SMX.SMX.LightsType, string>()
|
||||
{
|
||||
{ SMX.SMX.LightsType.LightsType_Pressed, "pressed" },
|
||||
{ SMX.SMX.LightsType.LightsType_Released, "released" },
|
||||
};
|
||||
|
||||
// Load any saved animations from disk. If useDefault is true, load the default
|
||||
// animation even if there's a user animation saved.
|
||||
public static void LoadSavedPanelAnimations(bool useDefault=false)
|
||||
{
|
||||
for(int pad = 0; pad < 2; ++pad)
|
||||
{
|
||||
foreach(var it in LightsTypeNames)
|
||||
LoadSavedAnimationType(pad, it.Key, useDefault);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveAnimationToDisk(int pad, SMX.SMX.LightsType type, byte[] data)
|
||||
{
|
||||
string filename = LightsTypeNames[type] + ".gif";
|
||||
string path = "Animations/Pad" + (pad+1) + "/" + filename;
|
||||
Helpers.SaveFileToSettings(path, data);
|
||||
}
|
||||
|
||||
// Read a saved PanelAnimation.
|
||||
//
|
||||
// Data will always be returned. If the user hasn't saved anything, we'll return
|
||||
// our default animation.
|
||||
public static byte[] ReadSavedAnimationType(int pad, SMX.SMX.LightsType type, bool useDefault=false)
|
||||
{
|
||||
string filename = LightsTypeNames[type] + ".gif";
|
||||
string path = "Animations/Pad" + (pad+1) + "/" + filename;
|
||||
byte[] gif = Helpers.ReadFileFromSettings(path);
|
||||
if(gif == null || useDefault)
|
||||
{
|
||||
// If the user has never loaded a file, load our default.
|
||||
Uri url = new Uri("pack://application:,,,/Resources/" + filename);
|
||||
StreamResourceInfo info = Application.GetResourceStream(url);
|
||||
gif = new byte[info.Stream.Length];
|
||||
info.Stream.Read(gif, 0, gif.Length);
|
||||
}
|
||||
return gif;
|
||||
}
|
||||
|
||||
// Load a PanelAnimation from disk.
|
||||
public static void LoadSavedAnimationType(int pad, SMX.SMX.LightsType type, bool useDefault=false)
|
||||
{
|
||||
byte[] gif = ReadSavedAnimationType(pad, type, useDefault);
|
||||
string error;
|
||||
SMX.SMX.LightsAnimation_Load(gif, pad, type, out error);
|
||||
}
|
||||
}
|
||||
|
||||
// This class just makes it easier to assemble binary command packets.
|
||||
@ -304,6 +398,11 @@ namespace smx_config
|
||||
if(LightsTimer.IsEnabled)
|
||||
return;
|
||||
|
||||
// We normally leave lights animation control enabled while this application is
|
||||
// running. Turn it off temporarily while we're showing the lights sample, or the
|
||||
// two will fight.
|
||||
SMX.SMX.LightsAnimation_SetAuto(false);
|
||||
|
||||
// Don't wait for an interval to send the first update.
|
||||
//AutoLightsColorRefreshColor();
|
||||
|
||||
@ -314,8 +413,8 @@ namespace smx_config
|
||||
{
|
||||
LightsTimer.Stop();
|
||||
|
||||
// Reenable auto-lights immediately, without waiting for lights to time out.
|
||||
SMX.SMX.ReenableAutoLights();
|
||||
// Turn lighting control back on.
|
||||
SMX.SMX.LightsAnimation_SetAuto(true);
|
||||
}
|
||||
|
||||
private void AutoLightsColorRefreshColor()
|
||||
|
@ -162,6 +162,24 @@ Use if the platform is too sensitive.</clr:String>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
|
||||
<!-- SelectableButton changes color when selected. -->
|
||||
<Style TargetType="{x:Type controls:SelectableButton}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Selected" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<!--<Setter Property="Foreground" Value="#FFF" />
|
||||
<Setter Property="Background" Value="#04F" />-->
|
||||
</Trigger>
|
||||
<!--
|
||||
<Trigger Property="IsMouseOver" Value="true">
|
||||
<Setter Property="Foreground" Value="#FFF" />
|
||||
<Setter Property="Background" Value="#04F" />
|
||||
</Trigger>
|
||||
-->
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type controls:PresetButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
@ -643,7 +661,6 @@ Use if the platform is too sensitive.</clr:String>
|
||||
<TabControl x:Name="Main" Margin="0,0,0,0" Visibility='Visible' HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
|
||||
<TabItem Header="Settings">
|
||||
<Grid Background="#FFE5E5E5" RenderTransformOrigin="0.5,0.5">
|
||||
|
||||
<StackPanel Margin="0,0,0,0" VerticalAlignment="Top">
|
||||
<TextBlock HorizontalAlignment="Center" Margin="0,15,0,10" VerticalAlignment="Top"
|
||||
TextAlignment="Center"
|
||||
@ -664,6 +681,26 @@ Use if the platform is too sensitive.</clr:String>
|
||||
|
||||
<Separator Margin="0,10,0,4" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"
|
||||
Margin="0,0,0,10">
|
||||
<controls:SelectableButton x:Name="PanelColorsButton" Content="Panel colors" Padding="15,2" Margin="5,10" Click="PressedColorModeButton" />
|
||||
<controls:SelectableButton x:Name="GIFAnimationsButton" Content="GIF animations" Padding="15,2" Margin="5,10" Click="PressedColorModeButton" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Name="GIFGroup" HorizontalAlignment="Center" Orientation="Vertical">
|
||||
<TextBlock TextAlignment="Center">
|
||||
Load panel animations
|
||||
</TextBlock>
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<Button Content="Load idle GIF" Padding="5,2" Margin="5,10" Name="LoadIdle" Click="LoadGIF" />
|
||||
<Button Content="Load pressed GIF" Padding="5,2" Margin="5,10" Name="LoadPressed" Click="LoadGIF" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Name="LeaveRunning" TextAlignment="Center">
|
||||
Leave this application running to play animations.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
Visibility="Visible"
|
||||
Name="ColorPickerGroup" HorizontalAlignment="Center" Orientation="Vertical">
|
||||
|
@ -71,6 +71,27 @@ namespace smx_config
|
||||
};
|
||||
}
|
||||
|
||||
private void PressedColorModeButton(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// The user pressed either the "panel colors" or "GIF animations" button.
|
||||
bool pressedPanelColors = sender == PanelColorsButton;
|
||||
|
||||
foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
|
||||
{
|
||||
SMX.SMXConfig config = activePad.Item2;
|
||||
|
||||
// If we're in panel colors mode, clear the AutoLightingUsePressedAnimations flag.
|
||||
// Otherwise, set it.
|
||||
if(pressedPanelColors)
|
||||
config.configFlags &= ~SMX.SMXConfigFlags.SMXConfigFlags_AutoLightingUsePressedAnimations;
|
||||
else
|
||||
config.configFlags |= SMX.SMXConfigFlags.SMXConfigFlags_AutoLightingUsePressedAnimations;
|
||||
SMX.SMX.SetConfig(activePad.Item1, config);
|
||||
}
|
||||
|
||||
CurrentSMXDevice.singleton.FireConfigurationChanged(null);
|
||||
}
|
||||
|
||||
private void LoadUIFromConfig(LoadFromConfigDelegateArgs args)
|
||||
{
|
||||
bool EitherControllerConnected = args.controller[0].info.connected || args.controller[1].info.connected;
|
||||
@ -80,6 +101,26 @@ namespace smx_config
|
||||
PanelColorP1.Visibility = args.controller[0].info.connected? Visibility.Visible:Visibility.Collapsed;
|
||||
PanelColorP2.Visibility = args.controller[1].info.connected? Visibility.Visible:Visibility.Collapsed;
|
||||
|
||||
// Show the color slider or GIF UI depending on which one is set in flags.
|
||||
// If both pads are turned on, just use the first one.
|
||||
foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
|
||||
{
|
||||
SMX.SMXConfig config = activePad.Item2;
|
||||
|
||||
// If SMXConfigFlags_AutoLightingUsePressedAnimations is set, show the GIF UI.
|
||||
// If it's not set, show the color slider UI.
|
||||
SMX.SMXConfigFlags flags = config.configFlags;
|
||||
bool usePressedAnimations = (flags & SMX.SMXConfigFlags.SMXConfigFlags_AutoLightingUsePressedAnimations) != 0;
|
||||
ColorPickerGroup.Visibility = usePressedAnimations? Visibility.Collapsed:Visibility.Visible;
|
||||
GIFGroup.Visibility = usePressedAnimations? Visibility.Visible:Visibility.Collapsed;
|
||||
|
||||
// Tell the color mode buttons which one is selected, to set the button highlight.
|
||||
PanelColorsButton.Selected = !usePressedAnimations;
|
||||
GIFAnimationsButton.Selected = usePressedAnimations;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
RefreshConnectedPadList(args);
|
||||
|
||||
// If a second controller has connected and we're on Both, see if we need to prompt
|
||||
@ -259,5 +300,48 @@ namespace smx_config
|
||||
|
||||
CurrentSMXDevice.singleton.FireConfigurationChanged(null);
|
||||
}
|
||||
|
||||
private void LoadGIF(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// If the "load idle GIF" button was pressed, load the released animation.
|
||||
// Otherwise, load the pressed animation.
|
||||
bool pressed = sender == this.LoadPressed;
|
||||
|
||||
// Prompt for a file to read.
|
||||
Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
|
||||
dialog.FileName = "Select an animated GIF";
|
||||
dialog.DefaultExt = ".gif";
|
||||
dialog.Filter = "Animated GIF (.gif)|*.gif";
|
||||
bool? result = dialog.ShowDialog();
|
||||
if(result == null || !(bool) result)
|
||||
return;
|
||||
|
||||
byte[] buf = Helpers.ReadBinaryFile(dialog.FileName);
|
||||
SMX.SMX.LightsType type = pressed? SMX.SMX.LightsType.LightsType_Pressed:SMX.SMX.LightsType.LightsType_Released;
|
||||
|
||||
foreach(Tuple<int,SMX.SMXConfig> activePad in ActivePad.ActivePads())
|
||||
{
|
||||
int pad = activePad.Item1;
|
||||
|
||||
// Load the animation.
|
||||
string error;
|
||||
if(!SMX.SMX.LightsAnimation_Load(buf, pad, type, out error))
|
||||
{
|
||||
// Any errors here are problems with the GIF, so there's no point trying
|
||||
// to load it for the second pad if the first returns an error. Just show the
|
||||
// error and stop.
|
||||
MessageBox.Show(error, "Error", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
|
||||
// Return without saving to settings on error.
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the GIF to disk so we can load it quickly later.
|
||||
Helpers.SaveAnimationToDisk(pad, type, buf);
|
||||
|
||||
// Refresh after loading a GIF to update the "Leave this application running" text.
|
||||
CurrentSMXDevice.singleton.FireConfigurationChanged(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
smx-config/Resources/pressed.gif
Normal file
BIN
smx-config/Resources/pressed.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 108 B |
BIN
smx-config/Resources/released.gif
Normal file
BIN
smx-config/Resources/released.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 141 B |
@ -191,6 +191,10 @@
|
||||
<Name>SMX</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\pressed.gif" />
|
||||
<Resource Include="Resources\released.gif" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
@ -199,4 +203,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
@ -7,6 +7,7 @@ using System.Windows.Input;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace smx_config
|
||||
{
|
||||
@ -259,6 +260,17 @@ namespace smx_config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A button with a selectable highlight.
|
||||
public class SelectableButton: Button
|
||||
{
|
||||
public static readonly DependencyProperty SelectedProperty = DependencyProperty.Register("Selected",
|
||||
typeof(bool), typeof(SelectableButton), new FrameworkPropertyMetadata(false));
|
||||
public bool Selected {
|
||||
get { return (bool) GetValue(SelectedProperty); }
|
||||
set { SetValue(SelectedProperty, value); }
|
||||
}
|
||||
}
|
||||
|
||||
// A button that selects a preset, and shows a checkmark if that preset is set.
|
||||
public class PresetButton: Control
|
||||
|
Loading…
x
Reference in New Issue
Block a user