diff --git a/TabletFriend/TabletFriend/Actions/ButtonActionResolver.cs b/TabletFriend/TabletFriend/Actions/ButtonActionResolver.cs index ee5db01..6eb1c8b 100644 --- a/TabletFriend/TabletFriend/Actions/ButtonActionResolver.cs +++ b/TabletFriend/TabletFriend/Actions/ButtonActionResolver.cs @@ -9,6 +9,7 @@ public static class ButtonActionResolver private const string _typeKeyword = "type "; private const string _toggleKeyword = "toggle "; private const string _cmdKeyword = "cmd "; + private const string _clickKeyword = "click "; private const string _waitKeyword = "wait "; private const string _holdKeyword = "hold "; private const string _releaseKeyword = "release "; @@ -36,6 +37,10 @@ public static ButtonAction Resolve(string actionString) { return ResolveCmdAction(actionString.Substring(_cmdKeyword.Length)); } + if (actionString.StartsWith(_clickKeyword)) + { + return ResolveClickAction(actionString.Substring(_clickKeyword.Length)); + } if (actionString.StartsWith(_waitKeyword)) { return ResolveWaitAction(actionString.Substring(_waitKeyword.Length)); @@ -75,6 +80,9 @@ private static ButtonAction ResolveTypeAction(string actionString) => private static ButtonAction ResolveCmdAction(string actionString) => new CmdAction(actionString.Trim()); + private static ButtonAction ResolveClickAction(string actionString) => + new ClickAction(actionString.Trim()); + private static ButtonAction ResolveWaitAction(string actionString) => new WaitAction(int.Parse(actionString)); diff --git a/TabletFriend/TabletFriend/Actions/ClickAction.cs b/TabletFriend/TabletFriend/Actions/ClickAction.cs new file mode 100644 index 0000000..126191f --- /dev/null +++ b/TabletFriend/TabletFriend/Actions/ClickAction.cs @@ -0,0 +1,142 @@ +using System.Drawing; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using WindowsInput.Events; + +namespace TabletFriend.Actions +{ + public class ClickAction : ButtonAction + { + private static System.Drawing.Point? _newClickCoordinates = null; + private static Rectangle _dragBoxFromMouseDown; + + private bool _initialized = false; + private readonly int _x; + private readonly int _y; + + public ClickAction(string cmd) + { + // The regular expression extracts the coordinates from the given cmd string. + // Furthermore it validates the format of the string. + // The string has to consist of two numbers separated by a comma "," (white spaces are ignored) like e.g + // "10,20" or " 10,20 " or "10, 20" or " 10 , 20 " + Regex expression = new Regex(@"^\s*(?\d+)\s*,\s*(?\d+)\s*$"); + Match match = expression.Match(cmd); + if (match.Success) + { + _x = int.Parse(match.Groups["x_coordinate"].Value); + _y = int.Parse(match.Groups["y_coordinate"].Value); + _initialized = true; + } + } + + public async override Task Invoke() + { + if (_initialized) + { + var sim = new EventBuilder(); + sim.MoveTo(_x, _y).Click(ButtonCode.Left); + await sim.Invoke(); + } + } + + public static void AddDragAndDropEventHandlers(ButtonBase uiButton, string key) + { + // Add event handlers to the click action button + // to support drag/drop for defining the destination coordinates. + uiButton.Name = key; + uiButton.PreviewMouseLeftButtonUp += MouseUp; + uiButton.PreviewMouseLeftButtonDown += MouseDown; + uiButton.PreviewMouseMove += MouseMove; + uiButton.PreviewQueryContinueDrag += QueryContinueDrag; + uiButton.PreviewGiveFeedback += GiveFeedback; + } + + private static void MouseDown(object sender, System.Windows.Input.MouseEventArgs e) + { + // Remember the point where the mouse down occurred. The DragSize indicates + // the size that the mouse can move before a drag event should be started. + System.Drawing.Size dragSize = System.Windows.Forms.SystemInformation.DragSize; + var x = System.Windows.Forms.Cursor.Position.X; + var y = System.Windows.Forms.Cursor.Position.Y; + + System.Windows.Point mpos = e.GetPosition(null); + // Create a rectangle using the DragSize, with the mouse position being + // at the center of the rectangle. + _dragBoxFromMouseDown = new Rectangle( + new System.Drawing.Point( + x - (dragSize.Width / 2), + y - (dragSize.Height / 2)), + dragSize); + } + + private static void MouseUp(object sender, System.Windows.Input.MouseEventArgs e) + { + // Reset the drag rectangle when the mouse button is raised. + _dragBoxFromMouseDown = Rectangle.Empty; + } + + private static void MouseMove(object sender, System.Windows.Input.MouseEventArgs e) + { + if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed) + { + var x = System.Windows.Forms.Cursor.Position.X; + var y = System.Windows.Forms.Cursor.Position.Y; + + // If the mouse moves outside the rectangle, start the drag. + if (_dragBoxFromMouseDown != Rectangle.Empty && + !_dragBoxFromMouseDown.Contains(x, y)) + { + _dragBoxFromMouseDown = Rectangle.Empty; + { + DataObject dataObj = new DataObject((sender as Button)); + System.Windows.DragDrop.DoDragDrop((sender as Button), dataObj, DragDropEffects.None); + if (_newClickCoordinates.HasValue) + { + var result = MessageBox.Show( + $"Save new click destination {_newClickCoordinates.Value}", + "Mouse Click Action", + MessageBoxButton.OKCancel); + + if (result == MessageBoxResult.OK) + { + var key = (sender as Button).Name; + LayoutManager.UpdateClickActionCoordinatesInCurrentLayoutFile(key, _newClickCoordinates.Value); + } + + _newClickCoordinates = null; + } + } + } + } + } + + private static void GiveFeedback(object sender, GiveFeedbackEventArgs e) + { + System.Windows.Input.Mouse.SetCursor(System.Windows.Input.Cursors.Cross); + e.Handled = true; + } + + private static void QueryContinueDrag(object sender, QueryContinueDragEventArgs e) + { + if (e.KeyStates == DragDropKeyStates.None) + { + var pointOnScreen = System.Windows.Forms.Cursor.Position; + var pointInMainWindow = Application.Current.MainWindow.PointFromScreen( + new System.Windows.Point(pointOnScreen.X, pointOnScreen.Y)); + var rs = Application.Current.MainWindow.RenderSize; + var dropDestinationInsideOfMainWindow = + (pointInMainWindow.X > 0 && pointInMainWindow.X < rs.Width) && + (pointInMainWindow.Y > 0 && pointInMainWindow.Y < rs.Height); + + if (!dropDestinationInsideOfMainWindow) + { + _newClickCoordinates = pointOnScreen; + } + } + } + } +} diff --git a/TabletFriend/TabletFriend/LayoutManager.cs b/TabletFriend/TabletFriend/LayoutManager.cs index de75642..451a8b3 100644 --- a/TabletFriend/TabletFriend/LayoutManager.cs +++ b/TabletFriend/TabletFriend/LayoutManager.cs @@ -1,4 +1,6 @@ -using System.Windows; +using System.IO; +using System.Text.RegularExpressions; +using System.Windows; using WpfAppBar; namespace TabletFriend @@ -57,5 +59,25 @@ public void LoadLayout(string path) EventBeacon.SendEvent("docking_changed", AppState.Settings.DockingMode); } + public static void UpdateClickActionCoordinatesInCurrentLayoutFile( + string keyNameInLayoutFile, + System.Drawing.Point newCoordinates) + { + if (AppState.CurrentLayout != null) + { + var layout = AppState.CurrentLayoutPath; + var currentLayoutFileContent = File.ReadAllText(layout); + var pattern = @$"({keyNameInLayoutFile}:.*?action:.*?click)\s+(?\d+)\s*,\s*(?\d+)"; + if (Regex.IsMatch(currentLayoutFileContent, pattern, RegexOptions.Singleline)) + { + var updatedLayoutFileContent = Regex.Replace( + currentLayoutFileContent, + pattern, + @$"$1 {newCoordinates.X},{newCoordinates.Y}", + RegexOptions.Singleline); + File.WriteAllText(layout, updatedLayoutFileContent); + } + } + } } } diff --git a/TabletFriend/TabletFriend/Models/ButtonModel.cs b/TabletFriend/TabletFriend/Models/ButtonModel.cs index 468aa4d..dfb12d3 100644 --- a/TabletFriend/TabletFriend/Models/ButtonModel.cs +++ b/TabletFriend/TabletFriend/Models/ButtonModel.cs @@ -15,6 +15,7 @@ public class ButtonModel : IDisposable public ButtonAction Action; public string Text = ""; + public string Key = ""; public object Icon; public Vector2 Position = Vector2.Zero; @@ -33,12 +34,13 @@ public class ButtonModel : IDisposable public ButtonVisibility Visibility; - public ButtonModel(ButtonData data) + public ButtonModel(string key, ButtonData data) { if (data == null) { return; } + Key = key; Text = (data.Text ?? "").Replace("\\n", Environment.NewLine); if (!string.IsNullOrEmpty(data.Icon)) diff --git a/TabletFriend/TabletFriend/Models/LayoutModel.cs b/TabletFriend/TabletFriend/Models/LayoutModel.cs index 2c050d5..1d37bd0 100644 --- a/TabletFriend/TabletFriend/Models/LayoutModel.cs +++ b/TabletFriend/TabletFriend/Models/LayoutModel.cs @@ -26,7 +26,7 @@ public LayoutModel(LayoutData data) foreach (var button in data.Buttons) { - Buttons.Add(new ButtonModel(button.Value)); + Buttons.Add(new ButtonModel(button.Key, button.Value)); } } diff --git a/TabletFriend/TabletFriend/UiFactory.cs b/TabletFriend/TabletFriend/UiFactory.cs index 3f357f8..55fa49b 100644 --- a/TabletFriend/TabletFriend/UiFactory.cs +++ b/TabletFriend/TabletFriend/UiFactory.cs @@ -1,5 +1,4 @@ -using MaterialDesignThemes.Wpf; -using System; +using System; using System.Collections.Generic; using System.Numerics; using System.Windows; @@ -265,6 +264,9 @@ Vector2 offset if (button.Action != null) { uiButton.Click += (e, o) => _ = button.Action.Invoke(); + + if (button.Action is ClickAction) + ClickAction.AddDragAndDropEventHandlers(uiButton, button.Key); } Canvas.SetTop(uiButton, theme.CellSize * position.Y + theme.Margin + offset.Y); diff --git a/TabletFriend/TabletFriend/files/layouts/sample_layout.yaml b/TabletFriend/TabletFriend/files/layouts/sample_layout.yaml index e4f5f77..d362d91 100644 --- a/TabletFriend/TabletFriend/files/layouts/sample_layout.yaml +++ b/TabletFriend/TabletFriend/files/layouts/sample_layout.yaml @@ -134,6 +134,19 @@ buttons: icon: pencil text: bluetooth button\nhold size: 2,1 + + # Left mouse click at a given location + # Drag and drop the button to specify new destination coordinates. + click1: + action: click 0,0 + text: Mouse\nClick 1 + size: 2,2 + style: accent + click2: + action: click 0,0 + text: Mouse\nClick 2 + size: 2,2 + style: accent docked_spacer: spacer: true