// 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.Runtime.InteropServices;

namespace System.Windows.Forms;

/// <summary>
///  A SplitContainer is a ContainerControl with 2 panels separated with a splitter
///  in the middle. This is a composite control. The user can drag and drop this control from Toolbox.
///  Controls can be added to the right panel and the left panel. The Orientation can be either Horizontal or Vertical.
///  The Controls inside the Panels would be redrawn with the new Orientation.
///  With this control the user need be aware of docking, z-order of the controls. The controls get parented when they are
///  dropped on the SpitContainer.
/// </summary>
[DefaultEvent(nameof(SplitterMoved))]
[Docking(DockingBehavior.AutoDock)]
[Designer($"System.Windows.Forms.Design.SplitContainerDesigner, {Assemblies.SystemDesign}")]
[SRDescription(nameof(SR.DescriptionSplitContainer))]
public partial class SplitContainer : ContainerControl, ISupportInitialize
{
    // Constants used during split container movement
    private const int DrawStart = 1;
    private const int DrawMove = 2;
    private const int DrawEnd = 3;

    private int _borderSize;

    // SplitContainer private Cached copies of public properties
    private Orientation _orientation = Orientation.Vertical;
    private BorderStyle _borderStyle = BorderStyle.None;
    private FixedPanel _fixedPanel = FixedPanel.None;

    private int _panel1MinSize = 25;    // Panel1 Minimum Size
    private int _newPanel1MinSize = 25; // New panel1 Minimum Size used for ISupportInitialize
    private int _panel2MinSize = 25;    // Panel2 Minimum Size
    private int _newPanel2MinSize = 25; // New panel2 Minimum Size used for ISupportInitialize
    private bool _tabStop = true;
    private int _panelSize;

    // Splitter properties
    private Rectangle _splitterRect;
    private int _splitterInc = 1;
    private int _splitterDistance = 50; // Default splitter distance
    private int _splitterWidth = 4;
    private int _newSplitterWidth = 4;  // New splitter width used for ISupportInitialize
    private int _splitDistance = 50;

    // Properties used using drawing a moving splitter
    private int _lastDrawSplit = 1;
    private int _initialSplitterDistance;
    private Point _anchor = Point.Empty;
    private bool _splitBegin;
    private bool _splitMove;
    private bool _splitBreak;

    // Split Cursor
    private Cursor? _overrideCursor;

    // Needed For Tabbing
    private Control? _nextActiveControl;
    private bool _callBaseVersion;
    private bool _splitterFocused;

    // Required to keep track of Splitter movements
    private bool _splitterClick;
    private bool _splitterDrag;

    // FixedPanel.None requires us to keep the Width/Height Ratio Depending on SplitContainer.Orientation
    private double _ratioWidth;
    private double _ratioHeight;
    private bool _resizeCalled;
    private bool _splitContainerScaling;
    private bool _setSplitterDistance;

    // Events
    private static readonly object s_eventMoving = new();
    private static readonly object s_eventMoved = new();

    // IMessageFilter implementation
    private SplitContainerMessageFilter? _splitContainerMessageFilter;

    // This would avoid re-entrant code into SelectNextControl.
    private bool _selectNextControl;

    // Initialization flag for ISupportInitialize
    private bool _initializing;

    public SplitContainer()
    {
        // either the left or top panel - LTR
        // either the right or top panel - RTL
        Panel1 = new SplitterPanel(this);
        // either the right or bottom panel - LTR
        // either the left or bottom panel - RTL
        Panel2 = new SplitterPanel(this);
        _splitterRect = default;

        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

        ((TypedControlCollection)Controls).AddInternal(Panel1);
        ((TypedControlCollection)Controls).AddInternal(Panel2);
        UpdateSplitter();
    }

    /// <summary>
    ///  This property is overridden to allow the AutoScroll to be set on all the panels when
    ///  The autoScroll on SplitContainer is shown.
    ///  Here we don't set the base value ... but set autoscroll for panels.
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [Localizable(true)]
    [DefaultValue(false)]
    [SRDescription(nameof(SR.FormAutoScrollDescr))]
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public override bool AutoScroll
    {
        get
        {
            // Always return false ... as Splitcontainer doesn't support AutoScroll
            return false;
        }
        set => base.AutoScroll = value;
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DefaultValue(typeof(Point), "0, 0")]
    public override Point AutoScrollOffset
    {
        get => base.AutoScrollOffset;
        set => base.AutoScrollOffset = value;
    }

    /// <summary>
    ///  Override AutoScrollMinSize to make it hidden from the user in the designer
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Browsable(false)]
    public new Size AutoScrollMinSize
    {
        get => base.AutoScrollMinSize;
        set => base.AutoScrollMinSize = value;
    }

    /// <summary>
    ///  Override AutoScrollMargin to make it hidden from the user in the designer
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Browsable(false)]
    public new Size AutoScrollMargin
    {
        get => base.AutoScrollMargin;
        set => base.AutoScrollMargin = value;
    }

    [SRCategory(nameof(SR.CatLayout))]
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [SRDescription(nameof(SR.FormAutoScrollPositionDescr))]
    public new Point AutoScrollPosition
    {
        get => base.AutoScrollPosition;
        set => base.AutoScrollPosition = value;
    }

    /// <summary>
    ///  Hide AutoSize, as it can mean more than one thing and might confuse users
    /// </summary>
    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public override bool AutoSize
    {
        get => base.AutoSize;
        set => base.AutoSize = value;
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public new event EventHandler? AutoSizeChanged
    {
        add => base.AutoSizeChanged += value;
        remove => base.AutoSizeChanged -= value;
    }

    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public override Image? BackgroundImage
    {
        get => base.BackgroundImage;
        set => base.BackgroundImage = value;
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public override ImageLayout BackgroundImageLayout
    {
        get => base.BackgroundImageLayout;
        set => base.BackgroundImageLayout = value;
    }

    /// <summary>
    ///  The binding manager for the container control.
    /// </summary>
    [Browsable(false)]
    [SRDescription(nameof(SR.ContainerControlBindingContextDescr))]
    public override BindingContext? BindingContext
    {
        get
        {
            return BindingContextInternal;
        }

        set
        {
            BindingContextInternal = value;
        }
    }

    /// <summary>
    ///  Indicates what type of border the Splitter control has. This value
    ///  comes from the System.Windows.Forms.BorderStyle enumeration.
    /// </summary>
    [DefaultValue(BorderStyle.None)]
    [SRCategory(nameof(SR.CatAppearance))]
    [DispId(PInvokeCore.DISPID_BORDERSTYLE)]
    [SRDescription(nameof(SR.SplitterBorderStyleDescr))]
    public BorderStyle BorderStyle
    {
        get => _borderStyle;
        set
        {
            SourceGenerated.EnumValidator.Validate(value);

            if (_borderStyle != value)
            {
                _borderStyle = value;
                Invalidate();
                SetInnerMostBorder(this);
                if (ParentInternal is SplitterPanel splitterPanel)
                {
                    SplitContainer sc = splitterPanel.Owner;
                    sc.SetInnerMostBorder(sc);
                }
            }

            switch (BorderStyle)
            {
                case BorderStyle.None:
                    _borderSize = 0;
                    break;
                case BorderStyle.FixedSingle:
                    _borderSize = 1;
                    break;
                case BorderStyle.Fixed3D:
                    _borderSize = 4;
                    break;
            }
        }
    }

    /// <summary>
    ///  Controls Collection...
    ///  This is overridden so that the Controls.Add ( ) is not Code Gen'd...
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public new ControlCollection Controls
    {
        get => base.Controls;
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public new event ControlEventHandler? ControlAdded
    {
        add => base.ControlAdded += value;
        remove => base.ControlAdded -= value;
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public new event ControlEventHandler? ControlRemoved
    {
        add => base.ControlRemoved += value;
        remove => base.ControlRemoved -= value;
    }

    /// <summary>
    ///  The dock property. The dock property controls to which edge
    ///  of the container this control is docked to. For example, when docked to
    ///  the top of the container, the control will be displayed flush at the
    ///  top of the container, extending the length of the container.
    /// </summary>
    public new DockStyle Dock
    {
        get => base.Dock;
        set
        {
            base.Dock = value;
            if (ParentInternal is SplitterPanel splitterPanel)
            {
                SplitContainer sc = splitterPanel.Owner;
                sc.SetInnerMostBorder(sc);
            }

            ResizeSplitContainer();
        }
    }

    /// <summary>
    ///  Deriving classes can override this to configure a default size for their control.
    ///  This is more efficient than setting the size in the control's constructor.
    /// </summary>
    protected override Size DefaultSize
    {
        get
        {
            return new Size(150, 100);
        }
    }

    /// <summary>
    ///  Indicates what type of border the Splitter control has. This value
    ///  comes from the System.Windows.Forms.BorderStyle enumeration.
    /// </summary>
    [DefaultValue(FixedPanel.None)]
    [SRCategory(nameof(SR.CatLayout))]
    [SRDescription(nameof(SR.SplitContainerFixedPanelDescr))]
    public FixedPanel FixedPanel
    {
        get
        {
            return _fixedPanel;
        }

        set
        {
            // valid values are 0x0 to 0x2
            SourceGenerated.EnumValidator.Validate(value);

            if (_fixedPanel != value)
            {
                _fixedPanel = value;
                // UpdatePanelSize !!
                switch (_fixedPanel)
                {
                    case FixedPanel.Panel2:
                        if (Orientation == Orientation.Vertical)
                        {
                            _panelSize = Width - SplitterDistanceInternal - SplitterWidthInternal;
                        }
                        else
                        {
                            _panelSize = Height - SplitterDistanceInternal - SplitterWidthInternal;
                        }

                        break;
                    default:
                        _panelSize = SplitterDistanceInternal;
                        break;
                }
            }
        }
    }

    /// <summary>
    ///  This property determines whether the splitter can move.
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [DefaultValue(false)]
    [Localizable(true)]
    [SRDescription(nameof(SR.SplitContainerIsSplitterFixedDescr))]

    public bool IsSplitterFixed { get; set; }

    // Private property used to check whether the splitter can be moved by the user.
    private bool IsSplitterMovable
    {
        get
        {
            if (Orientation == Orientation.Vertical)
            {
                return (Width >= Panel1MinSize + SplitterWidthInternal + Panel2MinSize);
            }
            else
            {
                return (Height >= Panel1MinSize + SplitterWidthInternal + Panel2MinSize);
            }
        }
    }

    // Refer to IsContainerControl property on Control for more details.
    internal override bool IsContainerControl
    {
        get
        {
            return true;
        }
    }

    /// <summary>
    ///  This Property sets or gets if the splitter is vertical or horizontal.
    /// </summary>
    [SRCategory(nameof(SR.CatBehavior))]
    [DefaultValue(Orientation.Vertical)]
    [Localizable(true)]
    [SRDescription(nameof(SR.SplitContainerOrientationDescr))]
    public Orientation Orientation
    {
        get { return _orientation; }
        set
        {
            // valid values are 0x0 to 0x1
            SourceGenerated.EnumValidator.Validate(value);
            if (_orientation != value)
            {
                _orientation = value;
                // update the splitterDistance to validate it w.r.t the new Orientation.
                _splitDistance = 0;
                SplitterDistance = SplitterDistanceInternal;
                UpdateSplitter();
            }
        }
    }

    private Cursor? OverrideCursor
    {
        get => _overrideCursor;
        set
        {
            if (_overrideCursor == value)
            {
                return;
            }

            _overrideCursor = value;

            if (IsHandleCreated)
            {
                // We want to instantly change the cursor if the mouse is within our bounds.
                PInvoke.GetCursorPos(out Point p);
                PInvokeCore.GetWindowRect(this, out var r);
                if ((r.left <= p.X && p.X < r.right && r.top <= p.Y && p.Y < r.bottom) || PInvoke.GetCapture() == HWND)
                {
                    PInvokeCore.SendMessage(this, PInvokeCore.WM_SETCURSOR, (WPARAM)HWND, (LPARAM)(int)PInvoke.HTCLIENT);
                }
            }
        }
    }

    /// <summary>
    ///  Indicates if either panel is collapsed
    /// </summary>
    private bool CollapsedMode
    {
        get
        {
            return Panel1Collapsed || Panel2Collapsed;
        }
    }

    /// <summary>
    ///  The Left or Top panel in the SplitContainer.
    /// </summary>
    [SRCategory(nameof(SR.CatAppearance))]
    [SRDescription(nameof(SR.SplitContainerPanel1Descr))]
    [Localizable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public SplitterPanel Panel1 { get; }

    /// <summary>
    ///  Collapses or restores the given panel
    /// </summary>
    private void CollapsePanel(SplitterPanel p, bool collapsing)
    {
        p.Collapsed = collapsing;
        if (collapsing)
        {
            p.Visible = false;
        }
        else
        {
            // restore panel
            p.Visible = true;
        }

        UpdateSplitter();
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public new Padding Padding
    {
        get => base.Padding;
        set => base.Padding = value;
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public new event EventHandler? PaddingChanged
    {
        add => base.PaddingChanged += value;
        remove => base.PaddingChanged -= value;
    }

    /// <summary>
    ///  Collapses or restores panel1
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [DefaultValue(false)]
    [SRDescription(nameof(SR.SplitContainerPanel1CollapsedDescr))]
    public bool Panel1Collapsed
    {
        get
        {
            return Panel1.Collapsed;
        }
        set
        {
            if (value != Panel1.Collapsed)
            {
                if (value && Panel2.Collapsed)
                {
                    CollapsePanel(Panel2, false);
                }

                CollapsePanel(Panel1, value);
            }
        }
    }

    /// <summary>
    ///  Collapses or restores panel2
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [DefaultValue(false)]
    [SRDescription(nameof(SR.SplitContainerPanel2CollapsedDescr))]
    public bool Panel2Collapsed
    {
        get
        {
            return Panel2.Collapsed;
        }
        set
        {
            if (value != Panel2.Collapsed)
            {
                if (value && Panel1.Collapsed)
                {
                    CollapsePanel(Panel1, false);
                }

                CollapsePanel(Panel2, value);
            }
        }
    }

    /// <summary>
    ///  This property determines the minimum distance of pixels of the splitter from the left or the top edge of Panel1.
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [DefaultValue(25)]
    [Localizable(true)]
    [SRDescription(nameof(SR.SplitContainerPanel1MinSizeDescr))]
    [RefreshProperties(RefreshProperties.All)]
    public int Panel1MinSize
    {
        get
        {
            return _panel1MinSize;
        }
        set
        {
            _newPanel1MinSize = value;
            if (value != Panel1MinSize && !_initializing)
            {
                ApplyPanel1MinSize(value);
            }
        }
    }

    /// <summary>
    ///  This is the Right or Bottom panel in the SplitContainer.
    /// </summary>
    [SRCategory(nameof(SR.CatAppearance))]
    [SRDescription(nameof(SR.SplitContainerPanel2Descr))]
    [Localizable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public SplitterPanel Panel2 { get; }

    /// <summary>
    ///  This property determines the minimum distance of pixels of the splitter from the right or the bottom edge of Panel2
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [DefaultValue(25)]
    [Localizable(true)]
    [SRDescription(nameof(SR.SplitContainerPanel2MinSizeDescr))]
    [RefreshProperties(RefreshProperties.All)]
    public int Panel2MinSize
    {
        get
        {
            return _panel2MinSize;
        }
        set
        {
            _newPanel2MinSize = value;
            if (value != Panel2MinSize && !_initializing)
            {
                ApplyPanel2MinSize(value);
            }
        }
    }

    /// <summary>
    ///  This property determines pixel distance of the splitter from the left or top edge.
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [Localizable(true),
    SettingsBindable(true)]
    [SRDescription(nameof(SR.SplitContainerSplitterDistanceDescr))]
    [DefaultValue(50)]
    public int SplitterDistance
    {
        get
        {
            return _splitDistance;
        }
        set
        {
            if (value != SplitterDistance)
            {
                ArgumentOutOfRangeException.ThrowIfNegative(value, nameof(SplitterDistance));

                try
                {
                    _setSplitterDistance = true;

                    if (Orientation == Orientation.Vertical)
                    {
                        if (value < Panel1MinSize)
                        {
                            value = Panel1MinSize;
                        }

                        if (value + SplitterWidthInternal > Width - Panel2MinSize)
                        {
                            value = Width - Panel2MinSize - SplitterWidthInternal;
                        }

                        if (value < 0)
                        {
                            throw new InvalidOperationException(SR.SplitterDistanceNotAllowed);
                        }

                        _splitDistance = value;
                        _splitterDistance = value;
                        Panel1.WidthInternal = SplitterDistance;
                    }
                    else
                    {
                        if (value < Panel1MinSize)
                        {
                            value = Panel1MinSize;
                        }

                        if (value + SplitterWidthInternal > Height - Panel2MinSize)
                        {
                            value = Height - Panel2MinSize - SplitterWidthInternal;
                        }

                        if (value < 0)
                        {
                            throw new InvalidOperationException(SR.SplitterDistanceNotAllowed);
                        }

                        _splitDistance = value;
                        _splitterDistance = value;
                        Panel1.HeightInternal = SplitterDistance;
                    }

                    switch (_fixedPanel)
                    {
                        case FixedPanel.Panel1:
                            _panelSize = SplitterDistance;
                            break;
                        case FixedPanel.Panel2:
                            if (Orientation == Orientation.Vertical)
                            {
                                _panelSize = Width - SplitterDistance - SplitterWidthInternal;
                            }
                            else
                            {
                                _panelSize = Height - SplitterDistance - SplitterWidthInternal;
                            }

                            break;
                    }

                    UpdateSplitter();
                }
                finally
                {
                    _setSplitterDistance = false;
                }

                OnSplitterMoved(new SplitterEventArgs(SplitterRectangle.X + SplitterRectangle.Width / 2, SplitterRectangle.Y + SplitterRectangle.Height / 2, SplitterRectangle.X, SplitterRectangle.Y));
            }
        }
    }

    private int SplitterDistanceInternal
    {
        get
        {
            return _splitterDistance;
        }
        set
        {
            SplitterDistance = value;
        }
    }

    /// <summary>
    ///  This determines the number of pixels the splitter moves in increments.This is defaulted to 1.
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [DefaultValue(1)]
    [Localizable(true)]
    [SRDescription(nameof(SR.SplitContainerSplitterIncrementDescr))]
    public int SplitterIncrement
    {
        get
        {
            return _splitterInc;
        }
        set
        {
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);

            _splitterInc = value;
        }
    }

    /// <summary>
    ///  This property determines the rectangle bounds of the splitter.
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [SRDescription(nameof(SR.SplitContainerSplitterRectangleDescr))]
    [Browsable(false)]
    public Rectangle SplitterRectangle
    {
        get
        {
            Rectangle r = _splitterRect;
            r.X = _splitterRect.X - Left;
            r.Y = _splitterRect.Y - Top;
            return r;
        }
    }

    /// <summary>
    ///  This property determines the thickness of the splitter.
    /// </summary>
    [SRCategory(nameof(SR.CatLayout))]
    [SRDescription(nameof(SR.SplitContainerSplitterWidthDescr))]
    [Localizable(true)]
    [DefaultValue(4)]
    public int SplitterWidth
    {
        get
        {
            return _splitterWidth;
        }
        set
        {
            _newSplitterWidth = value;
            if (value != SplitterWidth && !_initializing)
            {
                ApplySplitterWidth(value);
            }
        }
    }

    /// <summary>
    ///  We need to have a internal Property for the SplitterWidth which returns zero if we are in collapsed mode.
    ///  This property is used to Layout SplitContainer.
    /// </summary>
    private int SplitterWidthInternal
    {
        get
        {
            // if CollapsedMode then splitterwidth == 0;
            return CollapsedMode ? 0 : _splitterWidth;
        }
    }

    internal override bool SupportsUiaProviders => true;

    /// <summary>
    ///  Indicates whether the user can give the focus to this control using the TAB
    ///  key. This property is read-only.
    /// </summary>
    [SRCategory(nameof(SR.CatBehavior))]
    [DefaultValue(true)]
    [DispId(PInvokeCore.DISPID_TABSTOP)]
    [SRDescription(nameof(SR.ControlTabStopDescr))]
    public new bool TabStop
    {
        get => _tabStop;
        set
        {
            if (TabStop != value)
            {
                _tabStop = value;
                OnTabStopChanged(EventArgs.Empty);
            }
        }
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Bindable(false)]
    [AllowNull]
    public override string Text
    {
        get => base.Text;
        set => base.Text = value;
    }

    /// <summary>
    ///  ISupportInitialize support. Disables splitter panel min size and splitter width
    ///  validation during initialization.
    /// </summary>
    public void BeginInit()
    {
        _initializing = true;
    }

    /// <summary>
    ///  ISupportInitialize support. Enables splitter panel min size and splitter width
    ///  validation after initialization.
    /// </summary>
    public void EndInit()
    {
        _initializing = false;

        // validate and apply new value
        if (_newPanel1MinSize != _panel1MinSize)
        {
            ApplyPanel1MinSize(_newPanel1MinSize);
        }

        if (_newPanel2MinSize != _panel2MinSize)
        {
            ApplyPanel2MinSize(_newPanel2MinSize);
        }

        if (_newSplitterWidth != _splitterWidth)
        {
            ApplySplitterWidth(_newSplitterWidth);
        }
    }

    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public new event EventHandler? BackgroundImageChanged
    {
        add => base.BackgroundImageChanged += value;
        remove => base.BackgroundImageChanged -= value;
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public new event EventHandler? BackgroundImageLayoutChanged
    {
        add => base.BackgroundImageLayoutChanged += value;
        remove => base.BackgroundImageLayoutChanged -= value;
    }

    [SRCategory(nameof(SR.CatBehavior))]
    [SRDescription(nameof(SR.SplitterSplitterMovingDescr))]
    public event SplitterCancelEventHandler? SplitterMoving
    {
        add => Events.AddHandler(s_eventMoving, value);
        remove => Events.RemoveHandler(s_eventMoving, value);
    }

    [SRCategory(nameof(SR.CatBehavior))]
    [SRDescription(nameof(SR.SplitterSplitterMovedDescr))]
    public event SplitterEventHandler? SplitterMoved
    {
        add => Events.AddHandler(s_eventMoved, value);
        remove => Events.RemoveHandler(s_eventMoved, value);
    }

    [Browsable(false)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public new event EventHandler? TextChanged
    {
        add => base.TextChanged += value;
        remove => base.TextChanged -= value;
    }

    /// <summary>
    ///  Overrides the Control.OnGotFocus to Invalidate...
    /// </summary>
    protected override void OnGotFocus(EventArgs e)
    {
        base.OnGotFocus(e);
        Invalidate();
    }

    /// <summary>
    ///  Overrides the Control.OnKeydown for implementing splitter movements.
    /// </summary>
    protected override void OnKeyDown(KeyEventArgs e)
    {
        Debug.Assert(Enabled, "SplitContainer.OnKeyDown should not be called if the button is disabled");
        base.OnKeyDown(e);
        // If the Panel1MinSize + Panel2MinSize < SplitContainer.Size then carry on the splitter move...
        if (IsSplitterMovable && !IsSplitterFixed)
        {
            if (e.KeyData == Keys.Escape && _splitBegin)
            {
                _splitBegin = false;
                _splitBreak = true;
                return;
            }

            // valid Keys that move the splitter...
            if (_splitterFocused
                && (e.KeyData == Keys.Right || e.KeyData == Keys.Down || e.KeyData == Keys.Left || e.KeyData == Keys.Up))
            {
                if (_splitBegin)
                {
                    _splitMove = true;
                }

                // left OR up
                if (_splitterFocused && (e.KeyData == Keys.Left || e.KeyData == Keys.Up))
                {
                    _splitterDistance -= SplitterIncrement;
                    _splitterDistance = (_splitterDistance < Panel1MinSize) ? _splitterDistance + SplitterIncrement : Math.Max(_splitterDistance, _borderSize);
                }

                // right OR down
                if (_splitterFocused && (e.KeyData == Keys.Right || e.KeyData == Keys.Down))
                {
                    _splitterDistance += SplitterIncrement;
                    if (Orientation == Orientation.Vertical)
                    {
                        _splitterDistance = (_splitterDistance + SplitterWidth > Width - Panel2MinSize - _borderSize) ? _splitterDistance - SplitterIncrement : _splitterDistance;
                    }
                    else
                    {
                        _splitterDistance = (_splitterDistance + SplitterWidth > Height - Panel2MinSize - _borderSize) ? _splitterDistance - SplitterIncrement : _splitterDistance;
                    }
                }

                if (!_splitBegin)
                {
                    _splitBegin = true;
                }

                // draw Helper start
                if (_splitBegin && !_splitMove)
                {
                    _initialSplitterDistance = SplitterDistanceInternal;
                    DrawSplitBar(DrawStart);
                }
                else
                {
                    // draw helper move
                    DrawSplitBar(DrawMove);
                    // Moving by mouse .....gives the origin of the splitter..
                    Rectangle r = CalcSplitLine(_splitterDistance, 0);
                    int xSplit = r.X;
                    int ySplit = r.Y;
                    SplitterCancelEventArgs se = new(Left + SplitterRectangle.X + SplitterRectangle.Width / 2, Top + SplitterRectangle.Y + SplitterRectangle.Height / 2, xSplit, ySplit);
                    OnSplitterMoving(se);
                    if (se.Cancel)
                    {
                        SplitEnd(false);
                    }
                }
            } // End Valid Keys....
        } // End SplitterFixed Check...
    }

    /// <summary>
    ///  Overrides the Control.OnKeydown for implementing splitter movements.
    /// </summary>
    protected override void OnKeyUp(KeyEventArgs e)
    {
        base.OnKeyUp(e);
        if (_splitBegin && IsSplitterMovable)
        {
            if (_splitterFocused
                && (e.KeyData == Keys.Right || e.KeyData == Keys.Down || e.KeyData == Keys.Left || e.KeyData == Keys.Up))
            {
                DrawSplitBar(DrawEnd);
                ApplySplitterDistance();
                _splitBegin = false;
                _splitMove = false;
            }
        }

        if (_splitBreak)
        {
            _splitBreak = false;
            SplitEnd(false);
        }

        // Problem after KeyUp- focus rect and reversible lines leave a trace behind on the splitter.
        using Graphics g = CreateGraphicsInternal();
        if (BackgroundImage is null)
        {
            using var brush = BackColor.GetCachedSolidBrushScope();
            g.FillRectangle(brush, SplitterRectangle);
        }

        DrawFocus(g, SplitterRectangle);
    }

    /// <summary>
    ///  Overrides the Control.OnLayout.
    /// </summary>
    protected override void OnLayout(LayoutEventArgs e)
    {
        SetInnerMostBorder(this);

        if (IsSplitterMovable && !_setSplitterDistance)
        {
            ResizeSplitContainer();
        }

        base.OnLayout(e);
    }

    /// <summary>
    ///  Overrides the Control.OnLostFocus to Invalidate.
    /// </summary>
    protected override void OnLostFocus(EventArgs e)
    {
        base.OnLostFocus(e);
        Invalidate();
    }

    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        if (!IsSplitterFixed && IsSplitterMovable)
        {
            // change cursor if default and user hasnt changed the cursor.
            if (Cursor == DefaultCursor && SplitterRectangle.Contains(e.Location))
            {
                if (Orientation == Orientation.Vertical)
                {
                    OverrideCursor = Cursors.VSplit;
                }
                else
                {
                    OverrideCursor = Cursors.HSplit;
                }
            }
            else
            {
                OverrideCursor = null;
            }

            if (_splitterClick)
            {
                int x = e.X;
                int y = e.Y;
                _splitterDrag = true;
                SplitMove(x, y);
                if (Orientation == Orientation.Vertical)
                {
                    x = Math.Max(Math.Min(x, Width - Panel2MinSize), Panel1MinSize);
                    y = Math.Max(y, 0);
                }
                else
                {
                    y = Math.Max(Math.Min(y, Height - Panel2MinSize), Panel1MinSize);
                    x = Math.Max(x, 0);
                }

                Rectangle r = CalcSplitLine(GetSplitterDistance(e.X, e.Y), 0);
                int xSplit = r.X;
                int ySplit = r.Y;
                SplitterCancelEventArgs se = new(x, y, xSplit, ySplit);
                OnSplitterMoving(se);
                if (se.Cancel)
                {
                    SplitEnd(false);
                }
            }
        }
    }

    /// <summary>
    ///  Raises the <see cref="OnMouseLeave"/> event.
    /// </summary>
    protected override void OnMouseLeave(EventArgs e)
    {
        base.OnMouseLeave(e);
        if (!Enabled)
        {
            return;
        }

        OverrideCursor = null;
    }

    /// <summary>
    ///  Raises the <see cref="OnMouseDown"/> event.
    /// </summary>
    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        // If the Panel1MinSize + Panel2MinSize < SplitContainer.Size then carry on the splitter move...
        if (IsSplitterMovable && SplitterRectangle.Contains(e.Location))
        {
            if (!Enabled)
            {
                return;
            }

            if (e.Button == MouseButtons.Left && e.Clicks == 1 && !IsSplitterFixed)
            {
                // Focus the current splitter OnMouseDown.
                _splitterFocused = true;
                IContainerControl? c = ParentInternal?.GetContainerControl();
                if (c is not null)
                {
                    if (c is not ContainerControl cc)
                    {
                        c.ActiveControl = this;
                    }
                    else
                    {
                        cc.SetActiveControl(this);
                    }
                }

                SetActiveControl(null);
                _nextActiveControl = Panel2;

                SplitBegin(e.X, e.Y);
                _splitterClick = true;
            }
        }
    }

    /// <summary>
    ///  Raises the <see cref="OnMouseUp"/> event.
    /// </summary>
    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);
        if (!Enabled)
        {
            return;
        }

        if (!IsSplitterFixed && IsSplitterMovable && _splitterClick)
        {
            Capture = false;

            if (_splitterDrag)
            {
                CalcSplitLine(GetSplitterDistance(e.X, e.Y), 0);
                SplitEnd(true);
            }
            else
            {
                SplitEnd(false);
            }

            _splitterClick = false;
            _splitterDrag = false;
        }
    }

    /// <summary>
    ///  Overrides the Control.OnMove() to synchronize the
    ///  splitterRect with the position of the SplitContainer.
    /// </summary>
    protected override void OnMove(EventArgs e)
    {
        base.OnMove(e);
        SetSplitterRect(Orientation == Orientation.Vertical);
    }

    /// <summary>
    ///  Overrides the Control.OnPaint() to focus the Splitter.
    /// </summary>
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        if (Focused)
        {
            DrawFocus(e.Graphics, SplitterRectangle);
        }
    }

    /// <summary>
    ///  Inheriting classes should override this method to respond to the
    ///  splitterMoving event. This event occurs while the splitter is
    ///  being moved by the user.
    /// </summary>
    public void OnSplitterMoving(SplitterCancelEventArgs e)
    {
        ((SplitterCancelEventHandler?)Events[s_eventMoving])?.Invoke(this, e);
    }

    /// <summary>
    ///  Inheriting classes should override this method to respond to the
    ///  splitterMoved event. This event occurs when the user finishes
    ///  moving the splitter.
    /// </summary>
    public void OnSplitterMoved(SplitterEventArgs e)
    {
        ((SplitterEventHandler?)Events[s_eventMoved])?.Invoke(this, e);
    }

    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected override void OnRightToLeftChanged(EventArgs e)
    {
        base.OnRightToLeftChanged(e);
        // pass the RightToLeft value to the Parent.
        Panel1.RightToLeft = RightToLeft;
        Panel2.RightToLeft = RightToLeft;
        UpdateSplitter();
    }

    /// <summary>
    ///  Validate and set the minimum size for Panel1.
    /// </summary>
    private void ApplyPanel1MinSize(int value)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(value);

        if (Orientation == Orientation.Vertical)
        {
            if (DesignMode && Width != DefaultSize.Width && value + Panel2MinSize + SplitterWidth > Width)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.InvalidArgument, nameof(Panel1MinSize), value));
            }
        }
        else if (Orientation == Orientation.Horizontal)
        {
            if (DesignMode && Height != DefaultSize.Height && value + Panel2MinSize + SplitterWidth > Height)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.InvalidArgument, nameof(Panel1MinSize), value));
            }
        }

        _panel1MinSize = value;
        if (value > SplitterDistanceInternal)
        {
            SplitterDistanceInternal = value;  // Set the Splitter Distance to the end of Panel1
        }
    }

    /// <summary>
    ///  Validate and set the minimum size for Panel2.
    /// </summary>
    private void ApplyPanel2MinSize(int value)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(value);

        if (Orientation == Orientation.Vertical)
        {
            if (DesignMode && Width != DefaultSize.Width && value + Panel1MinSize + SplitterWidth > Width)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.InvalidArgument, nameof(Panel2MinSize), value.ToString()));
            }
        }
        else if (Orientation == Orientation.Horizontal)
        {
            if (DesignMode && Height != DefaultSize.Height && value + Panel1MinSize + SplitterWidth > Height)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.InvalidArgument, nameof(Panel2MinSize), value));
            }
        }

        _panel2MinSize = value;
        if (value > Panel2.Width)
        {
            SplitterDistanceInternal = Panel2.Width + SplitterWidthInternal;  // Set the Splitter Distance to the start of Panel2
        }
    }

    /// <summary>
    ///  Validate and set the splitter width.
    /// </summary>
    private void ApplySplitterWidth(int value)
    {
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);

        if (Orientation == Orientation.Vertical)
        {
            if (DesignMode && value + Panel1MinSize + Panel2MinSize > Width)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.InvalidArgument, nameof(SplitterWidth), value));
            }
        }
        else if (Orientation == Orientation.Horizontal)
        {
            if (DesignMode && value + Panel1MinSize + Panel2MinSize > Height)
            {
                throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.InvalidArgument, nameof(SplitterWidth), value));
            }
        }

        _splitterWidth = value;
        UpdateSplitter();
    }

    /// <summary>
    ///  Sets the split position to be the current split size. This is called
    ///  by splitEdit
    /// </summary>
    private void ApplySplitterDistance()
    {
        using (new Layout.LayoutTransaction(this, this, "SplitterDistance", false))
        {
            SplitterDistanceInternal = _splitterDistance;
        }

        // We need to invalidate when we have transparent background.
        if (BackColor == Color.Transparent)
        {
            // the panel1 retains the focus rect... so Invalidate the rect ...
            Invalidate();
        }

        if (Orientation == Orientation.Vertical)
        {
            if (RightToLeft == RightToLeft.No)
            {
                _splitterRect.X = Location.X + SplitterDistanceInternal;
            }
            else
            {
                _splitterRect.X = Right - SplitterDistanceInternal - SplitterWidthInternal;
            }
        }
        else
        {
            _splitterRect.Y = Location.Y + SplitterDistanceInternal;
        }
    }

    /// <summary>
    ///  Calculates the bounding rect of the split line. minWeight refers
    ///  to the minimum height or width of the splitline.
    /// </summary>
    private Rectangle CalcSplitLine(int splitSize, int minWeight)
    {
        Rectangle r = default;
        switch (Orientation)
        {
            case Orientation.Vertical:
                r.Width = SplitterWidthInternal;
                r.Height = Height;
                if (r.Width < minWeight)
                {
                    r.Width = minWeight;
                }

                if (RightToLeft == RightToLeft.No)
                {
                    r.X = Panel1.Location.X + splitSize;
                }
                else
                {
                    r.X = Width - splitSize - SplitterWidthInternal;
                }

                break;

            case Orientation.Horizontal:
                r.Width = Width;
                r.Height = SplitterWidthInternal;
                if (r.Width < minWeight)
                {
                    r.Width = minWeight;
                }

                r.Y = Panel1.Location.Y + splitSize;
                break;
        }

        return r;
    }

    protected override AccessibleObject CreateAccessibilityInstance()
        => new SplitContainerAccessibleObject(this);

    /// <summary>
    ///  Draws the splitter bar at the current location. Will automatically
    ///  cleanup anyplace the splitter was drawn previously.
    /// </summary>
    private void DrawSplitBar(int mode)
    {
        if (mode != DrawStart && _lastDrawSplit != -1)
        {
            DrawSplitHelper(_lastDrawSplit);
            _lastDrawSplit = -1;
        }

        // Bail if drawing with no old point...
        //
        else if (mode != DrawStart && _lastDrawSplit == -1)
        {
            return;
        }

        if (mode != DrawEnd)
        {
            if (_splitMove || _splitBegin)
            { // Splitter is moved by keys and not by mouse
                DrawSplitHelper(_splitterDistance);
                _lastDrawSplit = _splitterDistance;
            }
            else
            {
                DrawSplitHelper(_splitterDistance);
                _lastDrawSplit = _splitterDistance;
            }
        }
        else
        {
            if (_lastDrawSplit != -1)
            {
                DrawSplitHelper(_lastDrawSplit);
            }

            _lastDrawSplit = -1;
        }
    }

    /// <summary>
    ///  Draws the focus rectangle if the control has focus.
    /// </summary>
    private void DrawFocus(Graphics g, Rectangle r)
    {
        r.Inflate(-1, -1);
        ControlPaint.DrawFocusRectangle(g, r, ForeColor, BackColor);
    }

    /// <summary>
    ///  Draws the splitter line at the requested location. Should only be called
    ///  by drawSplitBar.
    /// </summary>
    private void DrawSplitHelper(int splitSize)
    {
        Rectangle r = CalcSplitLine(splitSize, 3);
        using GetDcScope dc = new(HWND, HRGN.Null, GET_DCX_FLAGS.DCX_CACHE | GET_DCX_FLAGS.DCX_LOCKWINDOWUPDATE);
        HBRUSH halftone = ControlPaint.CreateHalftoneHBRUSH();
        using ObjectScope objectScope = new(halftone);
        using SelectObjectScope selectBrush = new(dc, halftone);
        PInvoke.PatBlt(dc, r.X, r.Y, r.Width, r.Height, ROP_CODE.PATINVERT);

        GC.KeepAlive(this);
    }

    /// <summary>
    ///  Calculates the split size based on the mouse position (x, y).
    /// </summary>
    private int GetSplitterDistance(int x, int y)
    {
        int delta;
        if (Orientation == Orientation.Vertical)
        {
            delta = x - _anchor.X;
        }
        else
        {
            delta = y - _anchor.Y;
        }

        // Negative delta - moving to the left
        // Positive delta - moving to the right

        int size = 0;
        switch (Orientation)
        {
            case Orientation.Vertical:
                if (RightToLeft == RightToLeft.No)
                {
                    size = Math.Max(Panel1.Width + delta, _borderSize);
                }
                else
                {
                    // In RTL negative delta actually means increasing the size....
                    size = Math.Max(Panel1.Width - delta, _borderSize);
                }

                break;
            case Orientation.Horizontal:
                size = Math.Max(Panel1.Height + delta, _borderSize);
                break;
        }

        if (Orientation == Orientation.Vertical)
        {
            return Math.Max(Math.Min(size, Width - Panel2MinSize), Panel1MinSize);
        }
        else
        {
            return Math.Max(Math.Min(size, Height - Panel2MinSize), Panel1MinSize);
        }
    }

    /// <summary>
    ///  Process an arrowKey press by selecting the next control in the group
    ///  that the activeControl belongs to.
    /// </summary>
    private bool ProcessArrowKey(bool forward)
    {
        Control? group = this;
        if (ActiveControl is not null)
        {
            group = ActiveControl.ParentInternal;
        }

        return group?.SelectNextControl(ActiveControl, forward, false, false, true) ?? false;
    }

    /// <summary>
    ///  Re paint SplitterRect for SplitContainer
    /// </summary>
    private void RepaintSplitterRect()
    {
        if (IsHandleCreated)
        {
            try
            {
                using Graphics g = CreateGraphicsInternal();
                if (BackgroundImage is not null)
                {
                    using TextureBrush textureBrush = new(BackgroundImage, WrapMode.Tile);
                    g.FillRectangle(textureBrush, ClientRectangle);
                }
                else
                {
                    using var solidBrush = BackColor.GetCachedSolidBrushScope();
                    g.FillRectangle(solidBrush, _splitterRect);
                }
            }
            catch (ExternalException ex)
            {
                // GDI+ can transiently fail while the display session is transitioning (for example, lock/unlock).
                // Ignore repaint failures here and let normal painting recover once graphics resources are available.
                Debug.Fail($"{nameof(RepaintSplitterRect)} failed with hr={ex.ErrorCode}, message={ex}");
            }
        }
    }

    private void SetSplitterRect(bool vertical)
    {
        if (vertical)
        {
            _splitterRect.X = ((RightToLeft == RightToLeft.Yes) ? Width - _splitterDistance - SplitterWidthInternal : Location.X + _splitterDistance);
            _splitterRect.Y = Location.Y;
            _splitterRect.Width = SplitterWidthInternal;
            _splitterRect.Height = Height;
        }
        else
        {
            _splitterRect.X = Location.X;
            _splitterRect.Y = Location.Y + SplitterDistanceInternal;
            _splitterRect.Width = Width;
            _splitterRect.Height = SplitterWidthInternal;
        }
    }

    /// <summary>
    ///  Resize SplitContainer
    /// </summary>
    private void ResizeSplitContainer()
    {
        if (_splitContainerScaling)
        {
            return;
        }

        Panel1.SuspendLayout();
        Panel2.SuspendLayout();

        if (Width == 0)
        {
            // Set the correct Width iif the WIDTH has changed to ZERO.
            Panel1.Size = new Size(0, Panel1.Height);
            Panel2.Size = new Size(0, Panel2.Height);
        }
        else if (Height == 0)
        {
            // Set the correct Height iif the HEIGHT has changed to ZERO.
            Panel1.Size = new Size(Panel1.Width, 0);
            Panel2.Size = new Size(Panel2.Width, 0);
        }
        else
        {
            if (Orientation == Orientation.Vertical)
            {
                // If no panel is collapsed then do the default ...
                if (!CollapsedMode)
                {
                    if (FixedPanel == FixedPanel.Panel1)
                    {
                        Panel1.Size = new Size(_panelSize, Height);
                        Panel2.Size = new Size(Math.Max(Width - _panelSize - SplitterWidthInternal, Panel2MinSize), Height);
                    }

                    if (FixedPanel == FixedPanel.Panel2)
                    {
                        Panel2.Size = new Size(_panelSize, Height);
                        _splitterDistance = Math.Max(Width - _panelSize - SplitterWidthInternal, Panel1MinSize);
                        Panel1.WidthInternal = _splitterDistance;
                        Panel1.HeightInternal = Height;
                    }

                    if (FixedPanel == FixedPanel.None)
                    {
                        if (_ratioWidth != 0.0)
                        {
                            _splitterDistance = Math.Max((int)(Math.Floor(Width / _ratioWidth)), Panel1MinSize);
                        }

                        Panel1.WidthInternal = _splitterDistance; // Default splitter distance from left or top.
                        Panel1.HeightInternal = Height;
                        Panel2.Size = new Size(Math.Max(Width - _splitterDistance - SplitterWidthInternal, Panel2MinSize), Height);
                    }

                    if (RightToLeft == RightToLeft.No)
                    {
                        Panel2.Location = new Point(Panel1.WidthInternal + SplitterWidthInternal, 0);
                    }
                    else
                    {
                        Panel1.Location = new Point(Width - Panel1.WidthInternal, 0);
                    }

                    RepaintSplitterRect();
                    SetSplitterRect(true);
                }
                else
                {
                    if (Panel1Collapsed)
                    {
                        Panel2.Size = Size;
                        Panel2.Location = new Point(0, 0);
                    }
                    else if (Panel2Collapsed)
                    {
                        Panel1.Size = Size;
                        Panel1.Location = new Point(0, 0);
                    }
                }
            }
            else if (Orientation == Orientation.Horizontal)
            {
                // If no panel is collapsed then do the default ...
                if (!CollapsedMode)
                {
                    if (FixedPanel == FixedPanel.Panel1)
                    {
                        // Default splitter distance from left or top.
                        Panel1.Size = new Size(Width, _panelSize);
                        int panel2Start = _panelSize + SplitterWidthInternal;
                        Panel2.Size = new Size(Width, Math.Max(Height - panel2Start, Panel2MinSize));
                        Panel2.Location = new Point(0, panel2Start);
                    }

                    if (FixedPanel == FixedPanel.Panel2)
                    {
                        Panel2.Size = new Size(Width, _panelSize);
                        _splitterDistance = Math.Max(Height - Panel2.Height - SplitterWidthInternal, Panel1MinSize);
                        Panel1.HeightInternal = _splitterDistance;
                        Panel1.WidthInternal = Width;
                        int panel2Start = _splitterDistance + SplitterWidthInternal;
                        Panel2.Location = new Point(0, panel2Start);
                    }

                    if (FixedPanel == FixedPanel.None)
                    {
                        // NO PANEL FIXED !!
                        if (_ratioHeight != 0.0)
                        {
                            _splitterDistance = Math.Max((int)(Math.Floor(Height / _ratioHeight)), Panel1MinSize);
                        }

                        Panel1.HeightInternal = _splitterDistance; // Default splitter distance from left or top.
                        Panel1.WidthInternal = Width;
                        int panel2Start = _splitterDistance + SplitterWidthInternal;
                        Panel2.Size = new Size(Width, Math.Max(Height - panel2Start, Panel2MinSize));
                        Panel2.Location = new Point(0, panel2Start);
                    }

                    RepaintSplitterRect();
                    SetSplitterRect(false);
                }
                else
                {
                    if (Panel1Collapsed)
                    {
                        Panel2.Size = Size;
                        Panel2.Location = new Point(0, 0);
                    }
                    else if (Panel2Collapsed)
                    {
                        Panel1.Size = Size;
                        Panel1.Location = new Point(0, 0);
                    }
                }
            }

            try
            {
                _resizeCalled = true;
                ApplySplitterDistance();
            }
            finally
            {
                _resizeCalled = false;
            }
        }

        Panel1.ResumeLayout();
        Panel2.ResumeLayout();
    }

    /// <summary>
    ///  Scales an individual control's location, size, padding and margin.
    ///  If the control is top level, this will not scale the control's location.
    ///  This does not scale children or the size of auto sized controls. You can
    ///  omit scaling in any direction by changing BoundsSpecified.
    ///
    ///  After the control is scaled the RequiredScaling property is set to
    ///  BoundsSpecified.None.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
    {
        try
        {
            _splitContainerScaling = true;
            base.ScaleControl(factor, specified);

            float scale;
            if (_orientation == Orientation.Vertical)
            {
                scale = factor.Width;
            }
            else
            {
                scale = factor.Height;
            }

            SplitterWidth = (int)Math.Round(SplitterWidth * scale);
            _splitterDistance = (int)Math.Round(_splitterDistance * scale);
            _splitDistance = _splitterDistance;

            // If FixedPanel property is set.
            if (_panelSize != 0)
            {
                _panelSize = (int)Math.Round(_panelSize * scale);
            }
        }
        finally
        {
            _splitContainerScaling = false;
        }
    }

    protected override void Select(bool directed, bool forward)
    {
        // avoid re-entrant code.
        // SelectNextControl can call back on us.. and we might end up infinitely recursing.
        if (_selectNextControl)
        {
            return;
        }

        // continue selection iff panels have controls or tabstop is true.
        if ((Panel1.Controls.Count > 0 || Panel2.Controls.Count > 0) || TabStop)
        {
            SelectNextControlInContainer(this, forward, true, true, false);
        }
        else
        {
            // If this SplitContainer cannot be selected let the parent select the next in line
            try
            {
                Control? parent = ParentInternal;
                _selectNextControl = true;
                while (parent is not null)
                {
                    if (parent.SelectNextControl(this, forward, true, true, parent.ParentInternal is null))
                    {
                        break;
                    }

                    parent = parent.ParentInternal;
                }
            }
            finally
            {
                _selectNextControl = false;
            }
        }
    }

    /// <summary>
    ///  Selects the next control following ctl.
    /// </summary>
    private bool SelectNextControlInContainer(Control? ctl, bool forward, bool tabStopOnly,
                                  bool nested, bool wrap)
    {
        if (!Contains(ctl) ||
            (!nested && ctl.ParentInternal != this))
        {
            ctl = null;
        }

        SplitterPanel? firstPanel = null;
        do
        {
            ctl = GetNextControl(ctl, forward);

            if (ctl is SplitterPanel panel && panel.Visible)
            {
                // We have crossed over to the second Panel...
                if (firstPanel is not null)
                {
                    break;
                }

                firstPanel = panel;
            }

            if (!forward && firstPanel is not null && ctl?.ParentInternal != firstPanel)
            {
                // goback to start correct re-ordering ....
                ctl = firstPanel;
                break;
            }

            if (ctl is null)
            {
                break;
            }
            else
            {
                if (ctl.CanSelect && ctl.TabStop)
                {
                    if (ctl is SplitContainer splitContainer)
                    {
                        splitContainer.Select(forward, forward);
                    }
                    else
                    {
                        SelectNextActiveControl(ctl, forward, tabStopOnly, nested, wrap);
                    }

                    return true;
                }
            }
        }
        while (ctl is not null);
        if (ctl is not null && TabStop)
        {
            // we are on Splitter.....Focus it
            _splitterFocused = true;
            IContainerControl? c = ParentInternal?.GetContainerControl();
            if (c is not null)
            {
                if (c is not ContainerControl cc)
                {
                    c.ActiveControl = this;
                }
                else
                {
                    cc.SetActiveControl(this);
                }
            }

            SetActiveControl(null);
            _nextActiveControl = ctl;
            return true;
        }
        else
        {
            // If the splitter cannot be selected select the next control in the splitter
            bool selected = SelectNextControlInPanel(ctl, forward, tabStopOnly, nested, wrap);
            if (!selected)
            {
                Control? parent = ParentInternal;
                if (parent is not null)
                {
                    try
                    {
                        _selectNextControl = true;
                        parent.SelectNextControl(this, forward, true, true, true);
                    }
                    finally
                    {
                        _selectNextControl = false;
                    }
                }
            }
        }

        return false;
    }

    /// <summary>
    ///  Selects the next control following ctl.
    /// </summary>
    private bool SelectNextControlInPanel(Control? ctl, bool forward, bool tabStopOnly,
                                  bool nested, bool wrap)
    {
        if (!Contains(ctl) ||
            (!nested && ctl.ParentInternal != this))
        {
            ctl = null;
        }

        do
        {
            ctl = GetNextControl(ctl, forward);
            if (ctl is null || (ctl is SplitterPanel && ctl.Visible))
            {
                break;
            }
            else
            {
                if (ctl.CanSelect && (!tabStopOnly || ctl.TabStop))
                {
                    if (ctl is SplitContainer splitContainer)
                    {
                        splitContainer.Select(forward, forward);
                    }
                    else
                    {
                        SelectNextActiveControl(ctl, forward, tabStopOnly, nested, wrap);
                    }

                    return true;
                }
            }
        }
        while (ctl is not null);

        // If CTL is null .. we r out of the Current SplitContainer...
        if (ctl is null || (ctl is SplitterPanel && !ctl.Visible))
        {
            _callBaseVersion = true;
        }

        // IF the CTL == typeof(SplitterPanel) find the NEXT Control... so that we know
        // we can focus the NEXT control within this SPLITCONTAINER....
        else
        {
            ctl = GetNextControl(ctl, forward);
            if (forward)
            {
                _nextActiveControl = Panel2;
            }
            else
            {
                if (ctl is null || (ctl.ParentInternal is not null && !ctl.ParentInternal.Visible))
                {
                    _callBaseVersion = true;
                }
                else
                {
                    _nextActiveControl = Panel2;
                }
            }
        }

        return false;
    }

    // This will select the correct active control in the containerControl (if the passed in control is a containerControl)
    private static void SelectNextActiveControl(Control ctl, bool forward, bool tabStopOnly, bool nested, bool wrap)
    {
        if (ctl is ContainerControl container)
        {
            bool correctParentActiveControl = true;
            if (container.ParentInternal is not null)
            {
                IContainerControl? c = container.ParentInternal.GetContainerControl();
                if (c is not null)
                {
                    c.ActiveControl = container;
                    correctParentActiveControl = (c.ActiveControl == container);
                }
            }

            if (correctParentActiveControl)
            {
                ctl.SelectNextControl(null, forward, tabStopOnly, nested, wrap);
            }
        }
        else
        {
            ctl.Select();
        }
    }

    /// <summary>
    ///  Selects the innermost PANEL.
    /// </summary>
    private void SetInnerMostBorder(SplitContainer sc)
    {
        foreach (Control control in sc.Controls)
        {
            bool foundChildSplitContainer = false;
            if (control is SplitterPanel panel)
            {
                foreach (Control child in control.Controls)
                {
                    if (child is SplitContainer splitContainer && splitContainer.Dock == DockStyle.Fill)
                    {
                        // We need to overlay borders if the children have matching BorderStyles.
                        if (splitContainer.BorderStyle != BorderStyle)
                        {
                            break;
                        }

                        panel.BorderStyle = BorderStyle.None;
                        SetInnerMostBorder(splitContainer);
                        foundChildSplitContainer = true;
                    }
                }

                if (!foundChildSplitContainer)
                {
                    panel.BorderStyle = BorderStyle;
                }
            }
        }
    }

    /// <summary>
    ///  This protected override allows us to check is an unvalid value is set for Width and Height.
    ///  The SplitContainer would not throw on invalid Size (i.e Width and Height) settings, but would correct the error like Form
    ///  Say, the Panel1MinSize == 150 , Panel2MinSize == 50 and SplitterWidth == 4 and the user tries
    ///  to set SplitContainer.Width = 50 ... then this function would try to correct the value to 204.. instead of throwing.
    /// </summary>
    protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
    {
        // If we are changing Height, check if its greater than minimum else ... make it equal to the minimum
        if ((specified & BoundsSpecified.Height) != BoundsSpecified.None && Orientation == Orientation.Horizontal)
        {
            if (height < Panel1MinSize + SplitterWidthInternal + Panel2MinSize)
            {
                height = Panel1MinSize + SplitterWidthInternal + Panel2MinSize;
            }
        }

        // If we are changing Width, check if its greater than minimum else ... make it equal to the minimum
        if ((specified & BoundsSpecified.Width) != BoundsSpecified.None && Orientation == Orientation.Vertical)
        {
            if (width < Panel1MinSize + SplitterWidthInternal + Panel2MinSize)
            {
                width = Panel1MinSize + SplitterWidthInternal + Panel2MinSize;
            }
        }

        base.SetBoundsCore(x, y, width, height, specified);

        SetSplitterRect(Orientation == Orientation.Vertical);
    }

    /// <summary>
    ///  Begins the splitter moving.
    /// </summary>
    private void SplitBegin(int x, int y)
    {
        _anchor = new Point(x, y);
        _splitterDistance = GetSplitterDistance(x, y);
        _initialSplitterDistance = _splitterDistance;

        _splitContainerMessageFilter ??= new SplitContainerMessageFilter(this);

        Application.AddMessageFilter(_splitContainerMessageFilter);

        Capture = true;
        DrawSplitBar(DrawStart);
    }

    /// <summary>
    ///  The split movement.
    /// </summary>
    private void SplitMove(int x, int y)
    {
        int size = GetSplitterDistance(x, y);
        int delta = size - _initialSplitterDistance;
        int mod = delta % SplitterIncrement;
        if (_splitterDistance != size)
        {
            if (Orientation == Orientation.Vertical)
            {
                if (size + SplitterWidthInternal <= Width - Panel2MinSize - _borderSize)
                {
                    _splitterDistance = size - mod;
                }
            }
            else
            {
                if (size + SplitterWidthInternal <= Height - Panel2MinSize - _borderSize)
                {
                    _splitterDistance = size - mod;
                }
            }
        }

        DrawSplitBar(DrawMove);
    }

    /// <summary>
    ///  Finishes the split movement.
    /// </summary>
    private void SplitEnd(bool accept)
    {
        DrawSplitBar(DrawEnd);
        if (_splitContainerMessageFilter is not null)
        {
            Application.RemoveMessageFilter(_splitContainerMessageFilter);
            _splitContainerMessageFilter = null;
        }

        if (accept)
        {
            ApplySplitterDistance();
        }
        else if (_splitterDistance != _initialSplitterDistance)
        {
            _splitterClick = false;
            _splitterDistance = SplitterDistanceInternal = _initialSplitterDistance;
        }

        _anchor = Point.Empty;
    }

    /// <summary>
    ///  Update Splitter
    /// </summary>
    private void UpdateSplitter()
    {
        if (_splitContainerScaling)
        {
            return;
        }

        Panel1.SuspendLayout();
        Panel2.SuspendLayout();
        if (Orientation == Orientation.Vertical)
        {
            bool isRTL = RightToLeft == RightToLeft.Yes;

            // NO PANEL FIXED !!
            if (!CollapsedMode)
            {
                Panel1.HeightInternal = Height;
                Panel1.WidthInternal = _splitterDistance; // Default splitter distance from left or top.
                Panel2.Size = new Size(Width - _splitterDistance - SplitterWidthInternal, Height);

                if (!isRTL)
                {
                    Panel1.Location = new Point(0, 0);
                    Panel2.Location = new Point(_splitterDistance + SplitterWidthInternal, 0);
                }
                else
                {
                    Panel1.Location = new Point(Width - _splitterDistance, 0);
                    Panel2.Location = new Point(0, 0);
                }

                RepaintSplitterRect();
                SetSplitterRect(true /*Vertical*/);
                if (!_resizeCalled)
                {
                    _ratioWidth = (Width / (double)(Panel1.Width) > 0) ? Width / (double)(Panel1.Width) : _ratioWidth;
                }
            }
            else
            {
                if (Panel1Collapsed)
                {
                    Panel2.Size = Size;
                    Panel2.Location = new Point(0, 0);
                }
                else if (Panel2Collapsed)
                {
                    Panel1.Size = Size;
                    Panel1.Location = new Point(0, 0);
                }

                // Update Ratio when the splitContainer is in CollapsedMode.
                if (!_resizeCalled)
                {
                    _ratioWidth = (Width / (double)(_splitterDistance) > 0) ? Width / (double)(_splitterDistance) : _ratioWidth;
                }
            }
        }
        else
        {
            // NO PANEL FIXED !!
            if (!CollapsedMode)
            {
                Panel1.Location = new Point(0, 0);
                Panel1.WidthInternal = Width;

                Panel1.HeightInternal = SplitterDistanceInternal; // Default splitter distance from left or top.
                int panel2Start = _splitterDistance + SplitterWidthInternal;
                Panel2.Size = new Size(Width, Height - panel2Start);
                Panel2.Location = new Point(0, panel2Start);

                RepaintSplitterRect();
                SetSplitterRect(false/*Horizontal*/);

                if (!_resizeCalled)
                {
                    _ratioHeight = (Height / (double)(Panel1.Height) > 0) ? Height / (double)(Panel1.Height) : _ratioHeight;
                }
            }
            else
            {
                if (Panel1Collapsed)
                {
                    Panel2.Size = Size;
                    Panel2.Location = new Point(0, 0);
                }
                else if (Panel2Collapsed)
                {
                    Panel1.Size = Size;
                    Panel1.Location = new Point(0, 0);
                }

                // Update Ratio when the splitContainer is in CollapsedMode.
                if (!_resizeCalled)
                {
                    _ratioHeight = (Height / (double)(_splitterDistance) > 0) ? Height / (double)(_splitterDistance) : _ratioHeight;
                }
            }
        }

        Panel1.ResumeLayout();
        Panel2.ResumeLayout();
    }

    /// <summary>
    ///  Handles the WM_SETCURSOR message
    /// </summary>
    private void WmSetCursor(ref Message m)
    {
        // Accessing through the Handle property has side effects that break this logic. You must use InternalHandle.
        if ((HWND)m.WParamInternal == InternalHandle && m.LParamInternal.LOWORD == PInvoke.HTCLIENT)
        {
            Cursor.Current = OverrideCursor ?? Cursor;
        }
        else
        {
            DefWndProc(ref m);
        }
    }

    internal override Rectangle GetToolNativeScreenRectangle()
    {
        // Return splitter rectangle instead of the whole container rectangle to be consistent with the mouse ToolTip
        Rectangle containerRectangle = base.GetToolNativeScreenRectangle();
        Rectangle splitterRectangle = SplitterRectangle;
        return new Rectangle(containerRectangle.X + splitterRectangle.X, containerRectangle.Y + splitterRectangle.Y, splitterRectangle.Width, splitterRectangle.Height);
    }

    internal override void AfterControlRemoved(Control control, Control oldParent)
    {
        base.AfterControlRemoved(control, oldParent);
        if (control is SplitContainer && control.Dock == DockStyle.Fill)
        {
            SetInnerMostBorder(this);
        }
    }

    protected override bool ProcessDialogKey(Keys keyData)
    {
        if ((keyData & (Keys.Alt | Keys.Control)) == Keys.None)
        {
            Keys keyCode = keyData & Keys.KeyCode;
            switch (keyCode)
            {
                case Keys.Tab:
                    if (ProcessTabKey((keyData & Keys.Shift) == Keys.None))
                    {
                        return true;
                    }

                    break;
                case Keys.Left:
                case Keys.Right:
                case Keys.Up:
                case Keys.Down:
                    if (!_splitterFocused)
                    {
                        if (ProcessArrowKey(keyCode is Keys.Right or
                                        Keys.Down))
                        {
                            return true;
                        }
                    }
                    else
                    {
                        return false;
                    }

                    break;
            }
        }

        return base.ProcessDialogKey(keyData);
    }

    /// <summary>
    ///  This will process the TabKey for the SplitContainer. The Focus needs to Shift from controls to the Left of the Splitter
    ///  to the splitter and then to the controls on the right of the splitter. This override implements this Logic.
    /// </summary>
    protected override bool ProcessTabKey(bool forward)
    {
        // Don't Focus the Splitter if TabStop == False or if the Splitter is Fixed !!
        if (!TabStop || IsSplitterFixed)
        {
            return base.ProcessTabKey(forward);
        }

        if (_nextActiveControl is not null)
        {
            SetActiveControl(_nextActiveControl);
            _nextActiveControl = null;
        }

        if (SelectNextControlInPanel(ActiveControl, forward, true, true, true))
        {
            _nextActiveControl = null;
            _splitterFocused = false;
            return true;
        }
        else
        {
            if (_callBaseVersion)
            {
                _callBaseVersion = false;
                return base.ProcessTabKey(forward);
            }
            else
            {
                // We are om Splitter ......
                _splitterFocused = true;
                IContainerControl? c = ParentInternal?.GetContainerControl();
                if (c is not null)
                {
                    if (c is not ContainerControl cc)
                    {
                        c.ActiveControl = this;
                    }
                    else
                    {
                        cc.SetActiveControl(this);
                    }
                }

                SetActiveControl(null);
                return true;
            }
        }
    }

    protected override void OnMouseCaptureChanged(EventArgs e)
    {
        base.OnMouseCaptureChanged(e);
        if (_splitContainerMessageFilter is not null)
        {
            Application.RemoveMessageFilter(_splitContainerMessageFilter);
            _splitContainerMessageFilter = null;
        }
    }

    protected override void WndProc(ref Message msg)
    {
        switch (msg.MsgInternal)
        {
            case PInvokeCore.WM_SETCURSOR:
                WmSetCursor(ref msg);
                break;
            case PInvokeCore.WM_SETFOCUS:
                _splitterFocused = true;
                base.WndProc(ref msg);
                break;
            case PInvokeCore.WM_KILLFOCUS:
                _splitterFocused = false;
                base.WndProc(ref msg);
                break;

            default:
                base.WndProc(ref msg);
                break;
        }
    }

    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected override ControlCollection CreateControlsInstance()
    {
        return new SplitContainerTypedControlCollection(this, typeof(SplitterPanel), /*isReadOnly*/true);
    }
}
