UltimateZoneBuilder/Source/Core/Controls/CollapsibleSplitContainer.cs

389 lines
12 KiB
C#
Raw Normal View History

#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.ComponentModel;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
#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;
// 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;
}
#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.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;
e.Graphics.FillRectangle(new SolidBrush(this.BackColor), r);
Pen penlightlight = new Pen(SystemColors.ControlLightLight);
Pen pendark = new Pen(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 - 115) / 2), 8, 115);
// draw the background color for our control image
e.Graphics.FillRectangle(new SolidBrush(hot ? hotcolor : this.BackColor), new Rectangle(bounds.X + 1, bounds.Y, 6, 115));
// draw the top & bottom lines for our control image
e.Graphics.DrawLine(pendark, bounds.X + 1, bounds.Y, bounds.X + bounds.Width - 2, bounds.Y);
e.Graphics.DrawLine(pendark, bounds.X + 1, bounds.Y + bounds.Height, bounds.X + bounds.Width - 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 + 2, bounds.Y + 3));
e.Graphics.FillPolygon(brushdarkdark, ArrowPointArray(bounds.X + 2, bounds.Y + bounds.Height - 9));
}
// draw the dots for our control image using a loop
int x = bounds.X + 3;
int y = bounds.Y + 14;
for(int i = 0; i < 30; i++)
{
// light dot
e.Graphics.DrawRectangle(penlightlight, x, y + 1 + (i * 3), 1, 1);
// dark dot
e.Graphics.DrawRectangle(pendark, x - 1, y + (i * 3), 1, 1);
i++;
// light dot
e.Graphics.DrawRectangle(penlightlight, x + 2, y + 1 + (i * 3), 1, 1);
// dark dot
e.Graphics.DrawRectangle(pendark, x + 1, y + (i * 3), 1, 1);
}
}
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 - 115) / 2), r.Y, 115, 8);
// draw the background color for our control image
e.Graphics.FillRectangle(new SolidBrush(hot ? hotcolor : this.BackColor), new Rectangle(bounds.X, bounds.Y + 1, 115, 6));
// draw the left & right lines for our control image
e.Graphics.DrawLine(pendark, bounds.X, bounds.Y + 1, bounds.X, bounds.Y + bounds.Height - 2);
e.Graphics.DrawLine(pendark, bounds.X + bounds.Width, bounds.Y + 1, bounds.X + bounds.Width, bounds.Y + bounds.Height - 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 + 3, bounds.Y + 2));
e.Graphics.FillPolygon(brushdarkdark, ArrowPointArray(bounds.X + bounds.Width - 9, bounds.Y + 2));
}
// draw the dots for our control image using a loop
int x = bounds.X + 14;
int y = bounds.Y + 3;
for(int i = 0; i < 30; i++)
{
// light dot
e.Graphics.DrawRectangle(penlightlight, x + 1 + (i * 3), y, 1, 1);
// dark dot
e.Graphics.DrawRectangle(pendark, x + (i * 3), y - 1, 1, 1);
i++;
// light dot
e.Graphics.DrawRectangle(penlightlight, x + 1 + (i * 3), y + 2, 1, 1);
// dark dot
e.Graphics.DrawRectangle(pendark, x + (i * 3), y + 1, 1, 1);
}
}
}
#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 + 3, y + 3);
points[2] = new Point(x, y + 6);
}
else // Left arrow
{
points[0] = new Point(x + 3, y);
points[1] = new Point(x, y + 3);
points[2] = new Point(x + 3, y + 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 + 6, y);
points[2] = new Point(x + 3, y + 3);
}
else // Up arrow
{
points[0] = new Point(x + 3, y);
points[1] = new Point(x + 6, y + 4);
points[2] = new Point(x, y + 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() { }
#endregion
}
}