using System; using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace SpriteWave { public partial class SpriteWindow : TileWindow { private FileFormat _fmt; public FileFormat FormatToLoad { set { _fmt = value; } } private Edge[] _edges; private Edge _hlEdge; private Bitmap _checkerboard; private const int checkSize = 8; private readonly SolidBrush mouseOverBrush; private readonly Color outlineColor = Utils.FromRGB(0x303030); private const float outlineFactor = 4f; private const float scrollFactor = 5f; private const float zoomFactor = 5f; private const float zoomMin = 0.01f; private const float zoomMax = 1000f; private const int initialTopGap = 4; private MainForm _main; // 'zoom' = number of screen pixels that fit into the width of a scaled collage pixel private float _zoom; public float InitialZoom { get { return _window.Height / (float)((initialTopGap * 2 + _cl.Rows) * _cl.TileH); } } // 'xOff' and 'yOff' are measures in units of collage pixels private float _xOff, _yOff; public override bool Selected { get { return _isSel; } set { _isSel = value; if (_isSel) _hlEdge = null; } } public override SizeF TileDimensions { get { return new SizeF( _zoom * (float)_cl.TileW, _zoom * (float)_cl.TileH ); } } public override Rectangle VisibleCollageBounds { get { var canvas = new Rectangle( (int)_xOff, (int)_yOff, (int)((float)_window.Width / _zoom) + 1, (int)((float)_window.Height / _zoom) + 1 ); if (_cl == null || _cl.Bitmap == null) return canvas; var clRect = new Rectangle( 0, 0, _cl.Bitmap.Width, _cl.Bitmap.Height ); return Rectangle.Intersect(canvas, clRect); } } public string ExportExtension { get { return ".png"; } } private static Color ShadeColor(float shade) { int lum = (int)(shade * 255f); return Color.FromArgb(lum, lum, lum); } public SpriteWindow(MainForm main) : base(main) { _main = main; mouseOverBrush = new SolidBrush(SystemColors.ScrollBar); // Initialise all edges, excluding the invalid (centre) one _edges = new Edge[9]; for (int i = 0; i < 9; i++) { var kind = (EdgeKind)i; _edges[i] = kind != EdgeKind.None ? new Edge(kind) : null; } _checkerboard = new Bitmap(256, 256, PixelFormat.Format32bppArgb); Utils.ApplyCheckerboard( _checkerboard, checkSize, ShadeColor(Utils.AlphaShades[0]), ShadeColor(Utils.AlphaShades[1]) ); } public void Export(string fullPath, int scale) { string ext = this.ExportExtension; if (fullPath.Substring(fullPath.Length - ext.Length) != ext) fullPath += ext; //Bitmap output = new Bitmap(_cl.Bitmap); _cl.Bitmap.Scale(scale).Save(fullPath); } public override EdgeKind EdgeOf(Position loc) { if (_cl == null) return EdgeKind.None; int x = 0, y = 0; if (loc.row == -1) y = -1; if (loc.row == _cl.Rows) y = 1; if (loc.col == -1) x = -1; if (loc.col == _cl.Columns) x = 1; return Edge.Direction(x, y); } public override IPiece PieceAt(Position loc) { if (_cl == null || loc.col < -1 || loc.row < -1 || loc.col > _cl.Columns || loc.row > _cl.Rows) { return null; } EdgeKind kind = EdgeOf(loc); if (kind == EdgeKind.None) return _cl.TileAt(loc); return _edges[(int)kind]; } public bool ClearMousedEdge() { bool changed = _hlEdge != null; _hlEdge = null; return changed; } public bool HighlightEdgeAt(int x, int y) { bool wasOob; Position pos = GetPosition(x, y, out wasOob); EdgeKind kind = wasOob ? EdgeKind.None : EdgeOf(pos); Edge e = _edges[(int)kind]; bool changed = _hlEdge != e; _hlEdge = e; return changed; } public override void ResizeCollage(Edge msg) { if (msg.EdgeKind == EdgeKind.None) return; int x, y; Edge.GetCoords(msg.EdgeKind, out x, out y); var dist = msg.Distance; Func, Func, int> resizeAxis = (side, delta, max, insert, delete) => { int shift = 0; int length = Math.Abs(delta); for (int i = 0; i < length; i++) { if (side < 0) { if (delta < 0) shift += insert(0); else shift += delete(0); } if (side > 0) { if (delta < 0) shift += delete(--max); else shift += insert(max); } } return shift; }; int shiftX = resizeAxis(x, dist.col, _cl.Columns, _cl.InsertColumn, _cl.DeleteColumn); int shiftY = resizeAxis(y, dist.row, _cl.Rows, _cl.InsertRow, _cl.DeleteRow); ShiftCamera(shiftX, shiftY); msg.Distance = new Position(0, 0); Render(); } public override void ReceiveTile(Tile obj, Position loc) { if (_cl == null) { _cl = new Collage(_fmt, 1, false); _cl.AddBlankTiles(1); Activate(_main.toolBox); } EdgeKind kind = EdgeOf(loc); if (kind != EdgeKind.None) { int x, y; Edge.GetCoords(kind, out x, out y); Edge e = _edges[(int)kind]; e.Distance = new Position(x, y); ResizeCollage(e); if (x == -1) loc.col = 0; if (y == -1) loc.row = 0; } _selPos = loc; _cl.SetTile(_selPos, obj as Tile); Render(); } public override void ReceiveTile(Tile obj) { ReceiveTile(obj, _selPos); } public void EraseTile() { if (_cl == null || !_isSel) return; _cl.SetTile(_selPos, _fmt.NewTile()); Render(); Draw(); } private float AdjustScroll(ScrollBar scroll, float pos) { int val = (int)(pos * _zoom); if (val < scroll.Minimum) return (float)scroll.Minimum / _zoom; if (val > scroll.Maximum - scroll.LargeChange) return (float)(scroll.Maximum - scroll.LargeChange) / _zoom; return pos; } public override void ScrollTo(float x, float y) { _xOff = AdjustScroll(_scrollX, x); _yOff = AdjustScroll(_scrollY, y); } public override void Scroll(float dx, float dy) { float x = _xOff + (dx * scrollFactor); float y = _yOff + (dy * scrollFactor); ScrollTo(x, y); } public void Centre() { if (_cl == null) return; float wndW = (float)_window.Width / _zoom; float wndH = (float)_window.Height / _zoom; ScrollTo( (_cl.Width / 2f) - (wndW / 2f), (_cl.Height / 2f) - (wndH / 2f) ); UpdateBars(); } private void ShiftCamera(int cols, int rows) { float x = _xOff + (float)(cols * _cl.TileW) / 2f; float y = _yOff + (float)(rows * _cl.TileH) / 2f; ScrollTo(x, y); /* Whenever ShiftCamera() gets called, it is usually the case that the collage was resized. As such, the current selection position may now be out-of-bounds. We call MoveSelection() here as it will check to see whether it is still valid. */ MoveSelection(0, 0); bool useWidth = true; int delta = cols; if (cols == 0) { useWidth = false; delta = rows; } ZoomByTiles(delta / -2f, useWidth); } private void ShiftCamera(EdgeKind e) { int x, y; Edge.GetCoords(e, out x, out y); ShiftCamera(Math.Abs(x), Math.Abs(y)); } private void Zoom(float factor, int x, int y) { float xPos = _xOff + ((float)x / _zoom); float yPos = _yOff + ((float)y / _zoom); float z = _zoom * factor; if (z < zoomMin || z > zoomMax) return; _xOff = xPos - ((float)x / z); _yOff = yPos - ((float)y / z); _zoom = z; } public void ZoomOver(int delta, int x, int y) { float n = Math.Abs(delta); float amount = 1f + (n / zoomFactor); if (delta < 0) amount = 1 / amount; Zoom(amount, x, y); } public void ZoomIn(int delta) { ZoomOver(delta, _window.Width / 2, _window.Height / 2); } public void ZoomByTiles(float delta, bool useWidth = true) { if (_cl == null) return; float length = useWidth ? _cl.Columns : _cl.Rows; float amount = (length + Math.Abs(delta)) / length; if (delta < 0) amount = 1 / amount; Zoom(amount, _window.Width / 2, _window.Height / 2); } public override void UpdateBars() { if (_cl == null || _window == null) return; int wndW = _window.Width; int wndH = _window.Height; // When the MainForm (window) gets minimised, the width and height of (at least) the _spriteBox window get set to 0 if (wndW <= 0 || wndH <= 0) return; // Value (current position), LargeChange (slider size), Minimum, Maximum _scrollX.Inform( (int)(_xOff * _zoom), wndW, -wndW, wndW + (int)((float)_cl.Width * _zoom) ); _scrollY.Inform( (int)(_yOff * _zoom), wndH, -wndH, wndH + (int)((float)_cl.Height * _zoom) ); } public override Position GetPosition(int x, int y, out bool wasOob) { wasOob = false; if (_cl == null) return new Position(0, 0); float xPos = _xOff + ((float)x / _zoom); float yPos = _yOff + ((float)y / _zoom); int col = (int)(xPos / (float)_cl.TileW); int row = (int)(yPos / (float)_cl.TileH); col -= xPos < 0 ? 1 : 0; row -= yPos < 0 ? 1 : 0; wasOob = (col < -1 || col > _cl.Columns || row < -1 || row > _cl.Rows); return new Position(col, row); } public override RectangleF PieceHitbox(Position p) { SizeF tileSc = TileDimensions; float xCl = (p.col * _cl.TileW) - _xOff; float yCl = (p.row * _cl.TileH) - _yOff; return new RectangleF( xCl * _zoom, yCl * _zoom, tileSc.Width, tileSc.Height ); } public override PointF[] ShapeEdge(Edge edge) { if (edge == null || edge.EdgeKind == EdgeKind.None) return null; edge.Render(_cl); var tri = new PointF[3]; for (int i = 0; i < 3; i++) { tri[i].X = (edge.Shape[i].X - _xOff) * _zoom; tri[i].Y = (edge.Shape[i].Y - _yOff) * _zoom; } return tri; } public override void DrawCanvas(Graphics g) { Rectangle clBounds = VisibleCollageBounds; if (clBounds.Width <= 0 || clBounds.Height <= 0) return; using (Bitmap canvas = _cl.Bitmap.Clone(clBounds, _cl.Bitmap.PixelFormat)) { var area = new RectangleF( (float)(clBounds.X - _xOff) * _zoom, (float)(clBounds.Y - _yOff) * _zoom, (float)clBounds.Width * _zoom, (float)clBounds.Height * _zoom ); float chkW = _checkerboard.Width; float chkH = _checkerboard.Height; int chkCols = (int)Math.Ceiling(area.Width / chkW); int chkRows = (int)Math.Ceiling(area.Height / chkH); for (int i = 0; i < chkRows; i++) { for (int j = 0; j < chkCols; j++) { var src = new RectangleF(0, 0, chkW, chkH); var dst = new RectangleF( area.X + j*chkW, area.Y + i*chkH, chkW, chkH ); dst.Intersect(area); src.Width = dst.Width; src.Height = dst.Height; g.DrawImage(_checkerboard, dst, src, GraphicsUnit.Pixel); } } g.DrawImage(canvas, area); } } public override void DrawEdges(Graphics g) { Pen outline = new Pen(outlineColor, _zoom / outlineFactor); foreach (Edge e in _edges) { PointF[] tri = this.ShapeEdge(e); if (tri != null) { if (e == _hlEdge) g.FillPolygon(mouseOverBrush, tri); g.DrawPolygon(outline, tri); } } } private static float PadOffset(float value, float unit) { return -(value % unit); } public override void DrawGrid(Graphics g) { float wndW = _window.Width; float wndH = _window.Height; SizeF tileSc = TileDimensions; float xLn = PadOffset(_xOff * _zoom, tileSc.Width); float yLn = PadOffset(_yOff * _zoom, tileSc.Height); Pen p = _cl.GridPen; while (xLn < wndW) { g.DrawLine(p, xLn, 0, xLn, wndH); xLn += tileSc.Width; } while (yLn < wndH) { g.DrawLine(p, 0, yLn, wndW, yLn); yLn += tileSc.Height; } } protected override void windowScrollAction(object sender, MouseEventArgs e) { Keys mod = Control.ModifierKeys; bool ctrlKey = (mod & Keys.Control) != 0; bool shiftKey = (mod & Keys.Shift) != 0; if (ctrlKey) { ZoomOver(e.Delta / 120, e.X, e.Y); } else { float n = -e.Delta / 120; if (shiftKey) Scroll(n, 0); else Scroll(0, n); } UpdateBars(); Draw(); } protected override void xScrollAction(object sender, ScrollEventArgs e) { ScrollTo((float)e.NewValue / _zoom, _yOff); Draw(); } protected override void yScrollAction(object sender, ScrollEventArgs e) { ScrollTo(_xOff, (float)e.NewValue / _zoom); Draw(); } private void InsertCollageColumn(int pos) { int shift = _cl.InsertColumn(pos); Render(); ShiftCamera(shift, 0); Draw(); } private void InsertCollageRow(int pos) { int shift = _cl.InsertRow(pos); Render(); ShiftCamera(0, shift); Draw(); } private void DeleteCollageColumn(int pos) { int shift = _cl.DeleteColumn(pos); Render(); ShiftCamera(shift, 0); Draw(); } private void DeleteCollageRow(int pos) { int shift = _cl.DeleteRow(pos); Render(); ShiftCamera(0, shift); Draw(); } public void InsertEdge(EdgeKind kind) { if (_cl == null) return; int x, y; Edge.GetCoords(kind, out x, out y); if (x == -1) InsertCollageColumn(0); else if (x == 1) InsertCollageColumn(_cl.Columns); if (y == -1) InsertCollageRow(0); else if (y == 1) InsertCollageRow(_cl.Rows); } public void DeleteEdge(EdgeKind kind) { if (_cl == null) return; int x, y; Edge.GetCoords(kind, out x, out y); if (x == -1) DeleteCollageColumn(0); else if (x == 1) DeleteCollageColumn(_cl.Columns - 1); if (y == -1) DeleteCollageRow(0); else if (y == 1) DeleteCollageRow(_cl.Rows - 1); } public void FlipTile(Translation tr) { if (_cl == null || !_isSel) return; Tile t = _cl.TileAt(_selPos); if (t != null) { t.Translate(tr); //_cl.SetTile(_selPos, t); Render(); Draw(); } } } }