2020-08-29 14:26:59 +00:00
#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 Float version of the AngleControl!
// Supports changing snap angle
namespace CodeImp.DoomBuilder.GZBuilder.Controls
{
public partial class AngleControlF : UserControl
{
#region Variables
private float angle ;
private float angleoffset ;
private bool allowLoops = true ; //JBR
2022-12-19 23:22:57 +00:00
private bool startAtOne = false ; //JBR
private int turnThreshold = 16 ; //JBR
2020-08-29 14:26:59 +00:00
private float snapangle = 45f ; //JBR
private Rectangle drawRegion ;
private const int drawOffset = 2 ;
private const int markScaler = 5 ;
private Point origin ;
private Point startClick ; //JBR
//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 float Angle { get { return ( angle = = NO_ANGLE ? NO_ANGLE : angle - angleoffset ) ; } set { angle = ( value = = NO_ANGLE ? NO_ANGLE : value + angleoffset ) ; this . Refresh ( ) ; } }
public float AngleOffset { get { return angleoffset ; } set { angleoffset = value ; this . Refresh ( ) ; } }
public const float NO_ANGLE = float . 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 )
{
2022-12-20 17:05:03 +00:00
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." ) ;
2020-08-29 14:26:59 +00:00
}
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.")]
2022-12-19 23:22:57 +00:00
[DefaultValue(false)]
2020-08-29 14:26:59 +00:00
public bool StartAtOne { get { return startAtOne ; } set { startAtOne = value ; } } //JBR
[Description("Drag distance in pixels for user to change the loop number.")]
[DefaultValue(16)]
2022-12-20 17:05:03 +00:00
public int TurnThreshold { get { return turnThreshold ; } set { turnThreshold = value ; } } //JBR
2020-08-29 14:26:59 +00:00
[Description("Snap angle in degrees.")]
[DefaultValue(45f)]
public float SnapAngle { get { return snapangle ; } set { snapangle = value ; } } //JBR
#endregion
public AngleControlF ( )
{
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 float XYToDegrees ( Point xy , Point origin )
{
float xDiff = xy . X - origin . X ;
float yDiff = xy . Y - origin . Y ;
float angle = ( ( float ) Math . Atan2 ( - yDiff , xDiff ) * 180f / Angle2D . PI ) + 360f ;
if ( angle < 0f ) angle + = 360f ;
if ( angle > = 360f ) angle - = 360f ;
return angle ;
}
private static int GetNumLoops ( float angle ) //JBR
{
if ( angle = = NO_ANGLE ) return 0 ;
if ( angle > 0f ) return ( int ) angle / 360 ;
return ( ( int ) 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 ( float di = 0f ; di < 360f ; di + = snapangle )
{
PointF p1 = DegreesToXY ( di , origin . X - 6 , origin ) ;
PointF p2 = DegreesToXY ( di , 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 ( ) ;
2023-01-04 11:44:41 +00:00
string baseAngle = ( angle % 360 ) . ToString ( ) + "°" ;
2022-12-20 17:05:03 +00:00
StringFormat strFormat = new StringFormat ( ) ;
2020-08-29 14:26:59 +00:00
strFormat . LineAlignment = StringAlignment . Far ;
strFormat . Alignment = StringAlignment . Far ;
2022-12-20 17:05:03 +00:00
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 - 1 - Font . Size , strFormat ) ;
g . DrawString ( baseAngle , Font , fill , hpos + 1 , drawRegion . Bottom - 1 - Font . Size , strFormat ) ;
g . DrawString ( baseAngle , Font , fill , hpos - 1 , drawRegion . Bottom + 1 - Font . Size , strFormat ) ;
g . DrawString ( baseAngle , Font , fill , hpos + 1 , drawRegion . Bottom + 1 - Font . Size , strFormat ) ;
g . DrawString ( baseAngle , Font , text , hpos , drawRegion . Bottom - Font . Size , strFormat ) ;
}
}
2020-08-29 14:26:59 +00:00
}
// 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 ) ;
float thisAngle = XYToDegrees ( startClick , origin ) ;
2022-12-20 17:05:03 +00:00
if ( e . Button = = MouseButtons . Left )
2020-08-29 14:26:59 +00:00
{
thisAngle = ( float ) Math . Round ( thisAngle / snapangle ) * snapangle ;
if ( thisAngle = = 360f ) thisAngle = 0f ;
}
if ( allowLoops ) thisAngle + = ( float ) GetNumLoops ( angle ) * 360f ;
2022-12-20 17:05:03 +00:00
if ( e . Button = = MouseButtons . Middle )
{
if ( ( ModifierKeys & Keys . Control ) = = Keys . Control )
thisAngle = angle % 360 ;
else
return ;
}
if ( thisAngle ! = angle )
2020-08-29 14:26:59 +00:00
{
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 ) ;
2022-12-19 23:22:57 +00:00
if ( dist < - turnThreshold | | dist > = turnThreshold )
2022-12-20 17:05:03 +00:00
{
int mult = ( ( ModifierKeys & Keys . Shift ) = = Keys . Shift ) ? 5 : 1 ;
startClick = new Point ( e . X , e . Y ) ;
float thisAngle = angle + ( 360f * mult ) ;
if ( dist < 0 ) thisAngle = angle - ( 360f * mult ) ;
2020-08-29 14:26:59 +00:00
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 ) ;
float thisAngle = XYToDegrees ( startClick , origin ) ;
if ( e . Button = = MouseButtons . Left )
{
thisAngle = ( float ) Math . Round ( thisAngle / snapangle ) * snapangle ;
if ( thisAngle = = 360f ) thisAngle = 0f ;
}
if ( allowLoops ) thisAngle + = ( float ) GetNumLoops ( angle ) * 360f ;
if ( thisAngle ! = angle )
{
angle = thisAngle ;
if ( ! this . DesignMode & & AngleChanged ! = null ) AngleChanged ( this , EventArgs . Empty ) ; //Raise event
this . Refresh ( ) ;
}
}
}
#endregion
}
}