mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-12-11 21:01:22 +00:00
412 lines
13 KiB
C#
412 lines
13 KiB
C#
#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<int, int> 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<int, int>(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.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() { }
|
|
|
|
#endregion
|
|
}
|
|
}
|