diff --git a/README.md b/README.md index 0dc03c0..11d82f2 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,11 @@ I may be able to help you out & your question may help others in the future by b ## I am a Developer, how can i help? -- Currently i'm having issues dealing with both versions at the same time, mostly because of an ongoing bug in .NET project handling. +- Packaging for Linux & MacOS (Binary Tar for each platforms + .deb?) + +- ~~Currently i'm having issues dealing with both versions at the same time, mostly because of an ongoing bug in .NET project handling. It seems to be currently impossible to have have a multi-target project & have dependencies change depending on the target framework. -What i'm noticing is that, the dependency with the highest version is always used, even if it's not compatible with the framework that is being targeted. +What i'm noticing is that, the dependency with the highest version is always used, even if it's not compatible with the framework that is being targeted.~~ - You can't use multiple gestures at the same time, as gestures with higher requirements will cancel out the lower ones. diff --git a/TODO-Gestures.md b/TODO-Gestures.md new file mode 100644 index 0000000..1c6f7a6 --- /dev/null +++ b/TODO-Gestures.md @@ -0,0 +1,106 @@ +## Un-ordered List + +- [x] Absolute Position based gestures (Need to start at a specific point) +- [x] Relative Position based gestures (Can be started from anywhere) + +### Node-Based Gestures + +- [ ] Gesture Recording +- [ ] Gesture Recognition + - [ ] Any single touch in a start nodes is a gesture + - [ ] More than 2 touches is a gesture +- [ ] Manual Gesture Setup + - [-] Node Types + - [-] Shared Elements + - [x] IsGestureStart (bool) (length == 1 || index == 0) + - [x] IsGestureEnd (bool) (length == 1 || index == length - 1) + - [x] Position (Vector2) + - [x] Allowed Position Deviation (double) + - [x] Timestamp (double) + - [x] Allowed Timestamp Deviation (double) + - [x] IsHold (bool) + - [x] Hold Duration (double) + - [x] Nodes Can be dragged + - [ ] Nodes Can be resized (Only for the start and end nodes) + - [ ] Rectangle + - [ ] Circle + +### Basic Gestures + +#### Gestures + +- [x] Tap (Any) + - [x] Relative + - [x] Absolute + +- [x] Hold (Any) + - [x] Relative + - [x] Absolute + +- [x] Swipe (Single) + - [x] Relative + - [x] Absolute + +- [x] Pan (Single) + - [x] Relative + - [x] Absolute + +- [x] Pinch (Single) + - [x] Relative + - [x] Absolute + +- [x] Rotate (Single) + - [x] Relative + - [x] Absolute + +#### Gestures Unit Tests + +- [x] Tap (Any) + - [x] Relative + - [x] Absolute + +- [x] Hold (Any) + - [x] Relative + - [x] Absolute + +- [x] Swipe (Single) + - [x] Relative + - [x] Absolute + +- [x] Pan (Single) + - [x] Relative + - [x] Absolute + +- [x] Pinch (Single) + - [x] Relative + - [x] Absolute + +- [x] Rotate (Single) + - [x] Relative + - [x] Absolute + +#### Gestures Setup + +- [x] Tap (Any) + - [x] Relative + - [x] Absolute + +- [x] Hold (Any) + - [x] Relative + - [x] Absolute + +- [x] Swipe (Single) + - [x] Relative + - [x] Absolute + +- [x] Pan (Single) + - [x] Relative + - [x] Absolute + +- [x] Pinch (Single) + - [x] Relative + - [x] Absolute + +- [x] Rotate (Single) + - [x] Relative + - [x] Absolute \ No newline at end of file diff --git a/TODO.md b/TODO.md index 03fc719..3bab142 100644 --- a/TODO.md +++ b/TODO.md @@ -4,112 +4,17 @@ ~~Add a `OnExternalGestureCompleted` event to `IGestures` with as parameter an GestureCompletedEventArgs that contains the gesture type and the gesture data.~~ -## Un-ordered List - -- [x] Absolute Position based gestures (Need to start at a specific point) -- [x] Relative Position based gestures (Can be started from anywhere) - -### Node-Based Gestures +## Medium Priority Tasks -- [ ] Gesture Recording -- [ ] Gesture Recognition - - [ ] Any single touch in a start nodes is a gesture - - [ ] More than 2 touches is a gesture -- [ ] Manual Gesture Setup - - [-] Node Types - - [-] Shared Elements - - [x] IsGestureStart (bool) (length == 1 || index == 0) - - [x] IsGestureEnd (bool) (length == 1 || index == length - 1) - - [x] Position (Vector2) - - [x] Allowed Position Deviation (double) - - [x] Timestamp (double) - - [x] Allowed Timestamp Deviation (double) - - [x] IsHold (bool) - - [x] Hold Duration (double) - - [x] Nodes Can be dragged - - [ ] Nodes Can be resized (Only for the start and end nodes) - - [ ] Rectangle - - [ ] Circle - -### Basic Gestures - -#### Gestures - -- [x] Tap (Any) - - [x] Relative - - [x] Absolute - -- [x] Hold (Any) - - [x] Relative - - [x] Absolute - -- [x] Swipe (Single) - - [x] Relative - - [x] Absolute - -- [x] Pan (Single) - - [x] Relative - - [x] Absolute - -- [x] Pinch (Single) - - [x] Relative - - [x] Absolute - -- [x] Rotate (Single) - - [x] Relative - - [x] Absolute - -#### Gestures Unit Tests - -- [x] Tap (Any) - - [x] Relative - - [x] Absolute - -- [x] Hold (Any) - - [x] Relative - - [x] Absolute - -- [x] Swipe (Single) - - [x] Relative - - [x] Absolute - -- [x] Pan (Single) - - [x] Relative - - [x] Absolute - -- [x] Pinch (Single) - - [x] Relative - - [x] Absolute - -- [x] Rotate (Single) - - [x] Relative - - [x] Absolute - -#### Gestures Setup - -- [x] Tap (Any) - - [x] Relative - - [x] Absolute +### Bindings Rework -- [x] Hold (Any) - - [x] Relative - - [x] Absolute +- [ ] Wrap different versions of Binding under a single generic interface, with a `Press()` and `Release()` method. -- [x] Swipe (Single) - - [x] Relative - - [x] Absolute +### Debugger -- [x] Pan (Single) - - [x] Relative - - [x] Absolute +- [ ] Implement an In-app gesture debugger, draw the inputs as well as state changes when they happen. (Simillar to osu!lazer's replay analyzer or Rewind) -- [x] Pinch (Single) - - [x] Relative - - [x] Absolute - -- [x] Rotate (Single) - - [x] Relative - - [x] Absolute +## Documentation ### Dev Documentation (Github Wiki) @@ -122,6 +27,32 @@ For each of these topics, indicates in which way such systems could be improved, - [ ] Gesture Setup - [ ] Gesture Unit Tests +- [ ] Intended behaviors + - [ ] UX + - [ ] Connection Screen + - [ ] Binding Overview (Main View) + - [ ] Gesture Setup + - [ ] Gesture Selection Screen + - [ ] Gesture Setup Screen + - [ ] Options Selection Screen + - [ ] Binding Selection Screen + - [ ] Tweaks Screen (Area & Gesture specific tweaks) + - [ ] Gesture Editing + - [ ] Gesture Removal + + - [ ] Gestures + - [ ] Tap + - [ ] Hold + - [ ] Swipe + - [ ] Pan + - [ ] Pinch + - [ ] Rotate + +- [ ] UX Testing Procedures +- [ ] Gesture Testing Procedures + - [ ] Unit Tests + - [ ] Manual Testing + ### User Documentation (Github Pages) - [ ] A download link for the current platform & another to the github releases page @@ -146,7 +77,3 @@ For each of these topics, indicates in which way such systems could be improved, - [-] FAQ - [-] Basic - [-] Troubleshooting - -### Bindings Rework - -- [ ] Wrap different versions of Binding under a single generic interface, with a `Press()` and `Release()` method. diff --git a/Touch-Gestures-0.6.x/Extensions/TabletReferenceExtensions.cs b/Touch-Gestures-0.6.x/Extensions/TabletReferenceExtensions.cs index 9dffd17..20aea7b 100644 --- a/Touch-Gestures-0.6.x/Extensions/TabletReferenceExtensions.cs +++ b/Touch-Gestures-0.6.x/Extensions/TabletReferenceExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using OpenTabletDriver.Desktop.Reflection; using OpenTabletDriver.Plugin.Tablet; @@ -11,7 +12,10 @@ public static class TabletReferenceExtensions { public static SharedTabletReference ToShared(this TabletReference tablet, TouchSettings touchSettings) { - var digitizer = tablet.Properties.Specifications.Digitizer; + var digitizer = tablet?.Properties?.Specifications?.Digitizer; + + if (tablet == null || digitizer == null) + throw new ArgumentNullException(nameof(tablet)); var penDigitizer = new SharedTabletDigitizer { diff --git a/Touch-Gestures-0.6.x/GesturesHandler.cs b/Touch-Gestures-0.6.x/GesturesHandler.cs index 3a0c3da..800117b 100644 --- a/Touch-Gestures-0.6.x/GesturesHandler.cs +++ b/Touch-Gestures-0.6.x/GesturesHandler.cs @@ -28,21 +28,20 @@ public class GesturesHandler : IPositionedPipelineElement, IDispo { #region Constants - public const string PLUGIN_NAME = "Touch Gestures"; + private const string PLUGIN_NAME = "Touch Gestures"; #endregion #region Fields - - private GesturesDaemonBase? _daemon; - private TouchSettings _touchSettings = TouchSettings.Default; - private IOutputMode? _outputMode; - private BindableProfile? _profile; - private SharedTabletReference? _tablet; - private bool _awaitingDaemon; + + protected TouchSettings _touchSettings = TouchSettings.Default; + protected IOutputMode? _outputMode; + protected GesturesDaemonBase? _daemon; + protected BindableProfile? _profile; + protected SharedTabletReference? _tablet; + protected bool _awaitingDaemon; private bool _hasPreviousGestureStarted; - #endregion #region Initialization @@ -70,7 +69,7 @@ private static void WaitForDebugger() } } - public void Initialize() + public virtual void Initialize() { FetchTouchSettings(); @@ -91,7 +90,7 @@ public void Initialize() Log.Write(PLUGIN_NAME, "Touch Gestures Daemon has not been enabled, please enable it in the 'Tools' tab", LogLevel.Error); } - private void InitializeCore(TabletReference tablet) + protected virtual void InitializeCore(TabletReference tablet) { _tablet = tablet.ToShared(_touchSettings); @@ -112,7 +111,7 @@ private void InitializeCore(TabletReference tablet) } } - private void FetchTouchSettings() + protected void FetchTouchSettings() { if (_Driver is Driver driver && Tablet != null) { @@ -132,7 +131,7 @@ private void FetchTouchSettings() } } - private void AddServices() + protected void AddServices() { if (_tablet is BulletproofSharedTabletReference btablet) { @@ -175,7 +174,7 @@ private void AddServices() #region Methods - public void Consume(IDeviceReport report) + public virtual void Consume(IDeviceReport report) { if (report is ITouchReport touchReport) { @@ -220,10 +219,13 @@ public void HandleConflictingGestures(IEnumerable gestures, ITouchRepor #region Events Handlers + public void OnEmit(IDeviceReport e) + => Emit?.Invoke(e); + public void OnDaemonLoaded(object? sender, EventArgs e) => Initialize(); - public void OnProfileChanged(object? sender, EventArgs e) + public virtual void OnProfileChanged(object? sender, EventArgs e) { if (_profile == null) { @@ -256,7 +258,7 @@ public void OnProfileChanged(object? sender, EventArgs e) Log.Debug(PLUGIN_NAME, "Settings updated"); } - private void SortGestures() + protected void SortGestures() { if (_profile == null) return; @@ -282,6 +284,9 @@ public void Dispose() if (_awaitingDaemon) GesturesDaemonBase.DaemonLoaded -= OnDaemonLoaded; + if (_tablet != null) + _daemon?.RemoveTablet(_tablet); + _awaitingDaemon = false; GC.SuppressFinalize(this); diff --git a/Touch-Gestures-0.6.x/PenGesturesHandler.cs b/Touch-Gestures-0.6.x/PenGesturesHandler.cs new file mode 100644 index 0000000..b4e3f8b --- /dev/null +++ b/Touch-Gestures-0.6.x/PenGesturesHandler.cs @@ -0,0 +1,142 @@ +using System; +using System.Numerics; +using OpenTabletDriver.Plugin; +using OpenTabletDriver.Plugin.Attributes; +using OpenTabletDriver.Plugin.Tablet; +using OpenTabletDriver.Plugin.Tablet.Touch; +using TouchGestures.Extensions; +using TouchGestures.Lib.Entities; +using TouchGestures.Lib.Entities.Tablet; + +namespace TouchGestures +{ + [PluginName(PLUGIN_NAME)] + public class PenGesturesHandler : GesturesHandler + { + #region Constants + + private const string PLUGIN_NAME = "Pen Gestures"; + + #endregion + + #region Fields + + private readonly TouchReport _stubReport = new(1); + private readonly TouchPoint _stubPoint = new(); + + #endregion + + #region Initialization + + public override void Initialize() + { + // Filters are loaded before tools for some reasons, so we have to wait for the daemon to be loaded + _daemon = GesturesDaemonBase.Instance; + + // OTD 0.6.4.0 doesn't dispose of plugins when detecting tablets, so unsubscribing early is necessary + GesturesDaemonBase.DaemonLoaded -= OnDaemonLoaded; + _awaitingDaemon = false; + + if (Tablet != null) + InitializeCore(Tablet); + + if (_daemon == null) + Log.Write(PLUGIN_NAME, "Touch Gestures Daemon has not been enabled, please enable it in the 'Tools' tab", LogLevel.Error); + } + + protected override void InitializeCore(TabletReference tablet) + { + _tablet = tablet.ToShared(_touchSettings); + _tablet.Name = $"{_tablet.Name} (Pen Only)"; + + AddServices(); + + if (_daemon != null) + { + _daemon.AddTablet(_tablet); + _profile = _daemon.GetSettingsForTablet(_tablet.Name); + + if (_profile != null) + { + _profile.IsMultiTouch = false; + _profile.ProfileChanged += OnProfileChanged; + OnProfileChanged(this, EventArgs.Empty); + } + + Log.Write(PLUGIN_NAME, "Now handling touch gesture for: " + _tablet.Name); + } + } + + #endregion + + #region Methods + + public override void Consume(IDeviceReport report) + { + if (report is ITabletReport tabletReport) + { + if (tabletReport.Pressure > 0) + { + _stubPoint.Position = tabletReport.Position; + _stubReport.Touches[0] = _stubPoint; + } + else + _stubReport.Touches[0] = null; + + if (_daemon != null && _daemon.IsReady) + { + // Iterate through all conflicting gestures + HandleConflictingGestures(TapGestures, _stubReport); + HandleConflictingGestures(HoldGestures, _stubReport); + + // Iterate through all non-conflicting gestures + foreach (var gesture in NonConflictingGestures) + gesture.OnInput(_stubReport.Touches); + } + } + + OnEmit(report); + } + + #endregion + + #region Events Handlers + + public override void OnProfileChanged(object? sender, EventArgs e) + { + if (_profile == null) + { + Log.Write(PLUGIN_NAME, "Settings are null", LogLevel.Error); + return; + } + + if (_tablet != null) + { + var lpmm = _tablet.PenDigitizer?.GetLPMM() ?? Vector2.Zero; + + if (_tablet.PenDigitizer != null && lpmm != Vector2.Zero) + _profile.UpdateLPMM(_tablet); + else + Log.Write(PLUGIN_NAME, "LPMM is zero, this usually means that 'Touch Settings' hasn't been enabled or its maxes are set to zero", LogLevel.Error); + } + + TapGestures.Clear(); + HoldGestures.Clear(); + NonConflictingGestures.Clear(); + + SortGestures(); + + TapGestures.AddRange(_profile.TapGestures); + HoldGestures.AddRange(_profile.HoldGestures); + + NonConflictingGestures.AddRange(_profile.SwipeGestures); + NonConflictingGestures.AddRange(_profile.PanGestures); + NonConflictingGestures.AddRange(_profile.PinchGestures); + NonConflictingGestures.AddRange(_profile.RotateGestures); + + Log.Debug(PLUGIN_NAME, "Settings updated"); + } + + #endregion + } +} diff --git a/Touch-Gestures.Installer/TouchGesturesInstaller.cs b/Touch-Gestures.Installer/TouchGesturesInstaller.cs index abdf8a8..50e4969 100644 --- a/Touch-Gestures.Installer/TouchGesturesInstaller.cs +++ b/Touch-Gestures.Installer/TouchGesturesInstaller.cs @@ -26,11 +26,10 @@ public class TouchGesturesInstaller : ITool private static readonly FileInfo location = new(assembly.Location); private static readonly DirectoryInfo? pluginsDirectory = location.Directory?.Parent; - private static readonly string AssemblySuffix = OTD_VERSION == "0.5.x" ? "" : $"-{OTD_VERSION}"; private readonly DirectoryInfo OTDEnhancedOutputModeDirectory = null!; - private readonly string dependenciesResourcePath = $"Touch-Gestures.Installer{AssemblySuffix}.Touch-Gestures-{OTD_VERSION}.zip"; + private readonly string dependenciesResourcePath = $"Touch-Gestures.Installer.Touch-Gestures-{OTD_VERSION}.zip"; public TouchGesturesInstaller() { diff --git a/Touch-Gestures.Lib/Entities/BindableProfile.cs b/Touch-Gestures.Lib/Entities/BindableProfile.cs index f116f12..8541c8e 100644 --- a/Touch-Gestures.Lib/Entities/BindableProfile.cs +++ b/Touch-Gestures.Lib/Entities/BindableProfile.cs @@ -1,19 +1,20 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Numerics; using System.Reflection; using Newtonsoft.Json; using OpenTabletDriver.Plugin; using TouchGestures.Lib.Entities.Gestures; using TouchGestures.Lib.Entities.Gestures.Bases; using TouchGestures.Lib.Entities.Tablet; +using TouchGestures.Lib.Interfaces; using TouchGestures.Lib.Serializables.Gestures; namespace TouchGestures.Lib.Entities { [JsonObject(MemberSerialization.OptIn)] - public class BindableProfile : IEnumerable + public class BindableProfile : IGesturesProfile { public event EventHandler? ProfileChanged; @@ -22,6 +23,9 @@ public class BindableProfile : IEnumerable [JsonProperty] public string Name { get; set; } = string.Empty; + [JsonProperty] + public bool IsMultiTouch { get; set; } = true; + [JsonProperty] public List TapGestures { get; set; } = new(); @@ -44,6 +48,13 @@ public class BindableProfile : IEnumerable #region Methods + /// + /// Constructs the bindings for this profile using a set builder. + /// + /// The Tablet owning these bindings. + /// + /// TODO: Apply abstraction to bindings so that we use inherited classes or builders Instead of . + /// public virtual void ConstructBindings(SharedTabletReference? tablet = null) { foreach (var gesture in TapGestures) @@ -89,6 +100,40 @@ public void Add(Gesture gesture) } } + public void Remove(Gesture gesture) + { + switch(gesture) + { + case BindableTapGesture tapGesture: + TapGestures.Remove(tapGesture); + break; + case BindableHoldGesture holdGesture: + HoldGestures.Remove(holdGesture); + break; + case BindableSwipeGesture swipeGesture: + SwipeGestures.Remove(swipeGesture); + break; + case BindablePanGesture panGesture: + PanGestures.Remove(panGesture); + break; + case BindablePinchGesture pinchGesture: + RemovePinch(pinchGesture); + break; + default: + throw new ArgumentException("Unknown gesture type."); + } + } + + public void Clear() + { + TapGestures.Clear(); + HoldGestures.Clear(); + SwipeGestures.Clear(); + PanGestures.Clear(); + PinchGestures.Clear(); + RotateGestures.Clear(); + } + public void Update(SerializableProfile profile, SharedTabletReference tablet, Dictionary identifierToPlugin) { FromSerializable(profile, identifierToPlugin, tablet, this); @@ -98,11 +143,12 @@ public void Update(SerializableProfile profile, SharedTabletReference tablet, Di public void UpdateLPMM(SharedTabletReference tablet) { - if (tablet.TouchDigitizer == null) - return; + Vector2? lpmm = IsMultiTouch ? tablet.TouchDigitizer?.GetLPMM() : + tablet.PenDigitizer?.GetLPMM(); - foreach (var gesture in this) - gesture.LinesPerMM = tablet.TouchDigitizer.GetLPMM(); + if (lpmm != null && lpmm != Vector2.Zero) + foreach (var gesture in this) + gesture.LinesPerMM = (Vector2)lpmm; } private void AddPinch(BindablePinchGesture pinchGesture) @@ -113,6 +159,14 @@ private void AddPinch(BindablePinchGesture pinchGesture) RotateGestures.Add(pinchGesture); } + private void RemovePinch(BindablePinchGesture pinchGesture) + { + if (pinchGesture.DistanceThreshold > 0) + PinchGestures.Remove(pinchGesture); + else + RotateGestures.Remove(pinchGesture); + } + #endregion #region Event Handlers @@ -130,16 +184,10 @@ public static BindableProfile FromSerializable(SerializableProfile profile, Dict { var result = existingProfile ?? new BindableProfile(); result.Name = profile.Name; + result.IsMultiTouch = profile.IsMultiTouch; if (existingProfile != null) - { - result.TapGestures.Clear(); - result.HoldGestures.Clear(); - result.SwipeGestures.Clear(); - result.PanGestures.Clear(); - result.PinchGestures.Clear(); - result.RotateGestures.Clear(); - } + result.Clear(); foreach (var gesture in profile.TapGestures) { @@ -203,6 +251,7 @@ public static SerializableProfile ToSerializable(BindableProfile profile, Dictio var result = new SerializableProfile(); { result.Name = profile.Name; + result.IsMultiTouch = profile.IsMultiTouch; } foreach (var gesture in profile.TapGestures) diff --git a/Touch-Gestures.Lib/Entities/SerializableProfile.cs b/Touch-Gestures.Lib/Entities/SerializableProfile.cs index c202b5a..3e03e87 100644 --- a/Touch-Gestures.Lib/Entities/SerializableProfile.cs +++ b/Touch-Gestures.Lib/Entities/SerializableProfile.cs @@ -3,16 +3,20 @@ using System.Collections.Generic; using Newtonsoft.Json; using TouchGestures.Lib.Entities.Gestures.Bases; +using TouchGestures.Lib.Interfaces; using TouchGestures.Lib.Serializables.Gestures; namespace TouchGestures.Lib.Entities { [JsonObject(MemberSerialization.OptIn)] - public class SerializableProfile : IEnumerable + public class SerializableProfile : IGesturesProfile { [JsonProperty] public string Name { get; set; } = string.Empty; + [JsonProperty] + public bool IsMultiTouch { get; set; } = true; + [JsonProperty] public List TapGestures { get; set; } = new(); diff --git a/Touch-Gestures.Lib/Entities/SerializableSettings.cs b/Touch-Gestures.Lib/Entities/SerializableSettings.cs index 5314b8d..4718600 100644 --- a/Touch-Gestures.Lib/Entities/SerializableSettings.cs +++ b/Touch-Gestures.Lib/Entities/SerializableSettings.cs @@ -7,7 +7,7 @@ namespace TouchGestures.Lib.Entities public class SerializableSettings { [JsonProperty] - public int Version { get; set; } + public int Version { get; set; } = 1; [JsonProperty] public List Profiles { get; set; } = new(); diff --git a/Touch-Gestures.Lib/Entities/Settings.cs b/Touch-Gestures.Lib/Entities/Settings.cs index ba4b216..3a289ea 100644 --- a/Touch-Gestures.Lib/Entities/Settings.cs +++ b/Touch-Gestures.Lib/Entities/Settings.cs @@ -38,7 +38,7 @@ public Settings() #region Properties [JsonProperty] - public int Version { get; set; } + public int Version { get; set; } = 1; [JsonProperty] public List Profiles { get; set; } = new(); diff --git a/Touch-Gestures.Lib/Entities/Tablet/TouchReport.cs b/Touch-Gestures.Lib/Entities/Tablet/TouchReport.cs new file mode 100644 index 0000000..2706751 --- /dev/null +++ b/Touch-Gestures.Lib/Entities/Tablet/TouchReport.cs @@ -0,0 +1,23 @@ +using System; +using OpenTabletDriver.Plugin.Tablet.Touch; + +#nullable disable + +namespace TouchGestures.Lib.Entities.Tablet +{ + public class TouchReport : ITouchReport + { + public TouchReport() {} + + public TouchReport(byte count = 0) + { + Touches = new TouchPoint[count]; + + for (int i = 0; i < count; i++) + Touches[i] = new TouchPoint(); + } + + public TouchPoint[] Touches { get; set; } = Array.Empty(); + public byte[] Raw { get; set; } + } +} \ No newline at end of file diff --git a/Touch-Gestures.Lib/Interfaces/IGesturesProfile.cs b/Touch-Gestures.Lib/Interfaces/IGesturesProfile.cs new file mode 100644 index 0000000..9e133aa --- /dev/null +++ b/Touch-Gestures.Lib/Interfaces/IGesturesProfile.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using TouchGestures.Lib.Entities.Gestures.Bases; + +namespace TouchGestures.Lib.Interfaces +{ + public interface IGesturesProfile : IEnumerable + { + /// + /// The name of the tablet associated with the profile. + /// + /// + /// "(Pen Only)" may be append to the name of the profile if it is a pen (Single Touch) profile.
+ /// See for more information. + ///
+ [JsonProperty] + string Name { get; } + + /// + /// Whether or not the profile is a multi-touch profile. + /// + /// + /// May also indicate that the profile is a pen (Single Touch) profile when false. + /// + [JsonProperty] + bool IsMultiTouch { get; } + + /// + /// Add a gesture to the profile. + /// + void Add(Gesture gesture); + + /// + /// Remove a gesture from the profile. + /// + void Remove(Gesture gesture); + } +} \ No newline at end of file diff --git a/Touch-Gestures.UX/Attributes/MultiTouchOnlyAttribute.cs b/Touch-Gestures.UX/Attributes/MultiTouchOnlyAttribute.cs new file mode 100644 index 0000000..c0216b0 --- /dev/null +++ b/Touch-Gestures.UX/Attributes/MultiTouchOnlyAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace TouchGestures.UX.Attributes; + +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class MultiTouchOnlyAttribute(bool isMultiTouchOnly) : Attribute +{ + public bool IsMultiTouchOnly { get; } = isMultiTouchOnly; +} diff --git a/Touch-Gestures.UX/Controls/Containers/GestureBindingDisplay.axaml.cs b/Touch-Gestures.UX/Controls/Containers/GestureBindingDisplay.axaml.cs index 496056f..921a156 100644 --- a/Touch-Gestures.UX/Controls/Containers/GestureBindingDisplay.axaml.cs +++ b/Touch-Gestures.UX/Controls/Containers/GestureBindingDisplay.axaml.cs @@ -1,8 +1,8 @@ -using OpenTabletDriver.External.Avalonia.Controls; +using Avalonia.Controls; namespace TouchGestures.UX.Controls.Containers { - public partial class GestureBindingDisplay : BindingDisplay + public partial class GestureBindingDisplay : UserControl { public GestureBindingDisplay() { diff --git a/Touch-Gestures.UX/ViewModels/BindingsOverviewViewModel.cs b/Touch-Gestures.UX/ViewModels/BindingsOverviewViewModel.cs index 4dcd385..5eb9389 100644 --- a/Touch-Gestures.UX/ViewModels/BindingsOverviewViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/BindingsOverviewViewModel.cs @@ -71,7 +71,6 @@ public BindingsOverviewViewModel() IsReady = false; NextViewModel = this; - BackRequested = null!; } public BindingsOverviewViewModel(MainViewModel mainViewModel) @@ -83,7 +82,6 @@ public BindingsOverviewViewModel(MainViewModel mainViewModel) _parentViewModel.Disconnected += OnDisconnected; NextViewModel = this; - BackRequested = null!; } public BindingsOverviewViewModel(MainViewModel mainViewModel, SerializableSettings settings) : this(mainViewModel) @@ -94,8 +92,6 @@ public BindingsOverviewViewModel(MainViewModel mainViewModel, SerializableSettin #region Events private event EventHandler? TabletChanged; - - public override event EventHandler? BackRequested; public event EventHandler? SaveRequested; public event EventHandler? ProfileChanged; @@ -176,7 +172,10 @@ public void SetTablets(IEnumerable tablets) Tablets.Add(overview); } - SelectedTablet = Tablets.FirstOrDefault(); + if (SelectedTablet != null) + SelectedTablet = Tablets.FirstOrDefault(x => x.Name == SelectedTablet.Name); + + SelectedTablet ??= Tablets.FirstOrDefault(); if (SelectedTablet != null) SelectedTabletIndex = Tablets.IndexOf(SelectedTablet); @@ -214,7 +213,8 @@ public void StartSetupWizard() var x = Math.Round(SelectedTablet.Reference.Size.X, 5); var y = Math.Round(SelectedTablet.Reference.Size.Y, 5); - var setupWizard = new GestureSetupWizardViewModel(new Rect(0, 0, x, y)); + var setupWizard = new GestureSetupWizardViewModel(new Rect(0, 0, x, y), + SelectedTablet.Profile.IsMultiTouch); setupWizard.SetupCompleted += OnSetupCompleted; setupWizard.BackRequested += OnBackRequestedAhead; @@ -248,7 +248,7 @@ private void OnSearchTextChanged(string text) if (string.IsNullOrWhiteSpace(text)) CurrentGestureBindings.AddRange(SelectedTablet.Gestures); else - CurrentGestureBindings.AddRange(SelectedTablet.Gestures.Where(x => GestureNameStartsWith(x, text))); + CurrentGestureBindings.AddRange(SelectedTablet.Gestures.Where(x => GestureNameContains(x, text))); } /// @@ -325,7 +325,8 @@ private void OnEditRequested(object? sender, EventArgs e) var x = Math.Round(SelectedTablet.Reference.Size.X, 5); var y = Math.Round(SelectedTablet.Reference.Size.Y, 5); - var setupWizard = new GestureSetupWizardViewModel(new Rect(0, 0, x, y)); + var setupWizard = new GestureSetupWizardViewModel(new Rect(0, 0, x, y), + SelectedTablet.Profile.IsMultiTouch); // We need to check whenever the edit is completed & when te user goes back setupWizard.EditCompleted += (s, args) => OnEditCompleted(s, bindingDisplay, args); @@ -451,6 +452,11 @@ private static bool GestureNameStartsWith(GestureBindingDisplayViewModel gesture return gestureTileViewModel.Description?.StartsWith(text, StringComparison.CurrentCultureIgnoreCase) ?? false; } + private static bool GestureNameContains(GestureBindingDisplayViewModel gestureTileViewModel, string text) + { + return gestureTileViewModel.Description?.Contains(text, StringComparison.CurrentCultureIgnoreCase) ?? false; + } + #endregion #region Disposal @@ -476,7 +482,6 @@ public void Dispose() SaveRequested = null; ProfileChanged = null; - BackRequested = null; GC.SuppressFinalize(this); } diff --git a/Touch-Gestures.UX/ViewModels/Controls/Setups/GestureSetupViewModel.cs b/Touch-Gestures.UX/ViewModels/Controls/Setups/GestureSetupViewModel.cs index 365f48e..fb7ef9b 100644 --- a/Touch-Gestures.UX/ViewModels/Controls/Setups/GestureSetupViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/Controls/Setups/GestureSetupViewModel.cs @@ -77,8 +77,6 @@ public partial class GestureSetupViewModel : NavigableViewModel, IDisposable public GestureSetupViewModel() { - BackRequested = null!; - CanGoBack = true; CanGoNext = false; @@ -98,8 +96,6 @@ protected virtual void SubscribeToSettingsChanges() #region Events - public override event EventHandler? BackRequested; - public event EventHandler? SetupCompleted; public event EventHandler? EditCompleted; @@ -127,12 +123,25 @@ public int SelectedGestureSetupPickIndex } } + /// + /// Whether single touch gestures are supported for this setup. + /// + public virtual bool SingleTouchSupported { get; } = true; + + /// + /// Whether option selection is enabled for this setup when not multi-touch. + /// + public virtual bool SingleTouchOptionSelectionEnabled { get; } = true; + + /// + /// Whether this setup is for a multi-touch gesture or not. + /// + public bool IsMultiTouchSetup { get; set; } = true; + #endregion #region Methods - protected override void GoBack() => BackRequested?.Invoke(this, EventArgs.Empty); - [RelayCommand(CanExecute = nameof(CanGoNext))] protected virtual void GoNext() => throw new NotImplementedException("GoNext has not been overriden."); diff --git a/Touch-Gestures.UX/ViewModels/Controls/Setups/HoldSetupViewModel.cs b/Touch-Gestures.UX/ViewModels/Controls/Setups/HoldSetupViewModel.cs index d2d91c3..3e1e000 100644 --- a/Touch-Gestures.UX/ViewModels/Controls/Setups/HoldSetupViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/Controls/Setups/HoldSetupViewModel.cs @@ -20,7 +20,8 @@ namespace TouchGestures.UX.ViewModels.Controls.Setups; #nullable enable [Name("Hold"), Icon("Assets/Setups/Hold/hold.png"), - Description("A gesture completed by holding any specified number of fingers down for a specified amount of time")] + Description("A gesture completed by holding any specified number of fingers down for a specified amount of time"), + MultiTouchOnly(false)] public partial class HoldSetupViewModel : TapSetupViewModel { private readonly SerializableHoldGesture _gesture; @@ -49,6 +50,7 @@ public HoldSetupViewModel(Gesture gesture, Rect fullArea) : this(true) SelectedGestureSetupPickIndex = serializedHoldGesture.RequiredTouchesCount - 1; BindingDisplay.PluginProperty = serializedHoldGesture.PluginProperty; + BindingDisplay.Description = $"{serializedHoldGesture.RequiredTouchesCount}-Touch Hold"; SetupArea(fullArea, serializedHoldGesture.Bounds); } @@ -80,7 +82,7 @@ public HoldSetupViewModel(bool isEditing = false) SelectedGestureSetupPickIndex = 0; - BindingDisplay = new BindingDisplayViewModel(); + BindingDisplay = new BindingDisplayViewModel("1-Touch Hold", string.Empty, null); AreaDisplay = new AreaDisplayViewModel(); _gesture = new SerializableHoldGesture(); diff --git a/Touch-Gestures.UX/ViewModels/Controls/Setups/PanSetupViewModel.cs b/Touch-Gestures.UX/ViewModels/Controls/Setups/PanSetupViewModel.cs index d7578f8..f1efc60 100644 --- a/Touch-Gestures.UX/ViewModels/Controls/Setups/PanSetupViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/Controls/Setups/PanSetupViewModel.cs @@ -19,7 +19,8 @@ namespace TouchGestures.UX.ViewModels.Controls.Setups; using static AssetLoaderExtensions; [Name("Pan"), Icon("Assets/Setups/Swipe/swipe_up.png"), - Description("A gesture that can be repeated by swiping in a specified direction, without releasing the touch point.")] + Description("A gesture that can be repeated by swiping in a specified direction, without releasing the touch point."), + MultiTouchOnly(false)] public partial class PanSetupViewModel : SwipeSetupViewModel { private readonly SerializablePanGesture _gesture; diff --git a/Touch-Gestures.UX/ViewModels/Controls/Setups/PinchSetupViewModel.cs b/Touch-Gestures.UX/ViewModels/Controls/Setups/PinchSetupViewModel.cs index ddf9cc4..b72dc49 100644 --- a/Touch-Gestures.UX/ViewModels/Controls/Setups/PinchSetupViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/Controls/Setups/PinchSetupViewModel.cs @@ -20,7 +20,8 @@ namespace TouchGestures.UX.ViewModels.Controls.Setups; #nullable enable [Name("Pinch"), Icon("Assets/Setups/Pinch/pinch_inner.png"), - Description("A gesture completed by pinching, simillar to how you would zoom in, in various application")] + Description("A gesture completed by pinching, simillar to how you would zoom in, in various application"), + MultiTouchOnly(true)] public partial class PinchSetupViewModel : GestureSetupViewModel { private readonly SerializablePinchGesture _gesture; @@ -33,6 +34,8 @@ public partial class PinchSetupViewModel : GestureSetupViewModel [ObservableProperty] protected bool _isInner; + public override bool SingleTouchSupported { get; } = false; + #endregion #region Constructors diff --git a/Touch-Gestures.UX/ViewModels/Controls/Setups/RotateSetupViewModel.cs b/Touch-Gestures.UX/ViewModels/Controls/Setups/RotateSetupViewModel.cs index 2f87ce7..a5b7180 100644 --- a/Touch-Gestures.UX/ViewModels/Controls/Setups/RotateSetupViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/Controls/Setups/RotateSetupViewModel.cs @@ -20,7 +20,8 @@ namespace TouchGestures.UX.ViewModels.Controls.Setups; #nullable enable [Name("Rotation"), Icon("Assets/Setups/Rotation/rotation_clockwise.png"), - Description("A gesture completed by pinching & rotating 2 fingers, simillar to how you would rotate a map in various application")] + Description("A gesture completed by pinching & rotating 2 fingers, simillar to how you would rotate a map in various application"), + MultiTouchOnly(true)] public partial class RotateSetupViewModel : PinchSetupViewModel { private readonly SerializablePinchGesture _gesture; diff --git a/Touch-Gestures.UX/ViewModels/Controls/Setups/SwipeSetupViewModel.cs b/Touch-Gestures.UX/ViewModels/Controls/Setups/SwipeSetupViewModel.cs index 2d2b4db..a449dc6 100644 --- a/Touch-Gestures.UX/ViewModels/Controls/Setups/SwipeSetupViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/Controls/Setups/SwipeSetupViewModel.cs @@ -21,7 +21,8 @@ namespace TouchGestures.UX.ViewModels.Controls.Setups; using static AssetLoaderExtensions; [Name("Swipe"), Icon("Assets/Setups/Swipe/swipe_up.png"), -Description("A gesture completed by swiping in a specified direction, then releasing the touch point.")] + Description("A gesture completed by swiping in a specified direction, then releasing the touch point."), + MultiTouchOnly(false)] public partial class SwipeSetupViewModel : GestureSetupViewModel { private readonly SerializableSwipeGesture _gesture; diff --git a/Touch-Gestures.UX/ViewModels/Controls/Setups/TapSetupViewModel.cs b/Touch-Gestures.UX/ViewModels/Controls/Setups/TapSetupViewModel.cs index d96a0bc..308c0ba 100644 --- a/Touch-Gestures.UX/ViewModels/Controls/Setups/TapSetupViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/Controls/Setups/TapSetupViewModel.cs @@ -20,7 +20,8 @@ namespace TouchGestures.UX.ViewModels.Controls.Setups; #nullable enable [Name("Tap"), Icon("Assets/Setups/Tap/tap_triple.png"), - Description("A gesture completed by tapping with any specified number of fingers")] + Description("A gesture completed by tapping with any specified number of fingers"), + MultiTouchOnly(false)] public partial class TapSetupViewModel : GestureSetupViewModel { private readonly SerializableTapGesture _gesture; @@ -70,7 +71,7 @@ public TapSetupViewModel(bool isEditing = false) SelectedGestureSetupPickIndex = 0; - BindingDisplay = new BindingDisplayViewModel(); + BindingDisplay = new BindingDisplayViewModel("1-Touch Tap", string.Empty, null); AreaDisplay = new AreaDisplayViewModel(); _gesture = new SerializableTapGesture(); @@ -96,6 +97,7 @@ public TapSetupViewModel(Gesture gesture, Rect fullArea) : this(true) SelectedGestureSetupPickIndex = serializedTapGesture.RequiredTouchesCount - 1; BindingDisplay.PluginProperty = serializedTapGesture.PluginProperty; + BindingDisplay.Description = $"{serializedTapGesture.RequiredTouchesCount}-Touch Tap"; SetupArea(fullArea, serializedTapGesture.Bounds); } @@ -109,11 +111,18 @@ protected override void SubscribeToSettingsChanges() #endregion + #region Properties + + public override bool SingleTouchOptionSelectionEnabled { get; } = false; + + #endregion + #region Methods protected override void GoBack() { - if (IsBindingSelectionStepActive) // Step 2 + // We skip the option selection in the case a setup is single-touch. (Pen) + if (IsBindingSelectionStepActive && IsMultiTouchSetup) // Step 2 { IsBindingSelectionStepActive = false; IsOptionsSelectionStepActive = true; diff --git a/Touch-Gestures.UX/ViewModels/Controls/Tiles/GestureTileViewModel.cs b/Touch-Gestures.UX/ViewModels/Controls/Tiles/GestureTileViewModel.cs index d54ce01..eadcbd9 100644 --- a/Touch-Gestures.UX/ViewModels/Controls/Tiles/GestureTileViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/Controls/Tiles/GestureTileViewModel.cs @@ -7,9 +7,9 @@ namespace TouchGestures.UX.ViewModels.Controls.Tiles { - [Name("Non-Implemented")] - [Description("This gesture has not been implemented yet")] - [Icon("")] + [Name("Non-Implemented"), Icon(""), + Description("This gesture has not been implemented yet"), + MultiTouchOnly(false)] public partial class GestureTileViewModel : GestureTileViewModel where T : GestureSetupViewModel, new() { private const string DEFAULT_NAME = "Non-Implemented"; @@ -20,10 +20,12 @@ public GestureTileViewModel() var nameAttribute = (NameAttribute?)Attribute.GetCustomAttribute(typeof(T), typeof(NameAttribute)); var descriptionAttribute = (DescriptionAttribute?)Attribute.GetCustomAttribute(typeof(T), typeof(DescriptionAttribute)); var iconAttribute = (IconAttribute?)Attribute.GetCustomAttribute(typeof(T), typeof(IconAttribute)); + var multiTouchOnlyAttribute = (MultiTouchOnlyAttribute?)Attribute.GetCustomAttribute(typeof(T), typeof(MultiTouchOnlyAttribute)); GestureName = nameAttribute?.Name ?? DEFAULT_NAME; Description = descriptionAttribute?.Description ?? DEFAULT_DESCRIPTION; Icon = iconAttribute?.Icon; + IsMultiTouchOnly = multiTouchOnlyAttribute?.IsMultiTouchOnly ?? false; AssociatedSetup = new T(); } @@ -43,6 +45,11 @@ public partial class GestureTileViewModel : ViewModelBase [ObservableProperty] private GestureSetupViewModel _associatedSetup = new(); + [ObservableProperty] + private bool _isEnabled = true; + + public bool IsMultiTouchOnly { get; init; } + public event EventHandler? Selected; [RelayCommand] diff --git a/Touch-Gestures.UX/ViewModels/GestureSelectionScreenViewModel.cs b/Touch-Gestures.UX/ViewModels/GestureSelectionScreenViewModel.cs index 1d19c66..a28c21b 100644 --- a/Touch-Gestures.UX/ViewModels/GestureSelectionScreenViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/GestureSelectionScreenViewModel.cs @@ -76,15 +76,13 @@ public string SearchText #endregion - #region Events - - public override event EventHandler? BackRequested; - - #endregion - #region Methods - protected override void GoBack() => BackRequested?.Invoke(this, EventArgs.Empty); + public void HideMultiTouchTiles(bool isMultiTouch = true) + { + foreach (var gestureTileViewModel in CurrentGestureTiles) + gestureTileViewModel.IsEnabled = isMultiTouch || gestureTileViewModel.IsMultiTouchOnly == false; + } #endregion diff --git a/Touch-Gestures.UX/ViewModels/GestureSetupScreenViewModel.cs b/Touch-Gestures.UX/ViewModels/GestureSetupScreenViewModel.cs index ec1e2bf..7531c60 100644 --- a/Touch-Gestures.UX/ViewModels/GestureSetupScreenViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/GestureSetupScreenViewModel.cs @@ -10,17 +10,7 @@ public partial class GestureSetupScreenViewModel : NavigableViewModel { #region Constructors - public GestureSetupScreenViewModel() - { - BackRequested = null!; - CanGoBack = true; - } - - #endregion - - #region Events - - public override event EventHandler? BackRequested; + public GestureSetupScreenViewModel() => CanGoBack = true; #endregion @@ -30,12 +20,23 @@ public GestureSetupScreenViewModel() /// Start the gesture setup process. /// /// The view model to start the setup with. - public void StartSetup(GestureSetupViewModel gestureSetupViewModel) + public void StartSetup(GestureSetupViewModel gestureSetupViewModel, bool isMultiTouch = false) { NextViewModel = gestureSetupViewModel; NextViewModel.BackRequested += OnBackRequestedAhead; - gestureSetupViewModel.IsOptionsSelectionStepActive = true; + // There shouldn't be a situation where an unsupported gesture should make its way here normally, + // as these are hidden during the selection process. + + gestureSetupViewModel.IsOptionsSelectionStepActive = false; + + // Check if single touch even matters for the setup. + if (isMultiTouch || gestureSetupViewModel.SingleTouchOptionSelectionEnabled) + gestureSetupViewModel.IsOptionsSelectionStepActive = true; + else + gestureSetupViewModel.IsBindingSelectionStepActive = true; + + gestureSetupViewModel.IsMultiTouchSetup = isMultiTouch; } protected override void GoBack() @@ -43,7 +44,7 @@ protected override void GoBack() if (NextViewModel != null) NextViewModel.BackRequested -= OnBackRequestedAhead; - BackRequested?.Invoke(this, EventArgs.Empty); + base.GoBack(); } #endregion diff --git a/Touch-Gestures.UX/ViewModels/GestureSetupWizardViewModel.cs b/Touch-Gestures.UX/ViewModels/GestureSetupWizardViewModel.cs index 6fd8b50..529f877 100644 --- a/Touch-Gestures.UX/ViewModels/GestureSetupWizardViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/GestureSetupWizardViewModel.cs @@ -17,6 +17,7 @@ public partial class GestureSetupWizardViewModel : NavigableViewModel private Gesture _editedGesture = null!; private Rect _bounds; + private bool _isMultiTouch; #region Observable Fields @@ -36,20 +37,25 @@ public partial class GestureSetupWizardViewModel : NavigableViewModel #region Constructors - public GestureSetupWizardViewModel() + public GestureSetupWizardViewModel(bool isMultiTouch = true) { NextViewModel = _gestureSelectionScreenViewModel; + + _isMultiTouch = isMultiTouch; PropertyChanging += OnPropertyChanging; PropertyChanged += OnGestureChanged; GestureSelectionScreenViewModel.BackRequested += OnBackRequestedAhead; GestureSelectionScreenViewModel.GestureSelected += OnGestureSelected; + GestureSelectionScreenViewModel.HideMultiTouchTiles(isMultiTouch); + + CanGoBack = true; - GestureSetupScreenViewModel.BackRequested += OnBackRequestedAhead; + //GestureSetupScreenViewModel.BackRequested += OnBackRequestedAhead; } - public GestureSetupWizardViewModel(Rect bounds) : this() + public GestureSetupWizardViewModel(Rect bounds, bool isMultiTouch = true) : this(isMultiTouch) { _bounds = bounds; } @@ -58,8 +64,6 @@ public GestureSetupWizardViewModel(Rect bounds) : this() #region Events - public override event EventHandler? BackRequested; - public event EventHandler? SetupCompleted; public event EventHandler? EditCompleted; @@ -70,10 +74,10 @@ public GestureSetupWizardViewModel(Rect bounds) : this() protected override void GoBack() { - if (NextViewModel is GestureSelectionScreenViewModel || (NextViewModel is GestureSetupScreenViewModel && _editedGesture != null)) - BackRequested?.Invoke(this, EventArgs.Empty); - else + if (NextViewModel is GestureSetupScreenViewModel && _editedGesture == null) NextViewModel = GestureSelectionScreenViewModel; + else if (NextViewModel is GestureSelectionScreenViewModel || NextViewModel is GestureSetupScreenViewModel) + base.GoBack(); } /// @@ -105,7 +109,7 @@ public void Edit(GestureBindingDisplayViewModel bindingDisplay) // Subscribe to the events setupViewModel.EditCompleted += OnEditCompleted; - GestureSetupScreenViewModel.StartSetup(setupViewModel); + GestureSetupScreenViewModel.StartSetup(setupViewModel, _isMultiTouch); NextViewModel = GestureSetupScreenViewModel; } @@ -131,7 +135,7 @@ private void OnGestureSelected(object? sender, GestureTileViewModel selectedTile associatedSetup.AreaDisplay = new(_bounds); - GestureSetupScreenViewModel.StartSetup(associatedSetup); + GestureSetupScreenViewModel.StartSetup(associatedSetup, _isMultiTouch); NextViewModel = GestureSetupScreenViewModel; } diff --git a/Touch-Gestures.UX/ViewModels/MainViewModel.cs b/Touch-Gestures.UX/ViewModels/MainViewModel.cs index 413c25f..492d6a0 100644 --- a/Touch-Gestures.UX/ViewModels/MainViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/MainViewModel.cs @@ -12,10 +12,7 @@ using System.Linq; using TouchGestures.Lib.Entities; using System.Threading; -using TouchGestures.UX.Events; using Avalonia.Threading; -using System.Numerics; -using Avalonia; using Newtonsoft.Json; using TouchGestures.Lib.Converters; using TouchGestures.Lib.Entities.Tablet; @@ -86,8 +83,6 @@ public MainViewModel() BindingsOverviewViewModel.SaveRequested += OnSaveRequested; BindingsOverviewViewModel.ProfileChanged += OnProfileChanged; - BackRequested = null!; - CanGoBack = false; // TODO: Change in production to the home view //NextViewModel = _gestureSetupWizardViewModel; @@ -122,8 +117,6 @@ private void InitializeClient() public event EventHandler? SettingsChanged; - public override event EventHandler? BackRequested; - public event EventHandler? Ready; public event EventHandler? Disconnected; @@ -329,7 +322,7 @@ private async Task OnTabletsChangedCore(object? sender, IEnumerable OnSettingsChanged(_settings)); Dispatcher.UIThread.Post(() => BindingsOverviewViewModel.SetTablets(tablets)); - } + } } // diff --git a/Touch-Gestures.UX/ViewModels/NavigableViewModel.cs b/Touch-Gestures.UX/ViewModels/NavigableViewModel.cs index 24d3f5a..a7db854 100644 --- a/Touch-Gestures.UX/ViewModels/NavigableViewModel.cs +++ b/Touch-Gestures.UX/ViewModels/NavigableViewModel.cs @@ -8,7 +8,7 @@ namespace TouchGestures.UX.ViewModels; public abstract partial class NavigableViewModel : ViewModelBase { - public abstract event EventHandler? BackRequested; + public event EventHandler? BackRequested; public bool CanGoBack { get; init; } @@ -16,5 +16,9 @@ public abstract partial class NavigableViewModel : ViewModelBase protected NavigableViewModel? _nextViewModel = null; [RelayCommand(CanExecute = nameof(CanGoBack))] - protected abstract void GoBack(); + protected virtual void GoBack() + { + if (CanGoBack) + BackRequested?.Invoke(this, EventArgs.Empty); + } } \ No newline at end of file diff --git a/Touch-Gestures.UX/Views/Gestures/GestureSelectionScreen.axaml b/Touch-Gestures.UX/Views/Gestures/GestureSelectionScreen.axaml index 228445d..7f1a1b5 100644 --- a/Touch-Gestures.UX/Views/Gestures/GestureSelectionScreen.axaml +++ b/Touch-Gestures.UX/Views/Gestures/GestureSelectionScreen.axaml @@ -57,7 +57,7 @@ + Command="{Binding SelectGestureCommand}" IsEnabled="{Binding IsEnabled}" IsVisible="{Binding IsEnabled}" Margin="4" /> diff --git a/Touch-Gestures.UX/Views/Gestures/Setups/HoldSetup.axaml.cs b/Touch-Gestures.UX/Views/Gestures/Setups/HoldSetup.axaml.cs index b09b064..f0db2ec 100644 --- a/Touch-Gestures.UX/Views/Gestures/Setups/HoldSetup.axaml.cs +++ b/Touch-Gestures.UX/Views/Gestures/Setups/HoldSetup.axaml.cs @@ -1,6 +1,8 @@ +using Avalonia.Controls; + namespace TouchGestures.UX.Views.Gestures.Setups; -public partial class HoldSetup : GestureSetup +public partial class HoldSetup : UserControl { public HoldSetup() { diff --git a/Touch-Gestures.UX/Views/Gestures/Setups/PanSetup.axaml.cs b/Touch-Gestures.UX/Views/Gestures/Setups/PanSetup.axaml.cs index f54d289..773ec38 100644 --- a/Touch-Gestures.UX/Views/Gestures/Setups/PanSetup.axaml.cs +++ b/Touch-Gestures.UX/Views/Gestures/Setups/PanSetup.axaml.cs @@ -1,6 +1,8 @@ +using Avalonia.Controls; + namespace TouchGestures.UX.Views.Gestures.Setups; -public partial class PanSetup : GestureSetup +public partial class PanSetup : UserControl { public PanSetup() { diff --git a/Touch-Gestures.UX/Views/Gestures/Setups/PinchSetup.axaml.cs b/Touch-Gestures.UX/Views/Gestures/Setups/PinchSetup.axaml.cs index d30dccf..8ab577c 100644 --- a/Touch-Gestures.UX/Views/Gestures/Setups/PinchSetup.axaml.cs +++ b/Touch-Gestures.UX/Views/Gestures/Setups/PinchSetup.axaml.cs @@ -1,6 +1,8 @@ +using Avalonia.Controls; + namespace TouchGestures.UX.Views.Gestures.Setups; -public partial class RotateSetup : GestureSetup +public partial class RotateSetup : UserControl { public RotateSetup() { diff --git a/Touch-Gestures.UX/Views/Gestures/Setups/RotateSetup.axaml.cs b/Touch-Gestures.UX/Views/Gestures/Setups/RotateSetup.axaml.cs index 66f4a31..9092d97 100644 --- a/Touch-Gestures.UX/Views/Gestures/Setups/RotateSetup.axaml.cs +++ b/Touch-Gestures.UX/Views/Gestures/Setups/RotateSetup.axaml.cs @@ -1,6 +1,8 @@ +using Avalonia.Controls; + namespace TouchGestures.UX.Views.Gestures.Setups; -public partial class PinchSetup : GestureSetup +public partial class PinchSetup : UserControl { public PinchSetup() { diff --git a/Touch-Gestures.UX/Views/Gestures/Setups/SwipeSetup.axaml.cs b/Touch-Gestures.UX/Views/Gestures/Setups/SwipeSetup.axaml.cs index 2f3a6b5..3a53f89 100644 --- a/Touch-Gestures.UX/Views/Gestures/Setups/SwipeSetup.axaml.cs +++ b/Touch-Gestures.UX/Views/Gestures/Setups/SwipeSetup.axaml.cs @@ -1,6 +1,8 @@ +using Avalonia.Controls; + namespace TouchGestures.UX.Views.Gestures.Setups; -public partial class SwipeSetup : GestureSetup +public partial class SwipeSetup : UserControl { public SwipeSetup() { diff --git a/Touch-Gestures.UX/Views/Gestures/Setups/TapSetup.axaml.cs b/Touch-Gestures.UX/Views/Gestures/Setups/TapSetup.axaml.cs index 8e9d074..09d6e52 100644 --- a/Touch-Gestures.UX/Views/Gestures/Setups/TapSetup.axaml.cs +++ b/Touch-Gestures.UX/Views/Gestures/Setups/TapSetup.axaml.cs @@ -1,6 +1,8 @@ +using Avalonia.Controls; + namespace TouchGestures.UX.Views.Gestures.Setups; -public partial class TapSetup : GestureSetup +public partial class TapSetup : UserControl { public TapSetup() { diff --git a/Touch-Gestures/Extensions/TabletStateExtensions.cs b/Touch-Gestures/Extensions/TabletStateExtensions.cs index aa57703..bcfd91e 100644 --- a/Touch-Gestures/Extensions/TabletStateExtensions.cs +++ b/Touch-Gestures/Extensions/TabletStateExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using OpenTabletDriver.Plugin.Tablet; using OTD.EnhancedOutputMode.Lib.Tools; @@ -9,6 +10,9 @@ public static class TabletStateExtensions { public static SharedTabletReference ToShared(this TabletState tablet, TouchSettings touchSettings) { + if (tablet == null || tablet.Digitizer == null) + throw new ArgumentNullException(nameof(tablet)); + var digitizer = new SharedTabletDigitizer { Width = tablet.Digitizer.Width, @@ -25,25 +29,25 @@ public static SharedTabletReference ToShared(this TabletState tablet, TouchSetti MaxY = touchSettings.MaxY }; - var featureInitReport = tablet.Auxiliary.FeatureInitReport == null - ? new List() - : new List { tablet.Auxiliary.FeatureInitReport }; + var featureInitReport = tablet.Digitizer.FeatureInitReport == null + ? new List() + : new List { tablet.Digitizer.FeatureInitReport }; - var outputInitReport = tablet.Auxiliary.OutputInitReport == null + var outputInitReport = tablet.Digitizer.OutputInitReport == null ? new List() - : new List { tablet.Auxiliary.OutputInitReport }; + : new List { tablet.Digitizer.OutputInitReport }; var identifier = new SharedDeviceIdentifier { - VendorID = tablet.Auxiliary.VendorID, - ProductID = tablet.Auxiliary.ProductID, - InputReportLength = tablet.Auxiliary.InputReportLength, - OutputReportLength = tablet.Auxiliary.OutputReportLength, - ReportParser = tablet.Auxiliary.ReportParser, + VendorID = tablet.Digitizer.VendorID, + ProductID = tablet.Digitizer.ProductID, + InputReportLength = tablet.Digitizer.InputReportLength, + OutputReportLength = tablet.Digitizer.OutputReportLength, + ReportParser = tablet.Digitizer.ReportParser, FeatureInitReport = featureInitReport, OutputInitReport = outputInitReport, - DeviceStrings = tablet.Auxiliary.DeviceStrings, - InitializationStrings = tablet.Auxiliary.InitializationStrings + DeviceStrings = tablet.Digitizer.DeviceStrings, + InitializationStrings = tablet.Digitizer.InitializationStrings }; return new SharedTabletReference(tablet.TabletProperties.Name, digitizer, touchDigitizer, identifier); diff --git a/Touch-Gestures/GesturesHandler.cs b/Touch-Gestures/GesturesHandler.cs index 4216348..47152a7 100644 --- a/Touch-Gestures/GesturesHandler.cs +++ b/Touch-Gestures/GesturesHandler.cs @@ -24,16 +24,16 @@ public class GesturesHandler : IFilter, IGateFilter, IInitialize, IDisposable { #region Constants - public const string PLUGIN_NAME = "Touch Gestures"; + private const string PLUGIN_NAME = "Touch Gestures"; #endregion #region Fields - private TouchSettings _touchSettings => TouchSettings.Instance ?? TouchSettings.Default; - private GesturesDaemonBase? _daemon; - private BindableProfile? _profile; - private SharedTabletReference? _tablet; + protected TouchSettings _touchSettings => TouchSettings.Instance ?? TouchSettings.Default; + protected GesturesDaemonBase? _daemon; + protected BindableProfile? _profile; + protected SharedTabletReference? _tablet; private bool _hasPreviousGestureStarted; #endregion @@ -47,7 +47,7 @@ public GesturesHandler() #endif } - public void Initialize() + public virtual void Initialize() { if (!_touchSettings.IsTouchToggled) return; @@ -102,7 +102,7 @@ private void WaitForDebugger() public Vector2 Filter(Vector2 input) => input; - public bool Pass(IDeviceReport report, ref ITabletReport tabletreport) + public virtual bool Pass(IDeviceReport report, ref ITabletReport tabletreport) { if (report is ITouchReport touchReport) { @@ -147,7 +147,7 @@ public void HandleConflictingGestures(IEnumerable gestures, ITouchRepor #region Events Handlers - public void OnProfileChanged(object? sender, EventArgs e) + public virtual void OnProfileChanged(object? sender, EventArgs e) { if (_profile == null) { @@ -180,7 +180,7 @@ public void OnProfileChanged(object? sender, EventArgs e) Log.Debug(PLUGIN_NAME, "Settings updated"); } - private void SortGestures() + protected void SortGestures() { if (_profile == null) return; @@ -203,6 +203,9 @@ public void Dispose() if (_profile != null) _profile.ProfileChanged -= OnProfileChanged; + if (_tablet != null) + _daemon?.RemoveTablet(_tablet); + GC.SuppressFinalize(this); } diff --git a/Touch-Gestures/PenGesturesHandler.cs b/Touch-Gestures/PenGesturesHandler.cs new file mode 100644 index 0000000..b3b6e52 --- /dev/null +++ b/Touch-Gestures/PenGesturesHandler.cs @@ -0,0 +1,130 @@ +using System; +using System.Numerics; +using OpenTabletDriver.Plugin; +using OpenTabletDriver.Plugin.Attributes; +using OpenTabletDriver.Plugin.Tablet; +using OpenTabletDriver.Plugin.Tablet.Touch; +using TouchGestures.Extensions; +using TouchGestures.Lib.Entities; +using TouchGestures.Lib.Entities.Tablet; + +namespace TouchGestures +{ + [PluginName(PLUGIN_NAME)] + public class PenGesturesHandler : GesturesHandler + { + #region Constants + + private const string PLUGIN_NAME = "Pen Gestures"; + + #endregion + + #region Fields + + private readonly TouchReport _stubReport = new(1); + private readonly TouchPoint _stubPoint = new(); + + #endregion + + #region Constructors + + public override void Initialize() + { + _daemon = GesturesDaemonBase.Instance; + + if (Info.Driver.Tablet != null && _daemon != null) + { + _tablet = Info.Driver.Tablet.ToShared(_touchSettings); + _tablet.Name = $"{_tablet.Name} (Pen Only)"; + + _daemon.AddTablet(_tablet); + _profile = _daemon.GetSettingsForTablet(_tablet.Name); + + if (_profile != null) + { + _profile.IsMultiTouch = false; + _profile.ProfileChanged += OnProfileChanged; + OnProfileChanged(this, EventArgs.Empty); + } + + Log.Write(PLUGIN_NAME, "Now handling touch gesture for: " + _tablet.Name); + } + + if (_daemon == null) + { + Log.Write(PLUGIN_NAME, "Touch Gestures Daemon has not been enabled, please enable it in the 'Tools' tab", LogLevel.Error); + return; + } + } + + #endregion + + #region Methods + + public override bool Pass(IDeviceReport report, ref ITabletReport tabletreport) + { + if (report is ITabletReport tabletReport) + { + if (tabletReport.Pressure > 0) + { + _stubPoint.Position = tabletReport.Position; + _stubReport.Touches[0] = _stubPoint; + } + else + _stubReport.Touches[0] = null; + + if (_daemon != null && _daemon.IsReady && _touchSettings.IsTouchToggled) + { + // Iterate through all conflicting gestures + HandleConflictingGestures(TapGestures, _stubReport); + HandleConflictingGestures(HoldGestures, _stubReport); + + // Iterate through all non-conflicting gestures + foreach (var gesture in NonConflictingGestures) + gesture.OnInput(_stubReport.Touches); + } + } + + return true; + } + + #endregion + + #region Events Handlers + + public override void OnProfileChanged(object? sender, EventArgs e) + { + if (_profile == null) + { + Log.Write(PLUGIN_NAME, "Settings are null", LogLevel.Error); + return; + } + + if (_tablet != null) + { + if (_tablet.TouchDigitizer != null && _tablet.PenDigitizer?.GetLPMM() != Vector2.Zero) + _profile.UpdateLPMM(_tablet); + else + Log.Write(PLUGIN_NAME, "LPMM is zero, this is very unusual as the tablet's specifications should be defined in the internal tablet configuration.", LogLevel.Error); + } + + TapGestures.Clear(); + HoldGestures.Clear(); + NonConflictingGestures.Clear(); + + SortGestures(); + + TapGestures.AddRange(_profile.TapGestures); + HoldGestures.AddRange(_profile.HoldGestures); + + NonConflictingGestures.AddRange(_profile.SwipeGestures); + NonConflictingGestures.AddRange(_profile.PanGestures); + NonConflictingGestures.AddRange(_profile.PinchGestures); + NonConflictingGestures.AddRange(_profile.RotateGestures); + + Log.Debug(PLUGIN_NAME, "Settings updated"); + } + + #endregion + } +}