Creating a Tooltip rectangle and checking when the rectangle leaves the screen - c#

I am creating a Tooltip class. Generally, a rectangle will be drawn on top of the mouse position. If the rectangle hits the top side, or the right side, the rectangle would be drawn on the opposite side. This is my code for that:
private int LeavesScreen(Rectangle rectangle)
{
const int None = 0;
const int Top = 1;
const int Right = 2;
if (rectangle.Y < Viewport.Y)
return Top;
else if (rectangle.Right > Viewport.Right)
return Right;
return None;
}
public void Update(GameTime gameTime)
{
switch (LeavesScreen(Rect))
{
case 1: //Hits the top side of the screen
Destination = new Vector2(Rect.X, Rect.Y + Rect.Height);
Direction = Destination - new Vector2(Rect.X, Rect.Y);
Direction.Normalize();
break;
case 2: //Hits the right side of the screen
Destination = new Vector2(Rect.X - Rect.Width, Rect.Y);
Direction = Destination - new Vector2(Rect.X, Rect.Y);
Direction.Normalize();
break;
case 0:
Direction = Vector2.Zero;
break;
}
Position += Direction;
Rect = new Rectangle((int)Position.X, (int)Position.Y - Size.Width, Size.Width, Size.Height);
}
Doing this causes the rectangle to jump up and down, I guess this is because when the rectangle hits the edge, it gets moved, after being moved it detects that it does not hit the edge and then returns to the previous position, where it all repeats again.
NOTE:
I write this: (int)Position.Y - Size.Width so that the tooltip appears on top of the mouse, not below it.
I tried adding this inside case:0, this works, however, on the upper right half of the screen:
if (Viewport.Right - Rect.Right > Rect.Width + Viewport.Width * 0.02f)
Direction = Vector2.Zero;
if (Viewport.Top - Rect.Top < -Rect.Height - Viewport.Height * 0.02f)
Direction = Vector2.Zero;
EDIT: What's the difference if i try on the upper right or lower right side of the screen? Upper side works, lower side does not.
Note2: 0.02f is used so there is an empty space before it starts detecting collision again

Fixed the issue by taking a different approach. Instead of trying to reposition the rectangle to the opposite side of the mouse, I totally stop the rectangle from leaving the screen.
When trying to set the Vector2 Position of the tooltip, it checks if it leaves the borders.
Like this:
public void SetPosition(Vector2 newPosition)
{
//We remove Size.Height to make the tooltip appear above the mouse, not on top of it
newPosition = new Vector2(newPosition.X, newPosition.Y - Size.Height);
string LeavesBoundary = LeavesScreen(new Rectangle((int)newPosition.X, (int)newPosition.Y, (int)this.Size.Width, (int)this.Size.Height));
if (LeavesBoundary == "top")
{
this.Position = new Vector2(newPosition.X, Viewport.Top);
}
else if (LeavesBoundary == "right")
{
this.Position = new Vector2(Viewport.Right - this.Size.Width, newPosition.Y);
}
else if (LeavesBoundary == "topRight")
{
this.Position = new Vector2(Viewport.Right - this.Size.Width, Viewport.Top);
}
else
this.Position = newPosition;
}
And here is the updated LeavesScreen method:
private string LeavesScreen(Rectangle rectangle)
{
if (rectangle.Top < Viewport.Top)
{
//If the rectangle leaves the top part, create a new rectangle and see if it leaves the right part too. If it does, return "topRight"
Rectangle x = new Rectangle(rectangle.X, rectangle.Y + rectangle.Height, rectangle.Width, rectangle.Height);
if (x.Right > Viewport.Right)
return "topRight";
return "top";
}
if (rectangle.Right > Viewport.Right)
{
//Same as above, if it leaves right side, check if it leaves top side too.
Rectangle x = new Rectangle(rectangle.X - rectangle.Width, rectangle.Y, rectangle.Width, rectangle.Height);
if (x.Y < Viewport.Y)
return "topRight";
return "right";
}
return "none";
}

Related

Why when adding Handles.ArrowHandleCap to a line it's not drawing the ArrowHandleCap?

I'm using Editor Window maybe that's the problem ?
The idea is when connecting two nodes is also to make an arrow at the end position that will show the connecting flow direction.
In the screenshot when I'm connecting two nodes for example Window 0 to Window 1
So there should be an arrow at the end of the line near Window 1 showing indicating that Window 0 is connected to Window 1 so the flow is from Window 0 to Window 1.
But it's not drawing any ArrowHandleCap.
I don't mind to draw another simple white arrow at the end position but it's not working at all for now. Not drawing an arrow at all.
This is my Editor Window code :
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEditor.Graphs;
using UnityEngine.UI;
public class NodeEditor : EditorWindow
{
List<Rect> windows = new List<Rect>();
List<int> windowsToAttach = new List<int>();
List<int> attachedWindows = new List<int>();
int tab = 0;
float size = 10f;
[MenuItem("Window/Node editor")]
static void ShowEditor()
{
const int width = 600;
const int height = 600;
var x = (Screen.currentResolution.width - width) / 2;
var y = (Screen.currentResolution.height - height) / 2;
GetWindow<NodeEditor>().position = new Rect(x, y, width, height);
}
void OnGUI()
{
Rect graphPosition = new Rect(0f, 0f, position.width, position.height);
GraphBackground.DrawGraphBackground(graphPosition, graphPosition);
int selected = 0;
string[] options = new string[]
{
"Option1", "Option2", "Option3",
};
selected = EditorGUILayout.Popup("Label", selected, options);
if (windowsToAttach.Count == 2)
{
attachedWindows.Add(windowsToAttach[0]);
attachedWindows.Add(windowsToAttach[1]);
windowsToAttach = new List<int>();
}
if (attachedWindows.Count >= 2)
{
for (int i = 0; i < attachedWindows.Count; i += 2)
{
DrawNodeCurve(windows[attachedWindows[i]], windows[attachedWindows[i + 1]]);
}
}
BeginWindows();
if (GUILayout.Button("Create Node"))
{
windows.Add(new Rect(10, 10, 200, 40));
}
for (int i = 0; i < windows.Count; i++)
{
windows[i] = GUI.Window(i, windows[i], DrawNodeWindow, "Window " + i);
}
EndWindows();
}
void DrawNodeWindow(int id)
{
if (GUILayout.Button("Attach"))
{
windowsToAttach.Add(id);
}
GUI.DragWindow();
}
void DrawNodeCurve(Rect start, Rect end)
{
Vector3 startPos = new Vector3(start.x + start.width, start.y + start.height / 2, 0);
Vector3 endPos = new Vector3(end.x, end.y + end.height / 2, 0);
Vector3 startTan = startPos + Vector3.right * 50;
Vector3 endTan = endPos + Vector3.left * 50;
Color shadowCol = new Color(255, 255, 255);
for (int i = 0; i < 3; i++)
{// Draw a shadow
//Handles.DrawBezier(startPos, endPos, startTan, endTan, shadowCol, null, (i + 1) * 5);
}
Handles.DrawBezier(startPos, endPos, startTan, endTan, Color.white, null, 5);
Handles.color = Handles.xAxisColor;
Handles.ArrowHandleCap(0, endPos, Quaternion.LookRotation(Vector3.right), size, EventType.Repaint);
}
}
The problem is that the arrow is always behind the e.g. Window 0 since you call DrawNodeWindow always after DrawNodeCurve.
It happens because the arrow is always drawen starting from the endPos to the right direction with length = size so you always overlay it with the window later ... you have to change
// move your endpos to the left by size
var endPos = new Vector3(end.x - size, end.y + end.height / 2 , 0);
in order to have it start size pixels left before the actual end.x position.
However, as you can see it is still really small since it usually is used to display the arrow in 3D space - not using Pixel coordinates .. you might have to tweak arround or use something completely different.
How about e.g. simply using a GUI.DrawTexture instead with a given Arrow sprite?
// assign this as default reference via the Inspector for that script
[SerializeField] private Texture2D aTexture;
// ...
// since the drawTexture needs a rect which is not centered on the height anymore
// you have to use endPos.y - size / 2 for the Y start position of the texture
GUI.DrawTexture(new Rect(endPos.x, endPos.y - size / 2, size, size), aTexture, ScaleMode.StretchToFill);
like mentioned in the comment for all serialized fields in Unity you can already reference default assets for the script itself (in contrary to doing it for each instance like for MonoBehaviours) so with the NodeEditor script selected simply reference a downloaded arrow texture
If using a white arrow as texture you could then still change its color using
var color = GUI.color;
GUI.color = Handles.xAxisColor;
GUI.DrawTexture(new Rect(endPos.x, endPos.y - size / 2, size, size), aTexture, ScaleMode.StretchToFill);
GUI.color = color;
Result
P.S.: Arrow icon usedfor the example: https://iconsplace.com/red-icons/arrow-icon-14 you can already change the color directly on that page before downloading the icon ;)

Tooltip in PictureBox FillPie Coordinates C#

I'm drawing a Circle made of 360 FillPie. Each FillPie color is taken from a List. I want to return a string that says at which degree is the mouse and how much is the value of the list to put it on a tooltip.
List<int> datiDisco = new List<int>();
public void Paint (Graphics grafica)
{
try
{
for (int i = 0; i < datiDisco.Count; i++)
{
Brush penna = new SolidBrush(Color.FromArgb(255, ScalaGrigi(valori[i]), ScalaGrigi(valori[i]), ScalaGrigi(valori[i])));
grafica.FillPie(penna, 0, 0, 400, 400, i, 1.0f);
}
}
catch
{
}
}
until here the code is working and i managed to draw the circle with the correct color.Now i can't figure out how i can take the coordinate of each fillpie that i have drawn. Can someone help me?
Figuring out which pie segment the mouse cursor lies in is a simple application of trigonometry, specifically an application of the inverse tangent (aka arctangent or atan).
As a quick reminder for those who've encountered this before, or as a lesson for those who haven't, let's look quickly at the tangent function. Trigonometry deals with the geometry of right triangles, and by definition a right triangle has two sides and a hypotenuse. The hypotenuse is a special name for the side of the triangle opposite the right (90° or π/2) angle. The other two sides are helpfully just called sides.
The tangent function's value is the ratio of the side opposite an angle to the side adjacent to that angle. The arctangent is the angle whose tangent is equal to that ratio. Because of the symmetry of the function we need to calculate the angle, and then add or subtract an offset depending on the quadrant to extract the 'real' angle. In diagram form this looks like:
The tangent function has discontinuities at several points, namely when the adjacent side's length is 0 (90° and 270°), so we'll have to treat those points specially.
OK, enough math, now on to the practical application.
For this demo, create a new C# WinForms project, and on the default Form1 add a PictureBox.
First, since I don't have your color generation function, I use the following list of values and helper function:
List<int> values = Enumerable.Range(0, 360).ToList();
int Rescale(int x) => (int)(((double)x / 360.0) * 255.0);
In the constructor hook up a couple events, and set some properties:
public Form1()
{
InitializeComponent();
this.pictureBox1.BorderStyle = BorderStyle.Fixed3D;
this.pictureBox1.Size = new Size(50, 50);
this.Size = new Size(450, 450);
this.DoubleBuffered = true;
this.Paint += Form1_Paint;
this.MouseMove += Form1_MouseMove;
}
To paint the circle I use a slightly modified version of your OnPaint handler:
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
for (int i = 0; i < values.Count; i++)
{
Brush b = new SolidBrush(Color.FromArgb(255, Rescale(values[i]), 0, 0));
e.Graphics.FillPie(b, 0, 0, 400, 400, (float)i, 1.0f);
}
}
In the MouseMove event is where we do most of the heavy lifting:
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
this.pictureBox1.Location = new Point(e.X + 5, e.Y - 5);
int segment = (int)GetAngle(new Rectangle(0, 0, 400, 400), e.Location);
this.pictureBox1.BackColor = Color.FromArgb(255, Rescale(segment), 0, 0);
}
You may notice that since there are 360 wedges are in increments of a degree, I just truncated the angle. If you need more precision, or you decide to use segments greater than 1 degree, then you could use various rounding algorithms to round the angle to the nearest section of the pie.
At last, we're ready to implement the GetAngle function. First we calculate the center of the circle, because everything is relative to that.
int cx = (rect.Width + rect.X) / 2;
int cy = (rect.Height + rect.Y) / 2;
Next calculate the difference between the mouse's position and the center of the rectangle. (I've inverted the y coordinate to line up with 'standard' Cartesian coordinates, to make things easier, and match the coordinates you'd see in a math textbook.)
float x = pTo.X - cx;
float y = (cy - pTo.Y);
Next check for the arctangent's undefined points (and a couple of shortcuts we can take):
if ((int)x == 0)
{
if (y > 0) return 270;
else return 90;
}
else if ((int)y == 0)
{
if (x > 0) return 0;
else return 180;
}
Calculate the internal angle:
float ccwAngle = (float)Math.Atan(Math.Abs(y) / Math.Abs(x));
And map that angle to the appropriate quadrant:
if (x > 0 && y > 0)
{
}
else if (x < 0 && y > 0)
{
ccwAngle = (float)Math.PI - ccwAngle;
}
else if (x < 0 && y < 0)
{
ccwAngle = ccwAngle + (float)Math.PI;
}
else if (x > 0 && y < 0)
{
ccwAngle *= -1f;
}
Convert the angle from degrees to radians and normalize (make sure it's between 0° and 360°)
ccwAngle *= (float)(180 / Math.PI);
while (ccwAngle > 360) ccwAngle -= 360;
while (ccwAngle < 0) ccwAngle += 360;
Finally convert the counter-clockwise angle we needed to do the math into the clockwise angle that GDI uses, and return the value:
return 360f - ccwAngle;
All that together produces the final result:
(The code above is also available as a complete example in this gist)

Resize a box with the mouse while maintaining the aspect ratio?

I'm trying to create a resizable image overlay (for cropping purposes). It seems pretty easy to resize the overlay if I ignore the aspect ratio, but I can't figure out how to perform a constrained resize that respects the AR. I figure that I obviously can't obey the overlay's "grip" positions (or even borders) unless I force the mouse to follow it, but that seems unnatural, so I'll just have to rely on the mouse gesture (which I don't mind doing).
I can also easily resize the overlay and then force it into the proper dimensions afterwards (like every other question about this topic on this site is about), but it's not very intuitive when using a mouse.
This is sort of what I'm going for:
http://deepliquid.com/projects/Jcrop/demos.php?demo=live_crop
I've written an application like this before but it was browser-based so I used a javascript library. This is a desktop application and I haven't found a suitable library for this.
I've left a lot of details out of this code snippet and simplified some conditions with booleans.
private void pbImage_Paint(object sender, PaintEventArgs e)
{
//Overlay
e.Graphics.FillRectangle(brushRect, overlayRect);
// Grips
e.Graphics.FillRectangle(gripRect, leftTopGrip);
e.Graphics.FillRectangle(gripRect, rightTopGrip);
e.Graphics.FillRectangle(gripRect, leftBottomGrip);
e.Graphics.FillRectangle(gripRect, rightBottomGrip);
AdjustGrips();
base.OnPaint(e);
}
public void AdjustGrips()
{
// The next section only causes the grips to partly obey
// the AR - the rest of the overlay ignores it
if (overlayRect.Height * arWidth <= overlayRect.Width)
overlayRect.Width = overlayRect.Height * arWidth;
else if (overlayRect.Width * arHeight <= overlayRect.Height)
overlayRect.Height = overlayRect.Width * arHeight;
leftTopGrip.X = overlayRect.Left;
leftTopGrip.Y = overlayRect.Top;
rightTopGrip.X = overlayRect.Right - rightTopGrip.Width;
rightTopGrip.Y = overlayRect.Top;
leftBottomGrip.Y = overlayRect.Bottom - leftBottomGrip.Height;
leftBottomGrip.X = overlayRect.Left;
rightBottomGrip.X = overlayRect.Right - rightBottomGrip.Width;
rightBottomGrip.Y = overlayRect.Bottom - rightBottomGrip.Height;
}
private void pbImage_MouseMove(object sender, MouseEventArgs e)
{
Point pt = new Point(e.X, e.Y);
// Details elided
if (e.Button == MouseButtons.Left && mouseinGrip)
{
if (bottomRightIsGripped)
{
newOverlayRect.X = overlayRect.X;
newOverlayRect.Y = overlayRect.Y;
newOverlayRect.Width = pt.X - newOverlayRect.Left;
newOverlayRect.Height = pt.Y - newOverlayRect.Top;
if (newOverlayRect.X > newOverlayRect.Right)
{
newOverlayRect.Offset(-width, 0);
if (newOverlayRect.X < 0)
newOverlayRect.X = 0;
}
if (newOverlayRect.Y > newOverlayRect.Bottom)
{
newOverlayRect.Offset(0, -height);
if (newOverlayRect.Y < 0)
newOverlayRect.Y = 0;
}
pbImage.Invalidate();
oldOverlayRect = overlayRect = newOverlayRect;
Cursor = Cursors.SizeNWSE;
}
// Code for other grips elided
}
AdjustGrips();
pbImage.Update();
base.OnMouseMove(e);
}
// Mouse up and down elided
You have complete control over the new size for the overlay as it drags.
The example link that you've given, is simply selecting a starting point based on the click down, then selecting Max(Abs(pt.x - start.x), Abs(pt.y - start.y)), and basing the crop square off of that.
To use a non square ratio, normalize the distances first.
// given known data
//
// Point start;
// The starting location of the mouse down for the drag,
// or the top left / bottom right of the crop based on if the mouse is
// left/above the starting point
//
// Size ratio;
// The ratio of the result crop
//
// pt = (20)x(-20)
// start = (0),(0)
// ratio = (1)x(2)
var dist = new Point(pt.X - start.X, pt.Y - start.Y);
// "normalize" the vector from the ratio
// normalized vector is the distances with respect to the ratio
// ratio is (1)x(2). A (20)x(-20) is normalized as (20),(-10)
var normalized = new Point(dist.X / ratio.Width, dist.Y / ratio.Height);
// In our (20),(-10) example, we choose the ratio's height 20 as the larger normal.
// we will base our new size on the height
var largestNormal = (Math.Abs(normalized.X) > Math.Abs(normalized.Y)
? Math.Abs(normalized.X) : Math.Abs(normalized.Y);
// The calcedX will be 20, calcedY will be 40
var calcedOffset = (largestNormal * ratio.Width, largestNormal * ratio.Height);
// reflect the calculation back to the correct quarter
// final size is (20)x(-40)
if (distX < 0) calcedOffset.X *= -1;
if (distY < 0) calcedOffset.Y *= -1;
var newPt = new Point(start.X + calcedOffset.X, start.Y + calcedOffset.Y);
Notice that one of the lengths can grow greater than the mouse location, but it will never be less. This will have the effect of the mouse traveling along the edge of the new crop box, and the box maintaining ratio.
I've figured out what was causing the original problems in my code. Unlike a static image resize, the aspect ratio code depends on which grip you're "holding", so putting it in a common location for all cases (eg. when the grip positions are set) will not work. You can easily calculate the size of the what the rect should be on the next update, but the position should be set depending on which grip is being held.
If, for example, you're resizing by holding the top left grip, then the bottom and right sides of the cropping rectangle should remain stationary. If you leave the code the same, then the rectangle resizes correctly, but it moves around the canvas and/or the grips go out of sync with the corners of the rect. There is probably a better way to do this but here's some crude code that works. I've only included code for the bottom right and top left grips to illustrate the differences. Extraneous things like setting the mouse pointer and error checking omitted.
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
Point mousePosition = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Left)
{
// This resizeMode, moveMode and other booleans
// are set in the MouseUp event
if (resizeBottomLeft)
{
// Top and Right should remain static!
newCropRect.X = mousePosition.X;
newCropRect.Y = currentCropRect.Y;
newCropRect.Width = currentCropRect.Right - mousePosition.X;
newCropRect.Height = mousePosition.Y - newCropRect.Top;
if (newCropRect.X > newCropRect.Right)
{
newCropRect.Offset(cropBoxWidth, 0);
if (newCropRect.Right > ClientRectangle.Width)
newCropRect.Width = ClientRectangle.Width - newCropRect.X;
}
if (newCropRect.Y > newCropRect.Bottom)
{
newCropRect.Offset(0, -cropBoxHeight);
if (newCropRect.Y < 0)
newCropRect.Y = 0;
}
// Aspect Ratio + Positioning
if (newCropRect.Width > newCropRect.Height)
{
newCropRect.Height = (int)(newCropRect.Width / ASPECT_RATIO);
}
else
{
int newWidth = (int)(newCropRect.Height * ASPECT_RATIO);
newCropRect.X = newCropRect.Right - newWidth;
newCropRect.Width = newWidth;
}
}
else if (resizeTopRight)
{
// Bottom and Left should remain static!
newCropRect.X = oldCropRect.X;
newCropRect.Y = mousePosition.Y;
newCropRect.Width = mousePosition.X - newCropRect.Left;
newCropRect.Height = oldCropRect.Bottom - mousePosition.Y;
if (newCropRect.X > newCropRect.Right)
{
newCropRect.Offset(-cropBoxWidth, 0);
if (newCropRect.X < 0)
newCropRect.X = 0;
}
if (newCropRect.Y > newCropRect.Bottom)
{
newCropRect.Offset(0, cropBoxHeight);
if (newCropRect.Bottom > ClientRectangle.Height)
newCropRect.Y = ClientRectangle.Height - newCropRect.Height;
}
// Aspect Ratio + Positioning
if (newCropRect.Width > newCropRect.Height)
{
int newHeight = (int)(newCropRect.Width / ASPECT_RATIO);
newCropRect.Y = newCropRect.Bottom - newHeight;
newCropRect.Height = newHeight;
}
else
{
int newWidth = (int)(newCropRect.Height * ASPECT_RATIO);
newCropRect.Width = newWidth;
}
}
else if (moveMode) //Moving the rectangle
{
newMousePosition = mousePosition;
int dx = newMousePosition.X - oldMousePosition.X;
int dy = newMousePosition.Y - oldMousePosition.Y;
currentCropRect.Offset(dx, dy);
newCropRect = currentCropRect;
oldMousePosition = newMousePosition;
}
if (resizeMode || moveMode)
{
oldCropRect = currentCropRect = newCropRect;
// Set the new position of the grips
AdjustGrips();
pictureBox1.Invalidate();
pictureBox1.Update();
}
}
}

Alternative to GetWindowRect() for multi-monitor usage?

I am trying to reposition a form to bottom/right point of a control.
public void SetAutoLocation()
{
Rect rect;
GetWindowRect(referenceControl.Handle, out rect);
Point targetPoint;
targetPoint = new Point(rect.left, rect.top + referenceControl.Height);
if (rect.left + referenceControl.Width - this.Width < 0) //Outside left border
{
targetPoint.X = 0;
}
else
{
targetPoint.X = rect.left - this.Width + referenceControl.Width;
}
if (targetPoint.X + this.Width > System.Windows.Forms.SystemInformation.WorkingArea.Right) //Outside right border
{
targetPoint.X = System.Windows.Forms.SystemInformation.WorkingArea.Right - this.Width;
}
else if (targetPoint.X < 0)
targetPoint.X = 0;
if (targetPoint.Y + this.Height > System.Windows.Forms.SystemInformation.WorkingArea.Bottom) //Outside below border
{
targetPoint.Y = rect.top - this.Height;
}
if (targetPoint.Y < 0)
{
targetPoint.Y = 0;
}
if (targetPoint.X < 0)
{
targetPoint.X = 0;
}
this.Location = targetPoint;
this.Refresh();
}
The above code works well in single monitor display. But when the parent form is opened in dual monitor display, the form positions itself on the 1st monitor, as GetWindowRect() returns the rectangle inside the primary display.
So looking for some alternative to GetWindowRect() that might work on multi-display.
Use the Screen class to get the WorkingArea for the monitor that the control is on:
var screen = Screen.FromControl(referenceControl);
var area = screen.WorkingArea;
var rect = referenceControl.RectangleToScreen(
new Rectangle(0, 0, referenceControl.Width, referenceControl.Height));
// etc..
Note how RectangleToScreen can help you avoid having to pinvoke GetWindowRect().
If you consult MSDN it's clearly stated that SystemInformation.WorkingArea returns informations only for primary monitor :
WorkingArea always returns the work area of the primary monitor. If
you need the work area of a monitor in a multiple display environment,
you can call one of the overloads of Screen.GetWorkingArea.

Draw simple circle in XNA

I want to draw a 2d, filled, circle. I've looked everywhere and cannot seem to find anything that will even remotely help me draw a circle. I simply want to specify a height and width and location on my canvas.
Anyone know how?
Thanks!
XNA doesn't normally have an idea of a canvas you can paint on. Instead you can either create a circle in your favorite paint program and render it as a sprite or create a series vertexes in a 3D mesh to approximate a circle and render that.
You could also check out the sample framework that Jeff Weber uses in Farseer:
http://www.codeplex.com/FarseerPhysics
The demos have a dynamic texture generator that let's him make circles and rectangles (which the samples then use as the visualization of the physics simulation). You could just re-use that :-)
Had the same problem, as others already suggested you need to draw a square or rectangle with a circle texture on it. Here follows my method to create a circle texture runtime. Not the most efficient or fancy way to do it, but it works.
Texture2D createCircleText(int radius)
{
Texture2D texture = new Texture2D(GraphicsDevice, radius, radius);
Color[] colorData = new Color[radius*radius];
float diam = radius / 2f;
float diamsq = diam * diam;
for (int x = 0; x < radius; x++)
{
for (int y = 0; y < radius; y++)
{
int index = x * radius + y;
Vector2 pos = new Vector2(x - diam, y - diam);
if (pos.LengthSquared() <= diamsq)
{
colorData[index] = Color.White;
}
else
{
colorData[index] = Color.Transparent;
}
}
}
texture.SetData(colorData);
return texture;
}
Out of the box, there's no support for this in XNA. I'm assuming you're coming from some GDI background and just want to see something moving around onscreen. In a real game though, this is seldom if ever needed.
There's some helpful info here:
http://forums.xna.com/forums/t/7414.aspx
My advice to you would be to just fire up paint or something, and create the basic shapes yourself and use the Content Pipeline.
Another option (if you want to use a more complex gradient brush or something) is to draw a quad aligned to the screen and use a pixel shader.
What I did to solve this was to paint a rectangular texture, leaving the area of the rectangle which doesn't contain the circle transparent. You check to see if a point in the array is contained within a circle originating from the center of the rectangle.
Using the color data array is a bit weird because its not a 2D array. My solution was to bring in some 2D array logic into the scenario.
public Texture2D GetColoredCircle(float radius, Color desiredColor)
{
radius = radius / 2;
int width = (int)radius * 2;
int height = width;
Vector2 center = new Vector2(radius, radius);
Circle circle = new Circle(center, radius,false);
Color[] dataColors = new Color[width * height];
int row = -1; //increased on first iteration to zero!
int column = 0;
for (int i = 0; i < dataColors.Length; i++)
{
column++;
if(i % width == 0) //if we reach the right side of the rectangle go to the next row as if we were using a 2D array.
{
row++;
column = 0;
}
Vector2 point = new Vector2(row, column); //basically the next pixel.
if(circle.ContainsPoint(point))
{
dataColors[i] = desiredColor; //point lies within the radius. Paint it.
}
else
{
dataColors[i] = Color.Transparent; //point lies outside, leave it transparent.
}
}
Texture2D texture = new Texture2D(GraphicsDevice, width, height);
texture.SetData(0, new Rectangle(0, 0, width, height), dataColors, 0, width * height);
return texture;
}
And here's the method to check whether or not a point is contained within your circle:
public bool ContainsPoint(Vector2 point)
{
return ((point - this.Center).Length() <= this.Radius);
}
Hope this helps!
public Texture2D createCircleText(int radius, GraphicsDevice Devise,Color color,int tickenes)
{
Texture2D texture = new Texture2D(Devise, radius, radius);
Color[] colorData = new Color[radius * radius];
if (tickenes >= radius) tickenes = radius - 5;
float diam = radius / 2f;
float diamsq = diam * diam;
float intdiam = (radius-tickenes) / 2f;
float intdiamsq = intdiam * intdiam;
for (int x = 0; x < radius; x++)
{
for (int y = 0; y < radius; y++)
{
int index = x * radius + y;
Vector2 pos = new Vector2(x - diam, y - diam);
if (pos.LengthSquared() <= diamsq)
{
colorData[index] = color;
}
else
{
colorData[index] = Color.Transparent;
}
if (pos.LengthSquared() <= intdiamsq)
{
colorData[index] = Color.Transparent;
}
}
}
texture.SetData(colorData);
return texture;
}

Categories