summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SpriteEd/Main.storyboard9
-rw-r--r--SpriteEd/NSSpriteEdit.cs509
-rw-r--r--SpriteEd/Sprite.cs20
-rw-r--r--SpriteEd/ViewController.cs2
4 files changed, 530 insertions, 10 deletions
diff --git a/SpriteEd/Main.storyboard b/SpriteEd/Main.storyboard
index bf0d76a..f63c659 100644
--- a/SpriteEd/Main.storyboard
+++ b/SpriteEd/Main.storyboard
@@ -795,6 +795,15 @@
<menuItem title="Filled Circle" tag="5" id="7VK-vs-1sB">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
+ <menuItem title="Ellipse" tag="6" id="3IR-8c-BKr">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Filled Ellipse" tag="7" id="0yl-VM-kRo">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Flood Fill" tag="8" id="PdC-3B-Utg">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
</items>
</menu>
</popUpButtonCell>
diff --git a/SpriteEd/NSSpriteEdit.cs b/SpriteEd/NSSpriteEdit.cs
index 605e69f..0dbd806 100644
--- a/SpriteEd/NSSpriteEdit.cs
+++ b/SpriteEd/NSSpriteEdit.cs
@@ -62,12 +62,30 @@ namespace SpriteEd
/// <summary>
/// Draw a filled circle.
/// </summary>
- FilledCircle
+ FilledCircle,
+
+ /// <summary>
+ /// Draw an ellipse.
+ /// </summary>
+ Ellipse,
+
+ /// <summary>
+ /// Draw a filled ellipse.
+ /// </summary>
+ FilledEllipse,
+
+ /// <summary>
+ /// Flood fill.
+ /// </summary>
+ FloodFill
}
private Sprite m_sprite;
- private int m_cx;
- private int m_cy;
+ private Sprite m_undo;
+ private uint m_sx;
+ private uint m_sy;
+ private uint m_ex;
+ private uint m_ey;
/// <summary>
/// The sprite being edited.
@@ -77,6 +95,7 @@ namespace SpriteEd
set
{
m_sprite = value;
+ m_undo = new Sprite(m_sprite.Width, m_sprite.Height);
NeedsDisplay = true;
}
}
@@ -111,8 +130,6 @@ namespace SpriteEd
/// </summary>
public NSSpriteEdit()
{
- m_cx = -1;
- m_cy = -1;
Init();
}
@@ -135,12 +152,20 @@ namespace SpriteEd
Init();
}
+ /// <summary>
+ /// Initialise settings for the control.
+ /// </summary>
private void Init()
{
+ m_sx = uint.MaxValue;
+ m_sy = uint.MaxValue;
WantsLayer = true;
LayerContentsRedrawPolicy = NSViewLayerContentsRedrawPolicy.OnSetNeedsDisplay;
}
+ /// <summary>
+ /// Get the size of the edit block in the X direction.
+ /// </summary>
private uint BlockSizeX
{
get
@@ -163,6 +188,9 @@ namespace SpriteEd
}
}
+ /// <summary>
+ /// Get the size of the edit block in the Y direction.
+ /// </summary>
private uint BlockSizeY
{
get
@@ -185,6 +213,29 @@ namespace SpriteEd
}
}
+ /// <summary>
+ /// Save the undo state.
+ /// </summary>
+ private void SaveUndo()
+ {
+ m_undo.CopyFrom(m_sprite);
+ }
+
+ /// <summary>
+ /// Undo the last change.
+ /// </summary>
+ public void Undo()
+ {
+ m_sprite.CopyFrom(m_undo);
+ NeedsDisplay = true;
+ }
+
+ /// <summary>
+ /// Draw the grid given a sprite co-ord.
+ /// </summary>
+ /// <param name="context">The context to draw on.</param>
+ /// <param name="x">The X co-ord.</param>
+ /// <param name="y">The Y co-ord.</param>
private void DrawGrid(CGContext context, uint x, uint y)
{
uint bsx = BlockSizeX;
@@ -200,6 +251,13 @@ namespace SpriteEd
DrawLine(context, x, y - bsy, x, y, white);
}
+ /// <summary>
+ /// Draw the sprite cell given a sprite co-ord and colour.
+ /// </summary>
+ /// <param name="context">The context to draw on.</param>
+ /// <param name="x">The X co-ord.</param>
+ /// <param name="y">The Y co-ord.</param>
+ /// <param name="colour">The colour to draw.</param>
private void DrawCell(CGContext context, uint x, uint y, Colour colour)
{
uint bsx = BlockSizeX;
@@ -213,6 +271,12 @@ namespace SpriteEd
DrawFilledRect(context, rect, colour);
}
+ /// <summary>
+ /// Draw a filled rectangle.
+ /// </summary>
+ /// <param name="context">The context to draw on.</param>
+ /// <param name="rect">The rectangle to draw.</param>
+ /// <param name="colour">The colour.</param>
private void DrawFilledRect(CGContext context, CGRect rect, Colour colour)
{
using (CGColor cgcol = Util.ColourCG(colour))
@@ -222,6 +286,15 @@ namespace SpriteEd
}
}
+ /// <summary>
+ /// Draw a line.
+ /// </summary>
+ /// <param name="context">The context to draw on.</param>
+ /// <param name="x1">The start X co-ord.</param>
+ /// <param name="y1">The start Y co-ord.</param>
+ /// <param name="x2">The end X co-ord.</param>
+ /// <param name="y2">The end Y co-ord.</param>
+ /// <param name="colour">The colour.</param>
private void DrawLine(CGContext context, uint x1, uint y1, uint x2, uint y2, Colour colour)
{
using (CGColor cgcol = Util.ColourCG(colour))
@@ -235,6 +308,228 @@ namespace SpriteEd
}
/// <summary>
+ /// Plot a pixel either in the sprite definition or onscreen if context is set.
+ /// </summary>
+ /// <param name="context">The optional context to draw own.</param>
+ /// <param name="x">The sprite X co-ord.</param>
+ /// <param name="y">The sprite Y co-ord.</param>
+ /// <param name="colour">The colour to draw in.</param>
+ private void SpritePlot(CGContext context, int x, int y, uint colour)
+ {
+ if (x >=0 && x < m_sprite.Width && y >=0 && y < m_sprite.Height)
+ {
+ if (context != null)
+ {
+ DrawCell(context, (uint)x, (uint)y, Palette[colour]);
+ }
+ else
+ {
+ m_sprite[(uint)x, (uint)y] = colour;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Rank the co-ords from the stand and end points.
+ /// </summary>
+ /// <param name="x1">The lowest X co-ord.</param>
+ /// <param name="y1">The lowest Y co-ord.</param>
+ /// <param name="x2">The highest X co-ord.</param>
+ /// <param name="y2">The highest Y co-ord.</param>
+ private void RankCoords(out int x1, out int y1, out int x2, out int y2)
+ {
+ x1 = (int)Math.Min(m_sx, m_ex);
+ y1 = (int)Math.Min(m_sy, m_ey);
+ x2 = (int)Math.Max(m_sx, m_ex);
+ y2 = (int)Math.Max(m_sy, m_ey);
+ }
+
+ /// <summary>
+ /// Flood fill an area.
+ /// </summary>
+ /// <param name="x">The X co-ord to fill.</param>
+ /// <param name="y">The Y co-ord to fill.</param>
+ /// <param name="bg">The background colour to fill in.</param>
+ private void FloodFill(int x, int y, uint bg)
+ {
+ if (x < 0 || x >= m_sprite.Width ||
+ y < 0 || y >= m_sprite.Height ||
+ m_sprite[(uint)x,(uint)y] == Colour ||
+ m_sprite[(uint)x, (uint)y] != bg)
+ {
+ return;
+ }
+
+ m_sprite[(uint)x, (uint)y] = Colour;
+
+ FloodFill(x-1, y, bg);
+ FloodFill(x+1, y, bg);
+ FloodFill(x, y-1, bg);
+ FloodFill(x, y+1, bg);
+ }
+
+ /// <summary>
+ /// Draw a line on the sprite grid.
+ /// </summary>
+ /// <param name="context">If set, draw the result on this context rather than the sprite data.</param>
+ private void DrawSpriteLine(CGContext context)
+ {
+ int dx=(int)m_ex - (int)m_sx;
+ int dy=(int)m_ey - (int)m_sy;
+
+ int ix = Math.Sign(dx);
+ int iy = Math.Sign(dy);
+
+ dx = Math.Abs(dx);
+ dy = Math.Abs(dy);
+
+ int incrE;
+ int incrNE;
+ int d;
+ bool ymode;
+
+ if (dy > dx)
+ {
+ ymode = true;
+ d = dx * 2 - dy;
+ incrE = dx * 2;
+ incrNE = (dx - dy) * 2;
+ }
+ else
+ {
+ ymode = false;
+ d = dy * 2 - dx;
+ incrE = dy * 2;
+ incrNE = (dy - dx) * 2;
+ }
+
+ int x = (int)m_sx;
+ int y = (int)m_sy;
+
+ SpritePlot(context, x, y, Colour);
+
+ if (ymode)
+ {
+ while(y != m_ey)
+ {
+ if (d <= 0)
+ {
+ d += incrE;
+ y += iy;
+ }
+ else
+ {
+ d += incrNE;
+ y += iy;
+ x += ix;
+ }
+
+ SpritePlot(context, x, y, Colour);
+ }
+ }
+ else
+ {
+ while(x != m_ex)
+ {
+ if (d <= 0)
+ {
+ d += incrE;
+ x += ix;
+ }
+ else
+ {
+ d += incrNE;
+ y += iy;
+ x += ix;
+ }
+
+ if (context != null)
+ {
+ DrawCell(context, (uint)x, (uint)y, Palette[Colour]);
+ }
+ else
+ {
+ m_sprite[(uint)x, (uint)y] = Colour;
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Draw a rectangle on the sprite.
+ /// </summary>
+ /// <param name="context">If set, draw the result on this context rather than the sprite data.</param>
+ /// <param name="fill">If true then fill the rectangle.</param>
+ private void DrawSpriteRect(CGContext context, bool fill)
+ {
+ int x1, y1, x2, y2;
+
+ RankCoords(out x1, out y1, out x2, out y2);
+
+ if (fill)
+ {
+ for(int x = x1; x <= x2; x++)
+ {
+ for(int y = y1; y <= y2; y++)
+ {
+ SpritePlot(context, x, y, Colour);
+ }
+ }
+ }
+ else
+ {
+ for(int x = x1; x <= x2; x++)
+ {
+ SpritePlot(context, x, y1, Colour);
+ SpritePlot(context, x, y2, Colour);
+ }
+
+ for(int y = y1; y <= y2; y++)
+ {
+ SpritePlot(context, x1, y, Colour);
+ SpritePlot(context, x2, y, Colour);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Draw an ellipse or circle on the sprite.
+ /// </summary>
+ /// <param name="context">If set, draw the result on this context rather than the sprite data.</param>
+ /// <param name="circle">If true draw a circle.</param>
+ /// <param name="fill">If true then fill the shape.</param>
+ private void DrawSpriteEllipse(CGContext context, bool circle, bool fill)
+ {
+ double rx = Math.Abs((double)m_sx - (double)m_ex) + 1;
+ double ry = Math.Abs((double)m_sy - (double)m_ey) + 1;
+
+ if (circle)
+ {
+ rx = Math.Max(rx, ry);
+ ry = rx;
+ }
+
+ for(double a=0;a<=Math.PI;a+=Math.PI/180)
+ {
+ int x = (int)(Math.Sin(a) * rx);
+ int y = (int)(Math.Cos(a) * ry);
+
+ if (fill)
+ {
+ for(int f=-x;f<=x;f++)
+ {
+ SpritePlot(context, (int)m_sx + f, (int)m_sy + y, Colour);
+ }
+ }
+ else
+ {
+ SpritePlot(context, (int)m_sx + x, (int)m_sy + y, Colour);
+ SpritePlot(context, (int)m_sx - x, (int)m_sy + y, Colour);
+ }
+ }
+ }
+
+ /// <summary>
/// Redraw the control.
/// </summary>
/// <param name="dirtyRect">The dirty rectangle</param>
@@ -242,7 +537,6 @@ namespace SpriteEd
{
base.DrawRect (dirtyRect);
- // TODO: Draw control
if (m_sprite != null)
{
CGContext context = NSGraphicsContext.CurrentContext.GraphicsPort;
@@ -262,11 +556,210 @@ namespace SpriteEd
}
}
- if (m_cx != -1 && m_cx != -1)
+ if (m_sx != uint.MaxValue && m_sy != uint.MaxValue &&
+ m_ex != uint.MaxValue && m_ey != uint.MaxValue)
{
- DrawCell(context, (uint)m_cx, (uint)m_cy, Palette[Colour]);
+ switch(Mode)
+ {
+ case DrawingMode.Line:
+ DrawSpriteLine(context);
+ break;
+
+ case DrawingMode.Rect:
+ DrawSpriteRect(context, false);
+ break;
+
+ case DrawingMode.FilledRect:
+ DrawSpriteRect(context, true);
+ break;
+
+ case DrawingMode.Circle:
+ DrawSpriteEllipse(context, true, false);
+ break;
+
+ case DrawingMode.FilledCircle:
+ DrawSpriteEllipse(context, true, true);
+ break;
+
+ case DrawingMode.Ellipse:
+ DrawSpriteEllipse(context, false, false);
+ break;
+
+ case DrawingMode.FilledEllipse:
+ DrawSpriteEllipse(context, false, true);
+ break;
+
+ default:
+ break;
+ }
}
}
}
+
+ /// <summary>
+ /// Say we're a first responder for mouse events.
+ /// </summary>
+ /// <returns></returns>
+ public override bool AcceptsFirstResponder()
+ {
+ return true;
+ }
+
+ /// <summary>
+ /// Calculate the mouse events position on the sprite.
+ /// </summary>
+ /// <param name="theEvent">The mouse event.</param>
+ /// <param name="x">The returned X co-ord, or MaxValue if not on the sprite.</param>
+ /// <param name="y">The returned Y co-ord, or MaxValue if not on the sprite.</param>
+ /// <returns>True if the mouse is over the sprite.</returns>
+ private bool SpritePositionFromEvent(NSEvent theEvent, out uint x, out uint y)
+ {
+ bool ret = false;
+
+ var point = ConvertPointFromView(theEvent.LocationInWindow, null);
+
+ x = (uint)point.X / BlockSizeX;
+ y = (uint)(Frame.Height - point.Y) / BlockSizeY;
+
+ if (x < m_sprite.Width && y < m_sprite.Height)
+ {
+ ret = true;
+ }
+ else
+ {
+ x = uint.MaxValue;
+ y = uint.MaxValue;
+ }
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Called on mouse down.
+ /// </summary>
+ /// <param name="theEvent">The event.</param>
+ public override void MouseDown (NSEvent theEvent)
+ {
+ base.MouseDown(theEvent);
+
+ if (SpritePositionFromEvent(theEvent, out m_sx, out m_sy))
+ {
+ switch(Mode)
+ {
+ case DrawingMode.Point:
+ if (m_sprite[m_sx, m_sy] != Colour)
+ {
+ SaveUndo();
+ m_sprite[m_sx, m_sy] = Colour;
+ NeedsDisplay = true;
+ }
+ break;
+
+ case DrawingMode.FloodFill:
+ SaveUndo();
+ FloodFill((int)m_sx, (int)m_sy, m_sprite[m_sx, m_sy]);
+ NeedsDisplay = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Called on mouse dragged.
+ /// </summary>
+ /// <param name="theEvent">The event.</param>
+ public override void MouseDragged (NSEvent theEvent)
+ {
+ base.MouseDragged(theEvent);
+
+ if (SpritePositionFromEvent(theEvent, out m_ex, out m_ey))
+ {
+ switch(Mode)
+ {
+ case DrawingMode.Point:
+ if (m_sprite[m_ex, m_ey] != Colour)
+ {
+ m_sprite[m_ex, m_ey] = Colour;
+ NeedsDisplay = true;
+ }
+ break;
+
+ case DrawingMode.FloodFill:
+ break;
+
+ default:
+ NeedsDisplay = true;
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Called on mouse button up.
+ /// </summary>
+ /// <param name="theEvent">The event.</param>
+ public override void MouseUp (NSEvent theEvent)
+ {
+ base.MouseUp(theEvent);
+
+ if (SpritePositionFromEvent(theEvent, out m_ex, out m_ey))
+ {
+ switch(Mode)
+ {
+ case DrawingMode.Line:
+ SaveUndo();
+ DrawSpriteLine(null);
+ NeedsDisplay = true;
+ break;
+
+ case DrawingMode.Rect:
+ SaveUndo();
+ DrawSpriteRect(null, false);
+ NeedsDisplay = true;
+ break;
+
+ case DrawingMode.FilledRect:
+ SaveUndo();
+ DrawSpriteRect(null, true);
+ NeedsDisplay = true;
+ break;
+
+ case DrawingMode.Circle:
+ SaveUndo();
+ DrawSpriteEllipse(null, true, false);
+ NeedsDisplay = true;
+ break;
+
+ case DrawingMode.FilledCircle:
+ SaveUndo();
+ DrawSpriteEllipse(null, true, true);
+ NeedsDisplay = true;
+ break;
+
+ case DrawingMode.Ellipse:
+ SaveUndo();
+ DrawSpriteEllipse(null, false, false);
+ NeedsDisplay = true;
+ break;
+
+ case DrawingMode.FilledEllipse:
+ SaveUndo();
+ DrawSpriteEllipse(null, false, true);
+ NeedsDisplay = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ m_sx = uint.MaxValue;
+ m_sy = uint.MaxValue;
+ m_ex = uint.MaxValue;
+ m_ey = uint.MaxValue;
+ }
}
}
diff --git a/SpriteEd/Sprite.cs b/SpriteEd/Sprite.cs
index 09fed6b..c3da98a 100644
--- a/SpriteEd/Sprite.cs
+++ b/SpriteEd/Sprite.cs
@@ -121,5 +121,25 @@ namespace SpriteEd
}
}
}
+
+ /// <summary>
+ /// Copy sprite data from one sprite to another.
+ /// </summary>
+ /// <param name="from">The sprite to copy.</param>
+ public void CopyFrom(Sprite from)
+ {
+ Width = from.Width;
+ Height = from.Height;
+
+ m_data = new uint[Width, Height];
+
+ for(uint x = 0; x < Width; x++)
+ {
+ for(uint y = 0; y < Height; y++)
+ {
+ m_data[x,y] = from.m_data[x,y];
+ }
+ }
+ }
}
}
diff --git a/SpriteEd/ViewController.cs b/SpriteEd/ViewController.cs
index 8b4aca1..fe8aa79 100644
--- a/SpriteEd/ViewController.cs
+++ b/SpriteEd/ViewController.cs
@@ -52,8 +52,6 @@ namespace SpriteEd
m_SpriteEdit.Sprite = m_SpriteSet[(byte)m_SpriteNumber.IntValue];
m_SpriteEdit.Mode = NSSpriteEdit.DrawingMode.Point;
- m_SpriteSet[0][0,0] = 1;
-
OnColourStepper(m_ColourStepper);
OnSpriteStepper(m_SpriteStepper);
}