File: System\Windows\Forms\Controls\ToolStrips\ToolStripRenderer.cs
Web Access
Project: src\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Windows.Forms.Layout;
 
namespace System.Windows.Forms;
 
public abstract class ToolStripRenderer
{
    private static readonly object s_renderSplitButtonBackgroundEvent = new();
    private static readonly object s_renderItemBackgroundEvent = new();
    private static readonly object s_renderItemImageEvent = new();
    private static readonly object s_renderItemTextEvent = new();
    private static readonly object s_renderToolStripBackgroundEvent = new();
    private static readonly object s_renderGripEvent = new();
    private static readonly object s_renderButtonBackgroundEvent = new();
    private static readonly object s_renderLabelBackgroundEvent = new();
    private static readonly object s_renderMenuItemBackgroundEvent = new();
    private static readonly object s_renderDropDownButtonBackgroundEvent = new();
    private static readonly object s_renderOverflowButtonBackgroundEvent = new();
    private static readonly object s_renderImageMarginEvent = new();
    private static readonly object s_renderBorderEvent = new();
    private static readonly object s_renderArrowEvent = new();
    private static readonly object s_renderToolStripStatusLabelBackgroundEvent = new();
    private static readonly object s_renderSeparatorEvent = new();
    private static readonly object s_renderItemCheckEvent = new();
    private static readonly object s_renderToolStripPanelBackgroundEvent = new();
    private static readonly object s_renderToolStripContentPanelBackgroundEvent = new();
 
    private static readonly object s_renderStatusStripSizingGripEvent = new();
 
    private static ColorMatrix? s_disabledImageColorMatrix;
 
    private EventHandlerList? _events;
    private readonly bool _isSystemDefaultAlternative;
 
    private static bool s_isScalingInitialized;
    internal int _previousDeviceDpi = ScaleHelper.InitialSystemDpi;
 
    // arrows are rendered as isosceles triangles, whose heights are half the base in order to have 45 degree angles
    // Offset2X is half of the base
    // Offset2Y is height of the isosceles triangle
    private const int OFFSET_2PIXELS = 2;
    private const int OFFSET_4PIXELS = 4;
    protected static int Offset2X = OFFSET_2PIXELS;
    protected static int Offset2Y = OFFSET_2PIXELS;
    private static int s_offset4X = OFFSET_4PIXELS;
    private static int s_offset4Y = OFFSET_4PIXELS;
 
    protected ToolStripRenderer()
    {
    }
 
    internal ToolStripRenderer(bool isAutoGenerated) =>
        _isSystemDefaultAlternative = isAutoGenerated;
 
    // Used in building disabled images.
    private static ColorMatrix DisabledImageColorMatrix
    {
        get
        {
            if (s_disabledImageColorMatrix is not null)
            {
                return s_disabledImageColorMatrix;
            }
 
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
            bool isDarkMode = Application.IsDarkModeEnabled;
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
 
            if (isDarkMode)
            {
                // Dark mode color matrix
                float[][] greyscale =
                [
                    [0.2125f, 0.2125f, 0.2125f, 0, 0],
                    [0.2577f, 0.2577f, 0.2577f, 0, 0],
                    [0.0361f, 0.0361f, 0.0361f, 0, 0],
                    [0, 0, 0, 1, 0],
                    [-0.1f, -0.1f, -0.1f, 0, 1],
                ];
 
                float[][] transparency =
                [
                    [1, 0, 0, 0, 0],
                    [0, 1, 0, 0, 0],
                    [0, 0, 1, 0, 0],
                    [0, 0, 0, 0.8f, 0],
                    [0, 0, 0, 0, 0],
                 ];
 
                s_disabledImageColorMatrix = ControlPaint.MultiplyColorMatrix(transparency, greyscale);
            }
            else
            {
                // Light mode color matrix
                float[][] greyscale =
                [
                    [0.2125f, 0.2125f, 0.2125f, 0, 0],
                    [0.2577f, 0.2577f, 0.2577f, 0, 0],
                    [0.0361f, 0.0361f, 0.0361f, 0, 0],
                    [0, 0, 0, 1, 0],
                    [0.38f, 0.38f, 0.38f, 0, 1],
                ];
 
                float[][] transparency =
                [
                    [1, 0, 0, 0, 0],
                    [0, 1, 0, 0, 0],
                    [0, 0, 1, 0, 0],
                    [0, 0, 0, 0.7f, 0],
                    [0, 0, 0, 0, 0],
                ];
 
                s_disabledImageColorMatrix = ControlPaint.MultiplyColorMatrix(transparency, greyscale);
            }
 
            return s_disabledImageColorMatrix;
        }
    }
 
    /// <summary>
    ///  Gets the list of event handlers that are attached to this component.
    /// </summary>
    private EventHandlerList Events
    {
        get
        {
            _events ??= new EventHandlerList();
 
            return _events;
        }
    }
 
    /// <summary>
    ///  Defines, if there is a variation of the system default renderer, that is chosen by the system
    ///  to address the current environment context. Like DarkMode, HighContrast, LowRes etc.
    ///  (Used to be 'AutoGenerated', presuming to indicate, that the renderer was 'generated' (picked) by the system.)
    /// </summary>
    internal bool IsSystemDefaultAlternative
        => _isSystemDefaultAlternative;
 
    // if we're in a low contrast, high resolution situation, use this renderer under the covers instead.
    internal virtual ToolStripRenderer? RendererOverride => null;
 
    public event ToolStripArrowRenderEventHandler RenderArrow
    {
        add => AddHandler(s_renderArrowEvent, value);
        remove => RemoveHandler(s_renderArrowEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripRenderEventHandler RenderToolStripBackground
    {
        add => AddHandler(s_renderToolStripBackgroundEvent, value);
        remove => RemoveHandler(s_renderToolStripBackgroundEvent, value);
    }
 
    public event ToolStripPanelRenderEventHandler RenderToolStripPanelBackground
    {
        add => AddHandler(s_renderToolStripPanelBackgroundEvent, value);
        remove => RemoveHandler(s_renderToolStripPanelBackgroundEvent, value);
    }
 
    public event ToolStripContentPanelRenderEventHandler RenderToolStripContentPanelBackground
    {
        add => AddHandler(s_renderToolStripContentPanelBackgroundEvent, value);
        remove => RemoveHandler(s_renderToolStripContentPanelBackgroundEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripRenderEventHandler RenderToolStripBorder
    {
        add => AddHandler(s_renderBorderEvent, value);
        remove => RemoveHandler(s_renderBorderEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripItemRenderEventHandler RenderButtonBackground
    {
        add => AddHandler(s_renderButtonBackgroundEvent, value);
        remove => RemoveHandler(s_renderButtonBackgroundEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripItemRenderEventHandler RenderDropDownButtonBackground
    {
        add => AddHandler(s_renderDropDownButtonBackgroundEvent, value);
        remove => RemoveHandler(s_renderDropDownButtonBackgroundEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripItemRenderEventHandler RenderOverflowButtonBackground
    {
        add => AddHandler(s_renderOverflowButtonBackgroundEvent, value);
        remove => RemoveHandler(s_renderOverflowButtonBackgroundEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripGripRenderEventHandler RenderGrip
    {
        add => AddHandler(s_renderGripEvent, value);
        remove => RemoveHandler(s_renderGripEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripItemRenderEventHandler RenderItemBackground
    {
        add => AddHandler(s_renderItemBackgroundEvent, value);
        remove => RemoveHandler(s_renderItemBackgroundEvent, value);
    }
 
    /// <summary>
    ///  Draws the split button
    /// </summary>
    public event ToolStripItemImageRenderEventHandler RenderItemImage
    {
        add => AddHandler(s_renderItemImageEvent, value);
        remove => RemoveHandler(s_renderItemImageEvent, value);
    }
 
    /// <summary>
    ///  Draws the checkmark
    /// </summary>
    public event ToolStripItemImageRenderEventHandler RenderItemCheck
    {
        add => AddHandler(s_renderItemCheckEvent, value);
        remove => RemoveHandler(s_renderItemCheckEvent, value);
    }
 
    /// <summary>
    ///  Draws the split button
    /// </summary>
    public event ToolStripItemTextRenderEventHandler RenderItemText
    {
        add => AddHandler(s_renderItemTextEvent, value);
        remove => RemoveHandler(s_renderItemTextEvent, value);
    }
 
    public event ToolStripRenderEventHandler RenderImageMargin
    {
        add => AddHandler(s_renderImageMarginEvent, value);
        remove => RemoveHandler(s_renderImageMarginEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripItemRenderEventHandler RenderLabelBackground
    {
        add => AddHandler(s_renderLabelBackgroundEvent, value);
        remove => RemoveHandler(s_renderLabelBackgroundEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripItemRenderEventHandler RenderMenuItemBackground
    {
        add => AddHandler(s_renderMenuItemBackgroundEvent, value);
        remove => RemoveHandler(s_renderMenuItemBackgroundEvent, value);
    }
 
    /// <summary>
    ///  Draws the split button
    /// </summary>
    public event ToolStripItemRenderEventHandler RenderToolStripStatusLabelBackground
    {
        add => AddHandler(s_renderToolStripStatusLabelBackgroundEvent, value);
        remove => RemoveHandler(s_renderToolStripStatusLabelBackgroundEvent, value);
    }
 
    /// <summary>
    ///  Occurs when the display style has changed
    /// </summary>
    public event ToolStripRenderEventHandler RenderStatusStripSizingGrip
    {
        add => AddHandler(s_renderStatusStripSizingGripEvent, value);
        remove => RemoveHandler(s_renderStatusStripSizingGripEvent, value);
    }
 
    /// <summary>
    ///  Draws the split button
    /// </summary>
    public event ToolStripItemRenderEventHandler RenderSplitButtonBackground
    {
        add => AddHandler(s_renderSplitButtonBackgroundEvent, value);
        remove => RemoveHandler(s_renderSplitButtonBackgroundEvent, value);
    }
 
    public event ToolStripSeparatorRenderEventHandler RenderSeparator
    {
        add => AddHandler(s_renderSeparatorEvent, value);
        remove => RemoveHandler(s_renderSeparatorEvent, value);
    }
 
    #region EventHandlerSecurity
 
    private void AddHandler(object key, Delegate value) => Events.AddHandler(key, value);
 
    private void RemoveHandler(object key, Delegate value) => Events.RemoveHandler(key, value);
    #endregion
 
    public static Image CreateDisabledImage(Image normalImage) =>
        CreateDisabledImage(normalImage, null);
 
    public void DrawArrow(ToolStripArrowRenderEventArgs e)
    {
        OnRenderArrow(e);
 
        if (Events[s_renderArrowEvent] is ToolStripArrowRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the background color
    /// </summary>
    public void DrawToolStripBackground(ToolStripRenderEventArgs e)
    {
        OnRenderToolStripBackground(e);
 
        if (Events[s_renderToolStripBackgroundEvent] is ToolStripRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the background color
    /// </summary>
    public void DrawGrip(ToolStripGripRenderEventArgs e)
    {
        OnRenderGrip(e);
        if (Events[s_renderGripEvent] is ToolStripGripRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the item's background.
    /// </summary>
    public void DrawItemBackground(ToolStripItemRenderEventArgs e)
    {
        OnRenderItemBackground(e);
 
        if (Events[s_renderItemBackgroundEvent] is ToolStripItemRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the background color
    /// </summary>
    public void DrawImageMargin(ToolStripRenderEventArgs e)
    {
        OnRenderImageMargin(e);
 
        if (Events[s_renderImageMarginEvent] is ToolStripRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the background color
    /// </summary>
    public void DrawLabelBackground(ToolStripItemRenderEventArgs e)
    {
        OnRenderLabelBackground(e);
        if (Events[s_renderLabelBackgroundEvent] is ToolStripItemRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the item's background.
    /// </summary>
    public void DrawButtonBackground(ToolStripItemRenderEventArgs e)
    {
        OnRenderButtonBackground(e);
 
        if (Events[s_renderButtonBackgroundEvent] is ToolStripItemRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    public void DrawToolStripBorder(ToolStripRenderEventArgs e)
    {
        OnRenderToolStripBorder(e);
 
        if (Events[s_renderBorderEvent] is ToolStripRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the item's background.
    /// </summary>
    public void DrawDropDownButtonBackground(ToolStripItemRenderEventArgs e)
    {
        OnRenderDropDownButtonBackground(e);
 
        if (Events[s_renderDropDownButtonBackgroundEvent] is ToolStripItemRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the item's background.
    /// </summary>
    public void DrawOverflowButtonBackground(ToolStripItemRenderEventArgs e)
    {
        OnRenderOverflowButtonBackground(e);
 
        if (Events[s_renderOverflowButtonBackgroundEvent] is ToolStripItemRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw image
    /// </summary>
    public void DrawItemImage(ToolStripItemImageRenderEventArgs e)
    {
        OnRenderItemImage(e);
 
        if (Events[s_renderItemImageEvent] is ToolStripItemImageRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw image
    /// </summary>
    public void DrawItemCheck(ToolStripItemImageRenderEventArgs e)
    {
        OnRenderItemCheck(e);
 
        if (Events[s_renderItemCheckEvent] is ToolStripItemImageRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw text
    /// </summary>
    public void DrawItemText(ToolStripItemTextRenderEventArgs e)
    {
        OnRenderItemText(e);
 
        if (Events[s_renderItemTextEvent] is ToolStripItemTextRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the item's background.
    /// </summary>
    public void DrawMenuItemBackground(ToolStripItemRenderEventArgs e)
    {
        OnRenderMenuItemBackground(e);
 
        if (Events[s_renderMenuItemBackgroundEvent] is ToolStripItemRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the background color
    /// </summary>
    public void DrawSplitButton(ToolStripItemRenderEventArgs e)
    {
        OnRenderSplitButtonBackground(e);
 
        if (Events[s_renderSplitButtonBackgroundEvent] is ToolStripItemRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the background color
    /// </summary>
    public void DrawToolStripStatusLabelBackground(ToolStripItemRenderEventArgs e)
    {
        OnRenderToolStripStatusLabelBackground(e);
 
        if (Events[s_renderToolStripStatusLabelBackgroundEvent] is ToolStripItemRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    public void DrawStatusStripSizingGrip(ToolStripRenderEventArgs e)
    {
        OnRenderStatusStripSizingGrip(e);
 
        if (Events[s_renderStatusStripSizingGripEvent] is ToolStripRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    /// <summary>
    ///  Draw the separator
    /// </summary>
    public void DrawSeparator(ToolStripSeparatorRenderEventArgs e)
    {
        OnRenderSeparator(e);
        if (Events[s_renderSeparatorEvent] is ToolStripSeparatorRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    public void DrawToolStripPanelBackground(ToolStripPanelRenderEventArgs e)
    {
        OnRenderToolStripPanelBackground(e);
        if (Events[s_renderToolStripPanelBackgroundEvent] is ToolStripPanelRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    public void DrawToolStripContentPanelBackground(ToolStripContentPanelRenderEventArgs e)
    {
        OnRenderToolStripContentPanelBackground(e);
        if (Events[s_renderToolStripContentPanelBackgroundEvent] is ToolStripContentPanelRenderEventHandler eh)
        {
            eh(this, e);
        }
    }
 
    // consider make public
    internal virtual Region? GetTransparentRegion(ToolStrip toolStrip) => null;
 
    protected internal virtual void Initialize(ToolStrip toolStrip)
    {
    }
 
    protected internal virtual void InitializePanel(ToolStripPanel toolStripPanel)
    {
    }
 
    protected internal virtual void InitializeContentPanel(ToolStripContentPanel contentPanel)
    {
    }
 
    protected internal virtual void InitializeItem(ToolStripItem item)
    {
    }
 
    protected static void ScaleArrowOffsetsIfNeeded()
    {
        if (s_isScalingInitialized)
        {
            return;
        }
 
        Offset2X = ScaleHelper.ScaleToInitialSystemDpi(OFFSET_2PIXELS);
        Offset2Y = ScaleHelper.ScaleToInitialSystemDpi(OFFSET_2PIXELS);
        s_offset4X = ScaleHelper.ScaleToInitialSystemDpi(OFFSET_4PIXELS);
        s_offset4Y = ScaleHelper.ScaleToInitialSystemDpi(OFFSET_4PIXELS);
 
        s_isScalingInitialized = true;
    }
 
    protected static void ScaleArrowOffsetsIfNeeded(int dpi)
    {
        Offset2X = ScaleHelper.ScaleToDpi(OFFSET_2PIXELS, dpi);
        Offset2Y = ScaleHelper.ScaleToDpi(OFFSET_2PIXELS, dpi);
        s_offset4X = ScaleHelper.ScaleToDpi(OFFSET_4PIXELS, dpi);
        s_offset4Y = ScaleHelper.ScaleToDpi(OFFSET_4PIXELS, dpi);
    }
 
    /// <summary>
    ///  Renders an arrow on the ToolStrip control.
    /// </summary>
    /// <param name="e">A ToolStripArrowRenderEventArgs that contains the event data.</param>
    protected virtual void OnRenderArrow(ToolStripArrowRenderEventArgs e)
    {
        ArgumentNullException.ThrowIfNull(e);
 
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderArrow(e);
            return;
        }
 
        RenderArrowCore(e, e.ArrowColor);
    }
 
    /// <summary>
    ///  Base class method that handles shared arrow rendering functionality.
    /// </summary>
    /// <param name="e">The event arguments containing rendering information.</param>
    /// <param name="arrowColor">The color to use for the arrow.</param>
    /// <returns>The rendered arrow points.</returns>
    private protected void RenderArrowCore(
        ToolStripArrowRenderEventArgs e,
        Color arrowColor)
    {
        ArgumentNullException.ThrowIfNull(e);
 
        Graphics g = e.Graphics;
 
        Rectangle dropDownRect = e.ArrowRectangle;
 
        Point middle = new(
            dropDownRect.Left + dropDownRect.Width / 2,
            dropDownRect.Top + dropDownRect.Height / 2);
 
        // Scale arrow offsets if needed
        if (e.Item is not null
            && e.Item.DeviceDpi != _previousDeviceDpi
            && ScaleHelper.IsThreadPerMonitorV2Aware)
        {
            _previousDeviceDpi = e.Item.DeviceDpi;
            ScaleArrowOffsetsIfNeeded(e.Item.DeviceDpi);
        }
        else
        {
            ScaleArrowOffsetsIfNeeded();
        }
 
        // Using (offset4X - Offset2X) instead of (Offset2X) to compensate
        // for rounding error in scaling
        int horizontalOffset = ScaleHelper.IsScalingRequirementMet
            ? s_offset4X - Offset2X
            : Offset2X;
 
        // Use stackalloc for the 3 arrow points
        Span<Point> arrow = stackalloc Point[3];
 
        // Fill the points based on arrow direction
        switch (e.Direction)
        {
            case ArrowDirection.Up:
                arrow[0] = new(middle.X - Offset2X, middle.Y + 1);
                arrow[1] = new(middle.X + Offset2X + 1, middle.Y + 1);
                arrow[2] = new(middle.X, middle.Y - Offset2Y);
                break;
 
            case ArrowDirection.Left:
                arrow[0] = new(middle.X + Offset2X, middle.Y - s_offset4Y);
                arrow[1] = new(middle.X + Offset2X, middle.Y + s_offset4Y);
                arrow[2] = new(middle.X - horizontalOffset, middle.Y);
                break;
 
            case ArrowDirection.Right:
                arrow[0] = new(middle.X - Offset2X, middle.Y - s_offset4Y);
                arrow[1] = new(middle.X - Offset2X, middle.Y + s_offset4Y);
                arrow[2] = new(middle.X + horizontalOffset, middle.Y);
                break;
 
            default: // Down
                arrow[0] = new(middle.X - Offset2X, middle.Y - 1);
                arrow[1] = new(middle.X + Offset2X + 1, middle.Y - 1);
                arrow[2] = new(middle.X, middle.Y + Offset2Y);
                break;
        }
 
        using var brush = arrowColor.GetCachedSolidBrushScope();
        g.FillPolygon(brush, arrow);
    }
 
    /// <summary>
    ///  Draw the ToolStrip background. ToolStrip users should override this if they want to draw differently.
    /// </summary>
    protected virtual void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderToolStripBackground(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the border around the ToolStrip. This should be done as the last step.
    /// </summary>
    protected virtual void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderToolStripBorder(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the grip. ToolStrip users should override this if they want to draw differently.
    /// </summary>
    protected virtual void OnRenderGrip(ToolStripGripRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderGrip(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the items background
    /// </summary>
    protected virtual void OnRenderItemBackground(ToolStripItemRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderItemBackground(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the items background
    /// </summary>
    protected virtual void OnRenderImageMargin(ToolStripRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderImageMargin(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the button background
    /// </summary>
    protected virtual void OnRenderButtonBackground(ToolStripItemRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderButtonBackground(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the button background
    /// </summary>
    protected virtual void OnRenderDropDownButtonBackground(ToolStripItemRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderDropDownButtonBackground(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the button background
    /// </summary>
    protected virtual void OnRenderOverflowButtonBackground(ToolStripItemRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderOverflowButtonBackground(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the item's image. ToolStrip users should override this function to change the
    ///  drawing of all images.
    /// </summary>
    protected virtual void OnRenderItemImage(ToolStripItemImageRenderEventArgs e)
    {
        ArgumentNullException.ThrowIfNull(e);
 
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderItemImage(e);
            return;
        }
 
        Rectangle imageRect = e.ImageRectangle;
        Image? image = e.Image;
 
        if (imageRect != Rectangle.Empty && image is not null)
        {
            bool disposeImage = false;
 
            if (e.ShiftOnPress && e.Item is not null && e.Item.Pressed)
            {
                imageRect.X++;
            }
 
            if (e.Item is not null && !e.Item.Enabled)
            {
                image = CreateDisabledImage(image, e.ImageAttributes);
                disposeImage = true;
            }
 
            if (e.Item?.ImageScaling == ToolStripItemImageScaling.None)
            {
                e.Graphics.DrawImage(image, imageRect, new Rectangle(Point.Empty, imageRect.Size), GraphicsUnit.Pixel);
            }
            else
            {
                e.Graphics.DrawImage(image, imageRect);
            }
 
            if (disposeImage)
            {
                image.Dispose();
            }
        }
    }
 
    protected virtual void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
    {
        ArgumentNullException.ThrowIfNull(e);
 
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderItemCheck(e);
            return;
        }
 
        Rectangle imageRect = e.ImageRectangle;
        Image? image = e.Image;
 
        if (imageRect == Rectangle.Empty || image is null)
        {
            return;
        }
 
        if (e.Item is not null)
        {
            if (!e.Item.Enabled)
            {
                image = CreateDisabledImage(image, e.ImageAttributes);
            }
 
            if (SystemInformation.HighContrast && image is Bitmap bitmap)
            {
                Color backgroundColor = e.Item.Selected ? SystemColors.Highlight : e.Item.BackColor;
 
                if (ControlPaint.IsDark(backgroundColor))
                {
                    Image invertedImage = ControlPaint.CreateBitmapWithInvertedForeColor(bitmap, e.Item.BackColor);
                    image = invertedImage;
                }
            }
        }
 
        e.Graphics.DrawImage(image, imageRect, 0, 0, imageRect.Width,
        imageRect.Height, GraphicsUnit.Pixel, e.ImageAttributes);
    }
 
    /// <summary>
    ///  Draw the item's text. ToolStrip users should override this function to change the
    ///  drawing of all text.
    /// </summary>
    protected virtual void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
    {
        ArgumentNullException.ThrowIfNull(e);
 
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderItemText(e);
            return;
        }
 
        ToolStripItem? item = e.Item;
        Graphics g = e.Graphics;
        Color textColor = e.TextColor;
        Font? textFont = e.TextFont;
        string? text = e.Text;
        Rectangle textRect = e.TextRectangle;
        TextFormatFlags textFormat = e.TextFormat;
 
        // If we're disabled draw in a different color.
        textColor = (item is not null && item.Enabled)
            ? textColor
            : SystemColors.GrayText;
 
        if (e.TextDirection == ToolStripTextDirection.Horizontal
            || textRect.Width <= 0
            || textRect.Height <= 0)
        {
            TextRenderer.DrawText(g, text, textFont, textRect, textColor, textFormat);
            return;
        }
 
        // Perf: this is a bit heavy handed.. perhaps we can share the bitmap.
        Size textSize = LayoutUtils.FlipSize(textRect.Size);
        using Bitmap textBmp = new(textSize.Width, textSize.Height, PixelFormat.Format32bppPArgb);
        using Graphics textGraphics = Graphics.FromImage(textBmp);
 
        // Now draw the text.
        textGraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
 
        TextRenderer.DrawText(
            dc: textGraphics,
            text: text,
            font: textFont,
            bounds: new Rectangle(Point.Empty, textSize),
            foreColor: textColor,
            flags: textFormat);
 
        textBmp.RotateFlip((e.TextDirection == ToolStripTextDirection.Vertical90)
                ? RotateFlipType.Rotate90FlipNone
                : RotateFlipType.Rotate270FlipNone);
 
        g.DrawImage(textBmp, textRect);
    }
 
    /// <summary>
    ///  Draw the button background
    /// </summary>
    protected virtual void OnRenderLabelBackground(ToolStripItemRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderLabelBackground(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draw the items background
    /// </summary>
    protected virtual void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderMenuItemBackground(e);
            return;
        }
    }
 
    /// <summary>
    ///  Draws a toolbar separator. ToolStrip users should override this function to change the
    ///  drawing of all separators.
    /// </summary>
    protected virtual void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderSeparator(e);
            return;
        }
    }
 
    protected virtual void OnRenderToolStripPanelBackground(ToolStripPanelRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderToolStripPanelBackground(e);
            return;
        }
    }
 
    protected virtual void OnRenderToolStripContentPanelBackground(ToolStripContentPanelRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderToolStripContentPanelBackground(e);
            return;
        }
    }
 
    protected virtual void OnRenderToolStripStatusLabelBackground(ToolStripItemRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderToolStripStatusLabelBackground(e);
            return;
        }
    }
 
    // Used in building up the half pyramid of rectangles that are drawn in a
    // status strip sizing grip.
    private static readonly Rectangle[] s_baseSizeGripRectangles =
    [
        new(12, 0, 2, 2),
        new(8, 4, 2, 2),
        new(4, 8, 2, 2),
        new(0, 12, 2, 2),
        new(8, 0, 2, 2),
        new(4, 4, 2, 2),
        new(0, 8, 2, 2),
        new(4, 0, 2, 2),
        new(0, 4, 2, 2),
        new(1, 1, 2, 2),
    ];
 
    protected virtual void OnRenderStatusStripSizingGrip(ToolStripRenderEventArgs e)
    {
        ArgumentNullException.ThrowIfNull(e);
 
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderStatusStripSizingGrip(e);
            return;
        }
 
        OnRenderStatusStripSizingGrip(
            e: e,
            highLightBrush: SystemBrushes.ButtonHighlight,
            shadowBrush: SystemBrushes.GrayText);
    }
 
    private protected static void OnRenderStatusStripSizingGrip(
        ToolStripRenderEventArgs e,
        Brush highLightBrush,
        Brush shadowBrush)
    {
        if (e.ToolStrip is not StatusStrip statusStrip)
        {
            return;
        }
 
        Rectangle sizeGripBounds = statusStrip.SizeGripBounds;
 
        if (LayoutUtils.IsZeroWidthOrHeight(sizeGripBounds))
        {
            return;
        }
 
        Graphics g = e.Graphics;
        ReadOnlySpan<Rectangle> baseRectangles = s_baseSizeGripRectangles;
 
        // Reference height for sizing grips at 96 DPI (standard sizing)
        int scaledDefaultGripAreaHeight = ScaleHelper.ScaleToDpi(20, e.ToolStrip.DeviceDpi);
 
        // Calculate scaling based on the almost half of the current height
        // of the status strip's sizing grip area
        float heightScale = ((float)sizeGripBounds.Height / scaledDefaultGripAreaHeight);
 
        // Save the current graphics state before transformations
        GraphicsState originalState = g.Save();
 
        try
        {
            // Set anti-aliasing for smoother appearance
            SmoothingMode oldSmoothing = g.SmoothingMode;
            g.SmoothingMode = SmoothingMode.AntiAlias;
 
            // Translate to the corner where we'll start drawing
            bool isRtl = statusStrip.RightToLeft == RightToLeft.Yes;
            int cornerOffset = GetCornerOffset(statusStrip);
 
            // Set up the transform to scale from the bottom corner
            if (isRtl)
            {
                g.TranslateTransform(
                    sizeGripBounds.Left + cornerOffset,
                    sizeGripBounds.Bottom - cornerOffset);
            }
            else
            {
                g.TranslateTransform(
                    sizeGripBounds.Right - cornerOffset,
                    sizeGripBounds.Bottom - cornerOffset);
            }
 
            // Apply scaling
            g.ScaleTransform(heightScale, heightScale);
 
            // Draw the sizing grip dots in the scaled context
            foreach (Rectangle baseRect in baseRectangles)
            {
                Rectangle dotRect = new(
                    isRtl ? baseRect.X : -baseRect.X - baseRect.Width,
                    -baseRect.Y - baseRect.Height,
                    baseRect.Width,
                    baseRect.Height);
 
                // Highlight dot (top-left)
                Rectangle highlightRect = dotRect;
                highlightRect.Offset(-1, -1);
                g.FillEllipse(highLightBrush, highlightRect);
 
                // Shadow dot (bottom-right)
                Rectangle shadowRect = dotRect;
                shadowRect.Offset(1, 1);
                g.FillEllipse(shadowBrush, shadowRect);
            }
 
            // Restore the original smoothing mode
            g.SmoothingMode = oldSmoothing;
        }
        finally
        {
            // Always restore the original graphics state
            g.Restore(originalState);
        }
 
        // Helper method to determine corner offset based on form corner preference
        static int GetCornerOffset(StatusStrip statusStrip)
        {
            // Default offset
            int offset = 2;
 
            if (Environment.OSVersion.Version >= new Version(10, 0, 22000)
                && statusStrip.FindForm() is Form f)
            {
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
                offset = f.FormCornerPreference switch
                {
                    FormCornerPreference.Round => 4,
                    FormCornerPreference.RoundSmall => 3,
                    _ => 2
                };
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
            }
 
            return offset;
        }
    }
 
    /// <summary>
    ///  Draw the item's background.
    /// </summary>
    protected virtual void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e)
    {
        if (RendererOverride is not null)
        {
            RendererOverride.OnRenderSplitButtonBackground(e);
            return;
        }
    }
 
    // Only paint background effects if no BackColor has been set
    // or no background image has been set.
    internal static bool ShouldPaintBackground(Control control) =>
        control.RawBackColor == Color.Empty && control.BackgroundImage is null;
 
    private static Bitmap CreateDisabledImage(Image normalImage, ImageAttributes? imgAttrib)
    {
        ArgumentNullException.ThrowIfNull(normalImage);
 
        imgAttrib ??= new ImageAttributes();
 
        imgAttrib.ClearColorKey();
        imgAttrib.SetColorMatrix(DisabledImageColorMatrix);
 
        Size size = normalImage.Size;
        Bitmap disabledBitmap = new(size.Width, size.Height);
 
        using (Graphics graphics = Graphics.FromImage(disabledBitmap))
        {
            graphics.DrawImage(
                image: normalImage,
                destRect: new Rectangle(0, 0, size.Width, size.Height),
                srcX: 0,
                srcY: 0,
                srcWidth: size.Width,
                srcHeight: size.Height,
                srcUnit: GraphicsUnit.Pixel,
                imageAttr: imgAttrib);
        }
 
        return disabledBitmap;
    }
}