Skip to content

Commit

Permalink
Device: add API for capability labels
Browse files Browse the repository at this point in the history
  • Loading branch information
chyyran committed Feb 23, 2020
1 parent ac75f97 commit d6c4640
Show file tree
Hide file tree
Showing 14 changed files with 214 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Snowflake.Input.Device
{
/// <summary>
/// Represents a mapping from <see cref="DeviceCapability"/> to a label on the real device.
/// When enumerated,
/// </summary>
public interface IDeviceCapabilityLabels : IEnumerable<KeyValuePair<DeviceCapability, string>>
{
/// <summary>
/// Gets a friendly string representation of the provided device capability.
/// </summary>
/// <param name="capability">The <see cref="DeviceCapability"/> to get a name for.</param>
/// <returns>A string representation of the device capbility</returns>
string this[DeviceCapability capability] { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,11 @@ public interface IInputDeviceInstance
/// the natural mapping from controller element to a device capability.
/// </summary>
IDeviceLayoutMapping DefaultLayout { get; }

/// <summary>
/// Gets the friendly label names for capabilities this device instance supports.
/// This collection must at least have labels for all supported capabilities in <see cref="Capabilities"/>.
/// </summary>
IDeviceCapabilityLabels CapabilityLabels { get; }
}
}
35 changes: 35 additions & 0 deletions src/Snowflake.Framework.Tests/Input/LabelsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Snowflake.Input.Device;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;

namespace Snowflake.Input.Tests
{
public class LabelsTests
{
[Fact]
public void DefaultLabels_Test()
{
Assert.Equal("Button0", DefaultDeviceCapabilityLabels.DefaultLabels[DeviceCapability.Button0]);
foreach(var (c, l) in DefaultDeviceCapabilityLabels.DefaultLabels)
{
Assert.Equal(l, c.ToString());
}
}

[Fact]
public void DictLabels_Test()
{
var dictLabels = new Dictionary<DeviceCapability, string>()
{
{ DeviceCapability.Button0, "Button A" }
};

IDeviceCapabilityLabels labels = new DictionaryDeviceCapabilityLabels(dictLabels);
Assert.Equal("Button A", labels[DeviceCapability.Button0]);
Assert.Single(labels);
Assert.Equal(string.Empty, labels[DeviceCapability.Axis0Negative]);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using EnumsNET;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Snowflake.Input.Device
{
/// <summary>
/// Implements a default label mapping using the name of the capability enum element.
/// </summary>
public sealed class DefaultDeviceCapabilityLabels : IDeviceCapabilityLabels
{
/// <summary>
/// Default labels using the name of the capability enum element.
/// </summary>
public static IDeviceCapabilityLabels DefaultLabels => _DefaultLabels;

private static readonly DefaultDeviceCapabilityLabels _DefaultLabels = new DefaultDeviceCapabilityLabels();

public string this[DeviceCapability capability] => Enums.AsString(capability);

public IEnumerator<KeyValuePair<DeviceCapability, string>> GetEnumerator()
{
return Enums.GetMembers<DeviceCapability>()
.Select(e => KeyValuePair.Create(e.Value, e.AsString())).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using EnumsNET;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Snowflake.Input.Device
{
/// <summary>
/// Implements a label mapping using a backing dictionary. Missing labels are replaced with
/// the empty string.
/// </summary>
public sealed class DictionaryDeviceCapabilityLabels : IDeviceCapabilityLabels
{
/// <summary>
/// Create a new <see cref="IDeviceCapabilityLabels"/> using a backing dictionary.
/// </summary>
/// <param name="labels">The backing dictionary to use.</param>
public DictionaryDeviceCapabilityLabels(IDictionary<DeviceCapability, string> labels)
{
this.Labels = labels;
}

private IDictionary<DeviceCapability, string> Labels { get; }

public string this[DeviceCapability capability] =>
this.Labels.TryGetValue(capability, out string? value) ? value : string.Empty;

public IEnumerator<KeyValuePair<DeviceCapability, string>> GetEnumerator()
{
return this.Labels.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ public sealed class DirectInputDeviceInstance : IInputDeviceInstance
internal DirectInputDeviceInstance(int enumerationIndex,
int classEnumerationIndex, int uniqueNameEnumerationIndex,
int productEnumerationIndex,
IReadOnlyDictionary<DeviceCapability, (int offset, int rawId)> capabilities,
IDeviceLayoutMapping defaultLayout)
IReadOnlyDictionary<DeviceCapability, (int offset, int rawId)> capabilities,
IDeviceLayoutMapping defaultLayout,
IDeviceCapabilityLabels labels)
{
this.EnumerationIndex = enumerationIndex;
this.ClassEnumerationIndex = classEnumerationIndex;
this.NameEnumerationIndex = uniqueNameEnumerationIndex;
this.ProductEnumerationIndex = productEnumerationIndex;
this.DefaultLayout = defaultLayout;
this.CapabilityMap = capabilities;
this.CapabilityLabels = labels;
}

private IReadOnlyDictionary<DeviceCapability, (int offset, int rawId)> CapabilityMap { get; }
Expand All @@ -35,6 +37,8 @@ internal DirectInputDeviceInstance(int enumerationIndex,

public int ProductEnumerationIndex { get; }

public IDeviceCapabilityLabels CapabilityLabels { get; }

public int GetObjectOffset(DeviceCapability capability) =>
this.CapabilityMap.TryGetValue(capability, out (int offset, int _) val) ? val.offset : -1;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public KeyboardDeviceInstance()
this.Capabilities = DeviceCapabilityClasses.Keyboard
.Concat(DeviceCapabilityClasses.MouseButtons)
.Concat(DeviceCapabilityClasses.MouseCursor)
.Concat(new[] { DeviceCapability.Axis0,
DeviceCapability.Axis0Negative,
.Concat(new[] { DeviceCapability.Axis0,
DeviceCapability.Axis0Negative,
DeviceCapability.Axis0Positive })
.ToList();

Expand All @@ -69,5 +69,7 @@ public KeyboardDeviceInstance()
public int NameEnumerationIndex => 0;

public int ProductEnumerationIndex => 0;

public IDeviceCapabilityLabels CapabilityLabels => DefaultDeviceCapabilityLabels.DefaultLabels;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public sealed class PassthroughDeviceInstance : IInputDeviceInstance
public int NameEnumerationIndex => 0;

public int ProductEnumerationIndex => 0;

public IDeviceCapabilityLabels CapabilityLabels => DefaultDeviceCapabilityLabels.DefaultLabels;
}
}
3 changes: 3 additions & 0 deletions src/Snowflake.Framework/Input/Device/XInputDeviceInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,8 @@ public XInputDeviceInstance(int enumerationIndex)
public IDeviceLayoutMapping DefaultLayout { get; }

public int ProductEnumerationIndex => this.EnumerationIndex;

// todo: make XInput specific labels!
public IDeviceCapabilityLabels CapabilityLabels => DefaultDeviceCapabilityLabels.DefaultLabels;
}
}
22 changes: 22 additions & 0 deletions src/Snowflake.Framework/Snowflake.Framework.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using GraphQL.Types;
using Snowflake.Input.Device;
using System;
using System.Collections.Generic;
using System.Text;

namespace Snowflake.Support.GraphQLFrameworkQueries.Types.InputDevice
{
public class DeviceCapabilityLabelKeyValuePairGraphType : ObjectGraphType<KeyValuePair<DeviceCapability, string>>
{
public DeviceCapabilityLabelKeyValuePairGraphType()
{
Name = "DeviceCapabilityLabelKeyValuePair";
Description = "A label of a device capability";
Field<DeviceCapabilityEnum>("capability", description: "The capability this label describes.", resolve: c => c.Source.Key);
Field<StringGraphType>("label", description: "The label of the capability.", resolve: c => c.Source.Value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using GraphQL.Types;
using Snowflake.Input.Device;
using System;
using System.Collections.Generic;
using System.Text;

namespace Snowflake.Support.GraphQLFrameworkQueries.Types.InputDevice
{
public class DeviceCapabilityLabelsGraphType : ObjectGraphType<IDeviceCapabilityLabels>
{
public DeviceCapabilityLabelsGraphType()
{
Name = "DeviceCapabilityLabels";
Description = "A mapping of device capabilities to friendly string labels.";
Field<ListGraphType<DeviceCapabilityLabelKeyValuePairGraphType>>("labels", description: "A list of labels in this device instance",
resolve: c => c.Source);
foreach (var (key, _) in DefaultDeviceCapabilityLabels.DefaultLabels)
{
Field<StringGraphType>(key.ToString(), resolve: c => c.Source[key] ?? key.ToString());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public InputDeviceInstanceGraphType()
resolve: context => context.Source.Capabilities);
Field<ListGraphType<MappedControllerElementGraphType>>("defaultLayout",
description: "The default, assumed natural mapping from capability to virtual element without regard for any specific layout.",
resolve: context => (IEnumerable<MappedControllerElement>)context.Source.DefaultLayout);
resolve: context => context.Source.DefaultLayout);
Field<IntGraphType>("enumerationIndex",
description: "When enumerating devices with a given driver, the index of enumeration for this driver.",
resolve: context => context.Source.EnumerationIndex);
Expand All @@ -41,6 +41,9 @@ public InputDeviceInstanceGraphType()
"with regards to the specific type of device, as determined by unique VID/product name," +
"if and only if the driver disambiguates between different devices.",
resolve: context => context.Source.ProductEnumerationIndex);
Field<DeviceCapabilityLabelsGraphType>("capabilityLabels",
description: "Friendly labels for the device capabilities of this instance",
resolve: context => context.Source.CapabilityLabels);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,16 @@ public IEnumerable<IInputDevice> QueryConnectedDevices()

// todo add support for mapping overrides
instances.Add(new DirectInputDeviceInstance(allOrder, classOrder, nameOrder, prodOrder, capabilities,
GenerateDefaultMapping(capabilities.Keys)));
GenerateDefaultMapping(capabilities.Keys), DefaultDeviceCapabilityLabels.DefaultLabels));

yield return new InputDevice(vid, pid,
name, name, path, device.Information.InstanceGuid, instances.AsReadOnly());
}

}

private static readonly IDictionary<DeviceCapability, ControllerElement> _hidMappings =
new Dictionary<DeviceCapability, ControllerElement>()
{

{DeviceCapability.Button0, ControllerElement.ButtonA},
{DeviceCapability.Button1, ControllerElement.ButtonB},
{DeviceCapability.Button2, ControllerElement.ButtonX},
Expand Down

0 comments on commit d6c4640

Please sign in to comment.