ZoneBuilder/Source/Core/GZBuilder/Controls/AngleControl.cs
MaxED e34fe0d32f Added "Split Joined Sectors" Edit menu item and toolbar button. When enabled, joined sectors adjacent to drawn lines will be split.
Added "doomthingrotationangles" Game Configuration property. When enabled, editor actions related to changing thing angle will snap the resulting angle to 45 degree increments. This property is set to true for vanilla game configurations.
Fixed a crash when changing game configuration from one without Thing actions support to one with them while in Things mode.
Fixed, cosmetic, DB2 bug: current editing mode button was deselected after reloading resources.
Updated documentation ("Game Configuration - Basic Settings" page).
2023-01-05 00:27:03 +01:00

320 lines
9.6 KiB
C#

#region Namespaces
//Downloaded from
//Visual C# Kicks - http://vckicks.110mb.com
//The Code Project - http://www.codeproject.com
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Geometry;
using System.ComponentModel;
#endregion
//JBR Loops implementation by me, rest remains untouched
namespace CodeImp.DoomBuilder.GZBuilder.Controls
{
public partial class AngleControl : UserControl
{
#region Variables
private int angle;
private int angleoffset;
private bool allowLoops = true; //JBR
private bool startAtOne = false; //JBR
private int turnThreshold = 16; //JBR
private Rectangle drawRegion;
private const int drawOffset = 2;
private const int markScaler = 5;
private Point origin;
private Point startClick; //JBR
private bool doomangleclamping;
//UI colors
private readonly Color fillColor = SystemColors.Window;
private readonly Color fillInactiveColor = SystemColors.Control;
private readonly Color outlineColor = SystemColors.WindowFrame;
private readonly Color outlineInactiveColor = SystemColors.ControlDarkDark;
private readonly Color needleColor = SystemColors.ControlText;
private readonly Color needleInactiveColor = SystemColors.ControlDarkDark;
private readonly Color marksColor = SystemColors.ActiveBorder;
private readonly Color marksInactiveColor = SystemColors.ControlDark;
private readonly Color turnTextColor = SystemColors.ControlText;
private readonly Color turnTextInactiveColor = SystemColors.ControlDarkDark;
#endregion
#region Properties
public event EventHandler AngleChanged;
public int Angle { get { return (angle == NO_ANGLE ? NO_ANGLE : angle - angleoffset); } set { angle = (value == NO_ANGLE ? NO_ANGLE : value + angleoffset); this.Refresh(); } }
public int AngleOffset { get { return angleoffset; } set { angleoffset = value; this.Refresh(); } }
public bool DoomAngleClamping { get { return doomangleclamping; } set { doomangleclamping = value; } }
public const int NO_ANGLE = int.MinValue;
[Description("Allow loop changing, setting to false will restore old behaviour.")]
[DefaultValue(true)]
public bool AllowLoops //JBR
{
get { return allowLoops; }
set
{
allowLoops = value;
if (value)
{
this.toolTip.SetToolTip(this, "Left-click (and drag) to set snapped angle.\r\nRight-click (and drag) to set precise angle.\r\nMiddle-click (and drag) to set loop number. Hold Shift for larger step size. Hold Ctrl to reset loops.");
}
else
{
this.toolTip.SetToolTip(this, "Left-click (and drag) to set snapped angle.\r\nRight-click (and drag) to set precise angle.");
}
}
}
[Description("Start at loop 1 instead of loop 0, useful for checkpoint number match in SRB2.")]
[DefaultValue(false)]
public bool StartAtOne { get { return startAtOne; } set { startAtOne = value; } } //JBR
[Description("Drag distance in pixels for user to change the loop number.")]
[DefaultValue(16)]
public int TurnThreshold { get { return turnThreshold; } set { turnThreshold = value; } } //JBR
#endregion
public AngleControl()
{
InitializeComponent();
this.DoubleBuffered = true;
}
#region Methods
private void SetDrawRegion()
{
drawRegion = new Rectangle(0, 0, this.Width, this.Height);
drawRegion.X += 2;
drawRegion.Y += 2;
drawRegion.Width -= 4;
drawRegion.Height -= 4;
origin = new Point(drawRegion.Width / 2 + drawOffset, drawRegion.Height / 2 + drawOffset);
this.Refresh();
}
private static PointF DegreesToXY(float degrees, float radius, Point origin)
{
PointF xy = new PointF();
float radians = degrees * Angle2D.PI / 180.0f;
xy.X = (float)Math.Cos(radians) * radius + origin.X;
xy.Y = (float)Math.Sin(-radians) * radius + origin.Y;
return xy;
}
private static int XYToDegrees(Point xy, Point origin)
{
float xDiff = xy.X - origin.X;
float yDiff = xy.Y - origin.Y;
return ((int)Math.Round(Math.Atan2(-yDiff, xDiff) * 180.0 / Angle2D.PI) + 360) % 360;
}
private static int GetNumLoops(int angle) //JBR
{
if (angle == NO_ANGLE) return 0;
if (angle > 0) return angle / 360;
return (angle - 359) / 360;
}
#endregion
#region Events
private void AngleSelector_Load(object sender, EventArgs e)
{
SetDrawRegion();
}
private void AngleSelector_SizeChanged(object sender, EventArgs e)
{
this.Height = this.Width; // Keep it there and keep it square!
SetDrawRegion();
}
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen outline;
Pen needle;
Pen marks;
SolidBrush fill;
Brush center;
Brush text;
if (this.Enabled)
{
outline = new Pen(outlineColor, 2.0f);
fill = new SolidBrush(fillColor);
needle = new Pen(needleColor);
center = new SolidBrush(needleColor);
marks = new Pen(marksColor);
text = new SolidBrush(turnTextColor);
}
else
{
outline = new Pen(outlineInactiveColor, 2.0f);
fill = new SolidBrush(fillInactiveColor);
needle = new Pen(needleInactiveColor);
center = new SolidBrush(needleInactiveColor);
marks = new Pen(marksInactiveColor);
text = new SolidBrush(turnTextInactiveColor);
}
Rectangle originSquare = new Rectangle(origin.X - 1, origin.Y - 1, 3, 3);
//Draw circle
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawEllipse(outline, drawRegion);
g.FillEllipse(fill, drawRegion);
// Draw angle marks
int offset = this.Height / markScaler;
for (int i = 0; i < 360; i += 45)
{
PointF p1 = DegreesToXY(i, origin.X - 6, origin);
PointF p2 = DegreesToXY(i, origin.X - offset, origin);
g.DrawLine(marks, p1, p2);
}
//JBR Draw loop number
if (allowLoops)
{
int loop = GetNumLoops(angle);
if (startAtOne && loop >= 0) loop++;
string loopStr = "↺" + loop.ToString();
string baseAngle = (angle % 360).ToString() + "°";
StringFormat strFormat = new StringFormat();
strFormat.LineAlignment = StringAlignment.Far;
strFormat.Alignment = StringAlignment.Far;
int hpos = drawRegion.Right;
if (loop != (startAtOne ? 1 : 0))
{
g.DrawString(loopStr, Font, fill, hpos - 1, drawRegion.Bottom - 1, strFormat);
g.DrawString(loopStr, Font, fill, hpos + 1, drawRegion.Bottom - 1, strFormat);
g.DrawString(loopStr, Font, fill, hpos - 1, drawRegion.Bottom + 1, strFormat);
g.DrawString(loopStr, Font, fill, hpos + 1, drawRegion.Bottom + 1, strFormat);
g.DrawString(loopStr, Font, text, hpos, drawRegion.Bottom, strFormat);
if (this.Height > 64)
{
g.DrawString(baseAngle, Font, fill, hpos - 1, drawRegion.Bottom - 13, strFormat);
g.DrawString(baseAngle, Font, fill, hpos + 1, drawRegion.Bottom - 13, strFormat);
g.DrawString(baseAngle, Font, fill, hpos - 1, drawRegion.Bottom - 11, strFormat);
g.DrawString(baseAngle, Font, fill, hpos + 1, drawRegion.Bottom - 11, strFormat);
g.DrawString(baseAngle, Font, text, hpos, drawRegion.Bottom - 12, strFormat);
}
}
}
// Draw needle
if (angle != NO_ANGLE)
{
PointF anglePoint = DegreesToXY(angle, origin.X - 4, origin);
g.DrawLine(needle, origin, anglePoint);
}
g.SmoothingMode = SmoothingMode.HighSpeed; //Make the square edges sharp
g.FillRectangle(center, originSquare);
//mxd. Dispose brushes
fill.Dispose();
center.Dispose();
outline.Dispose();
marks.Dispose();
needle.Dispose();
base.OnPaint(e);
}
private void AngleSelector_MouseDown(object sender, MouseEventArgs e) //JBR supports looping
{
startClick = new Point(e.X, e.Y);
int thisAngle = XYToDegrees(startClick, origin);
if (e.Button == MouseButtons.Left)
{
thisAngle = (int)Math.Round(thisAngle / 45f) * 45;
if(thisAngle == 360) thisAngle = 0;
}
if (allowLoops) thisAngle += GetNumLoops(angle) * 360;
if (e.Button == MouseButtons.Middle)
{
if ((ModifierKeys & Keys.Control) == Keys.Control)
thisAngle = angle%360;
else
return;
}
if (thisAngle != angle)
{
angle = thisAngle;
if(!this.DesignMode && AngleChanged != null) AngleChanged(this, EventArgs.Empty); //Raise event
this.Refresh();
}
}
private void AngleSelector_MouseMove(object sender, MouseEventArgs e) //JBR supports looping
{
if (allowLoops && e.Button == MouseButtons.Middle)
{
int dist = (e.X - startClick.X) - (e.Y - startClick.Y);
if (dist < -turnThreshold || dist >= turnThreshold)
{
int mult = ((ModifierKeys & Keys.Shift) == Keys.Shift) ? 5 : 1;
startClick = new Point(e.X, e.Y);
int thisAngle = angle + (360 * mult);
if (dist < 0) thisAngle = angle - (360 * mult);
if (thisAngle != angle)
{
angle = thisAngle;
if (!this.DesignMode && AngleChanged != null) AngleChanged(this, EventArgs.Empty); //Raise event
this.Refresh();
}
}
}
if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right)
{
startClick = new Point(e.X, e.Y);
int thisAngle = XYToDegrees(startClick, origin);
if(e.Button == MouseButtons.Left || doomangleclamping)
{
thisAngle = (int)Math.Round(thisAngle / 45f) * 45;
if(thisAngle == 360) thisAngle = 0;
}
if (allowLoops) thisAngle += GetNumLoops(angle) * 360;
if (thisAngle != angle)
{
angle = thisAngle;
if(!this.DesignMode && AngleChanged != null) AngleChanged(this, EventArgs.Empty); //Raise event
this.Refresh();
}
}
}
#endregion
}
}