using System; using System.ComponentModel; using System.Runtime.InteropServices; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.LowLevel; using UnityEngine.InputSystem.Utilities; ////TODO: expose whether pen actually has eraser and which barrel buttons it has ////TODO: hook up pointerId in backend to allow identifying different pens ////REVIEW: have surface distance property to detect how far pen is when hovering? ////REVIEW: does it make sense to have orientation support for pen, too? namespace UnityEngine.InputSystem.LowLevel { /// /// Default state layout for pen devices. /// // IMPORTANT: Must match with PenInputState in native. [StructLayout(LayoutKind.Explicit, Size = 36)] public struct PenState : IInputStateTypeInfo { /// /// Format code for PenState. /// /// Returns "PEN ". /// public static FourCC Format => new FourCC('P', 'E', 'N'); /// /// Current screen-space position of the pen. /// /// Screen-space position. /// [InputControl(usage = "Point", dontReset = true)] [FieldOffset(0)] public Vector2 position; /// /// Screen-space motion delta. /// /// Screen-space motion delta. /// [InputControl(usage = "Secondary2DMotion", layout = "Delta")] [FieldOffset(8)] public Vector2 delta; /// /// The way the pen is leaned over perpendicular to the tablet surface. X goes [-1..1] left to right /// (with -1 and 1 being completely flush to the surface) and Y goes [-1..1] bottom to top. /// /// Amount pen is leaning over. /// [InputControl(layout = "Vector2", displayName = "Tilt", usage = "Tilt")] [FieldOffset(16)] public Vector2 tilt; /// /// Pressure with which the pen is pressed against the surface. 0 is none, 1 is full pressure. /// /// Pressure with which the pen is pressed. /// /// May go beyond 1 depending on pressure calibration on the system. The maximum pressure point /// may be set to less than the physical maximum pressure point determined by the hardware. /// /// [InputControl(layout = "Analog", usage = "Pressure", defaultState = 0.0f)] [FieldOffset(24)] public float pressure; /// /// Amount by which the pen is rotated around itself. /// /// Rotation of the pen around itself. /// [InputControl(layout = "Axis", displayName = "Twist", usage = "Twist")] [FieldOffset(28)] public float twist; /// /// Button mask for which buttons on the pen are active. /// /// Bitmask for buttons on the pen. [InputControl(name = "tip", displayName = "Tip", layout = "Button", bit = (int)PenButton.Tip, usage = "PrimaryAction")] [InputControl(name = "press", useStateFrom = "tip", synthetic = true, usages = new string[0])] [InputControl(name = "eraser", displayName = "Eraser", layout = "Button", bit = (int)PenButton.Eraser)] [InputControl(name = "inRange", displayName = "In Range?", layout = "Button", bit = (int)PenButton.InRange, synthetic = true)] [InputControl(name = "barrel1", displayName = "Barrel Button #1", layout = "Button", bit = (int)PenButton.BarrelFirst, alias = "barrelFirst", usage = "SecondaryAction")] [InputControl(name = "barrel2", displayName = "Barrel Button #2", layout = "Button", bit = (int)PenButton.BarrelSecond, alias = "barrelSecond")] [InputControl(name = "barrel3", displayName = "Barrel Button #3", layout = "Button", bit = (int)PenButton.BarrelThird, alias = "barrelThird")] [InputControl(name = "barrel4", displayName = "Barrel Button #4", layout = "Button", bit = (int)PenButton.BarrelFourth, alias = "barrelFourth")] // "Park" unused controls. [InputControl(name = "radius", layout = "Vector2", format = "VEC2", sizeInBits = 64, usage = "Radius", offset = InputStateBlock.AutomaticOffset)] [InputControl(name = "pointerId", layout = "Digital", format = "UINT", sizeInBits = 32, offset = InputStateBlock.AutomaticOffset)] ////TODO: this should be used [FieldOffset(32)] public ushort buttons; // Not currently used, but still needed in this struct for padding, // as il2cpp does not implement FieldOffset. [FieldOffset(34)] ushort displayIndex; /// /// Set or unset the bit in for the given . /// /// Button whose state to set. /// Whether the button is on or off. /// Same PenState with an updated mask. public PenState WithButton(PenButton button, bool state = true) { Debug.Assert((int)button < 16, $"Expected button < 16, so we fit into the 16 bit wide bitmask"); var bit = 1U << (int)button; if (state) buttons |= (ushort)bit; else buttons &= (ushort)~bit; return this; } /// public FourCC format => Format; } } namespace UnityEngine.InputSystem { /// /// Enumeration of buttons on a . /// public enum PenButton { /// /// Button at the tip of a pen. /// /// Tip, /// /// Button located end of pen opposite to . /// /// /// Pens do not necessarily have an eraser. If a pen doesn't, the respective button /// does nothing and will always be unpressed. /// /// Eraser, /// /// First button on the side of the pen. /// /// BarrelFirst, /// /// Second button on the side of the pen. /// /// BarrelSecond, /// /// Artificial button that indicates whether the pen is in detection range or not. /// /// /// Range detection may not be supported by a pen/tablet. /// /// InRange, /// /// Third button on the side of the pen. /// /// BarrelThird, /// /// Fourth button on the side of the pen. /// /// BarrelFourth, /// /// Synonym for . /// Barrel1 = BarrelFirst, /// /// Synonym for . /// Barrel2 = BarrelSecond, /// /// Synonym for . /// Barrel3 = BarrelThird, /// /// Synonym for . /// Barrel4 = BarrelFourth, } /// /// Represents a pen/stylus input device. /// /// /// Unlike mice but like touch, pens are absolute pointing devices moving across a fixed /// surface area. /// /// The acts as a button that is considered pressed as long as the pen is in contact with the /// tablet surface. /// [InputControlLayout(stateType = typeof(PenState), isGenericTypeOfDevice = true)] public class Pen : Pointer { ////TODO: give the tip and eraser a very low press point /// /// The tip button of the pen. /// /// Control representing the tip button. /// public ButtonControl tip { get; protected set; } /// /// The eraser button of the pen, i.e. the button on the end opposite to the tip. /// /// Control representing the eraser button. /// /// If the pen does not have an eraser button, this control will still be present /// but will not trigger. /// /// public ButtonControl eraser { get; protected set; } /// /// The button on the side of the pen barrel and located closer to the tip of the pen. /// /// Control representing the first side button. /// /// If the pen does not have barrel buttons, this control will still be present /// but will not trigger. /// /// public ButtonControl firstBarrelButton { get; protected set; } /// /// The button on the side of the pen barrel and located closer to the eraser end of the pen. /// /// Control representing the second side button. /// /// If the pen does not have barrel buttons, this control will still be present /// but will not trigger. /// /// public ButtonControl secondBarrelButton { get; protected set; } /// /// Third button the side of the pen barrel. /// /// Control representing the third side button. /// /// If the pen does not have a third barrel buttons, this control will still be present /// but will not trigger. /// /// public ButtonControl thirdBarrelButton { get; protected set; } /// /// Fourth button the side of the pen barrel. /// /// Control representing the fourth side button. /// /// If the pen does not have a fourth barrel buttons, this control will still be present /// but will not trigger. /// /// public ButtonControl fourthBarrelButton { get; protected set; } /// /// Button control that indicates whether the pen is in range of the tablet surface or not. /// /// /// This is a synthetic control (). /// /// If range detection is not supported by the pen, this button will always be "pressed". /// /// public ButtonControl inRange { get; protected set; } /// /// Orientation of the pen relative to the tablet surface, i.e. the amount by which it is leaning /// over along the X and Y axis. /// /// Control presenting the amount the pen is leaning over. /// /// X axis goes from [-1..1] left to right with -1 and 1 meaning the pen is flush with the tablet surface. Y axis /// goes from [-1..1] bottom to top. /// public Vector2Control tilt { get; protected set; } /// /// Rotation of the pointer around its own axis. 0 means the pointer is facing away from the user (12 'o clock position) /// and ~1 means the pointer has been rotated clockwise almost one full rotation. /// /// Control representing the twist of the pen around itself. /// /// Twist is generally only supported by pens and even among pens, twist support is rare. An example product that /// supports twist is the Wacom Art Pen. /// /// The axis of rotation is the vector facing away from the pointer surface when the pointer is facing straight up /// (i.e. the surface normal of the pointer surface). When the pointer is tilted, the rotation axis is tilted along /// with it. /// public AxisControl twist { get; protected set; } /// /// The pen that was active or connected last or null if there is no pen. /// public new static Pen current { get; internal set; } /// /// Return the given pen button. /// /// Pen button to return. /// is not a valid pen button. public ButtonControl this[PenButton button] { get { switch (button) { case PenButton.Tip: return tip; case PenButton.Eraser: return eraser; case PenButton.BarrelFirst: return firstBarrelButton; case PenButton.BarrelSecond: return secondBarrelButton; case PenButton.BarrelThird: return thirdBarrelButton; case PenButton.BarrelFourth: return fourthBarrelButton; case PenButton.InRange: return inRange; default: throw new InvalidEnumArgumentException(nameof(button), (int)button, typeof(PenButton)); } } } /// /// Make this the last used pen, i.e. . /// /// /// This is called automatically by the system when a pen is added or receives /// input. /// public override void MakeCurrent() { base.MakeCurrent(); current = this; } /// /// Called when the pen is removed from the system. /// protected override void OnRemoved() { base.OnRemoved(); if (current == this) current = null; } /// protected override void FinishSetup() { tip = GetChildControl("tip"); eraser = GetChildControl("eraser"); firstBarrelButton = GetChildControl("barrel1"); secondBarrelButton = GetChildControl("barrel2"); thirdBarrelButton = GetChildControl("barrel3"); fourthBarrelButton = GetChildControl("barrel4"); inRange = GetChildControl("inRange"); tilt = GetChildControl("tilt"); twist = GetChildControl("twist"); base.FinishSetup(); } } }