Skip to content

Commit ff237f8

Browse files
committed
element overlay
1 parent 7119cc0 commit ff237f8

File tree

6 files changed

+350
-84
lines changed

6 files changed

+350
-84
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ obj/
1010

1111
# Artifacts
1212
artifacts/
13+
src/.idea/
14+
*.user
15+
desktop.ini

src/FlaUInspect/Core/ElementOverlay.cs

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,51 @@
99
namespace FlaUInspect.Core;
1010

1111
public class ElementOverlay : IDisposable {
12-
public ElementOverlay() {
13-
}
12+
public ElementOverlayConfiguration Configuration { get; }
1413

15-
public ElementOverlay(Color color) {
16-
Color = color;
17-
}
14+
1815

19-
public ElementOverlay(int size, int margin) {
20-
Size = size;
21-
Margin = margin;
22-
}
16+
public ElementOverlay(ElementOverlayConfiguration configuration) {
17+
Configuration = configuration;
2318

24-
public ElementOverlay(int size, int margin, Color color) {
25-
Size = size;
26-
Margin = margin;
27-
Color = color;
2819
}
2920

30-
public int Size { get; } = 2;
31-
32-
public int Margin { get; } = 0;
21+
// public ElementOverlay(int size, int margin) {
22+
// Size = size;
23+
// Margin = margin;
24+
// }
25+
//
26+
// public ElementOverlay(int size, int margin, Color color) {
27+
// Size = size;
28+
// Margin = margin;
29+
// Color = color;
30+
// }
31+
32+
// public int Size { get; } = 2;
33+
//
34+
// public int Margin { get; } = 0;
35+
//
36+
// public Color Color { get; set; }
37+
38+
public static Rectangle[] FillRectangleFactory(ElementOverlayConfiguration config, Rectangle rectangle) {
39+
return [
40+
new Rectangle(rectangle.X - config.Margin, rectangle.Y - config.Margin, rectangle.Width + 2 * config.Margin, rectangle.Height + 2 * config.Margin)];
41+
}
3342

34-
public Color Color { get; set; }
43+
public static Rectangle[] BoundRectangleFactory(ElementOverlayConfiguration config, Rectangle rectangle) {
44+
return [
45+
new (rectangle.X - config.Margin, rectangle.Y - config.Margin, config.Size, rectangle.Height + 2 * config.Margin),
46+
new (rectangle.X - config.Margin, rectangle.Y - config.Margin, rectangle.Width + 2 * config.Margin, config.Size),
47+
new (rectangle.X + rectangle.Width - config.Size + config.Margin, rectangle.Y - config.Margin, config.Size, rectangle.Height + 2 * config.Margin),
48+
new (rectangle.X - config.Margin, rectangle.Y + rectangle.Height - config.Size + config.Margin, rectangle.Width + 2 * config.Margin, config.Size)
49+
];
50+
}
3551

3652
public void Dispose() {
53+
Hide();
54+
}
55+
56+
public void Hide() {
3757
foreach (OverlayRectangleForm overlayRectangleForm in _overlayRectangleFormList) {
3858
overlayRectangleForm.Hide();
3959
overlayRectangleForm.Close();
@@ -44,24 +64,25 @@ public void Dispose() {
4464

4565
private OverlayRectangleForm[] _overlayRectangleFormList = [];
4666

47-
public void CreateAndShowForms(Rectangle rectangle) {
48-
Color color1 = Color.FromArgb(255, Color.R, Color.G, Color.B);
67+
public void Show(Rectangle rectangle) {
68+
Color color1 = Color.FromArgb(255, Configuration.Color.R, Configuration.Color.G, Configuration.Color.B);
69+
Rectangle[] rectangles = Configuration.RectangleFactory?.Invoke(Configuration, rectangle) ?? BoundRectangleFactory(Configuration, rectangle);
4970
// Rectangle[] rectangles = [
50-
// new (rectangle.X - Margin, rectangle.Y - Margin, Size, rectangle.Height + 2 * Margin),
51-
// new (rectangle.X - Margin, rectangle.Y - Margin, rectangle.Width + 2 * Margin, Size),
52-
// new (rectangle.X + rectangle.Width - Size + Margin, rectangle.Y - Margin, Size, rectangle.Height + 2 * Margin),
53-
// new (rectangle.X - Margin, rectangle.Y + rectangle.Height - Size + Margin, rectangle.Width + 2 * Margin, Size)
71+
// new (rectangle.X - Configuration.Margin, rectangle.Y - Configuration.Margin, Configuration.Size, rectangle.Height + 2 * Configuration.Margin),
72+
// new (rectangle.X - Configuration.Margin, rectangle.Y - Configuration.Margin, rectangle.Width + 2 * Configuration.Margin, Configuration.Size),
73+
// new (rectangle.X + rectangle.Width - Configuration.Size + Configuration.Margin, rectangle.Y - Configuration.Margin, Configuration.Size, rectangle.Height + 2 * Configuration.Margin),
74+
// new (rectangle.X - Configuration.Margin, rectangle.Y + rectangle.Height - Configuration.Size + Configuration.Margin, rectangle.Width + 2 * Configuration.Margin, Configuration.Size)
75+
// ];
76+
// Rectangle[] rectangles = [
77+
// new (rectangle.X - Margin, rectangle.Y - Margin, rectangle.Width + Margin, rectangle.Height + Margin),
5478
// ];
55-
Rectangle[] rectangles = [
56-
new (rectangle.X - Margin, rectangle.Y - Margin, rectangle.Width + Margin, rectangle.Height + Margin),
57-
];
5879

5980
List<OverlayRectangleForm> rectangleForms = [];
6081

6182
foreach (Rectangle rectangle1 in rectangles) {
6283
OverlayRectangleForm overlayRectangleForm1 = new ();
6384
overlayRectangleForm1.BackColor = color1;
64-
overlayRectangleForm1.Opacity = Color.A / 255d;
85+
overlayRectangleForm1.Opacity = Configuration.Color.A / 255d;
6586
OverlayRectangleForm overlayRectangleForm2 = overlayRectangleForm1;
6687
rectangleForms.Add(overlayRectangleForm2);
6788
SetWindowPos(overlayRectangleForm2.Handle, new IntPtr(-1), rectangle1.X, rectangle1.Y, rectangle1.Width, rectangle1.Height, 16 /*0x10*/);
@@ -84,4 +105,6 @@ private static extern bool SetWindowPos(
84105

85106
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
86107
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
87-
}
108+
}
109+
110+
public record class ElementOverlayConfiguration(int Size, int Margin, Color Color, Func<ElementOverlayConfiguration, Rectangle, Rectangle[]>? RectangleFactory = null);
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// // Description : Definition of GlobalMouseHook.cs class
2+
// //
3+
// // Copyright © 2025 - 2025, Alcon. All rights reserved.
4+
5+
namespace FlaUInspect.Core;
6+
7+
using System;
8+
using System.Diagnostics;
9+
using System.Runtime.InteropServices;
10+
11+
public static class GlobalMouseHook
12+
{
13+
private static IntPtr _hookId = IntPtr.Zero;
14+
private static LowLevelMouseProc _proc = HookCallback;
15+
16+
public static event Action<int, int>? MouseMove; // your event
17+
18+
public static void Start()
19+
{
20+
_hookId = SetHook(_proc);
21+
}
22+
23+
public static void Stop()
24+
{
25+
UnhookWindowsHookEx(_hookId);
26+
}
27+
28+
private static IntPtr SetHook(LowLevelMouseProc proc)
29+
{
30+
using (var curProcess = Process.GetCurrentProcess())
31+
using (var curModule = curProcess.MainModule!)
32+
{
33+
return SetWindowsHookEx(WH_MOUSE_LL, proc,
34+
GetModuleHandle(curModule.ModuleName), 0);
35+
}
36+
}
37+
38+
private const int WH_MOUSE_LL = 14;
39+
private const int WM_MOUSEMOVE = 0x0200;
40+
41+
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
42+
43+
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
44+
{
45+
if (nCode >= 0 && wParam == (IntPtr)WM_MOUSEMOVE)
46+
{
47+
MSLLHOOKSTRUCT data = Marshal.PtrToStructure<MSLLHOOKSTRUCT>(lParam);
48+
MouseMove?.Invoke(data.pt.x, data.pt.y);
49+
}
50+
return CallNextHookEx(_hookId, nCode, wParam, lParam);
51+
}
52+
53+
[StructLayout(LayoutKind.Sequential)]
54+
private struct MSLLHOOKSTRUCT
55+
{
56+
public POINT pt;
57+
public int mouseData;
58+
public int flags;
59+
public int time;
60+
public IntPtr dwExtraInfo;
61+
}
62+
63+
[StructLayout(LayoutKind.Sequential)]
64+
private struct POINT { public int x; public int y; }
65+
66+
[DllImport("user32.dll")]
67+
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn,
68+
IntPtr hMod, uint dwThreadId);
69+
70+
[DllImport("user32.dll")]
71+
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
72+
73+
[DllImport("user32.dll")]
74+
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
75+
76+
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
77+
private static extern IntPtr GetModuleHandle(string lpModuleName);
78+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// // Description : Definition of HoverMouseMode.cs class
2+
// //
3+
// // Copyright © 2025 - 2025, Alcon. All rights reserved.
4+
5+
using System.Drawing;
6+
using System.Windows.Input;
7+
using FlaUI.Core;
8+
using FlaUI.Core.AutomationElements;
9+
using Mouse = FlaUI.Core.Input.Mouse;
10+
using Point = System.Drawing.Point;
11+
12+
namespace FlaUInspect.Core;
13+
14+
public class HoverMouseMode {
15+
private readonly AutomationBase _automationBase;
16+
private ElementOverlay? _elementOverlay;
17+
private AutomationElement? _hoveredElement;
18+
19+
// Add a field to track the last refresh time
20+
private DateTime _lastRefresh = DateTime.MinValue;
21+
22+
public HoverMouseMode(AutomationBase automationBase) {
23+
_automationBase = automationBase;
24+
25+
}
26+
27+
public bool IsEnabled { get; set; }
28+
29+
public void Refresh() {
30+
// Throttle: only allow refresh every 300ms
31+
if ((DateTime.Now - _lastRefresh).TotalMilliseconds < 300) {
32+
return;
33+
}
34+
_lastRefresh = DateTime.Now;
35+
36+
if (!IsEnabled) {
37+
_elementOverlay?.Dispose();
38+
_hoveredElement = null;
39+
return;
40+
}
41+
42+
if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) {
43+
Point screenPos = Mouse.Position;
44+
45+
try {
46+
AutomationElement? automationElement = _automationBase?.FromPoint(screenPos);
47+
48+
if (automationElement == null) {
49+
_elementOverlay?.Dispose();
50+
_hoveredElement = null;
51+
return;
52+
}
53+
54+
if (automationElement?.Properties.ProcessId == Environment.ProcessId) {
55+
_elementOverlay?.Dispose();
56+
_hoveredElement = null;
57+
return;
58+
}
59+
60+
if (_hoveredElement == null || !automationElement.Equals(_hoveredElement)) {
61+
_elementOverlay?.Dispose();
62+
_hoveredElement = automationElement;
63+
64+
try {
65+
ElementOverlay elementOverlay = new ( new ElementOverlayConfiguration(0, 0, Color.FromArgb(25, Color.Red), ElementOverlay.FillRectangleFactory));
66+
elementOverlay.Show(automationElement.Properties.BoundingRectangle.Value);
67+
_elementOverlay = elementOverlay;
68+
} catch {
69+
// ignored
70+
}
71+
}
72+
} catch {
73+
// ignored
74+
}
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)