Implemented a custom theme with a new color scheme and extensively refined UI logic and architecture for improved modernity and consistency. Relocated and reworked numerous options (mount device, select device, share app, uninstall app, pull-to-desktop, filters, etc.), and updated all message boxes to use the new themed styling with enhanced visual polish. All message boxes now use custom themed styling with enhanced visual polish. Corrected grammatical or logical flaws across text, tooltips, and title updates throughout the application. Added smooth animations to left-side navigation / container elements. Fine-tuned sizing, positioning, and colors across numerous UI components. Enhanced GalleryView with proper favorites support including context menu integration, favorite border styling and favorite badge, as well as some bug fixes. Implemented custom modern ToggleSwitch component (iOS-like) with animations. Completely overhauled quest option and rookie option menus to utilize new toggle switches in modernized layouts. Refined sorting and installation status logic to streamline UX; rookie now also functions as an efficient installed-quest-app browser with easily accessible view/uninstall controls. Added WebView2.dll validation to ensure runtime dependencies exist. Re-implemented trailer option. GalleryView is now shown on very first launch, but rookie remembers your preferred view thereafter, so list-view users won't be bothered, while everyone still gets to see the new gallery view at least once. Gallery performance has also been validated on very-low-spec hardware and confirmed to run fine and fast there, due to numerous optimizations. Given the extensive scope of changes across this commit series for beta-2.35-yt, I believe this update represents a significant milestone warranting v3.0 designation. In my opinion these changes represent one of the most significant set of logical and visual changes and enhancements the rookie application has seen in years. Changes have been summarized in changelog.txt for update.
856 lines
40 KiB
C#
856 lines
40 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
|
|
namespace JR.Utils.GUI.Forms
|
|
{
|
|
public class FlexibleMessageBox
|
|
{
|
|
#region Public statics
|
|
|
|
public static double MAX_WIDTH_FACTOR = 0.7;
|
|
public static double MAX_HEIGHT_FACTOR = 0.9;
|
|
public static Font FONT = SystemFonts.MessageBoxFont;
|
|
|
|
#endregion
|
|
|
|
#region Public show functions
|
|
|
|
public static DialogResult Show(string text)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(string text, string caption)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1);
|
|
}
|
|
|
|
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(null, text, caption, buttons, icon, defaultButton);
|
|
}
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
|
{
|
|
return FlexibleMessageBoxForm.Show(owner, text, caption, buttons, icon, defaultButton);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal form class
|
|
|
|
private class FlexibleMessageBoxForm : Form
|
|
{
|
|
#region Constants and P/Invoke
|
|
|
|
private const int CS_DROPSHADOW = 0x00020000;
|
|
private const int WM_NCLBUTTONDOWN = 0xA1;
|
|
private const int HT_CAPTION = 0x2;
|
|
private const int BORDER_RADIUS = 12;
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
|
|
|
|
[DllImport("user32.dll")]
|
|
private static extern bool ReleaseCapture();
|
|
|
|
protected override CreateParams CreateParams
|
|
{
|
|
get
|
|
{
|
|
CreateParams cp = base.CreateParams;
|
|
cp.ClassStyle |= CS_DROPSHADOW;
|
|
return cp;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Form-Designer generated code
|
|
|
|
private System.ComponentModel.IContainer components = null;
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing && (components != null))
|
|
{
|
|
components.Dispose();
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private void CloseButton_Click(object sender, EventArgs e)
|
|
{
|
|
this.DialogResult = DialogResult.Cancel;
|
|
this.Close();
|
|
}
|
|
|
|
private void TitlePanel_MouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left)
|
|
{
|
|
ReleaseCapture();
|
|
SendMessage(this.Handle, WM_NCLBUTTONDOWN, (IntPtr)HT_CAPTION, IntPtr.Zero);
|
|
}
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
components = new System.ComponentModel.Container();
|
|
button1 = new System.Windows.Forms.Button();
|
|
richTextBoxMessage = new System.Windows.Forms.RichTextBox();
|
|
FlexibleMessageBoxFormBindingSource = new System.Windows.Forms.BindingSource(components);
|
|
panel1 = new System.Windows.Forms.Panel();
|
|
pictureBoxForIcon = new System.Windows.Forms.PictureBox();
|
|
button2 = new System.Windows.Forms.Button();
|
|
button3 = new System.Windows.Forms.Button();
|
|
titlePanel = new System.Windows.Forms.Panel();
|
|
titleLabel = new System.Windows.Forms.Label();
|
|
closeButton = new System.Windows.Forms.Button();
|
|
((System.ComponentModel.ISupportInitialize)FlexibleMessageBoxFormBindingSource).BeginInit();
|
|
panel1.SuspendLayout();
|
|
((System.ComponentModel.ISupportInitialize)pictureBoxForIcon).BeginInit();
|
|
titlePanel.SuspendLayout();
|
|
SuspendLayout();
|
|
//
|
|
// titlePanel
|
|
//
|
|
titlePanel.BackColor = System.Drawing.Color.FromArgb(20, 24, 29);
|
|
titlePanel.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
|
titlePanel.Location = new System.Drawing.Point(6, 6);
|
|
titlePanel.Name = "titlePanel";
|
|
titlePanel.Size = new System.Drawing.Size(248, 28);
|
|
titlePanel.TabIndex = 10;
|
|
titlePanel.Controls.Add(closeButton);
|
|
titlePanel.Controls.Add(titleLabel);
|
|
titlePanel.MouseDown += TitlePanel_MouseDown;
|
|
//
|
|
// titleLabel
|
|
//
|
|
titleLabel.AutoSize = false;
|
|
titleLabel.Dock = System.Windows.Forms.DockStyle.Fill;
|
|
titleLabel.ForeColor = System.Drawing.Color.White;
|
|
titleLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
|
titleLabel.Location = new System.Drawing.Point(0, 0);
|
|
titleLabel.Name = "titleLabel";
|
|
titleLabel.Padding = new System.Windows.Forms.Padding(18, 0, 0, 0);
|
|
titleLabel.Size = new System.Drawing.Size(218, 28);
|
|
titleLabel.TabIndex = 0;
|
|
titleLabel.Text = "<Caption>";
|
|
titleLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
|
titleLabel.MouseDown += TitlePanel_MouseDown;
|
|
//
|
|
// closeButton
|
|
//
|
|
closeButton.Dock = System.Windows.Forms.DockStyle.Right;
|
|
closeButton.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
|
closeButton.FlatAppearance.BorderSize = 0;
|
|
closeButton.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(200, 60, 60);
|
|
closeButton.BackColor = System.Drawing.Color.FromArgb(20, 24, 29);
|
|
closeButton.ForeColor = System.Drawing.Color.White;
|
|
closeButton.Font = new System.Drawing.Font("Segoe UI", 9F);
|
|
closeButton.Location = new System.Drawing.Point(218, 0);
|
|
closeButton.Name = "closeButton";
|
|
closeButton.Size = new System.Drawing.Size(30, 28);
|
|
closeButton.TabIndex = 1;
|
|
closeButton.TabStop = false;
|
|
closeButton.Text = "✕";
|
|
closeButton.Click += CloseButton_Click;
|
|
//
|
|
// button1
|
|
//
|
|
button1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
|
button1.DialogResult = System.Windows.Forms.DialogResult.OK;
|
|
button1.Location = new System.Drawing.Point(16, 80);
|
|
button1.Name = "button1";
|
|
button1.Size = new System.Drawing.Size(75, 28);
|
|
button1.TabIndex = 2;
|
|
button1.Text = "OK";
|
|
button1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
|
button1.FlatAppearance.BorderSize = 0;
|
|
button1.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(60, 65, 80);
|
|
button1.BackColor = System.Drawing.Color.FromArgb(42, 45, 58);
|
|
button1.ForeColor = System.Drawing.Color.White;
|
|
button1.Font = new System.Drawing.Font("Segoe UI", 9F);
|
|
button1.Cursor = System.Windows.Forms.Cursors.Hand;
|
|
button1.Visible = false;
|
|
//
|
|
// richTextBoxMessage
|
|
//
|
|
richTextBoxMessage.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom
|
|
| System.Windows.Forms.AnchorStyles.Left
|
|
| System.Windows.Forms.AnchorStyles.Right;
|
|
richTextBoxMessage.BackColor = System.Drawing.Color.FromArgb(20, 24, 29);
|
|
richTextBoxMessage.ForeColor = System.Drawing.Color.White;
|
|
richTextBoxMessage.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
|
richTextBoxMessage.DataBindings.Add(new System.Windows.Forms.Binding("Text", FlexibleMessageBoxFormBindingSource, "MessageText", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
|
|
richTextBoxMessage.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
|
richTextBoxMessage.Location = new System.Drawing.Point(52, 6);
|
|
richTextBoxMessage.Margin = new System.Windows.Forms.Padding(0);
|
|
richTextBoxMessage.Name = "richTextBoxMessage";
|
|
richTextBoxMessage.ReadOnly = true;
|
|
richTextBoxMessage.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.Vertical;
|
|
richTextBoxMessage.Size = new System.Drawing.Size(190, 20);
|
|
richTextBoxMessage.TabIndex = 0;
|
|
richTextBoxMessage.TabStop = false;
|
|
richTextBoxMessage.Text = "<Message>";
|
|
richTextBoxMessage.LinkClicked += new System.Windows.Forms.LinkClickedEventHandler(richTextBoxMessage_LinkClicked);
|
|
//
|
|
// panel1
|
|
//
|
|
panel1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom
|
|
| System.Windows.Forms.AnchorStyles.Left
|
|
| System.Windows.Forms.AnchorStyles.Right;
|
|
panel1.BackColor = System.Drawing.Color.FromArgb(20, 24, 29);
|
|
panel1.Controls.Add(pictureBoxForIcon);
|
|
panel1.Controls.Add(richTextBoxMessage);
|
|
panel1.Location = new System.Drawing.Point(6, 34);
|
|
panel1.Name = "panel1";
|
|
panel1.Size = new System.Drawing.Size(248, 59);
|
|
panel1.TabIndex = 1;
|
|
//
|
|
// pictureBoxForIcon
|
|
//
|
|
pictureBoxForIcon.BackColor = System.Drawing.Color.Transparent;
|
|
pictureBoxForIcon.Location = new System.Drawing.Point(15, 15);
|
|
pictureBoxForIcon.Name = "pictureBoxForIcon";
|
|
pictureBoxForIcon.Size = new System.Drawing.Size(32, 32);
|
|
pictureBoxForIcon.TabIndex = 8;
|
|
pictureBoxForIcon.TabStop = false;
|
|
//
|
|
// button2
|
|
//
|
|
button2.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
|
button2.DialogResult = System.Windows.Forms.DialogResult.OK;
|
|
button2.Location = new System.Drawing.Point(97, 80);
|
|
button2.Name = "button2";
|
|
button2.Size = new System.Drawing.Size(75, 28);
|
|
button2.TabIndex = 3;
|
|
button2.Text = "OK";
|
|
button2.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
|
button2.FlatAppearance.BorderSize = 0;
|
|
button2.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(60, 65, 80);
|
|
button2.BackColor = System.Drawing.Color.FromArgb(42, 45, 58);
|
|
button2.ForeColor = System.Drawing.Color.White;
|
|
button2.Font = new System.Drawing.Font("Segoe UI", 9F);
|
|
button2.Cursor = System.Windows.Forms.Cursors.Hand;
|
|
button2.Visible = false;
|
|
//
|
|
// button3
|
|
//
|
|
button3.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
|
button3.DialogResult = System.Windows.Forms.DialogResult.OK;
|
|
button3.Location = new System.Drawing.Point(178, 80);
|
|
button3.Name = "button3";
|
|
button3.Size = new System.Drawing.Size(75, 28);
|
|
button3.TabIndex = 0;
|
|
button3.Text = "OK";
|
|
button3.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
|
button3.FlatAppearance.BorderSize = 0;
|
|
button3.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(60, 65, 80);
|
|
button3.BackColor = System.Drawing.Color.FromArgb(42, 45, 58);
|
|
button3.ForeColor = System.Drawing.Color.White;
|
|
button3.Font = new System.Drawing.Font("Segoe UI", 9F);
|
|
button3.Cursor = System.Windows.Forms.Cursors.Hand;
|
|
button3.Visible = false;
|
|
//
|
|
// FlexibleMessageBoxForm
|
|
//
|
|
AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
|
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
|
BackColor = System.Drawing.Color.FromArgb(25, 25, 30);
|
|
ForeColor = System.Drawing.Color.White;
|
|
FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
|
ClientSize = new System.Drawing.Size(260, 115);
|
|
Padding = new System.Windows.Forms.Padding(5);
|
|
Controls.Add(titlePanel);
|
|
Controls.Add(button3);
|
|
Controls.Add(button2);
|
|
Controls.Add(panel1);
|
|
Controls.Add(button1);
|
|
MaximizeBox = false;
|
|
MinimizeBox = false;
|
|
MinimumSize = new System.Drawing.Size(276, 120);
|
|
Name = "FlexibleMessageBoxForm";
|
|
ShowIcon = false;
|
|
SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
|
|
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
|
Text = "<Caption>";
|
|
Shown += new System.EventHandler(FlexibleMessageBoxForm_Shown);
|
|
((System.ComponentModel.ISupportInitialize)FlexibleMessageBoxFormBindingSource).EndInit();
|
|
panel1.ResumeLayout(false);
|
|
((System.ComponentModel.ISupportInitialize)pictureBoxForIcon).EndInit();
|
|
titlePanel.ResumeLayout(false);
|
|
ResumeLayout(false);
|
|
|
|
// Apply rounded corners and custom painting
|
|
this.Paint += FlexibleMessageBoxForm_Paint;
|
|
button1.Paint += RoundedButton_Paint;
|
|
button2.Paint += RoundedButton_Paint;
|
|
button3.Paint += RoundedButton_Paint;
|
|
|
|
// Setup hover effects for buttons
|
|
SetupButtonHover(button1);
|
|
SetupButtonHover(button2);
|
|
SetupButtonHover(button3);
|
|
|
|
Activate();
|
|
}
|
|
|
|
private Button button1;
|
|
private BindingSource FlexibleMessageBoxFormBindingSource;
|
|
private RichTextBox richTextBoxMessage;
|
|
private Panel panel1;
|
|
private PictureBox pictureBoxForIcon;
|
|
private Button button2;
|
|
private Button button3;
|
|
private Panel titlePanel;
|
|
private Label titleLabel;
|
|
private Button closeButton;
|
|
|
|
#endregion
|
|
|
|
#region Custom Painting
|
|
|
|
private Dictionary<Button, bool> _buttonHoverState = new Dictionary<Button, bool>();
|
|
private const int SHADOW_SIZE = 2;
|
|
private const int CONTENT_RADIUS = 10;
|
|
|
|
private void FlexibleMessageBoxForm_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
|
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
|
|
|
int w = this.Width;
|
|
int h = this.Height;
|
|
|
|
// Draw shadow gradient layers around the content area
|
|
for (int i = SHADOW_SIZE; i >= 1; i--)
|
|
{
|
|
int alpha = (SHADOW_SIZE - i + 1) * 12; // 12, 24, 36, 48, 60
|
|
Rectangle shadowRect = new Rectangle(
|
|
SHADOW_SIZE - i,
|
|
SHADOW_SIZE - i,
|
|
w - (SHADOW_SIZE - i) * 2 - 1,
|
|
h - (SHADOW_SIZE - i) * 2 - 1);
|
|
|
|
using (Pen shadowPen = new Pen(Color.FromArgb(alpha, 0, 0, 0), 1))
|
|
using (GraphicsPath shadowPath = GetRoundedRectPath(shadowRect, CONTENT_RADIUS + i))
|
|
{
|
|
e.Graphics.DrawPath(shadowPen, shadowPath);
|
|
}
|
|
}
|
|
|
|
// Draw content background
|
|
Rectangle contentRect = new Rectangle(SHADOW_SIZE, SHADOW_SIZE, w - SHADOW_SIZE * 2, h - SHADOW_SIZE * 2);
|
|
using (GraphicsPath contentPath = GetRoundedRectPath(contentRect, CONTENT_RADIUS))
|
|
{
|
|
using (SolidBrush bgBrush = new SolidBrush(Color.FromArgb(20, 24, 29)))
|
|
{
|
|
e.Graphics.FillPath(bgBrush, contentPath);
|
|
}
|
|
|
|
// Draw thin border
|
|
using (Pen borderPen = new Pen(Color.FromArgb(70, 80, 100), 1f))
|
|
{
|
|
e.Graphics.DrawPath(borderPen, contentPath);
|
|
}
|
|
}
|
|
|
|
// Apply rounded region to form (with shadow area)
|
|
using (GraphicsPath regionPath = GetRoundedRectPath(new Rectangle(0, 0, w, h), CONTENT_RADIUS + SHADOW_SIZE))
|
|
{
|
|
this.Region = new Region(regionPath);
|
|
}
|
|
}
|
|
|
|
private void SetupButtonHover(Button btn)
|
|
{
|
|
_buttonHoverState[btn] = false;
|
|
|
|
btn.MouseEnter += (s, e) =>
|
|
{
|
|
_buttonHoverState[btn] = true;
|
|
btn.Invalidate();
|
|
};
|
|
|
|
btn.MouseLeave += (s, e) =>
|
|
{
|
|
_buttonHoverState[btn] = false;
|
|
btn.Invalidate();
|
|
};
|
|
}
|
|
|
|
private void RoundedButton_Paint(object sender, PaintEventArgs e)
|
|
{
|
|
Button btn = sender as Button;
|
|
if (btn == null) return;
|
|
|
|
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
|
e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
|
|
|
bool isHovered = _buttonHoverState.ContainsKey(btn) && _buttonHoverState[btn];
|
|
|
|
int radius = 4;
|
|
|
|
// Use a rect that's 1 pixel smaller to avoid edge clipping
|
|
Rectangle drawRect = new Rectangle(1, 1, btn.Width - 2, btn.Height - 2);
|
|
|
|
// Fill entire button area with parent background first to clear previous state
|
|
using (SolidBrush clearBrush = new SolidBrush(Color.FromArgb(20, 24, 29)))
|
|
{
|
|
e.Graphics.FillRectangle(clearBrush, 0, 0, btn.Width, btn.Height);
|
|
}
|
|
|
|
using (GraphicsPath path = GetRoundedRectPath(drawRect, radius))
|
|
{
|
|
// Determine colors based on hover state
|
|
Color bgColor = isHovered
|
|
? Color.FromArgb(93, 203, 173) // Accent color on hover
|
|
: btn.BackColor;
|
|
|
|
Color textColor = isHovered
|
|
? Color.FromArgb(20, 20, 20) // Dark text on accent
|
|
: btn.ForeColor;
|
|
|
|
// Draw background
|
|
using (SolidBrush brush = new SolidBrush(bgColor))
|
|
{
|
|
e.Graphics.FillPath(brush, path);
|
|
}
|
|
|
|
// Draw subtle border on normal state
|
|
if (!isHovered)
|
|
{
|
|
using (Pen borderPen = new Pen(Color.FromArgb(70, 75, 90), 1))
|
|
{
|
|
e.Graphics.DrawPath(borderPen, path);
|
|
}
|
|
}
|
|
|
|
// Draw text centered in original button bounds
|
|
TextRenderer.DrawText(e.Graphics, btn.Text, btn.Font,
|
|
new Rectangle(0, 0, btn.Width, btn.Height), textColor,
|
|
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
|
|
}
|
|
|
|
// Set region to full button size (not the draw rect)
|
|
using (GraphicsPath regionPath = GetRoundedRectPath(new Rectangle(0, 0, btn.Width, btn.Height), radius))
|
|
{
|
|
btn.Region = new Region(regionPath);
|
|
}
|
|
}
|
|
|
|
private GraphicsPath GetRoundedRectPath(Rectangle rect, int radius)
|
|
{
|
|
GraphicsPath path = new GraphicsPath();
|
|
|
|
if (radius <= 0)
|
|
{
|
|
path.AddRectangle(rect);
|
|
return path;
|
|
}
|
|
|
|
int diameter = radius * 2;
|
|
|
|
// Ensure diameter doesn't exceed rect dimensions
|
|
diameter = Math.Min(diameter, Math.Min(rect.Width, rect.Height));
|
|
radius = diameter / 2;
|
|
|
|
Rectangle arcRect = new Rectangle(rect.Location, new Size(diameter, diameter));
|
|
|
|
// Top left arc
|
|
path.AddArc(arcRect, 180, 90);
|
|
|
|
// Top right arc
|
|
arcRect.X = rect.Right - diameter;
|
|
path.AddArc(arcRect, 270, 90);
|
|
|
|
// Bottom right arc
|
|
arcRect.Y = rect.Bottom - diameter;
|
|
path.AddArc(arcRect, 0, 90);
|
|
|
|
// Bottom left arc
|
|
arcRect.X = rect.Left;
|
|
path.AddArc(arcRect, 90, 90);
|
|
|
|
path.CloseFigure();
|
|
return path;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private constants
|
|
|
|
private static readonly string STANDARD_MESSAGEBOX_SEPARATOR_LINES = "---------------------------\n";
|
|
private static readonly string STANDARD_MESSAGEBOX_SEPARATOR_SPACES = " ";
|
|
|
|
private enum ButtonID { OK = 0, CANCEL, YES, NO, ABORT, RETRY, IGNORE };
|
|
|
|
private enum TwoLetterISOLanguageID { en, de, es, it };
|
|
private static readonly string[] BUTTON_TEXTS_ENGLISH_EN = { "OK", "Cancel", "&Yes", "&No", "&Abort", "&Retry", "&Ignore" };
|
|
private static readonly string[] BUTTON_TEXTS_GERMAN_DE = { "OK", "Abbrechen", "&Ja", "&Nein", "&Abbrechen", "&Wiederholen", "&Ignorieren" };
|
|
private static readonly string[] BUTTON_TEXTS_SPANISH_ES = { "Aceptar", "Cancelar", "&Sí", "&No", "&Abortar", "&Reintentar", "&Ignorar" };
|
|
private static readonly string[] BUTTON_TEXTS_ITALIAN_IT = { "OK", "Annulla", "&Sì", "&No", "&Interrompi", "&Riprova", "&Ignora" };
|
|
|
|
#endregion
|
|
|
|
#region Private members
|
|
|
|
private MessageBoxDefaultButton defaultButton;
|
|
private int visibleButtonsCount;
|
|
private readonly TwoLetterISOLanguageID languageID = TwoLetterISOLanguageID.en;
|
|
|
|
#endregion
|
|
|
|
#region Private constructor
|
|
|
|
private FlexibleMessageBoxForm()
|
|
{
|
|
InitializeComponent();
|
|
_ = Enum.TryParse<TwoLetterISOLanguageID>(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, out languageID);
|
|
KeyPreview = true;
|
|
KeyUp += FlexibleMessageBoxForm_KeyUp;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private helper functions
|
|
|
|
private static string[] GetStringRows(string message)
|
|
{
|
|
if (string.IsNullOrEmpty(message))
|
|
{
|
|
return null;
|
|
}
|
|
return message.Split(new char[] { '\n' }, StringSplitOptions.None);
|
|
}
|
|
|
|
private string GetButtonText(ButtonID buttonID)
|
|
{
|
|
int buttonTextArrayIndex = Convert.ToInt32(buttonID);
|
|
switch (languageID)
|
|
{
|
|
case TwoLetterISOLanguageID.de: return BUTTON_TEXTS_GERMAN_DE[buttonTextArrayIndex];
|
|
case TwoLetterISOLanguageID.es: return BUTTON_TEXTS_SPANISH_ES[buttonTextArrayIndex];
|
|
case TwoLetterISOLanguageID.it: return BUTTON_TEXTS_ITALIAN_IT[buttonTextArrayIndex];
|
|
default: return BUTTON_TEXTS_ENGLISH_EN[buttonTextArrayIndex];
|
|
}
|
|
}
|
|
|
|
private static double GetCorrectedWorkingAreaFactor(double workingAreaFactor)
|
|
{
|
|
const double MIN_FACTOR = 0.2;
|
|
const double MAX_FACTOR = 1.0;
|
|
return workingAreaFactor < MIN_FACTOR ? MIN_FACTOR : workingAreaFactor > MAX_FACTOR ? MAX_FACTOR : workingAreaFactor;
|
|
}
|
|
|
|
private static void SetDialogStartPosition(FlexibleMessageBoxForm flexibleMessageBoxForm, IWin32Window owner)
|
|
{
|
|
flexibleMessageBoxForm.StartPosition = FormStartPosition.Manual;
|
|
|
|
// Try to get owner form, fallback to active form if owner is null
|
|
Form ownerForm = null;
|
|
if (owner != null)
|
|
{
|
|
ownerForm = owner as Form;
|
|
if (ownerForm == null)
|
|
{
|
|
Control ownerControl = Control.FromHandle(owner.Handle);
|
|
ownerForm = ownerControl?.FindForm();
|
|
}
|
|
}
|
|
|
|
// Fallback to active form if no owner specified
|
|
if (ownerForm == null)
|
|
{
|
|
ownerForm = Form.ActiveForm;
|
|
}
|
|
|
|
if (ownerForm != null && ownerForm.Visible)
|
|
{
|
|
// Center relative to owner window
|
|
int x = ownerForm.Left + (ownerForm.Width - flexibleMessageBoxForm.Width) / 2;
|
|
int y = ownerForm.Top + (ownerForm.Height - flexibleMessageBoxForm.Height) / 2;
|
|
|
|
// Ensure the dialog stays within screen bounds
|
|
Screen screen = Screen.FromControl(ownerForm);
|
|
x = Math.Max(screen.WorkingArea.Left, Math.Min(x, screen.WorkingArea.Right - flexibleMessageBoxForm.Width));
|
|
y = Math.Max(screen.WorkingArea.Top, Math.Min(y, screen.WorkingArea.Bottom - flexibleMessageBoxForm.Height));
|
|
|
|
flexibleMessageBoxForm.Left = x;
|
|
flexibleMessageBoxForm.Top = y;
|
|
}
|
|
else
|
|
{
|
|
// No owner found: center on current screen
|
|
CenterOnScreen(flexibleMessageBoxForm);
|
|
}
|
|
}
|
|
|
|
private static void CenterOnScreen(FlexibleMessageBoxForm form)
|
|
{
|
|
Screen screen = Screen.FromPoint(Cursor.Position);
|
|
form.Left = screen.WorkingArea.Left + (screen.WorkingArea.Width - form.Width) / 2;
|
|
form.Top = screen.WorkingArea.Top + (screen.WorkingArea.Height - form.Height) / 2;
|
|
}
|
|
|
|
private static void SetDialogSizes(FlexibleMessageBoxForm flexibleMessageBoxForm, string text, string caption)
|
|
{
|
|
flexibleMessageBoxForm.MaximumSize = new Size(Convert.ToInt32(SystemInformation.WorkingArea.Width * FlexibleMessageBoxForm.GetCorrectedWorkingAreaFactor(MAX_WIDTH_FACTOR)),
|
|
Convert.ToInt32(SystemInformation.WorkingArea.Height * FlexibleMessageBoxForm.GetCorrectedWorkingAreaFactor(MAX_HEIGHT_FACTOR)));
|
|
|
|
string[] stringRows = GetStringRows(text);
|
|
if (stringRows == null) return;
|
|
|
|
int textHeight = TextRenderer.MeasureText(text, FONT).Height;
|
|
const int SCROLLBAR_WIDTH_OFFSET = 15;
|
|
int longestTextRowWidth = stringRows.Max(textForRow => TextRenderer.MeasureText(textForRow, FONT).Width);
|
|
int captionWidth = TextRenderer.MeasureText(caption, SystemFonts.CaptionFont).Width;
|
|
int textWidth = Math.Max(longestTextRowWidth + SCROLLBAR_WIDTH_OFFSET, captionWidth);
|
|
|
|
int marginWidth = flexibleMessageBoxForm.Width - flexibleMessageBoxForm.richTextBoxMessage.Width;
|
|
int marginHeight = flexibleMessageBoxForm.Height - flexibleMessageBoxForm.richTextBoxMessage.Height;
|
|
|
|
flexibleMessageBoxForm.Size = new Size(textWidth + marginWidth, textHeight + marginHeight);
|
|
}
|
|
|
|
private static void SetDialogIcon(FlexibleMessageBoxForm flexibleMessageBoxForm, MessageBoxIcon icon)
|
|
{
|
|
switch (icon)
|
|
{
|
|
case MessageBoxIcon.Information:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Information.ToBitmap();
|
|
break;
|
|
case MessageBoxIcon.Warning:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Warning.ToBitmap();
|
|
break;
|
|
case MessageBoxIcon.Error:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Error.ToBitmap();
|
|
break;
|
|
case MessageBoxIcon.Question:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Question.ToBitmap();
|
|
break;
|
|
default:
|
|
flexibleMessageBoxForm.pictureBoxForIcon.Visible = false;
|
|
flexibleMessageBoxForm.richTextBoxMessage.Left -= flexibleMessageBoxForm.pictureBoxForIcon.Width;
|
|
flexibleMessageBoxForm.richTextBoxMessage.Width += flexibleMessageBoxForm.pictureBoxForIcon.Width;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static void SetDialogButtons(FlexibleMessageBoxForm flexibleMessageBoxForm, MessageBoxButtons buttons, MessageBoxDefaultButton defaultButton)
|
|
{
|
|
switch (buttons)
|
|
{
|
|
case MessageBoxButtons.AbortRetryIgnore:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 3;
|
|
flexibleMessageBoxForm.button1.Visible = true;
|
|
flexibleMessageBoxForm.button1.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.ABORT);
|
|
flexibleMessageBoxForm.button1.DialogResult = DialogResult.Abort;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.RETRY);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.Retry;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.IGNORE);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.Ignore;
|
|
flexibleMessageBoxForm.ControlBox = false;
|
|
break;
|
|
|
|
case MessageBoxButtons.OKCancel:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 2;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.OK);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.OK;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.CANCEL);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.Cancel;
|
|
flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3;
|
|
break;
|
|
|
|
case MessageBoxButtons.RetryCancel:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 2;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.RETRY);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.Retry;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.CANCEL);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.Cancel;
|
|
flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3;
|
|
break;
|
|
|
|
case MessageBoxButtons.YesNo:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 2;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.YES);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.Yes;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.NO);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.No;
|
|
flexibleMessageBoxForm.ControlBox = false;
|
|
break;
|
|
|
|
case MessageBoxButtons.YesNoCancel:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 3;
|
|
flexibleMessageBoxForm.button1.Visible = true;
|
|
flexibleMessageBoxForm.button1.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.YES);
|
|
flexibleMessageBoxForm.button1.DialogResult = DialogResult.Yes;
|
|
flexibleMessageBoxForm.button2.Visible = true;
|
|
flexibleMessageBoxForm.button2.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.NO);
|
|
flexibleMessageBoxForm.button2.DialogResult = DialogResult.No;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.CANCEL);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.Cancel;
|
|
flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3;
|
|
break;
|
|
|
|
case MessageBoxButtons.OK:
|
|
default:
|
|
flexibleMessageBoxForm.visibleButtonsCount = 1;
|
|
flexibleMessageBoxForm.button3.Visible = true;
|
|
flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.OK);
|
|
flexibleMessageBoxForm.button3.DialogResult = DialogResult.OK;
|
|
flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3;
|
|
break;
|
|
}
|
|
flexibleMessageBoxForm.defaultButton = defaultButton;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private event handlers
|
|
|
|
private void FlexibleMessageBoxForm_Shown(object sender, EventArgs e)
|
|
{
|
|
int buttonIndexToFocus;
|
|
switch (defaultButton)
|
|
{
|
|
case MessageBoxDefaultButton.Button1:
|
|
default:
|
|
buttonIndexToFocus = 1;
|
|
break;
|
|
case MessageBoxDefaultButton.Button2:
|
|
buttonIndexToFocus = 2;
|
|
break;
|
|
case MessageBoxDefaultButton.Button3:
|
|
buttonIndexToFocus = 3;
|
|
break;
|
|
}
|
|
|
|
if (buttonIndexToFocus > visibleButtonsCount)
|
|
buttonIndexToFocus = visibleButtonsCount;
|
|
|
|
Button buttonToFocus = buttonIndexToFocus == 3 ? button3 : buttonIndexToFocus == 2 ? button2 : button1;
|
|
_ = buttonToFocus.Focus();
|
|
}
|
|
|
|
private void richTextBoxMessage_LinkClicked(object sender, LinkClickedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
_ = Process.Start(e.LinkText);
|
|
}
|
|
finally
|
|
{
|
|
Cursor.Current = Cursors.Default;
|
|
}
|
|
}
|
|
|
|
private void FlexibleMessageBoxForm_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.Control && (e.KeyCode == Keys.C || e.KeyCode == Keys.Insert))
|
|
{
|
|
string buttonsTextLine = (button1.Visible ? button1.Text + STANDARD_MESSAGEBOX_SEPARATOR_SPACES : string.Empty)
|
|
+ (button2.Visible ? button2.Text + STANDARD_MESSAGEBOX_SEPARATOR_SPACES : string.Empty)
|
|
+ (button3.Visible ? button3.Text + STANDARD_MESSAGEBOX_SEPARATOR_SPACES : string.Empty);
|
|
|
|
string textForClipboard = STANDARD_MESSAGEBOX_SEPARATOR_LINES
|
|
+ Text + Environment.NewLine
|
|
+ STANDARD_MESSAGEBOX_SEPARATOR_LINES
|
|
+ richTextBoxMessage.Text + Environment.NewLine
|
|
+ STANDARD_MESSAGEBOX_SEPARATOR_LINES
|
|
+ buttonsTextLine.Replace("&", string.Empty) + Environment.NewLine
|
|
+ STANDARD_MESSAGEBOX_SEPARATOR_LINES;
|
|
|
|
Clipboard.SetText(textForClipboard);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public string CaptionText { get; set; }
|
|
public string MessageText { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region Public show function
|
|
|
|
public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton)
|
|
{
|
|
FlexibleMessageBoxForm flexibleMessageBoxForm = new FlexibleMessageBoxForm
|
|
{
|
|
ShowInTaskbar = false,
|
|
CaptionText = caption,
|
|
MessageText = text
|
|
};
|
|
flexibleMessageBoxForm.FlexibleMessageBoxFormBindingSource.DataSource = flexibleMessageBoxForm;
|
|
flexibleMessageBoxForm.titleLabel.Text = caption;
|
|
|
|
SetDialogButtons(flexibleMessageBoxForm, buttons, defaultButton);
|
|
SetDialogIcon(flexibleMessageBoxForm, icon);
|
|
|
|
flexibleMessageBoxForm.Font = FONT;
|
|
flexibleMessageBoxForm.richTextBoxMessage.Font = FONT;
|
|
|
|
SetDialogSizes(flexibleMessageBoxForm, text, caption);
|
|
SetDialogStartPosition(flexibleMessageBoxForm, owner);
|
|
|
|
return flexibleMessageBoxForm.ShowDialog(owner);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |