File: System\Windows\Forms\Controls\ToolStrips\ToolStripHighContrastRenderer.cs
Web Access
Project: src\src\System.Windows.Forms\src\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.Collections.Specialized;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
 
namespace System.Windows.Forms;
 
// this renderer supports high contrast for ToolStripProfessional and ToolStripSystemRenderer.
internal class ToolStripHighContrastRenderer : ToolStripSystemRenderer
{
    private const int GRIP_PADDING = 4;
 
    private BitVector32 _options;
    private static readonly int s_optionsDottedBorder = BitVector32.CreateMask();
    private static readonly int s_optionsDottedGrip = BitVector32.CreateMask(s_optionsDottedBorder);
    private static readonly int s_optionsFillWhenSelected = BitVector32.CreateMask(s_optionsDottedGrip);
 
    public ToolStripHighContrastRenderer(bool systemRenderMode)
    {
        _options[s_optionsDottedBorder | s_optionsDottedGrip | s_optionsFillWhenSelected] = !systemRenderMode;
    }
 
    public bool DottedBorder
    {
        get { return _options[s_optionsDottedBorder]; }
    }
 
    public bool DottedGrip
    {
        get { return _options[s_optionsDottedGrip]; }
    }
 
    public bool FillWhenSelected
    {
        get { return _options[s_optionsFillWhenSelected]; }
    }
 
    // this is a renderer override, so return null so we don't get into an infinite loop.
    internal override ToolStripRenderer? RendererOverride
    {
        get { return null; }
    }
 
    protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
    {
        base.OnRenderArrow(e);
    }
 
    protected override void OnRenderGrip(ToolStripGripRenderEventArgs e)
    {
        if (DottedGrip)
        {
            Graphics g = e.Graphics;
            Rectangle bounds = e.GripBounds;
            ToolStrip toolStrip = e.ToolStrip;
 
            int height = (toolStrip.Orientation == Orientation.Horizontal) ? bounds.Height : bounds.Width;
            int width = (toolStrip.Orientation == Orientation.Horizontal) ? bounds.Width : bounds.Height;
 
            int numRectangles = (height - (GRIP_PADDING * 2)) / 4;
 
            if (numRectangles > 0)
            {
                Rectangle[] shadowRects = new Rectangle[numRectangles];
                int startY = GRIP_PADDING;
                int startX = (width / 2);
 
                for (int i = 0; i < numRectangles; i++)
                {
                    shadowRects[i] = (toolStrip.Orientation == Orientation.Horizontal) ?
                                        new Rectangle(startX, startY, 2, 2) :
                                        new Rectangle(startY, startX, 2, 2);
 
                    startY += 4;
                }
 
                g.FillRectangles(SystemBrushes.ControlLight, shadowRects);
            }
        }
        else
        {
            base.OnRenderGrip(e);
        }
    }
 
    protected override void OnRenderDropDownButtonBackground(ToolStripItemRenderEventArgs e)
    {
        if (FillWhenSelected)
        {
            RenderItemInternalFilled(e, false);
        }
        else
        {
            base.OnRenderDropDownButtonBackground(e);
            if (e.Item.Pressed)
            {
                e.Graphics.DrawRectangle(SystemPens.ButtonHighlight, new Rectangle(0, 0, e.Item.Width - 1, e.Item.Height - 1));
            }
        }
    }
 
    protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
    {
        if (e.Image is not { } image)
        {
            return;
        }
 
        if (Image.GetPixelFormatSize(image.PixelFormat) > 16)
        {
            // For 24, 32 bit images, just paint normally - mapping the color table is not
            // going to work when you can have full color.
            base.OnRenderItemCheck(e);
            return;
        }
 
        RenderItemImageOfLowColorDepth(e);
    }
 
    protected override void OnRenderImageMargin(ToolStripRenderEventArgs e)
    {
        // do nothing
    }
 
    protected override void OnRenderItemBackground(ToolStripItemRenderEventArgs e)
    {
        base.OnRenderItemBackground(e);
    }
 
    protected override void OnRenderSplitButtonBackground(ToolStripItemRenderEventArgs e)
    {
        Rectangle bounds = new(Point.Empty, e.Item.Size);
        Graphics g = e.Graphics;
 
        if (e.Item is ToolStripSplitButton item)
        {
            Rectangle dropDownRect = item.DropDownButtonBounds;
 
            if (item.Pressed)
            {
                g.DrawRectangle(SystemPens.ButtonHighlight, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1);
            }
            else if (item.Selected)
            {
                g.FillRectangle(SystemBrushes.Highlight, bounds);
                g.DrawRectangle(SystemPens.HighlightText, dropDownRect);
 
                DrawHightContrastDashedBorder(g, e.Item);
            }
 
            Color arrowColor = item.Selected && !item.Pressed ? SystemColors.HighlightText : SystemColors.ControlText;
            DrawArrow(new ToolStripArrowRenderEventArgs(g, item, dropDownRect, arrowColor, ArrowDirection.Down));
        }
    }
 
    protected override void OnRenderStatusStripSizingGrip(ToolStripRenderEventArgs e)
    {
        base.OnRenderStatusStripSizingGrip(e);
    }
 
    protected override void OnRenderLabelBackground(ToolStripItemRenderEventArgs e)
    {
        if (FillWhenSelected)
        {
            RenderItemInternalFilled(e);
        }
        else
        {
            base.OnRenderLabelBackground(e);
        }
    }
 
    protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
    {
        base.OnRenderMenuItemBackground(e);
        if (!e.Item.IsOnDropDown && e.Item.Pressed)
        {
            e.Graphics.DrawRectangle(SystemPens.ButtonHighlight, 0, 0, e.Item.Width - 1, e.Item.Height - 1);
        }
 
        if (e.Item is ToolStripMenuItem menuItem && !e.Item.IsOnDropDown && (menuItem.Checked || menuItem.Selected))
        {
            Graphics g = e.Graphics;
            Rectangle bounds = new(Point.Empty, menuItem.Size);
 
            g.FillRectangle(SystemBrushes.Highlight, bounds);
            g.DrawRectangle(SystemPens.ControlLight, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1);
            if (menuItem.Selected)
            {
                DrawHightContrastDashedBorder(g, menuItem);
            }
        }
    }
 
    protected override void OnRenderOverflowButtonBackground(ToolStripItemRenderEventArgs e)
    {
        if (FillWhenSelected)
        {
            RenderItemInternalFilled(e, /*pressFill = */false);
            ToolStripItem item = e.Item;
            Graphics g = e.Graphics;
            Color arrowColor = !item.Enabled ? SystemColors.ControlDark
                : item.Selected && !item.Pressed ? SystemColors.HighlightText
                : SystemColors.ControlText;
            DrawArrow(new ToolStripArrowRenderEventArgs(g, item, new Rectangle(Point.Empty, item.Size), arrowColor, ArrowDirection.Down));
        }
        else
        {
            base.OnRenderOverflowButtonBackground(e);
        }
    }
 
    protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
    {
        if (e.Item.Selected && (!e.Item.Pressed || e.Item is ToolStripButton))
        {
            e.DefaultTextColor = SystemColors.HighlightText;
        }
        else if (e.TextColor != SystemColors.HighlightText && e.TextColor != SystemColors.ControlText)
        {
            // we'll change the DefaultTextColor, if someone wants to change this,manually set the TextColor property.
            if (e.Item.Selected || e.Item.Pressed)
            {
                e.DefaultTextColor = SystemColors.HighlightText;
            }
            else
            {
                e.DefaultTextColor = SystemColors.ControlText;
            }
        }
 
        // ToolstripButtons and ToolstripMenuItems that are checked are rendered with a highlight
        // background. In that case, set the text color to highlight as well.
        if ((typeof(ToolStripButton).IsAssignableFrom(e.Item.GetType())
            && ((ToolStripButton)e.Item).DisplayStyle != ToolStripItemDisplayStyle.Image
            && ((ToolStripButton)e.Item).Checked)
            || (typeof(ToolStripMenuItem).IsAssignableFrom(e.Item.GetType())
            && ((ToolStripMenuItem)e.Item).DisplayStyle != ToolStripItemDisplayStyle.Image
            && !e.Item.IsOnDropDown
            && ((ToolStripMenuItem)e.Item).Checked))
        {
            e.TextColor = SystemColors.HighlightText;
        }
 
        base.OnRenderItemText(e);
    }
 
    protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e)
    {
        // do nothing. LOGO requirements ask us not to paint background effects behind
    }
 
    protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
    {
        Rectangle bounds = new(Point.Empty, e.ToolStrip.Size);
        Graphics g = e.Graphics;
 
        if (e.ToolStrip is ToolStripDropDown)
        {
            g.DrawRectangle(SystemPens.ButtonHighlight, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1);
 
            if (e.ToolStrip is not ToolStripOverflow)
            {
                // make the neck connected.
                g.FillRectangle(SystemBrushes.Control, e.ConnectedArea);
            }
        }
        else if (e.ToolStrip is MenuStrip)
        {
            // do nothing
        }
        else if (e.ToolStrip is StatusStrip)
        {
            g.DrawRectangle(SystemPens.ButtonShadow, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1);
        }
        else
        {
            RenderToolStripBackgroundInternal(e);
        }
    }
 
    private void RenderToolStripBackgroundInternal(ToolStripRenderEventArgs e)
    {
        Rectangle bounds = new(Point.Empty, e.ToolStrip.Size);
        Graphics g = e.Graphics;
 
        if (DottedBorder)
        {
            using Pen p = new(SystemColors.ButtonShadow)
            {
                DashStyle = DashStyle.Dot
            };
 
            bool oddWidth = ((bounds.Width & 0x1) == 0x1);
            bool oddHeight = ((bounds.Height & 0x1) == 0x1);
            int indent = 2;
 
            // top
            g.DrawLine(p, bounds.X + indent, bounds.Y, bounds.Width - 1, bounds.Y);
            // bottom
            g.DrawLine(p, bounds.X + indent, bounds.Height - 1, bounds.Width - 1, bounds.Height - 1);
 
            // left
            g.DrawLine(p, bounds.X, bounds.Y + indent, bounds.X, bounds.Height - 1);
            // right
            g.DrawLine(p, bounds.Width - 1, bounds.Y + indent, bounds.Width - 1, bounds.Height - 1);
 
            // connecting pixels
 
            // top left connecting pixel - always drawn
            g.FillRectangle(SystemBrushes.ButtonShadow, new Rectangle(1, 1, 1, 1));
 
            if (oddWidth)
            {
                // top right pixel
                g.FillRectangle(SystemBrushes.ButtonShadow, new Rectangle(bounds.Width - 2, 1, 1, 1));
            }
 
            // bottom connecting pixels - drawn only if height is odd
            if (oddHeight)
            {
                // bottom left
                g.FillRectangle(SystemBrushes.ButtonShadow, new Rectangle(1, bounds.Height - 2, 1, 1));
            }
 
            // top and bottom right connecting pixel - drawn only if height and width are odd
            if (oddHeight && oddWidth)
            {
                // bottom right
                g.FillRectangle(SystemBrushes.ButtonShadow, new Rectangle(bounds.Width - 2, bounds.Height - 2, 1, 1));
            }
        }
        else
        {
            // draw solid border
            bounds.Width -= 1;
            bounds.Height -= 1;
            g.DrawRectangle(SystemPens.ButtonShadow, bounds);
        }
    }
 
    protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e)
    {
        Pen foreColorPen = SystemPens.ButtonShadow;
 
        Graphics g = e.Graphics;
        Rectangle bounds = new(Point.Empty, e.Item.Size);
 
        if (e.Vertical)
        {
            if (bounds.Height >= 8)
            {
                bounds.Inflate(0, -4);     // scoot down 4PX and start drawing
            }
 
            // Draw dark line
            int startX = bounds.Width / 2;
 
            g.DrawLine(foreColorPen, startX, bounds.Top, startX, bounds.Bottom - 1);
        }
        else
        {
            // horizontal separator
            if (bounds.Width >= 4)
            {
                bounds.Inflate(-2, 0);        // scoot over 2PX and start drawing
            }
 
            // Draw dark line
            int startY = bounds.Height / 2;
 
            g.DrawLine(foreColorPen, bounds.Left, startY, bounds.Right - 1, startY);
        }
    }
 
    // Indicates whether system is currently set to high contrast 'white on black' mode
    internal static bool IsHighContrastWhiteOnBlack()
    {
        return SystemColors.Control.ToArgb() == Color.Black.ToArgb();
    }
 
    protected override void OnRenderItemImage(ToolStripItemImageRenderEventArgs e)
    {
        if (e.Image is not { } image)
        {
            return;
        }
 
        if (Image.GetPixelFormatSize(image.PixelFormat) > 16)
        {
            // For 24, 32 bit images, just paint normally - mapping the color table is not
            // going to work when you can have full color.
            base.OnRenderItemImage(e);
            return;
        }
 
        RenderItemImageOfLowColorDepth(e);
    }
 
    protected override void OnRenderButtonBackground(ToolStripItemRenderEventArgs e)
    {
        if (FillWhenSelected)
        {
            if (e.Item is ToolStripButton button && button.Checked)
            {
                Graphics g = e.Graphics;
                Rectangle bounds = new(Point.Empty, e.Item.Size);
 
                g.FillRectangle(SystemBrushes.Highlight, bounds);
 
                if (button.Selected)
                {
                    DrawHightContrastDashedBorder(g, button);
                }
                else
                {
                    g.DrawRectangle(SystemPens.ControlLight, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1);
                }
            }
            else
            {
                RenderItemInternalFilled(e);
            }
        }
        else
        {
            base.OnRenderButtonBackground(e);
        }
    }
 
    private static void RenderItemInternalFilled(ToolStripItemRenderEventArgs e)
    {
        RenderItemInternalFilled(e, /*pressFill=*/true);
    }
 
    private static void RenderItemInternalFilled(ToolStripItemRenderEventArgs e, bool pressFill)
    {
        Graphics g = e.Graphics;
        Rectangle bounds = new(Point.Empty, e.Item.Size);
 
        if (e.Item.Pressed)
        {
            if (pressFill)
            {
                g.FillRectangle(SystemBrushes.Highlight, bounds);
            }
            else
            {
                g.DrawRectangle(SystemPens.ControlLight, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1);
            }
        }
        else if (e.Item.Selected)
        {
            g.FillRectangle(SystemBrushes.Highlight, bounds);
            DrawHightContrastDashedBorder(g, e.Item);
        }
    }
 
    private static void DrawHightContrastDashedBorder(Graphics graphics, ToolStripItem item)
    {
        Rectangle bounds = item.ClientBounds;
        float[] dashValues = [2, 2];
        int penWidth = 2;
 
        Pen focusPen1 = new(SystemColors.ControlText, penWidth)
        {
            DashPattern = dashValues
        };
 
        Pen focusPen2 = new(SystemColors.Control, penWidth)
        {
            DashPattern = dashValues,
            DashOffset = 2
        };
 
        graphics.DrawRectangle(focusPen1, bounds);
        graphics.DrawRectangle(focusPen2, bounds);
    }
 
    private void RenderItemImageOfLowColorDepth(ToolStripItemImageRenderEventArgs e)
    {
        if (e.Image is not { } image)
        {
            return;
        }
 
        ToolStripItem item = e.Item;
 
        using ImageAttributes attrs = new();
 
        if (IsHighContrastWhiteOnBlack() && !(FillWhenSelected && (item.Pressed || item.Selected)))
        {
            // Translate white, black and blue to colors visible in high contrast mode.
            Span<(Color OldColor, Color NewColor)> map =
            [
                new(Color.Black, Color.White),
                new(Color.White, Color.Black),
                new(Color.FromArgb(0, 0, 128), Color.White)
            ];
 
            attrs.SetRemapTable(ColorAdjustType.Bitmap, map);
        }
 
        Graphics g = e.Graphics;
        Rectangle imageRect = e.ImageRectangle;
 
        if (item.ImageScaling == ToolStripItemImageScaling.None)
        {
            g.DrawImage(image, imageRect, 0, 0, imageRect.Width, imageRect.Height, GraphicsUnit.Pixel, attrs);
        }
        else
        {
            g.DrawImage(image, imageRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attrs);
        }
    }
}