From bbe4050b407f1f3b39461675e26ab5723be55a51 Mon Sep 17 00:00:00 2001 From: jp64k <122999544+jp64k@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:57:09 +0100 Subject: [PATCH] Completed comprehensive rookie redesign with extensive UI/UX modernization, new theme and architectural improvements 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. --- ADB.cs | 4 +- AndroidSideloader.csproj | 7 +- App.config | 6 +- FlexibleMessageBox.cs | 769 ++++---- GalleryView.cs | 141 +- MainForm.Designer.cs | 1278 +++++++------ MainForm.cs | 1896 ++++++++++++++----- MainForm.resx | 52 +- Properties/Settings.Designer.cs | 6 +- Properties/Settings.settings | 6 +- QuestForm.Designer.cs | 774 ++++---- QuestForm.cs | 115 +- RoundButton.cs | 53 +- SettingsForm.Designer.cs | 1120 ++++++++---- SettingsForm.cs | 217 ++- SettingsForm.resx | 3046 ------------------------------- ToggleSwitch.cs | 264 +++ UpdateForm.Designer.cs | 129 +- UpdateForm.cs | 197 +- Updater.cs | 4 +- Utilities/SettingsManager.cs | 7 +- changelog.txt | 37 +- version | 2 +- 23 files changed, 4618 insertions(+), 5512 deletions(-) create mode 100644 ToggleSwitch.cs diff --git a/ADB.cs b/ADB.cs index 3c080b3..f00c6c3 100644 --- a/ADB.cs +++ b/ADB.cs @@ -305,7 +305,7 @@ namespace AndroidSideloader _ = ADB.RunAdbCommandToString($"pull \"/sdcard/Android/data/{MainForm.CurrPCKG}\" \"{Environment.CurrentDirectory}\""); Program.form.changeTitle("Uninstalling game..."); _ = Sideloader.UninstallGame(MainForm.CurrPCKG); - Program.form.changeTitle("Reinstalling Game"); + Program.form.changeTitle("Reinstalling game..."); ret += ADB.RunAdbCommandToString($"install -g \"{path}\""); _ = ADB.RunAdbCommandToString($"push \"{Environment.CurrentDirectory}\\{MainForm.CurrPCKG}\" /sdcard/Android/data/"); string directoryToDelete = Path.Combine(Environment.CurrentDirectory, MainForm.CurrPCKG); @@ -317,7 +317,7 @@ namespace AndroidSideloader } } - Program.form.changeTitle(" \n\n"); + Program.form.changeTitle(""); return ret; } } diff --git a/AndroidSideloader.csproj b/AndroidSideloader.csproj index 406308c..f18d336 100644 --- a/AndroidSideloader.csproj +++ b/AndroidSideloader.csproj @@ -155,7 +155,7 @@ .\SergeUtils.dll - C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.dll + ..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.dll @@ -172,7 +172,7 @@ - C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Drawing.dll + ..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.2\System.Drawing.dll @@ -211,6 +211,9 @@ NewApps.cs + + Component + Form diff --git a/App.config b/App.config index 519115b..32efb8b 100644 --- a/App.config +++ b/App.config @@ -33,7 +33,7 @@ True - Microsoft Sans Serif, 9pt + Microsoft Sans Serif, 10pt @@ -135,13 +135,13 @@ False - DarkGray + White 25, 25, 25 - 25, 25, 25 + 42, 45, 58 25, 25, 25 diff --git a/FlexibleMessageBox.cs b/FlexibleMessageBox.cs index 587e65a..299d746 100644 --- a/FlexibleMessageBox.cs +++ b/FlexibleMessageBox.cs @@ -1,246 +1,72 @@ 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 { - /* FlexibleMessageBox – A flexible replacement for the .NET MessageBox - * - * Author: Jörg Reichert (public@jreichert.de) - * Contributors: Thanks to: David Hall, Roink - * Version: 1.3 - * Published at: http://www.codeproject.com/Articles/601900/FlexibleMessageBox - * - ************************************************************************************************************ - * Features: - * - It can be simply used instead of MessageBox since all important static "Show"-Functions are supported - * - It is small, only one source file, which could be added easily to each solution - * - It can be resized and the content is correctly word-wrapped - * - It tries to auto-size the width to show the longest text row - * - It never exceeds the current desktop working area - * - It displays a vertical scrollbar when needed - * - It does support hyperlinks in text - * - * Because the interface is identical to MessageBox, you can add this single source file to your project - * and use the FlexibleMessageBox almost everywhere you use a standard MessageBox. - * The goal was NOT to produce as many features as possible but to provide a simple replacement to fit my - * own needs. Feel free to add additional features on your own, but please left my credits in this class. - * - ************************************************************************************************************ - * Usage examples: - * - * FlexibleMessageBox.Show("Just a text"); - * - * FlexibleMessageBox.Show("A text", - * "A caption"); - * - * FlexibleMessageBox.Show("Some text with a link: www.google.com", - * "Some caption", - * MessageBoxButton - * - * - * - * - * - * s.AbortRetryIgnore, - * MessageBoxIcon.Information, - * MessageBoxDefaultButton.Button2); - * - * var dialogResult = FlexibleMessageBox.Show("Do you know the answer to life the universe and everything?", - * "One short question", - * MessageBoxButtons.YesNo); - * - ************************************************************************************************************ - * THE SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS", WITHOUT WARRANTY - * OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL THE AUTHOR BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF THIS - * SOFTWARE. - * - ************************************************************************************************************ - * History: - * Version 1.3 - 19.Dezember 2014 - * - Added refactoring function GetButtonText() - * - Used CurrentUICulture instead of InstalledUICulture - * - Added more button localizations. Supported languages are now: ENGLISH, GERMAN, SPANISH, ITALIAN - * - Added standard MessageBox handling for "copy to clipboard" with + and + - * - Tab handling is now corrected (only tabbing over the visible buttons) - * - Added standard MessageBox handling for ALT-Keyboard shortcuts - * - SetDialogSizes: Refactored completely: Corrected sizing and added caption driven sizing - * - * Version 1.2 - 10.August 2013 - * - Do not ShowInTaskbar anymore (original MessageBox is also hidden in taskbar) - * - Added handling for Escape-Button - * - Adapted top right close button (red X) to behave like MessageBox (but hidden instead of deactivated) - * - * Version 1.1 - 14.June 2013 - * - Some Refactoring - * - Added internal form class - * - Added missing code comments, etc. - * - * Version 1.0 - 15.April 2013 - * - Initial Version - */ public class FlexibleMessageBox { #region Public statics - /// - /// Defines the maximum width for all FlexibleMessageBox instances in percent of the working area. - /// - /// Allowed values are 0.2 - 1.0 where: - /// 0.2 means: The FlexibleMessageBox can be at most half as wide as the working area. - /// 1.0 means: The FlexibleMessageBox can be as wide as the working area. - /// - /// Default is: 70% of the working area width. - /// public static double MAX_WIDTH_FACTOR = 0.7; - - /// - /// Defines the maximum height for all FlexibleMessageBox instances in percent of the working area. - /// - /// Allowed values are 0.2 - 1.0 where: - /// 0.2 means: The FlexibleMessageBox can be at most half as high as the working area. - /// 1.0 means: The FlexibleMessageBox can be as high as the working area. - /// - /// Default is: 90% of the working area height. - /// public static double MAX_HEIGHT_FACTOR = 0.9; - - /// - /// Defines the font for all FlexibleMessageBox instances. - /// - /// Default is: SystemFonts.MessageBoxFont - /// public static Font FONT = SystemFonts.MessageBoxFont; #endregion #region Public show functions - /// - /// Shows the specified message box. - /// - /// The text. - /// The dialog result. public static DialogResult Show(string text) { return FlexibleMessageBoxForm.Show(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); } - /// - /// Shows the specified message box. - /// - /// The owner. - /// The text. - /// The dialog result. public static DialogResult Show(IWin32Window owner, string text) { return FlexibleMessageBoxForm.Show(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); } - /// - /// Shows the specified message box. - /// - /// The text. - /// The caption. - /// The dialog result. public static DialogResult Show(string text, string caption) { return FlexibleMessageBoxForm.Show(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); } - /// - /// Shows the specified message box. - /// - /// The owner. - /// The text. - /// The caption. - /// The dialog result. public static DialogResult Show(IWin32Window owner, string text, string caption) { return FlexibleMessageBoxForm.Show(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); } - /// - /// Shows the specified message box. - /// - /// The text. - /// The caption. - /// The buttons. - /// The dialog result. public static DialogResult Show(string text, string caption, MessageBoxButtons buttons) { return FlexibleMessageBoxForm.Show(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); } - /// - /// Shows the specified message box. - /// - /// The owner. - /// The text. - /// The caption. - /// The buttons. - /// The dialog result. public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons) { return FlexibleMessageBoxForm.Show(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); } - /// - /// Shows the specified message box. - /// - /// The text. - /// The caption. - /// The buttons. - /// The icon. - /// public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) { return FlexibleMessageBoxForm.Show(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1); } - /// - /// Shows the specified message box. - /// - /// The owner. - /// The text. - /// The caption. - /// The buttons. - /// The icon. - /// The dialog result. public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) { return FlexibleMessageBoxForm.Show(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1); } - /// - /// Shows the specified message box. - /// - /// The text. - /// The caption. - /// The buttons. - /// The icon. - /// The default button. - /// The dialog result. public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) { return FlexibleMessageBoxForm.Show(null, text, caption, buttons, icon, defaultButton); } - /// - /// Shows the specified message box. - /// - /// The owner. - /// The text. - /// The caption. - /// The buttons. - /// The icon. - /// The default button. - /// The dialog result. 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); @@ -250,23 +76,37 @@ namespace JR.Utils.GUI.Forms #region Internal form class - /// - /// The form to show the customized message box. - /// It is defined as an internal class to keep the public interface of the FlexibleMessageBox clean. - /// 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 - /// - /// Erforderliche Designervariable. - /// private System.ComponentModel.IContainer components = null; - /// - /// Verwendete Ressourcen bereinigen. - /// - /// True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False. protected override void Dispose(bool disposing) { if (disposing && (components != null)) @@ -276,10 +116,21 @@ namespace JR.Utils.GUI.Forms base.Dispose(disposing); } - /// - /// Erforderliche Methode für die Designerunterstützung. - /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. - /// + 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(); @@ -290,23 +141,75 @@ namespace JR.Utils.GUI.Forms 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 = ""; + 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.AutoSize = true; button1.DialogResult = System.Windows.Forms.DialogResult.OK; - button1.Location = new System.Drawing.Point(11, 67); - button1.MinimumSize = new System.Drawing.Size(0, 24); + button1.Location = new System.Drawing.Point(16, 80); button1.Name = "button1"; - button1.Size = new System.Drawing.Size(75, 24); + button1.Size = new System.Drawing.Size(75, 28); button1.TabIndex = 2; button1.Text = "OK"; - button1.UseVisualStyleBackColor = true; + 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 @@ -314,16 +217,17 @@ namespace JR.Utils.GUI.Forms 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.White; + 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(50, 26); + 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(200, 20); + richTextBoxMessage.Size = new System.Drawing.Size(190, 20); richTextBoxMessage.TabIndex = 0; richTextBoxMessage.TabStop = false; richTextBoxMessage.Text = ""; @@ -334,18 +238,18 @@ namespace JR.Utils.GUI.Forms 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.White; + panel1.BackColor = System.Drawing.Color.FromArgb(20, 24, 29); panel1.Controls.Add(pictureBoxForIcon); panel1.Controls.Add(richTextBoxMessage); - panel1.Location = new System.Drawing.Point(-3, -4); + panel1.Location = new System.Drawing.Point(6, 34); panel1.Name = "panel1"; - panel1.Size = new System.Drawing.Size(268, 59); + 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, 19); + pictureBoxForIcon.Location = new System.Drawing.Point(15, 15); pictureBoxForIcon.Name = "pictureBoxForIcon"; pictureBoxForIcon.Size = new System.Drawing.Size(32, 32); pictureBoxForIcon.TabIndex = 8; @@ -355,80 +259,273 @@ namespace JR.Utils.GUI.Forms // 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(92, 67); - button2.MinimumSize = new System.Drawing.Size(0, 24); + button2.Location = new System.Drawing.Point(97, 80); button2.Name = "button2"; - button2.Size = new System.Drawing.Size(75, 24); + button2.Size = new System.Drawing.Size(75, 28); button2.TabIndex = 3; button2.Text = "OK"; - button2.UseVisualStyleBackColor = true; + 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.AutoSize = true; button3.DialogResult = System.Windows.Forms.DialogResult.OK; - button3.Location = new System.Drawing.Point(173, 67); - button3.MinimumSize = new System.Drawing.Size(0, 24); + button3.Location = new System.Drawing.Point(178, 80); button3.Name = "button3"; - button3.Size = new System.Drawing.Size(75, 24); + button3.Size = new System.Drawing.Size(75, 28); button3.TabIndex = 0; button3.Text = "OK"; - button3.UseVisualStyleBackColor = true; + 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; - ClientSize = new System.Drawing.Size(260, 102); + 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); - DataBindings.Add(new System.Windows.Forms.Binding("Text", FlexibleMessageBoxFormBindingSource, "CaptionText", true)); MaximizeBox = false; MinimizeBox = false; - MinimumSize = new System.Drawing.Size(276, 140); + MinimumSize = new System.Drawing.Size(276, 120); Name = "FlexibleMessageBoxForm"; ShowIcon = false; - SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show; + SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; Text = ""; Shown += new System.EventHandler(FlexibleMessageBoxForm_Shown); ((System.ComponentModel.ISupportInitialize)FlexibleMessageBoxFormBindingSource).EndInit(); panel1.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)pictureBoxForIcon).EndInit(); + titlePanel.ResumeLayout(false); ResumeLayout(false); - PerformLayout(); + + // 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 System.Windows.Forms.Button button1; - private System.Windows.Forms.BindingSource FlexibleMessageBoxFormBindingSource; - private System.Windows.Forms.RichTextBox richTextBoxMessage; - private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.PictureBox pictureBoxForIcon; - private System.Windows.Forms.Button button2; - private System.Windows.Forms.Button button3; + 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 _buttonHoverState = new Dictionary(); + 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 - //These separators are used for the "copy to clipboard" standard operation, triggered by Ctrl + C (behavior and clipboard format is like in a standard MessageBox) private static readonly string STANDARD_MESSAGEBOX_SEPARATOR_LINES = "---------------------------\n"; private static readonly string STANDARD_MESSAGEBOX_SEPARATOR_SPACES = " "; - //These are the possible buttons (in a standard MessageBox) private enum ButtonID { OK = 0, CANCEL, YES, NO, ABORT, RETRY, IGNORE }; - //These are the buttons texts for different languages. - //If you want to add a new language, add it here and in the GetButtonText-Function private enum TwoLetterISOLanguageID { en, de, es, it }; - private static readonly string[] BUTTON_TEXTS_ENGLISH_EN = { "OK", "Cancel", "&Yes", "&No", "&Abort", "&Retry", "&Ignore" }; //Note: This is also the fallback language + 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" }; @@ -445,16 +542,10 @@ namespace JR.Utils.GUI.Forms #region Private constructor - /// - /// Initializes a new instance of the class. - /// private FlexibleMessageBoxForm() { InitializeComponent(); - - //Try to evaluate the language. If this fails, the fallback language English will be used _ = Enum.TryParse(CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, out languageID); - KeyPreview = true; KeyUp += FlexibleMessageBoxForm_KeyUp; } @@ -463,120 +554,104 @@ namespace JR.Utils.GUI.Forms #region Private helper functions - /// - /// Gets the string rows. - /// - /// The message. - /// The string rows as 1-dimensional array private static string[] GetStringRows(string message) { if (string.IsNullOrEmpty(message)) { return null; } - - string[] messageRows = message.Split(new char[] { '\n' }, StringSplitOptions.None); - return messageRows; + return message.Split(new char[] { '\n' }, StringSplitOptions.None); } - /// - /// Gets the button text for the CurrentUICulture language. - /// Note: The fallback language is English - /// - /// The ID of the button. - /// The button text 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]; } } - /// - /// Ensure the given working area factor in the range of 0.2 - 1.0 where: - /// - /// 0.2 means: 20 percent of the working area height or width. - /// 1.0 means: 100 percent of the working area height or width. - /// - /// The given working area factor. - /// The corrected given working area factor. 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; } - /// - /// Set the dialogs start position when given. - /// Otherwise center the dialog on the current screen. - /// - /// The FlexibleMessageBox dialog. - /// The owner. private static void SetDialogStartPosition(FlexibleMessageBoxForm flexibleMessageBoxForm, IWin32Window owner) { - //If no owner given: Center on current screen - if (owner == null) + flexibleMessageBoxForm.StartPosition = FormStartPosition.Manual; + + // Try to get owner form, fallback to active form if owner is null + Form ownerForm = null; + if (owner != null) { - Screen screen = Screen.FromPoint(Cursor.Position); - flexibleMessageBoxForm.StartPosition = FormStartPosition.Manual; - flexibleMessageBoxForm.Left = screen.Bounds.Left + (screen.Bounds.Width / 2) - (flexibleMessageBoxForm.Width / 2); - flexibleMessageBoxForm.Top = screen.Bounds.Top + (screen.Bounds.Height / 2) - (flexibleMessageBoxForm.Height / 2); + 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); } } - /// - /// Calculate the dialogs start size (Try to auto-size width to show longest text row). - /// Also set the maximum dialog size. - /// - /// The FlexibleMessageBox dialog. - /// The text (the longest text row is used to calculate the dialog width). - /// The caption (this can also affect the dialog width). + 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) { - //First set the bounds for the maximum dialog size flexibleMessageBoxForm.MaximumSize = new Size(Convert.ToInt32(SystemInformation.WorkingArea.Width * FlexibleMessageBoxForm.GetCorrectedWorkingAreaFactor(MAX_WIDTH_FACTOR)), Convert.ToInt32(SystemInformation.WorkingArea.Height * FlexibleMessageBoxForm.GetCorrectedWorkingAreaFactor(MAX_HEIGHT_FACTOR))); - //Get rows. Exit if there are no rows to render... string[] stringRows = GetStringRows(text); - if (stringRows == null) - { - return; - } + if (stringRows == null) return; - //Calculate whole text height int textHeight = TextRenderer.MeasureText(text, FONT).Height; - - //Calculate width for longest text line 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); - //Calculate margins int marginWidth = flexibleMessageBoxForm.Width - flexibleMessageBoxForm.richTextBoxMessage.Width; int marginHeight = flexibleMessageBoxForm.Height - flexibleMessageBoxForm.richTextBoxMessage.Height; - //Set calculated dialog size (if the calculated values exceed the maximums, they were cut by windows forms automatically) - flexibleMessageBoxForm.Size = new Size(textWidth + marginWidth, - textHeight + marginHeight); + flexibleMessageBoxForm.Size = new Size(textWidth + marginWidth, textHeight + marginHeight); } - /// - /// Set the dialogs icon. - /// When no icon is used: Correct placement and width of rich text box. - /// - /// The FlexibleMessageBox dialog. - /// The MessageBoxIcon. private static void SetDialogIcon(FlexibleMessageBoxForm flexibleMessageBoxForm, MessageBoxIcon icon) { switch (icon) @@ -594,7 +669,6 @@ namespace JR.Utils.GUI.Forms flexibleMessageBoxForm.pictureBoxForIcon.Image = SystemIcons.Question.ToBitmap(); break; default: - //When no icon is used: Correct placement and width of rich text box. flexibleMessageBoxForm.pictureBoxForIcon.Visible = false; flexibleMessageBoxForm.richTextBoxMessage.Left -= flexibleMessageBoxForm.pictureBoxForIcon.Width; flexibleMessageBoxForm.richTextBoxMessage.Width += flexibleMessageBoxForm.pictureBoxForIcon.Width; @@ -602,93 +676,68 @@ namespace JR.Utils.GUI.Forms } } - /// - /// Set dialog buttons visibilities and texts. - /// Also set a default button. - /// - /// The FlexibleMessageBox dialog. - /// The buttons. - /// The default button. private static void SetDialogButtons(FlexibleMessageBoxForm flexibleMessageBoxForm, MessageBoxButtons buttons, MessageBoxDefaultButton defaultButton) { - //Set the buttons visibilities and texts 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; @@ -698,12 +747,9 @@ namespace JR.Utils.GUI.Forms flexibleMessageBoxForm.button3.Visible = true; flexibleMessageBoxForm.button3.Text = flexibleMessageBoxForm.GetButtonText(ButtonID.OK); flexibleMessageBoxForm.button3.DialogResult = DialogResult.OK; - flexibleMessageBoxForm.CancelButton = flexibleMessageBoxForm.button3; break; } - - //Set default button (used in FlexibleMessageBoxForm_Shown) flexibleMessageBoxForm.defaultButton = defaultButton; } @@ -711,17 +757,9 @@ namespace JR.Utils.GUI.Forms #region Private event handlers - /// - /// Handles the Shown event of the FlexibleMessageBoxForm control. - /// - /// The source of the event. - /// The instance containing the event data. private void FlexibleMessageBoxForm_Shown(object sender, EventArgs e) { - Button buttonToFocus; - int buttonIndexToFocus; - //Set the default button... switch (defaultButton) { case MessageBoxDefaultButton.Button1: @@ -737,20 +775,12 @@ namespace JR.Utils.GUI.Forms } if (buttonIndexToFocus > visibleButtonsCount) - { buttonIndexToFocus = visibleButtonsCount; - } - - buttonToFocus = buttonIndexToFocus == 3 ? button3 : buttonIndexToFocus == 2 ? button2 : button1; + Button buttonToFocus = buttonIndexToFocus == 3 ? button3 : buttonIndexToFocus == 2 ? button2 : button1; _ = buttonToFocus.Focus(); } - /// - /// Handles the LinkClicked event of the richTextBoxMessage control. - /// - /// The source of the event. - /// The instance containing the event data. private void richTextBoxMessage_LinkClicked(object sender, LinkClickedEventArgs e) { try @@ -758,33 +788,20 @@ namespace JR.Utils.GUI.Forms Cursor.Current = Cursors.WaitCursor; _ = Process.Start(e.LinkText); } - catch (Exception) - { - //Let the caller of FlexibleMessageBoxForm decide what to do with this exception... - throw; - } finally { Cursor.Current = Cursors.Default; } - } - /// - /// Handles the KeyUp event of the richTextBoxMessage control. - /// - /// The source of the event. - /// The instance containing the event data. private void FlexibleMessageBoxForm_KeyUp(object sender, KeyEventArgs e) { - //Handle standard key strikes for clipboard copy: "Ctrl + C" and "Ctrl + Insert" 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); - //Build same clipboard text like the standard .Net MessageBox string textForClipboard = STANDARD_MESSAGEBOX_SEPARATOR_LINES + Text + Environment.NewLine + STANDARD_MESSAGEBOX_SEPARATOR_LINES @@ -793,75 +810,47 @@ namespace JR.Utils.GUI.Forms + buttonsTextLine.Replace("&", string.Empty) + Environment.NewLine + STANDARD_MESSAGEBOX_SEPARATOR_LINES; - //Set text in clipboard Clipboard.SetText(textForClipboard); } } #endregion - #region Properties (only used for binding) + #region Properties - /// - /// The text that is been used for the heading. - /// public string CaptionText { get; set; } - - /// - /// The text that is been used in the FlexibleMessageBoxForm. - /// public string MessageText { get; set; } #endregion #region Public show function - /// - /// Shows the specified message box. - /// - /// The owner. - /// The text. - /// The caption. - /// The buttons. - /// The icon. - /// The default button. - /// The dialog result. public static DialogResult Show(IWin32Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) { - //Create a new instance of the FlexibleMessageBox form FlexibleMessageBoxForm flexibleMessageBoxForm = new FlexibleMessageBoxForm { ShowInTaskbar = false, - - //Bind the caption and the message text CaptionText = caption, MessageText = text }; flexibleMessageBoxForm.FlexibleMessageBoxFormBindingSource.DataSource = flexibleMessageBoxForm; + flexibleMessageBoxForm.titleLabel.Text = caption; - //Set the buttons visibilities and texts. Also set a default button. SetDialogButtons(flexibleMessageBoxForm, buttons, defaultButton); - - //Set the dialogs icon. When no icon is used: Correct placement and width of rich text box. SetDialogIcon(flexibleMessageBoxForm, icon); - //Set the font for all controls flexibleMessageBoxForm.Font = FONT; flexibleMessageBoxForm.richTextBoxMessage.Font = FONT; - //Calculate the dialogs start size (Try to auto-size width to show longest text row). Also set the maximum dialog size. SetDialogSizes(flexibleMessageBoxForm, text, caption); - - //Set the dialogs start position when given. Otherwise center the dialog on the current screen. SetDialogStartPosition(flexibleMessageBoxForm, owner); - //Show the dialog return flexibleMessageBoxForm.ShowDialog(owner); } #endregion - } //class FlexibleMessageBoxForm + } #endregion } -} +} \ No newline at end of file diff --git a/GalleryView.cs b/GalleryView.cs index 58c838e..85853f2 100644 --- a/GalleryView.cs +++ b/GalleryView.cs @@ -1,4 +1,5 @@ using AndroidSideloader; +using AndroidSideloader.Utilities; using System; using System.Collections.Generic; using System.Drawing; @@ -53,6 +54,11 @@ public class FastGalleryPanel : Control private int _selectedIndex = -1; private bool _isHoveringDeleteButton = false; + // Context Menu & Favorites + private ContextMenuStrip _contextMenu; + private int _rightClickedIndex = -1; + private HashSet _favoritesCache; + // Rendering private Bitmap _backBuffer; @@ -68,8 +74,9 @@ public class FastGalleryPanel : Control // Theme colors private static readonly Color TileBorderHover = Color.FromArgb(93, 203, 173); private static readonly Color TileBorderSelected = Color.FromArgb(200, 200, 200); + private static readonly Color TileBorderFavorite = Color.FromArgb(255, 215, 0); + private static readonly Color BadgeFavoriteBg = Color.FromArgb(200, 255, 180, 0); private static readonly Color TextColor = Color.FromArgb(245, 255, 255, 255); - private static readonly Color BadgeUpdateBg = Color.FromArgb(180, 76, 175, 80); private static readonly Color BadgeInstalledBg = Color.FromArgb(180, 60, 145, 230); private static readonly Color DeleteButtonBg = Color.FromArgb(200, 180, 50, 50); private static readonly Color DeleteButtonHoverBg = Color.FromArgb(255, 220, 70, 70); @@ -80,6 +87,7 @@ public class FastGalleryPanel : Control public event EventHandler TileClicked; public event EventHandler TileDoubleClicked; public event EventHandler TileDeleteClicked; + public event EventHandler TileRightClicked; public event EventHandler SortChanged; private class TileAnimationState @@ -96,6 +104,8 @@ public class FastGalleryPanel : Control public float TargetTooltipOpacity = 0f; public float DeleteButtonOpacity = 0f; public float TargetDeleteButtonOpacity = 0f; + public float FavoriteOpacity = 0f; + public float TargetFavoriteOpacity = 0f; } public FastGalleryPanel(List items, int tileWidth, int tileHeight, int spacing, int initialWidth, int initialHeight) @@ -109,6 +119,11 @@ public class FastGalleryPanel : Control _cacheOrder = new Queue(); _tileStates = new Dictionary(); _sortButtons = new List