#region ================== Copyright (c) 2015 MaxED // Parts of the code are based on "Collapsible Splitter control in C#" by Furty // http://www.codeproject.com/Articles/3025/Collapsible-Splitter-control-in-C #endregion #region ================== Namespaces using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Reflection; using System.Windows.Forms; using CodeImp.DoomBuilder.Windows; #endregion namespace CodeImp.DoomBuilder.Controls { [Designer("System.Windows.Forms.Design.SplitContainerDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] public class CollapsibleSplitContainer : SplitContainer, ISupportInitialize { #region ================== Private Properties // Declare and define some base properties private bool hot; private bool collapsed; private readonly Color hotcolor = CalculateColor(SystemColors.Highlight, SystemColors.Window, 70); private Rectangle bounds; private readonly Dictionary scaled; // Storesome settings private int storedpanel1minsize; private int storedpanel2minsize; private int storedsplitterdistance; #endregion #region ================== Public Properties [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsCollapsed { get { return collapsed; } set { collapsed = value; ToggleSplitter(); } } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int SplitPosition { get { return GetSplitPosition(); } set { storedsplitterdistance = value; if(!IsCollapsed) ToggleSplitter(); } } [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] private new int SplitterWidth { get { return base.SplitterWidth; } set { base.SplitterWidth = value; } } #endregion #region ================== Constructor public CollapsibleSplitContainer() { // Register mouse events this.Click += OnClick; this.Resize += OnResize; this.MouseLeave += OnMouseLeave; this.MouseMove += OnMouseMove; this.MouseUp += OnMouseUp; //mxd. Set drawing style const ControlStyles cs = ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer; this.SetStyle(cs, true); object[] args = new object[] { cs, true }; MethodInfo objMethodInfo = typeof(Control).GetMethod("SetStyle", BindingFlags.NonPublic | BindingFlags.Instance); objMethodInfo.Invoke(this.Panel1, args); objMethodInfo.Invoke(this.Panel2, args); // Force the width to 8px so that everything always draws correctly this.SplitterWidth = 8; //mxd. Create some scaled coordinates... int[] coords = new[] { 1, 2, 3, 4, 6, 8, 9, 14, 115 }; scaled = new Dictionary(coords.Length); foreach(int i in coords) scaled[i] = (int)Math.Round(i * MainForm.DPIScaler.Width); } #endregion #region ================== Event Handlers protected override void OnMouseDown(MouseEventArgs e) { // if the hider control isn't hot, let the base resize action occur if(!this.hot && this.Panel1.Visible && this.Panel2.Visible) base.OnMouseDown(e); } private void OnResize(object sender, EventArgs e) { this.Invalidate(); } private void OnMouseMove(object sender, MouseEventArgs e) { // check to see if the mouse cursor position is within the bounds of our control if(bounds.Contains(e.Location)) { if(!this.hot) { this.hot = true; this.Cursor = Cursors.Hand; this.Invalidate(); } } else { if(this.hot) { this.hot = false; this.Invalidate(); } this.Cursor = Cursors.Default; } } private void OnMouseLeave(object sender, EventArgs e) { // ensure that the hot state is removed this.hot = false; this.Cursor = Cursors.Default; //mxd this.Invalidate(); } // User may've moved the splitter... private void OnMouseUp(object sender, MouseEventArgs mouseEventArgs) { if(!collapsed) storedsplitterdistance = GetSplitPosition(); } private void OnClick(object sender, EventArgs e) { if(FixedPanel != FixedPanel.None && hot) { collapsed = !collapsed; ToggleSplitter(); this.Invalidate(); } } #endregion #region ================== Paint protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if(FixedPanel == FixedPanel.None) return; // find the rectangle for the splitter and paint it Rectangle r = this.SplitterRectangle; using(SolidBrush brushbg = new SolidBrush(this.BackColor)) { e.Graphics.FillRectangle(brushbg, r); } Pen pendark = new Pen(SystemColors.ControlDark); SolidBrush brushlightlight = new SolidBrush(SystemColors.ControlLightLight); SolidBrush brushdark = new SolidBrush(SystemColors.ControlDark); SolidBrush brushdarkdark = new SolidBrush(SystemColors.ControlDarkDark); // Check the docking style and create the control rectangle accordingly if(this.Orientation == Orientation.Vertical) { // create a new rectangle in the vertical center of the splitter for our collapse control button bounds = new Rectangle(r.X, r.Y + ((r.Height - scaled[115]) / 2), scaled[8], scaled[115]); // draw the background color for our control image using(SolidBrush bg = new SolidBrush(hot ? hotcolor : this.BackColor)) { e.Graphics.FillRectangle(bg, new Rectangle(bounds.X + scaled[1], bounds.Y, scaled[6], scaled[115])); } // draw the top & bottom lines for our control image e.Graphics.DrawLine(pendark, bounds.X + scaled[1], bounds.Y, bounds.X + bounds.Width - scaled[2], bounds.Y); e.Graphics.DrawLine(pendark, bounds.X + scaled[1], bounds.Y + bounds.Height, bounds.X + bounds.Width - scaled[2], bounds.Y + bounds.Height); if(this.Enabled) { // draw the arrows for our control image // the ArrowPointArray is a point array that defines an arrow shaped polygon e.Graphics.FillPolygon(brushdarkdark, ArrowPointArray(bounds.X + scaled[2], bounds.Y + scaled[3])); e.Graphics.FillPolygon(brushdarkdark, ArrowPointArray(bounds.X + scaled[2], bounds.Y + bounds.Height - scaled[9])); } // draw the dots for our control image using a loop int x = bounds.X + scaled[3]; int y = bounds.Y + scaled[14]; for(int i = 0; i < 30; i++) { // light dot e.Graphics.FillRectangle(brushlightlight, x, y + scaled[1] + (i * scaled[3]), scaled[2], scaled[2]); // dark dot e.Graphics.FillRectangle(brushdark, x - scaled[1], y + (i * scaled[3]), scaled[2], scaled[2]); i++; // light dot e.Graphics.FillRectangle(brushlightlight, x + scaled[2], y + scaled[1] + (i * scaled[3]), scaled[2], scaled[2]); // dark dot e.Graphics.FillRectangle(brushdark, x + scaled[1], y + (i * scaled[3]), scaled[2], scaled[2]); } } else // Should be Orientation.Horizontal { // create a new rectangle in the horizontal center of the splitter for our collapse control button bounds = new Rectangle(r.X + ((r.Width - scaled[115]) / 2), r.Y, scaled[115], scaled[8]); // draw the background color for our control image using(SolidBrush bg = new SolidBrush(hot ? hotcolor : this.BackColor)) { e.Graphics.FillRectangle(bg, new Rectangle(bounds.X, bounds.Y + scaled[1], scaled[115], scaled[6])); } // draw the left & right lines for our control image e.Graphics.DrawLine(pendark, bounds.X, bounds.Y + scaled[1], bounds.X, bounds.Y + bounds.Height - scaled[2]); e.Graphics.DrawLine(pendark, bounds.X + bounds.Width, bounds.Y + scaled[1], bounds.X + bounds.Width, bounds.Y + bounds.Height - scaled[2]); if(this.Enabled) { // draw the arrows for our control image // the ArrowPointArray is a point array that defines an arrow shaped polygon e.Graphics.FillPolygon(brushdarkdark, ArrowPointArray(bounds.X + scaled[3], bounds.Y + scaled[2])); e.Graphics.FillPolygon(brushdarkdark, ArrowPointArray(bounds.X + bounds.Width - scaled[9], bounds.Y + scaled[2])); } // draw the dots for our control image using a loop int x = bounds.X + scaled[14]; int y = bounds.Y + scaled[3]; for(int i = 0; i < 30; i++) { // light dot e.Graphics.FillRectangle(brushlightlight, x + scaled[1] + (i * scaled[3]), y, scaled[2], scaled[2]); // dark dot e.Graphics.FillRectangle(brushdark, x + (i * scaled[3]), y - scaled[1], scaled[2], scaled[2]); i++; // light dot e.Graphics.FillRectangle(brushlightlight, x + scaled[1] + (i * scaled[3]), y + scaled[2], scaled[2], scaled[2]); // dark dot e.Graphics.FillRectangle(brushdark, x + (i * scaled[3]), y + scaled[1], scaled[2], scaled[2]); } } //mxd. Dispose brushes pendark.Dispose(); brushlightlight.Dispose(); brushdark.Dispose(); brushdarkdark.Dispose(); } #endregion #region ================== Helper methods private void ToggleSplitter() { //mxd. Toggle visibility switch(FixedPanel) { case FixedPanel.Panel1: Panel1.Visible = !collapsed; if(collapsed) { storedsplitterdistance = SplitterDistance; storedpanel1minsize = Panel1MinSize; Panel1MinSize = 0; SplitterDistance = 0; } else { Panel1MinSize = storedpanel1minsize; SplitterDistance = Math.Min(this.Width, storedsplitterdistance); } break; case FixedPanel.Panel2: Panel2.Visible = !collapsed; if(collapsed) { storedpanel2minsize = Panel2MinSize; Panel2MinSize = 0; } else { Panel2MinSize = storedpanel2minsize; } if(Orientation == Orientation.Vertical) { if(collapsed) storedsplitterdistance = this.Width - SplitterDistance; SplitterDistance = (collapsed ? this.Width : Math.Max(0, this.Width - storedsplitterdistance)); } else { if(collapsed) storedsplitterdistance = this.Height - SplitterDistance; SplitterDistance = (collapsed ? this.Height : Math.Max(0, this.Height - storedsplitterdistance)); } break; } } private int GetSplitPosition() { switch(FixedPanel) { case FixedPanel.Panel1: return (Panel1.Visible ? SplitterDistance : storedsplitterdistance); case FixedPanel.Panel2: if(Panel2.Visible) { if(Orientation == Orientation.Vertical) return Math.Max(0, this.Width - SplitterDistance); else return Math.Max(0, this.Height - SplitterDistance); } else { return storedsplitterdistance; } } return SplitterDistance; } // This creates a point array to draw a arrow-like polygon private Point[] ArrowPointArray(int x, int y) { Point[] points = new Point[3]; // Right or left arrows if(Orientation == Orientation.Vertical) { if((FixedPanel == FixedPanel.Panel2 && Panel2.Visible) || (FixedPanel == FixedPanel.Panel1 && !Panel1.Visible)) // Right arrow { points[0] = new Point(x, y); points[1] = new Point(x + scaled[3], y + scaled[3]); points[2] = new Point(x, y + scaled[6]); } else // Left arrow { points[0] = new Point(x + scaled[3], y); points[1] = new Point(x, y + scaled[3]); points[2] = new Point(x + scaled[3], y + scaled[6]); } } else // Up or down arrows { if((FixedPanel == FixedPanel.Panel2 && Panel2.Visible) || (FixedPanel == FixedPanel.Panel1 && !Panel1.Visible)) // Down arrow { points[0] = new Point(x, y); points[1] = new Point(x + scaled[6], y); points[2] = new Point(x + scaled[3], y + scaled[3]); } else // Up arrow { points[0] = new Point(x + scaled[3], y); points[1] = new Point(x + scaled[6], y + scaled[4]); points[2] = new Point(x, y + scaled[4]); } } return points; } // this method was borrowed from the RichUI Control library by Sajith M private static Color CalculateColor(Color front, Color back, int alpha) { // solid color obtained as a result of alpha-blending Color frontColor = Color.FromArgb(255, front); Color backColor = Color.FromArgb(255, back); float frontRed = frontColor.R; float frontGreen = frontColor.G; float frontBlue = frontColor.B; float backRed = backColor.R; float backGreen = backColor.G; float backBlue = backColor.B; float fRed = frontRed * alpha / 255 + backRed * ((float)(255 - alpha) / 255); float fGreen = frontGreen * alpha / 255 + backGreen * ((float)(255 - alpha) / 255); float fBlue = frontBlue * alpha / 255 + backBlue * ((float)(255 - alpha) / 255); return Color.FromArgb(255, (byte)fRed, (byte)fGreen, (byte)fBlue); } // ISupportInitialize methods. Not needed for .Net 4 and higher //public void BeginInit() { } //public void EndInit() { } // biwa. Fills the stored sized with some value other than 0. Otherwise // the error list will not be shown correctly when toggling the panel public void SetSizes() { storedpanel1minsize = Panel1MinSize; storedpanel2minsize = Panel2MinSize; } #endregion } }