-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGridForm.cs
More file actions
252 lines (214 loc) · 8.42 KB
/
GridForm.cs
File metadata and controls
252 lines (214 loc) · 8.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
// Copyright (c) 2012-2026 The Hello World Writer (https://www.thehelloworldwriter.com).
// Licensed under the MIT License. See the LICENSE file in the project root for more information.
namespace GridlyDots;
using System.Diagnostics;
/// <summary>The main grid form. Displays a transparent dot grid overlay across the screen.</summary>
public class GridForm : Form
{
/// <summary>Win32 message sent when the app gains or loses focus.</summary>
private const int WM_ACTIVATEAPP = 0x001C;
/// <summary>Win32 extended style for layered (transparent) windows.</summary>
private const int WS_EX_LAYERED = 0x00080000;
/// <summary>Win32 extended style for click-through windows.</summary>
private const int WS_EX_TRANSPARENT = 0x00000020;
private readonly GridPainter gridPainter = new();
private readonly NotifyIcon notifyIcon = new();
private readonly ContextMenuStrip contextMenuStrip = new();
private readonly System.Windows.Forms.Timer topmostTimer = new();
private readonly ToolStripMenuItem settingsMenuItem = new(Strings.SettingsMenuItem);
public GridForm()
{
SuspendLayout();
// Dimensions BEFORE mode (setting the mode clears dimensions)
AutoScaleDimensions = new SizeF(96F, 96F);
AutoScaleMode = AutoScaleMode.Dpi;
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath!);
FormBorderStyle = FormBorderStyle.None;
TopMost = true;
ShowInTaskbar = false;
BackColor = Color.Black;
TransparencyKey = Color.Black;
DoubleBuffered = true;
CreateControls();
Load += OnFormLoad;
Paint += OnFormPaint;
FormClosed += OnFormClosed;
ResumeLayout(false);
PerformLayout();
}
/// <summary>Builds the timer, context menu, and notification icon.</summary>
private void CreateControls()
{
// Topmost timer — brings form back to front after another app activates
topmostTimer.Interval = 500;
topmostTimer.Tick += OnTopmostTimerTick;
// Context menu — Settings (bold)
settingsMenuItem.Font = new Font(settingsMenuItem.Font, settingsMenuItem.Font.Style | FontStyle.Bold);
settingsMenuItem.Click += OnSettingsMenuItemClick;
// Context menu — Stealth Mode
var stealthModeMenuItem = new ToolStripMenuItem(Strings.StealthModeMenuItem)
{
ToolTipText = Strings.StealthModeTooltip,
};
stealthModeMenuItem.Click += OnStealthModeMenuItemClick;
// Context menu — About...
var aboutMenuItem = new ToolStripMenuItem(Strings.AboutMenuItem);
aboutMenuItem.Click += OnAboutMenuItemClick;
// Context menu — Quit
var quitMenuItem = new ToolStripMenuItem(Strings.QuitMenuItem);
quitMenuItem.Click += (s, e) => Close();
// Assemble context menu
// TODO: Check if this is still needed e.g. on 4K 200% displays
// contextMenuStrip.Font = SystemFonts.MessageBoxFont;
contextMenuStrip.RenderMode = ToolStripRenderMode.System;
contextMenuStrip.Items.AddRange([
settingsMenuItem,
stealthModeMenuItem,
new ToolStripSeparator(),
aboutMenuItem,
new ToolStripSeparator(),
quitMenuItem,
]);
// System tray icon
notifyIcon.Icon = Icon;
notifyIcon.ContextMenuStrip = contextMenuStrip;
notifyIcon.Text = Strings.AppName;
notifyIcon.Visible = true;
notifyIcon.MouseClick += OnNotifyIconMouseClick;
}
/// <summary>Gets the grid painter instance.</summary>
public GridPainter GPainter => gridPainter;
/// <summary>Adds layered and transparent extended window styles for click-through transparency.</summary>
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
return cp;
}
}
/// <summary>Intercepts the WM_ACTIVATEAPP message to restore always-on-top state.</summary>
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_ACTIVATEAPP && Visible)
{
topmostTimer.Start();
}
base.WndProc(ref m);
}
/// <summary>Sets the form bounds to cover all screens and loads settings.</summary>
private void OnFormLoad(object? sender, EventArgs e)
{
Bounds = SystemInformation.VirtualScreen;
LoadSettings();
}
/// <summary>Paints the dot grid on the form surface.</summary>
private void OnFormPaint(object? sender, PaintEventArgs e)
{
gridPainter.Draw(e.Graphics, ClientRectangle);
}
/// <summary>Saves settings when the form is closed.</summary>
private void OnFormClosed(object? sender, FormClosedEventArgs e)
{
SaveSettings();
}
/// <summary>Brings the form to front after the timer delay, restoring always-on-top state.</summary>
private void OnTopmostTimerTick(object? sender, EventArgs e)
{
topmostTimer.Stop();
if (Visible && !contextMenuStrip.Visible)
{
BringToFront();
}
}
/// <summary>Confirms and hides the notification icon, entering stealth mode.</summary>
private void OnStealthModeMenuItemClick(object? sender, EventArgs e)
{
var result = MessageBox.Show(this,
Strings.StealthModeConfirmMessage,
Strings.StealthModeConfirmCaption,
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning,
MessageBoxDefaultButton.Button2);
if (result == DialogResult.Yes)
{
notifyIcon.Visible = false;
}
}
/// <summary>Opens the settings dialog.</summary>
private void OnSettingsMenuItemClick(object? sender, EventArgs e)
{
BringToFront();
using var settingsForm = new SettingsForm();
settingsForm.ShowDialog(this);
}
/// <summary>Opens the about dialog.</summary>
private void OnAboutMenuItemClick(object? sender, EventArgs e)
{
BringToFront();
using var aboutForm = new AboutForm();
aboutForm.ShowDialog(this);
}
/// <summary>Opens settings on left-click of the tray icon.</summary>
private void OnNotifyIconMouseClick(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
settingsMenuItem.PerformClick();
}
}
/// <summary>Loads grid settings from the JSON settings file.</summary>
private void LoadSettings()
{
try
{
var settingsFile = ProgramSettings.CreateSettingsFile();
var settings = settingsFile.Load();
gridPainter.DotSize = settings.DotSize > 0 ? settings.DotSize : 2;
gridPainter.DotSpacing = settings.DotSpacing > 0 ? settings.DotSpacing : 24;
gridPainter.DotColor = !string.IsNullOrEmpty(settings.DotColorCode)
? ColorTranslator.FromHtml(settings.DotColorCode)
: Color.Gray;
Opacity = settings.Opacity > 0 ? settings.Opacity : 0.30;
}
catch (Exception ex)
{
Debug.WriteLine(Strings.FailedToLoadSettings(ex));
}
}
/// <summary>Saves current grid settings to the JSON settings file.</summary>
private void SaveSettings()
{
try
{
var settingsFile = ProgramSettings.CreateSettingsFile();
var settings = new ProgramSettings
{
DotSize = gridPainter.DotSize,
DotSpacing = gridPainter.DotSpacing,
DotColorCode = ColorTranslator.ToHtml(gridPainter.DotColor),
Opacity = Opacity,
};
// force: true creates the file if it doesn't exist yet. This is essential for
// installed mode (first run, no settings file) and harmless for portable mode
// (settings file already exists next to the executable).
settingsFile.Save(settings, force: true);
}
catch (Exception ex)
{
Debug.WriteLine(Strings.FailedToSaveSettings(ex));
}
}
/// <summary>Disposes components not in the Controls hierarchy.</summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
topmostTimer.Dispose();
notifyIcon.Dispose();
contextMenuStrip.Dispose();
}
base.Dispose(disposing);
}
}