Files
renderdoc/renderdocui/Controls/PipelineFlowchart.cs
T
2016-02-07 18:50:45 +01:00

382 lines
12 KiB
C#

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2016 Baldur Karlsson
* Copyright (c) 2014 Crytek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace renderdocui.Controls
{
public partial class PipelineFlowchart : UserControl
{
public PipelineFlowchart()
{
InitializeComponent();
this.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);
}
public void SetStages(KeyValuePair<string, string>[] stages)
{
m_StageNames = stages;
m_StagesEnabled = new bool[stages.Length];
m_StageFlows = new bool[stages.Length];
for(int i=0; i < stages.Length; i++)
m_StageFlows[i] = true;
Invalidate();
}
#region Events
private static readonly object SelectedStageChangedEvent = new object();
[Description("Event raised when the selected pipeline stage is changed."), Category("Behavior")]
public event EventHandler<EventArgs> SelectedStageChanged
{
add { Events.AddHandler(SelectedStageChangedEvent, value); }
remove { Events.RemoveHandler(SelectedStageChangedEvent, value); }
}
protected virtual void OnSelectedStageChanged(EventArgs e)
{
EventHandler<EventArgs> handler = (EventHandler<EventArgs>)Events[SelectedStageChangedEvent];
if (handler != null)
handler(this, e);
}
#endregion
#region Data Properties
private bool[] m_StagesEnabled = null;
private bool[] m_StageFlows = null;
private KeyValuePair<string, string>[] m_StageNames = null;
public void SetStagesEnabled(bool[] enabled)
{
if (m_StagesEnabled != null && enabled.Length == m_StagesEnabled.Length)
m_StagesEnabled = enabled;
Invalidate();
}
public void SetStageName(int index, KeyValuePair<string, string> name)
{
if (index >= 0 && index < m_StageNames.Length)
m_StageNames[index] = name;
Invalidate();
}
public void IsolateStage(int index)
{
if (m_StageNames != null && index >= 0 && index < m_StageNames.Length)
m_StageFlows[index] = false;
}
private bool IsStageEnabled(int index)
{
return m_StagesEnabled != null && m_StagesEnabled[index];
}
private int m_HoverStage = -1;
private int m_SelectedStage = 0;
[Browsable(false)]
public int SelectedStage
{
get
{
return m_SelectedStage;
}
set
{
if (value >= 0 && m_StageNames != null && value < m_StageNames.Length)
{
m_SelectedStage = value;
Invalidate();
OnSelectedStageChanged(new EventArgs());
}
}
}
#endregion
#region Constants and positions/dimensions
const int BoxBorderWidth = 3;
const int MinBoxDimension = 25;
const int MaxBoxCornerRadius = 20;
const float BoxCornerRadiusFraction = 1.0f / 6.0f;
const float ArrowHeadSize = 6.0f;
const int MinBoxMargin = 4;
const int BoxLabelMargin = 8;
const float BoxMarginFraction = 0.02f;
private Rectangle TotalAreaRect
{
get
{
Rectangle rect = ClientRectangle;
rect.Inflate(-1, -1);
rect.X += Padding.Left;
rect.Width -= Padding.Left + Padding.Right;
rect.Y += Padding.Top;
rect.Height -= Padding.Top + Padding.Bottom;
rect.Inflate(-BoxBorderWidth, -BoxBorderWidth);
return rect;
}
}
private float BoxMargin
{
get
{
float margin = Math.Max(TotalAreaRect.Width, TotalAreaRect.Height) * BoxMarginFraction;
margin = Math.Max(MinBoxMargin, margin);
return margin;
}
}
private int NumGaps { get { return m_StageNames == null ? 1 : m_StageNames.Length - 1; } }
private int NumItems { get { return m_StageNames == null ? 2 : m_StageNames.Length; } }
private SizeF BoxSize
{
get
{
float boxeswidth = TotalAreaRect.Width - NumGaps * BoxMargin;
float boxdim = Math.Min(TotalAreaRect.Height, boxeswidth / NumItems);
boxdim = Math.Max(MinBoxDimension, boxdim);
float oblongwidth = Math.Max(0, (boxeswidth - boxdim * NumItems) / NumItems);
return new SizeF(boxdim + oblongwidth, boxdim);
}
}
private RectangleF GetBoxRect(int i)
{
return new RectangleF(TotalAreaRect.X + i * (BoxSize.Width + BoxMargin),
TotalAreaRect.Y + TotalAreaRect.Height / 2 - BoxSize.Height / 2,
BoxSize.Width, BoxSize.Height);
}
#endregion
#region Painting
GraphicsPath RoundedRect(RectangleF rect, int radius)
{
GraphicsPath ret = new GraphicsPath();
ret.StartFigure();
ret.AddArc(rect.X, rect.Y, 2 * radius, 2 * radius, 180, 90);
ret.AddLine(rect.X + radius, rect.Y, rect.X + rect.Width - radius, rect.Y);
ret.AddArc(rect.X + rect.Width - radius * 2, rect.Y, 2 * radius, 2 * radius, 270, 90);
ret.AddLine(rect.X + rect.Width, rect.Y + radius, rect.X + rect.Width, rect.Y + rect.Height - radius);
ret.AddArc(rect.X + rect.Width - 2*radius, rect.Y + rect.Height - 2 * radius, 2 * radius, 2 * radius, 0, 90);
ret.AddLine(rect.X + rect.Width - radius, rect.Y + rect.Height, rect.X + radius, rect.Y + rect.Height);
ret.AddArc(rect.X, rect.Y + rect.Height - 2*radius, 2 * radius, 2 * radius, 90, 90);
ret.CloseFigure();
return ret;
}
void DrawArrow(Graphics dc, Brush b, Pen p, float headsize, float y, float left, float right)
{
dc.DrawLine(p, new PointF(left, y), new PointF(right, y));
using (GraphicsPath head = new GraphicsPath())
{
head.StartFigure();
head.AddLine(new PointF(right, y), new PointF(right - headsize, y - headsize));
head.AddLine(new PointF(right - headsize, y - headsize), new PointF(right - headsize, y + headsize));
head.CloseFigure();
dc.FillPath(b, head);
}
}
private void PipelineFlowchart_Paint(object sender, PaintEventArgs e)
{
if (m_StageNames == null)
return;
var dc = e.Graphics;
dc.CompositingQuality = CompositingQuality.HighQuality;
dc.SmoothingMode = SmoothingMode.AntiAlias;
int radius = (int)Math.Min(MaxBoxCornerRadius, BoxSize.Height * BoxCornerRadiusFraction);
float arrowY = TotalAreaRect.Y + TotalAreaRect.Height / 2;
using (var pen = new Pen(Brushes.Black, BoxBorderWidth))
using (var selectedpen = new Pen(Brushes.Red, BoxBorderWidth))
{
for (int i = 0; i < NumGaps; i++)
{
if (!m_StageFlows[i] || !m_StageFlows[i + 1])
continue;
float right = TotalAreaRect.X + (i + 1) * (BoxSize.Width + BoxMargin);
float left = right - BoxMargin;
DrawArrow(dc, Brushes.Black, pen, ArrowHeadSize, arrowY, left, right);
}
for (int i = 0; i < NumItems; i++)
{
RectangleF boxrect = GetBoxRect(i);
var backBrush = Brushes.Gainsboro;
var textBrush = Brushes.Black;
var outlinePen = pen;
if (!IsStageEnabled(i))
{
backBrush = Brushes.DarkGray;
//textBrush = Brushes.Gainsboro;
}
if (i == m_HoverStage)
{
backBrush = Brushes.LightYellow;
}
if (i == SelectedStage)
{
//backBrush = Brushes.Coral;
outlinePen = selectedpen;
}
using (var boxpath = RoundedRect(boxrect, radius))
{
dc.FillPath(backBrush, boxpath);
dc.DrawPath(outlinePen, boxpath);
}
StringFormat format = new StringFormat();
format.LineAlignment = StringAlignment.Center;
format.Alignment = StringAlignment.Center;
var s = m_StageNames[i].Value;
var size = dc.MeasureString(s, Font);
// Decide whether we can draw the whole stage name or just the abbreviation.
// This can look a little awkward sometimes if it's sometimes abbreviated sometimes
// not. Maybe it should always abbreviate or not at all?
if (size.Width + BoxLabelMargin > (float)boxrect.Width)
{
s = s.Replace(" ", "\n");
size = dc.MeasureString(s, Font);
if (size.Width + BoxLabelMargin > (float)boxrect.Width ||
size.Height + BoxLabelMargin > (float)boxrect.Height)
{
s = m_StageNames[i].Key;
size = dc.MeasureString(s, Font);
}
}
dc.DrawString(s, Font, textBrush, new PointF(boxrect.X + boxrect.Width / 2, arrowY), format);
}
}
}
#endregion
#region Mouse Handling
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
int old = m_HoverStage;
m_HoverStage = -1;
for(int i=0; i < NumItems; i++)
{
if (GetBoxRect(i).Contains(e.Location))
{
m_HoverStage = i;
break;
}
}
if(m_HoverStage != old)
Invalidate();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
m_HoverStage = -1;
Invalidate();
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
for (int i = 0; i < NumItems; i++)
{
if (GetBoxRect(i).Contains(e.Location))
{
SelectedStage = i;
break;
}
}
}
#endregion
}
}