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 "SMXPanelAnimation.h"
|
||||||
#include "SMXManager.h"
|
#include "SMXManager.h"
|
||||||
#include "SMXDevice.h"
|
#include "SMXDevice.h"
|
||||||
@ -185,10 +227,7 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.
|
||||||
// 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 = {
|
vector<pair<int,int>> graphic_positions = {
|
||||||
{ 0,0 },
|
{ 0,0 },
|
||||||
{ 1,0 },
|
{ 1,0 },
|
||||||
@ -201,21 +240,20 @@ namespace {
|
|||||||
{ 2,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)
|
||||||
|
{
|
||||||
dst.clear();
|
dst.clear();
|
||||||
|
|
||||||
// The top-left corner for this panel:
|
// The top-left corner for this panel:
|
||||||
int x = graphic_positions[panel].first * 8;
|
int x = graphic_positions[panel].first * 5;
|
||||||
int y = graphic_positions[panel].second * 8;
|
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 dy = 0; dy < 4; ++dy)
|
||||||
for(int dx = 0; dx < 4; ++dx)
|
for(int dx = 0; dx < 4; ++dx)
|
||||||
dst.push_back(src.get(x+dx*2, y+dy*2));
|
dst.push_back(src.get(x+dx, y+dy));
|
||||||
|
|
||||||
// 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,11 @@ namespace smx_config
|
|||||||
SMX_Internal_OpenConsole();
|
SMX_Internal_OpenConsole();
|
||||||
|
|
||||||
CurrentSMXDevice.singleton = new CurrentSMXDevice();
|
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)
|
private void UnhandledExceptionEventHandler(object sender, UnhandledExceptionEventArgs e)
|
||||||
|
@ -2,7 +2,9 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Resources;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using SMXJSON;
|
using SMXJSON;
|
||||||
|
|
||||||
@ -227,6 +229,34 @@ namespace smx_config
|
|||||||
v = M;
|
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 "".
|
// Read path. If an error is encountered, return "".
|
||||||
public static string ReadFile(string path)
|
public static string ReadFile(string path)
|
||||||
{
|
{
|
||||||
@ -238,6 +268,70 @@ namespace smx_config
|
|||||||
return "";
|
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.
|
// This class just makes it easier to assemble binary command packets.
|
||||||
@ -304,6 +398,11 @@ namespace smx_config
|
|||||||
if(LightsTimer.IsEnabled)
|
if(LightsTimer.IsEnabled)
|
||||||
return;
|
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.
|
// Don't wait for an interval to send the first update.
|
||||||
//AutoLightsColorRefreshColor();
|
//AutoLightsColorRefreshColor();
|
||||||
|
|
||||||
@ -314,8 +413,8 @@ namespace smx_config
|
|||||||
{
|
{
|
||||||
LightsTimer.Stop();
|
LightsTimer.Stop();
|
||||||
|
|
||||||
// Reenable auto-lights immediately, without waiting for lights to time out.
|
// Turn lighting control back on.
|
||||||
SMX.SMX.ReenableAutoLights();
|
SMX.SMX.LightsAnimation_SetAuto(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AutoLightsColorRefreshColor()
|
private void AutoLightsColorRefreshColor()
|
||||||
|
@ -162,6 +162,24 @@ Use if the platform is too sensitive.</clr:String>
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</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}">
|
<Style TargetType="{x:Type controls:PresetButton}">
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<Setter.Value>
|
<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">
|
<TabControl x:Name="Main" Margin="0,0,0,0" Visibility='Visible' HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
|
||||||
<TabItem Header="Settings">
|
<TabItem Header="Settings">
|
||||||
<Grid Background="#FFE5E5E5" RenderTransformOrigin="0.5,0.5">
|
<Grid Background="#FFE5E5E5" RenderTransformOrigin="0.5,0.5">
|
||||||
|
|
||||||
<StackPanel Margin="0,0,0,0" VerticalAlignment="Top">
|
<StackPanel Margin="0,0,0,0" VerticalAlignment="Top">
|
||||||
<TextBlock HorizontalAlignment="Center" Margin="0,15,0,10" VerticalAlignment="Top"
|
<TextBlock HorizontalAlignment="Center" Margin="0,15,0,10" VerticalAlignment="Top"
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
@ -664,6 +681,26 @@ Use if the platform is too sensitive.</clr:String>
|
|||||||
|
|
||||||
<Separator Margin="0,10,0,4" />
|
<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
|
<StackPanel
|
||||||
Visibility="Visible"
|
Visibility="Visible"
|
||||||
Name="ColorPickerGroup" HorizontalAlignment="Center" Orientation="Vertical">
|
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)
|
private void LoadUIFromConfig(LoadFromConfigDelegateArgs args)
|
||||||
{
|
{
|
||||||
bool EitherControllerConnected = args.controller[0].info.connected || args.controller[1].info.connected;
|
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;
|
PanelColorP1.Visibility = args.controller[0].info.connected? Visibility.Visible:Visibility.Collapsed;
|
||||||
PanelColorP2.Visibility = args.controller[1].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);
|
RefreshConnectedPadList(args);
|
||||||
|
|
||||||
// If a second controller has connected and we're on Both, see if we need to prompt
|
// 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);
|
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>
|
<Name>SMX</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="Resources\pressed.gif" />
|
||||||
|
<Resource Include="Resources\released.gif" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
<!-- 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.
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
@ -7,6 +7,7 @@ using System.Windows.Input;
|
|||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace smx_config
|
namespace smx_config
|
||||||
{
|
{
|
||||||
@ -260,6 +261,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.
|
// A button that selects a preset, and shows a checkmark if that preset is set.
|
||||||
public class PresetButton: Control
|
public class PresetButton: Control
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user