548 lines
18 KiB
C#
548 lines
18 KiB
C#
using System.Diagnostics;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Runtime.Versioning;
|
|
|
|
namespace DevDrive;
|
|
|
|
/// <summary>
|
|
/// Retro-styled dialog that prompts the user to select which startup apps to launch.
|
|
/// </summary>
|
|
[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<StartupApp> _apps;
|
|
private readonly List<RetroCheckBox> _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;
|
|
|
|
/// <summary>
|
|
/// Creates a new startup dialog for the given apps.
|
|
/// </summary>
|
|
public StartupDialog(List<StartupApp> apps, string driveName)
|
|
{
|
|
_apps = apps;
|
|
_driveName = driveName;
|
|
|
|
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
|
|
|
|
Text = "STARTUP";
|
|
FormBorderStyle = FormBorderStyle.None;
|
|
StartPosition = FormStartPosition.CenterScreen;
|
|
BackColor = BackgroundDark;
|
|
Opacity = 0.86;
|
|
|
|
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);
|
|
|
|
// Set the window region to rounded rectangle for transparent corners
|
|
UpdateWindowRegion();
|
|
|
|
var yPos = headerHeight;
|
|
|
|
foreach (var app in _apps)
|
|
{
|
|
var checkbox = new RetroCheckBox
|
|
{
|
|
Text = app.Name,
|
|
Tag = app,
|
|
Checked = app.CheckedByDefault,
|
|
Location = new Point(padding, yPos),
|
|
Size = new Size(contentWidth - (padding * 2), itemHeight - 4)
|
|
};
|
|
|
|
_checkboxes.Add(checkbox);
|
|
Controls.Add(checkbox);
|
|
yPos += itemHeight;
|
|
}
|
|
|
|
var buttonWidth = 110;
|
|
var buttonHeight = 36;
|
|
var buttonY = contentHeight - footerHeight + 17;
|
|
|
|
var launchButton = new RetroButton
|
|
{
|
|
Text = "► LAUNCH",
|
|
Location = new Point(contentWidth - padding - buttonWidth - 12 - buttonWidth, buttonY),
|
|
Size = new Size(buttonWidth, buttonHeight),
|
|
IsPrimary = true
|
|
};
|
|
launchButton.Click += (s, e) => { LaunchSelectedApps(); Close(); };
|
|
Controls.Add(launchButton);
|
|
|
|
var skipButton = new RetroButton
|
|
{
|
|
Text = "SKIP",
|
|
Location = new Point(contentWidth - padding - buttonWidth, buttonY),
|
|
Size = new Size(buttonWidth, buttonHeight),
|
|
IsPrimary = false
|
|
};
|
|
skipButton.Click += (s, e) => Close();
|
|
Controls.Add(skipButton);
|
|
|
|
_animTimer = new System.Windows.Forms.Timer { Interval = 50 };
|
|
_animTimer.Tick += OnAnimTick;
|
|
_animTimer.Start();
|
|
}
|
|
|
|
private void OnAnimTick(object? sender, EventArgs e)
|
|
{
|
|
_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;
|
|
|
|
// Apply subtle bulge transformation
|
|
var centerX = Width / 2f;
|
|
var centerY = Height / 2f;
|
|
var bulgeStrength = 0.08f;
|
|
|
|
// Create a slight perspective transform for CRT bulge
|
|
var scaleX = 1.0f - bulgeStrength;
|
|
var scaleY = 1.0f - bulgeStrength;
|
|
|
|
g.TranslateTransform(centerX, centerY);
|
|
g.ScaleTransform(scaleX + bulgeStrength, scaleY + bulgeStrength);
|
|
g.TranslateTransform(-centerX, -centerY);
|
|
|
|
using var backgroundBrush = new SolidBrush(Color.FromArgb(240, BackgroundDark));
|
|
g.FillRectangle(backgroundBrush, 0, 0, Width, Height);
|
|
|
|
var glowAlpha = (int)(180 + _glowPulse);
|
|
var pulsingCyan = Color.FromArgb(Math.Min(255, glowAlpha), 0, 255, 255);
|
|
|
|
// Inner content
|
|
DrawContent(g, pulsingCyan);
|
|
|
|
// Reset transform for effects
|
|
g.ResetTransform();
|
|
|
|
// CRT bezel effect - subtle outer shadow
|
|
using var bezelBrush = new LinearGradientBrush(
|
|
new Rectangle(0, 0, Width, Height),
|
|
Color.FromArgb(60, 0, 0, 0),
|
|
Color.Transparent,
|
|
LinearGradientMode.Vertical);
|
|
|
|
var screenRect = new Rectangle(3, 3, Width - 6, Height - 6);
|
|
using var screenPath = CreateRoundedRectangle(screenRect, 12);
|
|
|
|
var bezelRect = new Rectangle(0, 0, Width, Height);
|
|
using var outerPath = CreateRoundedRectangle(bezelRect, 16);
|
|
using var innerPath = CreateRoundedRectangle(new Rectangle(4, 4, Width - 8, Height - 8), 10);
|
|
|
|
var bezelRegion = new Region(outerPath);
|
|
bezelRegion.Exclude(innerPath);
|
|
g.FillRegion(bezelBrush, bezelRegion);
|
|
|
|
// Outer glow
|
|
using var outerGlow = new Pen(Color.FromArgb((int)(20 + _glowPulse / 2), AccentCyan), 3);
|
|
g.DrawPath(outerGlow, CreateRoundedRectangle(new Rectangle(1, 1, Width - 2, Height - 2), 14));
|
|
|
|
// Main border
|
|
using var borderPen = new Pen(pulsingCyan, 2);
|
|
g.DrawPath(borderPen, screenPath);
|
|
|
|
// Corner reflections for glass effect
|
|
DrawCRTReflections(g);
|
|
}
|
|
|
|
private void UpdateWindowRegion()
|
|
{
|
|
using var path = CreateRoundedRectangle(new Rectangle(0, 0, Width, Height), 16);
|
|
Region = new Region(path);
|
|
}
|
|
|
|
protected override void OnResize(EventArgs e)
|
|
{
|
|
base.OnResize(e);
|
|
UpdateWindowRegion();
|
|
}
|
|
|
|
private void DrawContent(Graphics g, Color pulsingCyan)
|
|
{
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
private GraphicsPath CreateRoundedRectangle(Rectangle rect, int radius)
|
|
{
|
|
var path = new GraphicsPath();
|
|
var diameter = radius * 2;
|
|
|
|
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
|
|
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
|
|
path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
|
|
path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
|
|
path.CloseFigure();
|
|
|
|
return path;
|
|
}
|
|
|
|
private void DrawCRTReflections(Graphics g)
|
|
{
|
|
// Top-left corner reflection
|
|
using var reflection1 = new LinearGradientBrush(
|
|
new Rectangle(8, 8, 60, 40),
|
|
Color.FromArgb(25, 255, 255, 255),
|
|
Color.Transparent,
|
|
45f);
|
|
using var reflectPath1 = CreateRoundedRectangle(new Rectangle(8, 8, 60, 40), 8);
|
|
g.FillPath(reflection1, reflectPath1);
|
|
|
|
// Subtle center highlight
|
|
using var reflection2 = new LinearGradientBrush(
|
|
new Rectangle(Width / 4, 15, Width / 2, 25),
|
|
Color.FromArgb(8, 255, 255, 255),
|
|
Color.Transparent,
|
|
LinearGradientMode.Vertical);
|
|
using var reflectPath2 = CreateRoundedRectangle(new Rectangle(Width / 4, 15, Width / 2, 25), 4);
|
|
g.FillPath(reflection2, reflectPath2);
|
|
}
|
|
|
|
private void LaunchSelectedApps()
|
|
{
|
|
foreach (var checkbox in _checkboxes)
|
|
{
|
|
if (!checkbox.Checked || checkbox.Tag is not StartupApp app)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shows the startup dialog if there are apps to display.
|
|
/// </summary>
|
|
public static void ShowIfNeeded(List<StartupApp> apps, string driveName)
|
|
{
|
|
if (apps.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Application.EnableVisualStyles();
|
|
Application.SetCompatibleTextRenderingDefault(false);
|
|
using var dialog = new StartupDialog(apps, driveName);
|
|
dialog.ShowDialog();
|
|
}
|
|
|
|
protected override void OnMouseDown(MouseEventArgs e)
|
|
{
|
|
base.OnMouseDown(e);
|
|
if (e.Button == MouseButtons.Left && e.Y < 60)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Custom retro-styled checkbox control.
|
|
/// </summary>
|
|
[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(200, 40, 40, 56) : Color.FromArgb(180, 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Custom retro-styled button control.
|
|
/// </summary>
|
|
[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(200, Color.FromArgb(60, accentColor));
|
|
}
|
|
else if (_hovering)
|
|
{
|
|
bgColor = Color.FromArgb(180, Color.FromArgb(30, accentColor));
|
|
}
|
|
else
|
|
{
|
|
bgColor = Color.FromArgb(140, 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);
|
|
}
|
|
}
|