diff --git a/StartupDialog.cs b/StartupDialog.cs
index 6d92cd9..1d39174 100644
--- a/StartupDialog.cs
+++ b/StartupDialog.cs
@@ -1,109 +1,199 @@
using System.Diagnostics;
+using System.Drawing.Drawing2D;
using System.Runtime.Versioning;
namespace DevDrive;
///
-/// Dialog that prompts the user to select which startup apps to launch.
+/// Retro-styled dialog that prompts the user to select which startup apps to launch.
///
[SupportedOSPlatform("windows")]
public class StartupDialog : Form
{
+ private static readonly Color BackgroundDark = Color.FromArgb(24, 24, 32);
+ private static readonly Color BackgroundMid = Color.FromArgb(32, 32, 44);
+ private static readonly Color AccentCyan = Color.FromArgb(0, 255, 255);
+ private static readonly Color AccentMagenta = Color.FromArgb(255, 0, 128);
+ private static readonly Color TextLight = Color.FromArgb(240, 240, 255);
+ private static readonly Color TextDim = Color.FromArgb(140, 140, 160);
+ private static readonly Color BorderColor = Color.FromArgb(60, 60, 80);
+
private readonly List _apps;
- private readonly List _checkboxes = [];
- private readonly Button _launchButton;
- private readonly Button _cancelButton;
+ private readonly List _checkboxes = [];
+ private readonly string _driveName;
+ private readonly System.Windows.Forms.Timer _animTimer;
+ private float _scanlineOffset;
+ private float _glowPulse;
+ private float _flickerTarget;
+ private float _flickerCurrent;
+ private float _brightLineY;
+ private int _flickerCounter;
///
/// Creates a new startup dialog for the given apps.
///
- /// List of startup apps to display.
- /// Name of the drive for the title.
public StartupDialog(List apps, string driveName)
{
_apps = apps;
+ _driveName = driveName;
- Text = $"{driveName} - Startup Apps";
- FormBorderStyle = FormBorderStyle.FixedDialog;
+ SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
+
+ Text = "STARTUP";
+ FormBorderStyle = FormBorderStyle.None;
StartPosition = FormStartPosition.CenterScreen;
- MaximizeBox = false;
- MinimizeBox = false;
- AutoScaleMode = AutoScaleMode.Dpi;
+ BackColor = BackgroundDark;
- var padding = 16;
- var checkboxHeight = 28;
- var buttonHeight = 32;
- var buttonWidth = 100;
-
- var contentHeight = padding + (_apps.Count * checkboxHeight) + padding + buttonHeight + padding;
- var contentWidth = 350;
+ var itemHeight = 44;
+ var headerHeight = 60;
+ var footerHeight = 70;
+ var padding = 20;
+ var contentWidth = 380;
+ var contentHeight = headerHeight + (_apps.Count * itemHeight) + footerHeight;
ClientSize = new Size(contentWidth, contentHeight);
- var yPos = padding;
+ var yPos = headerHeight;
foreach (var app in _apps)
{
- var checkbox = new CheckBox
+ var checkbox = new RetroCheckBox
{
Text = app.Name,
Tag = app,
Checked = app.CheckedByDefault,
Location = new Point(padding, yPos),
- Size = new Size(contentWidth - (padding * 2), checkboxHeight),
- Font = new Font("Segoe UI", 10)
+ Size = new Size(contentWidth - (padding * 2), itemHeight - 4)
};
_checkboxes.Add(checkbox);
Controls.Add(checkbox);
- yPos += checkboxHeight;
+ yPos += itemHeight;
}
- yPos += padding;
+ var buttonWidth = 110;
+ var buttonHeight = 36;
+ var buttonY = contentHeight - footerHeight + 17;
- _launchButton = new Button
+ var launchButton = new RetroButton
{
- Text = "Launch",
- DialogResult = DialogResult.OK,
- Location = new Point(contentWidth - padding - buttonWidth - 8 - buttonWidth, yPos),
+ Text = "► LAUNCH",
+ Location = new Point(contentWidth - padding - buttonWidth - 12 - buttonWidth, buttonY),
Size = new Size(buttonWidth, buttonHeight),
- Font = new Font("Segoe UI", 9)
+ IsPrimary = true
};
- _launchButton.Click += OnLaunchClick;
- Controls.Add(_launchButton);
+ launchButton.Click += (s, e) => { LaunchSelectedApps(); Close(); };
+ Controls.Add(launchButton);
- _cancelButton = new Button
+ var skipButton = new RetroButton
{
- Text = "Skip",
- DialogResult = DialogResult.Cancel,
- Location = new Point(contentWidth - padding - buttonWidth, yPos),
+ Text = "SKIP",
+ Location = new Point(contentWidth - padding - buttonWidth, buttonY),
Size = new Size(buttonWidth, buttonHeight),
- Font = new Font("Segoe UI", 9)
+ IsPrimary = false
};
- Controls.Add(_cancelButton);
+ skipButton.Click += (s, e) => Close();
+ Controls.Add(skipButton);
- AcceptButton = _launchButton;
- CancelButton = _cancelButton;
+ _animTimer = new System.Windows.Forms.Timer { Interval = 50 };
+ _animTimer.Tick += OnAnimTick;
+ _animTimer.Start();
}
- private void OnLaunchClick(object? sender, EventArgs e)
+ private void OnAnimTick(object? sender, EventArgs e)
{
- LaunchSelectedApps();
+ _scanlineOffset = (_scanlineOffset + 0.3f) % 6f;
+
+ _glowPulse = (float)(Math.Sin(Environment.TickCount / 800.0) * 20 + 20);
+
+ _brightLineY = (_brightLineY + 1.5f) % (Height + 40);
+
+ _flickerCounter++;
+ if (_flickerCounter >= 8)
+ {
+ _flickerCounter = 0;
+ _flickerTarget = Random.Shared.Next(0, 5);
+ }
+ _flickerCurrent += (_flickerTarget - _flickerCurrent) * 0.3f;
+
+ Invalidate();
+ }
+
+ protected override void OnFormClosed(FormClosedEventArgs e)
+ {
+ _animTimer.Stop();
+ _animTimer.Dispose();
+ base.OnFormClosed(e);
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ base.OnPaint(e);
+ var g = e.Graphics;
+ g.SmoothingMode = SmoothingMode.AntiAlias;
+
+ var glowAlpha = (int)(180 + _glowPulse);
+ var pulsingCyan = Color.FromArgb(Math.Min(255, glowAlpha), 0, 255, 255);
+
+ using var borderPen = new Pen(pulsingCyan, 2);
+ g.DrawRectangle(borderPen, 1, 1, Width - 3, Height - 3);
+
+ using var outerGlow = new Pen(Color.FromArgb((int)(20 + _glowPulse / 2), AccentCyan), 6);
+ g.DrawRectangle(outerGlow, -2, -2, Width + 3, Height + 3);
+
+ using var innerBorder = new Pen(BorderColor, 1);
+ g.DrawRectangle(innerBorder, 4, 4, Width - 9, Height - 9);
+
+ using var headerBrush = new LinearGradientBrush(
+ new Rectangle(0, 0, Width, 60),
+ Color.FromArgb((int)(40 + _glowPulse / 2), AccentCyan),
+ Color.FromArgb(5, AccentCyan),
+ LinearGradientMode.Vertical);
+ g.FillRectangle(headerBrush, 5, 5, Width - 10, 55);
+
+ using var titleFont = new Font("Consolas", 14, FontStyle.Bold);
+ using var titleBrush = new SolidBrush(pulsingCyan);
+ var title = $"▸ {_driveName.ToUpper()}";
+ g.DrawString(title, titleFont, titleBrush, 20, 15);
+
+ using var subtitleFont = new Font("Consolas", 9);
+ using var subtitleBrush = new SolidBrush(TextDim);
+ g.DrawString("Select applications to launch", subtitleFont, subtitleBrush, 20, 38);
+
+ using var linePen = new Pen(BorderColor, 1);
+ g.DrawLine(linePen, 10, 58, Width - 10, 58);
+
+ var footerY = Height - 70;
+ g.DrawLine(linePen, 10, footerY, Width - 10, footerY);
+
+ var scanOffset = (int)_scanlineOffset;
+ for (var y = scanOffset; y < Height; y += 3)
+ {
+ var scanAlpha = 10 + (y % 6 == scanOffset ? 5 : 0);
+ using var scanlinePen = new Pen(Color.FromArgb(scanAlpha, 0, 0, 0), 1);
+ g.DrawLine(scanlinePen, 0, y, Width, y);
+ }
+
+ var brightY = (int)_brightLineY - 20;
+ if (brightY >= 0 && brightY < Height)
+ {
+ using var brightScanline = new Pen(Color.FromArgb(12, 255, 255, 255), 3);
+ g.DrawLine(brightScanline, 0, brightY, Width, brightY);
+ }
+
+ var flickerAlpha = (int)_flickerCurrent;
+ if (flickerAlpha > 0)
+ {
+ using var flickerBrush = new SolidBrush(Color.FromArgb(flickerAlpha, 255, 255, 255));
+ g.FillRectangle(flickerBrush, 0, 0, Width, Height);
+ }
}
- ///
- /// Launches all selected apps.
- ///
private void LaunchSelectedApps()
{
foreach (var checkbox in _checkboxes)
{
- if (!checkbox.Checked)
- {
- continue;
- }
-
- if (checkbox.Tag is not StartupApp app)
+ if (!checkbox.Checked || checkbox.Tag is not StartupApp app)
{
continue;
}
@@ -139,8 +229,6 @@ public class StartupDialog : Form
///
/// Shows the startup dialog if there are apps to display.
///
- /// List of startup apps.
- /// Name of the drive.
public static void ShowIfNeeded(List apps, string driveName)
{
if (apps.Count == 0)
@@ -148,54 +236,215 @@ public class StartupDialog : Form
return;
}
- if (apps.Count == 1)
- {
- var app = apps[0];
- var result = MessageBox.Show(
- $"Launch {app.Name}?",
- $"{driveName} - Startup",
- MessageBoxButtons.YesNo,
- MessageBoxIcon.Question
- );
-
- if (result == DialogResult.Yes)
- {
- LaunchSingleApp(app);
- }
- return;
- }
-
Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
using var dialog = new StartupDialog(apps, driveName);
dialog.ShowDialog();
}
- private static void LaunchSingleApp(StartupApp app)
+ protected override void OnMouseDown(MouseEventArgs e)
{
- try
+ base.OnMouseDown(e);
+ if (e.Button == MouseButtons.Left && e.Y < 60)
{
- var startInfo = new ProcessStartInfo
- {
- FileName = Environment.ExpandEnvironmentVariables(app.Path),
- UseShellExecute = true
- };
-
- if (!string.IsNullOrWhiteSpace(app.Arguments))
- {
- startInfo.Arguments = Environment.ExpandEnvironmentVariables(app.Arguments);
- }
-
- if (!string.IsNullOrWhiteSpace(app.WorkingDirectory))
- {
- startInfo.WorkingDirectory = Environment.ExpandEnvironmentVariables(app.WorkingDirectory);
- }
-
- Process.Start(startInfo);
- Console.WriteLine($"[DevDrive] Launched: {app.Name}");
- }
- catch (Exception ex)
- {
- Console.WriteLine($"[DevDrive] Failed to launch {app.Name}: {ex.Message}");
+ NativeMethods.ReleaseCapture();
+ NativeMethods.SendMessage(Handle, 0xA1, 0x2, 0);
}
}
+
+ private static class NativeMethods
+ {
+ [System.Runtime.InteropServices.DllImport("user32.dll")]
+ public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
+
+ [System.Runtime.InteropServices.DllImport("user32.dll")]
+ public static extern bool ReleaseCapture();
+ }
+}
+
+///
+/// Custom retro-styled checkbox control.
+///
+[SupportedOSPlatform("windows")]
+public class RetroCheckBox : Control
+{
+ private static readonly Color BackgroundDark = Color.FromArgb(24, 24, 32);
+ private static readonly Color AccentCyan = Color.FromArgb(0, 255, 255);
+ private static readonly Color AccentMagenta = Color.FromArgb(255, 0, 128);
+ private static readonly Color TextLight = Color.FromArgb(240, 240, 255);
+ private static readonly Color BorderColor = Color.FromArgb(60, 60, 80);
+
+ private bool _checked;
+ private bool _hovering;
+
+ public bool Checked
+ {
+ get => _checked;
+ set { _checked = value; Invalidate(); }
+ }
+
+ public RetroCheckBox()
+ {
+ SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
+ Size = new Size(200, 40);
+ Cursor = Cursors.Hand;
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ var g = e.Graphics;
+ g.SmoothingMode = SmoothingMode.AntiAlias;
+
+ var bgColor = _hovering ? Color.FromArgb(40, 40, 56) : Color.FromArgb(28, 28, 40);
+ using var bgBrush = new SolidBrush(bgColor);
+ g.FillRectangle(bgBrush, 0, 0, Width, Height);
+
+ var boxSize = 20;
+ var boxX = 8;
+ var boxY = (Height - boxSize) / 2;
+
+ var boxBorderColor = _checked ? AccentCyan : BorderColor;
+ using var boxPen = new Pen(boxBorderColor, _checked ? 2 : 1);
+ g.DrawRectangle(boxPen, boxX, boxY, boxSize, boxSize);
+
+ if (_checked)
+ {
+ using var checkBrush = new SolidBrush(AccentCyan);
+ var innerPadding = 5;
+ g.FillRectangle(checkBrush, boxX + innerPadding, boxY + innerPadding,
+ boxSize - (innerPadding * 2) + 1, boxSize - (innerPadding * 2) + 1);
+
+ using var glowPen = new Pen(Color.FromArgb(60, AccentCyan), 4);
+ g.DrawRectangle(glowPen, boxX - 1, boxY - 1, boxSize + 2, boxSize + 2);
+ }
+
+ using var textFont = new Font("Consolas", 11, FontStyle.Bold);
+ var textColor = _checked ? TextLight : Color.FromArgb(160, 160, 180);
+ using var textBrush = new SolidBrush(textColor);
+ var textX = boxX + boxSize + 14;
+ var textY = (Height - textFont.Height) / 2;
+ g.DrawString(Text, textFont, textBrush, textX, textY);
+
+ if (_checked)
+ {
+ using var accentPen = new Pen(AccentCyan, 2);
+ g.DrawLine(accentPen, 0, Height - 1, Width, Height - 1);
+ }
+ }
+
+ protected override void OnMouseEnter(EventArgs e)
+ {
+ _hovering = true;
+ Invalidate();
+ base.OnMouseEnter(e);
+ }
+
+ protected override void OnMouseLeave(EventArgs e)
+ {
+ _hovering = false;
+ Invalidate();
+ base.OnMouseLeave(e);
+ }
+
+ protected override void OnClick(EventArgs e)
+ {
+ Checked = !Checked;
+ base.OnClick(e);
+ }
+}
+
+///
+/// Custom retro-styled button control.
+///
+[SupportedOSPlatform("windows")]
+public class RetroButton : Control
+{
+ private static readonly Color AccentCyan = Color.FromArgb(0, 255, 255);
+ private static readonly Color AccentMagenta = Color.FromArgb(255, 0, 128);
+ private static readonly Color TextLight = Color.FromArgb(240, 240, 255);
+ private static readonly Color BorderColor = Color.FromArgb(60, 60, 80);
+
+ private bool _hovering;
+ private bool _pressing;
+
+ public bool IsPrimary { get; set; }
+
+ public RetroButton()
+ {
+ SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
+ Size = new Size(100, 36);
+ Cursor = Cursors.Hand;
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ var g = e.Graphics;
+ g.SmoothingMode = SmoothingMode.AntiAlias;
+
+ var accentColor = IsPrimary ? AccentCyan : AccentMagenta;
+
+ Color bgColor;
+ if (_pressing)
+ {
+ bgColor = Color.FromArgb(60, accentColor);
+ }
+ else if (_hovering)
+ {
+ bgColor = Color.FromArgb(30, accentColor);
+ }
+ else
+ {
+ bgColor = Color.FromArgb(10, accentColor);
+ }
+
+ using var bgBrush = new SolidBrush(bgColor);
+ g.FillRectangle(bgBrush, 0, 0, Width, Height);
+
+ var borderWidth = _hovering ? 2 : 1;
+ using var borderPen = new Pen(accentColor, borderWidth);
+ g.DrawRectangle(borderPen, borderWidth / 2, borderWidth / 2,
+ Width - borderWidth, Height - borderWidth);
+
+ if (_hovering && IsPrimary)
+ {
+ using var glowPen = new Pen(Color.FromArgb(40, accentColor), 4);
+ g.DrawRectangle(glowPen, -1, -1, Width + 1, Height + 1);
+ }
+
+ using var textFont = new Font("Consolas", 10, FontStyle.Bold);
+ using var textBrush = new SolidBrush(accentColor);
+ var textSize = g.MeasureString(Text, textFont);
+ var textX = (Width - textSize.Width) / 2;
+ var textY = (Height - textSize.Height) / 2;
+ g.DrawString(Text, textFont, textBrush, textX, textY);
+ }
+
+ protected override void OnMouseEnter(EventArgs e)
+ {
+ _hovering = true;
+ Invalidate();
+ base.OnMouseEnter(e);
+ }
+
+ protected override void OnMouseLeave(EventArgs e)
+ {
+ _hovering = false;
+ _pressing = false;
+ Invalidate();
+ base.OnMouseLeave(e);
+ }
+
+ protected override void OnMouseDown(MouseEventArgs e)
+ {
+ _pressing = true;
+ Invalidate();
+ base.OnMouseDown(e);
+ }
+
+ protected override void OnMouseUp(MouseEventArgs e)
+ {
+ _pressing = false;
+ Invalidate();
+ base.OnMouseUp(e);
+ }
}