|
// 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;
}
}
|