// 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.ButtonInternal; internal abstract class RadioButtonBaseAdapter : CheckableControlBaseAdapter { internal RadioButtonBaseAdapter(ButtonBase control) : base(control) { } protected new RadioButton Control => (RadioButton)base.Control; protected void DrawCheckFlat( PaintEventArgs e, LayoutData layout, Color checkColor, Color checkBackground, Color checkBorder) { DrawCheckBackgroundFlat(e, layout.CheckBounds, checkBorder, checkBackground); DrawCheckOnly(e, layout, checkColor, disabledColors: true); } protected void DrawCheckBackground3DLite( PaintEventArgs e, Rectangle bounds, Color checkBackground, ColorData colors, bool disabledColors) { Graphics g = e.GraphicsInternal; Color field = checkBackground; if (!Control.Enabled && disabledColors) { field = SystemColors.Control; } using var fieldBrush = field.GetCachedSolidBrushScope(); using var dark = colors.ButtonShadow.GetCachedPenScope(); using var light = colors.ButtonFace.GetCachedPenScope(); using var lightLight = colors.Highlight.GetCachedPenScope(); bounds.Width--; bounds.Height--; // Fall a little short of SW, NW, NE, SE because corners come out nasty g.DrawPie(dark, bounds, 135 + 1, 90 - 2); g.DrawPie(dark, bounds, 225 + 1, 90 - 2); g.DrawPie(lightLight, bounds, 315 + 1, 90 - 2); g.DrawPie(lightLight, bounds, 45 + 1, 90 - 2); bounds.Inflate(-1, -1); g.FillEllipse(fieldBrush, bounds); g.DrawEllipse(light, bounds); } protected void DrawCheckBackgroundFlat(PaintEventArgs e, Rectangle bounds, Color borderColor, Color checkBackground) { Color field = checkBackground; Color border = borderColor; if (!Control.Enabled) { // If we are not in contrast theme OR we opted into the legacy behavior if (!SystemInformation.HighContrast) { border = ControlPaint.ContrastControlDark; } // Otherwise we are in a contrast theme field = SystemColors.Control; } double scale = GetDpiScaleRatio(); using DeviceContextHdcScope hdc = new(e); using CreatePenScope borderPen = new(border); using CreateBrushScope fieldBrush = new(field); if (scale > 1.1) { // In high DPI mode when we draw an ellipse as three rectangles, the quality of ellipse is poor. Draw // it directly as an ellipse. bounds.Width--; bounds.Height--; hdc.DrawAndFillEllipse(borderPen, fieldBrush, bounds); bounds.Inflate(-1, -1); } else { DrawAndFillEllipse(hdc, borderPen, fieldBrush, bounds); } } // Helper method to overcome the poor GDI ellipse drawing routine private static void DrawAndFillEllipse(HDC hdc, HPEN borderPen, HBRUSH fieldBrush, Rectangle bounds) { Debug.Assert(!hdc.IsNull, "Calling DrawAndFillEllipse with null wg"); if (hdc.IsNull) { return; } hdc.FillRectangle(fieldBrush, new(bounds.X + 2, bounds.Y + 2, 8, 8)); hdc.FillRectangle(fieldBrush, new(bounds.X + 4, bounds.Y + 1, 4, 10)); hdc.FillRectangle(fieldBrush, new(bounds.X + 1, bounds.Y + 4, 10, 4)); hdc.DrawLine(borderPen, new(bounds.X + 4, bounds.Y + 0), new(bounds.X + 8, bounds.Y + 0)); hdc.DrawLine(borderPen, new(bounds.X + 4, bounds.Y + 11), new(bounds.X + 8, bounds.Y + 11)); hdc.DrawLine(borderPen, new(bounds.X + 2, bounds.Y + 1), new(bounds.X + 4, bounds.Y + 1)); hdc.DrawLine(borderPen, new(bounds.X + 8, bounds.Y + 1), new(bounds.X + 10, bounds.Y + 1)); hdc.DrawLine(borderPen, new(bounds.X + 2, bounds.Y + 10), new(bounds.X + 4, bounds.Y + 10)); hdc.DrawLine(borderPen, new(bounds.X + 8, bounds.Y + 10), new(bounds.X + 10, bounds.Y + 10)); hdc.DrawLine(borderPen, new(bounds.X + 0, bounds.Y + 4), new(bounds.X + 0, bounds.Y + 8)); hdc.DrawLine(borderPen, new(bounds.X + 11, bounds.Y + 4), new(bounds.X + 11, bounds.Y + 8)); hdc.DrawLine(borderPen, new(bounds.X + 1, bounds.Y + 2), new(bounds.X + 1, bounds.Y + 4)); hdc.DrawLine(borderPen, new(bounds.X + 1, bounds.Y + 8), new(bounds.X + 1, bounds.Y + 10)); hdc.DrawLine(borderPen, new(bounds.X + 10, bounds.Y + 2), new(bounds.X + 10, bounds.Y + 4)); hdc.DrawLine(borderPen, new(bounds.X + 10, bounds.Y + 8), new(bounds.X + 10, bounds.Y + 10)); } private static int GetScaledNumber(int n, double scale) { return (int)(n * scale); } protected void DrawCheckOnly(PaintEventArgs e, LayoutData layout, Color checkColor, bool disabledColors) { if (!Control.Checked) { return; } if (!Control.Enabled && disabledColors) { checkColor = SystemColors.ControlDark; } double scale = GetDpiScaleRatio(); using DeviceContextHdcScope hdc = new(e); using CreateBrushScope brush = new(checkColor); // Circle drawing doesn't work at this size int offset = 5; Rectangle vCross = new( layout.CheckBounds.X + GetScaledNumber(offset, scale), layout.CheckBounds.Y + GetScaledNumber(offset - 1, scale), GetScaledNumber(2, scale), GetScaledNumber(4, scale)); hdc.FillRectangle(vCross, brush); Rectangle hCross = new( layout.CheckBounds.X + GetScaledNumber(offset - 1, scale), layout.CheckBounds.Y + GetScaledNumber(offset, scale), GetScaledNumber(4, scale), GetScaledNumber(2, scale)); hdc.FillRectangle(hCross, brush); } protected ButtonState GetState() { ButtonState style = default; if (Control.Checked) { style |= ButtonState.Checked; } else { style |= ButtonState.Normal; } if (!Control.Enabled) { style |= ButtonState.Inactive; } if (Control.MouseIsDown) { style |= ButtonState.Pushed; } return style; } protected void DrawCheckBox(PaintEventArgs e, LayoutData layout) { Rectangle check = layout.CheckBounds; if (!Application.RenderWithVisualStyles) { check.X--; // compensate for Windows drawing slightly offset to right } ButtonState style = GetState(); if (Application.RenderWithVisualStyles) { using DeviceContextHdcScope hdc = new(e); RadioButtonRenderer.DrawRadioButtonWithVisualStyles( hdc, new Point(check.Left, check.Top), RadioButtonRenderer.ConvertFromButtonState(style, Control.MouseIsOver), Control.HWNDInternal); } else { ControlPaint.DrawRadioButton(e.GraphicsInternal, check, style); } } protected void AdjustFocusRectangle(LayoutData layout) { if (string.IsNullOrEmpty(Control.Text)) { // When a RadioButton has no text, AutoSize sets the size to zero // and thus there's no place around which to draw the focus rectangle. // So, when AutoSize == true we want the focus rectangle to be rendered around the circle area. // Otherwise, it should encircle all the available space next to the box (like it's done in WPF and ComCtl32). layout.Focus = Control.AutoSize ? layout.CheckBounds : layout.Field; } } internal override LayoutOptions CommonLayout() { LayoutOptions layout = base.CommonLayout(); layout.CheckAlign = Control.CheckAlign; return layout; } }