diff options
-rw-r--r-- | SpriteEd/Main.storyboard | 9 | ||||
-rw-r--r-- | SpriteEd/NSSpriteEdit.cs | 509 | ||||
-rw-r--r-- | SpriteEd/Sprite.cs | 20 | ||||
-rw-r--r-- | SpriteEd/ViewController.cs | 2 |
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); } |