File: System\Windows\Forms\Rendering\ModernControlVisualStyles.cs
Web Access
Project: src\winforms\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.Drawing;
 
namespace System.Windows.Forms;
 
/// <summary>
///  Defines shared device-independent metrics for modern control chrome. All values are logical
///  (96-DPI) pixels and are scaled to the target device DPI at the point of use.
/// </summary>
internal static class ModernControlVisualStyles
{
    /// <summary>Stroke thickness of the modern rounded control border.</summary>
    internal const int BorderThickness = 1;
 
    /// <summary>Extra width added to the modern ComboBox drop-down button beyond the native metric.</summary>
    internal const int ComboBoxButtonExtraWidth = 4;
 
    /// <summary>Horizontal clearance so the flat native edit child's square corners clear the field's rounded arcs.</summary>
    internal const int ComboBoxFieldArcClearance = 2;
 
    /// <summary>Minimal modern ComboBox field chrome inset (excludes classic 3D-border metrics).</summary>
    internal const int ComboBoxStyleInset = 1;
 
    /// <summary>Corner radius of a modern text field's rounded frame.</summary>
    internal const int FieldCornerRadius = 15;
 
    /// <summary>Height of the animated focus underline band drawn beneath a focused modern field.</summary>
    internal const int FocusBandHeight = 4;
 
    /// <summary>Scale factor applied to the GroupBox caption font in modern mode.</summary>
    internal const float GroupBoxCaptionFontScale = 1.15f;
 
    /// <summary>Gap between the GroupBox caption text and the surrounding frame line.</summary>
    internal const int GroupBoxCaptionGap = 4;
 
    /// <summary>Inset from the GroupBox bottom frame to its content area.</summary>
    internal const int GroupBoxContentBottomInset = 4;
 
    /// <summary>Inset from the GroupBox left/right frame to its content area.</summary>
    internal const int GroupBoxContentHorizontalInset = 8;
 
    /// <summary>Inset from the GroupBox top frame (below the caption) to its content area.</summary>
    internal const int GroupBoxContentTopInset = 8;
 
    /// <summary>Corner radius of the GroupBox rounded frame.</summary>
    internal const int GroupBoxCornerRadius = 8;
 
    /// <summary>Extra baseline leeway added to the modern flat GroupBox border inset.</summary>
    internal const int GroupBoxFlatBaselineLeeway = 1;
 
    /// <summary>Horizontal padding around the GroupBox header text.</summary>
    internal const int GroupBoxHeaderHorizontalPadding = 10;
 
    /// <summary>Vertical padding around the GroupBox header text.</summary>
    internal const int GroupBoxHeaderVerticalPadding = 5;
 
    /// <summary>Extra content inset for the modern Popup-style GroupBox, applied on top of the header height.</summary>
    internal const int GroupBoxPopupContentInset = 2;
 
    /// <summary>
    ///  Inset between a control's border and its content, shared by modern text fields and the up-down
    ///  control. Added on top of the border-padding component (see <see cref="GetFieldPadding"/>).
    /// </summary>
    internal const int InternalChromeInset = 2;
 
    /// <summary>Border-padding component for a <see cref="BorderStyle.Fixed3D"/> border.</summary>
    internal const int Fixed3DBorderPadding = 2;
 
    /// <summary>Border-padding component for a <see cref="BorderStyle.FixedSingle"/> border.</summary>
    internal const int FixedSingleBorderPadding = 1;
 
    /// <summary>Border-padding component for a control with <see cref="BorderStyle.None"/>.</summary>
    internal const int NoBorderPadding = 1;
 
    /// <summary>Corner radius of the up-down control's rounded frame.</summary>
    internal const int UpDownCornerRadius = 14;
 
    internal static Padding GetFieldPadding(
        BorderStyle borderStyle,
        Padding userPadding,
        Size focusBorderMetrics,
        float textScaleFactor,
        int deviceDpi)
    {
        Size scaledFocusBorderMetrics = GetFocusBorderMetrics(
            focusBorderMetrics,
            textScaleFactor,
            deviceDpi);
 
        int horizontalOffset = scaledFocusBorderMetrics.Width;
        int verticalOffset = scaledFocusBorderMetrics.Height;
 
        Padding borderPadding = borderStyle switch
        {
            BorderStyle.Fixed3D => new Padding(
                left: ScaleToDpi(Fixed3DBorderPadding, deviceDpi) + horizontalOffset,
                top: ScaleToDpi(Fixed3DBorderPadding, deviceDpi) + verticalOffset,
                right: ScaleToDpi(Fixed3DBorderPadding, deviceDpi) + horizontalOffset,
                bottom: ScaleToDpi(Fixed3DBorderPadding, deviceDpi) + verticalOffset),
 
            BorderStyle.FixedSingle => new Padding(
                left: ScaleToDpi(FixedSingleBorderPadding, deviceDpi) + horizontalOffset,
                top: ScaleToDpi(FixedSingleBorderPadding, deviceDpi) + verticalOffset,
                right: ScaleToDpi(FixedSingleBorderPadding, deviceDpi) + horizontalOffset,
                bottom: ScaleToDpi(FixedSingleBorderPadding, deviceDpi) + verticalOffset),
 
            BorderStyle.None => new Padding(
                left: ScaleToDpi(NoBorderPadding, deviceDpi),
                top: ScaleToDpi(NoBorderPadding, deviceDpi),
                right: ScaleToDpi(NoBorderPadding, deviceDpi) + horizontalOffset,
                bottom: ScaleToDpi(NoBorderPadding, deviceDpi) + verticalOffset),
            _ => Padding.Empty
        };
 
        return borderPadding
            + new Padding(ScaleToDpi(InternalChromeInset, deviceDpi))
            + userPadding;
    }
 
    internal static Size GetFocusBorderMetrics(
        Size focusBorderMetrics,
        float textScaleFactor,
        int deviceDpi)
        => new(
            ScaleFocusMetric(
                focusBorderMetrics.Width,
                textScaleFactor,
                deviceDpi),
            ScaleFocusMetric(
                focusBorderMetrics.Height,
                textScaleFactor,
                deviceDpi));
 
    internal static int GetFocusBandHeight(
        Size focusBorderMetrics,
        float textScaleFactor,
        int deviceDpi)
    {
        Size scaledFocusBorderMetrics = GetFocusBorderMetrics(
            focusBorderMetrics,
            textScaleFactor,
            deviceDpi);
        int scaledFocusBandHeight = ScaleFocusMetric(
            FocusBandHeight,
            textScaleFactor,
            deviceDpi);
 
        return Math.Max(
            scaledFocusBandHeight,
            scaledFocusBorderMetrics.Height);
    }
 
    internal static int GetPreferredFieldHeight(
        int fontHeight,
        Padding fieldPadding,
        int deviceDpi)
    {
        int preferredHeight = fontHeight + fieldPadding.Vertical;
 
        int roundedChromeMinimumHeight = ScaleToDpi(
            FieldCornerRadius,
            deviceDpi)
            + ScaleToDpi(BorderThickness, deviceDpi)
            + ScaleToDpi(InternalChromeInset, deviceDpi);
 
        return Math.Max(preferredHeight, roundedChromeMinimumHeight);
    }
 
    private static int ScaleFocusMetric(
        int metric,
        float textScaleFactor,
        int deviceDpi)
    {
        float scale = Math.Clamp(textScaleFactor, 1f, 2.25f);
 
        int dpiScaledMetric = ScaleToDpi(
            Math.Max(metric, BorderThickness),
            deviceDpi);
 
        return Math.Max(
            ScaleToDpi(BorderThickness, deviceDpi),
            (int)Math.Ceiling(dpiScaledMetric * scale));
    }
 
    private static int ScaleToDpi(int value, int deviceDpi)
        => ScaleHelper.ScaleToDpi(value, deviceDpi);
}