CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

MapPanel.cs (16947B)


      1 //
      2 // Copyright 2020 Electronic Arts Inc.
      3 //
      4 // The Command & Conquer Map Editor and corresponding source code is free 
      5 // software: you can redistribute it and/or modify it under the terms of 
      6 // the GNU General Public License as published by the Free Software Foundation, 
      7 // either version 3 of the License, or (at your option) any later version.
      8 
      9 // The Command & Conquer Map Editor and corresponding source code is distributed 
     10 // in the hope that it will be useful, but with permitted additional restrictions 
     11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
     12 // distributed with this program. You should have received a copy of the 
     13 // GNU General Public License along with permitted additional restrictions 
     14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
     15 using MobiusEditor.Event;
     16 using MobiusEditor.Interface;
     17 using MobiusEditor.Model;
     18 using MobiusEditor.Utility;
     19 using System;
     20 using System.Collections.Generic;
     21 using System.ComponentModel;
     22 using System.Drawing;
     23 using System.Drawing.Drawing2D;
     24 using System.Linq;
     25 using System.Runtime.InteropServices;
     26 using System.Windows.Forms;
     27 
     28 namespace MobiusEditor.Controls
     29 {
     30     public partial class MapPanel : Panel
     31     {
     32         private bool updatingCamera;
     33         private Rectangle cameraBounds;
     34         private Point lastScrollPosition;
     35 
     36         private (Point map, SizeF client)? referencePositions;
     37 
     38         private Matrix mapToViewTransform = new Matrix();
     39         private Matrix viewToPageTransform = new Matrix();
     40 
     41         private Matrix compositeTransform = new Matrix();
     42         private Matrix invCompositeTransform = new Matrix();
     43 
     44         private readonly HashSet<Point> invalidateCells = new HashSet<Point>();
     45         private bool fullInvalidation;
     46 
     47         private Image mapImage;
     48         public Image MapImage
     49         {
     50             get => mapImage;
     51             set
     52             {
     53                 if (mapImage != value)
     54                 {
     55                     mapImage = value;
     56                     UpdateCamera();
     57                 }
     58             }
     59         }
     60 
     61         private int minZoom = 1;
     62         public int MinZoom
     63         {
     64             get => minZoom;
     65             set
     66             {
     67                 if (minZoom != value)
     68                 {
     69                     minZoom = value;
     70                     Zoom = zoom;
     71                 }
     72             }
     73         }
     74 
     75         private int maxZoom = 8;
     76         public int MaxZoom
     77         {
     78             get => maxZoom;
     79             set
     80             {
     81                 if (maxZoom != value)
     82                 {
     83                     maxZoom = value;
     84                     Zoom = zoom;
     85                 }
     86             }
     87         }
     88 
     89         private int zoomStep = 1;
     90         public int ZoomStep
     91         {
     92             get => zoomStep;
     93             set
     94             {
     95                 if (zoomStep != value)
     96                 {
     97                     zoomStep = value;
     98                     Zoom = (Zoom / zoomStep) * zoomStep;
     99                 }
    100             }
    101         }
    102 
    103         private int zoom = 1;
    104         public int Zoom
    105         {
    106             get => zoom;
    107             set
    108             {
    109                 var newZoom = Math.Max(MinZoom, Math.Min(MaxZoom, value));
    110                 if (zoom != newZoom)
    111                 {
    112                     zoom = newZoom;
    113 
    114                     var clientPosition = PointToClient(MousePosition);
    115                     referencePositions = (ClientToMap(clientPosition), new SizeF(clientPosition.X / (float)ClientSize.Width, clientPosition.Y / (float)ClientSize.Height));
    116 
    117                     UpdateCamera();
    118                 }
    119             }
    120         }
    121 
    122         private int quality = Properties.Settings.Default.Quality;
    123         public int Quality
    124         {
    125             get => quality;
    126             set
    127             {
    128                 if (quality != value)
    129                 {
    130                     quality = value;
    131                     Invalidate();
    132                 }
    133             }
    134         }
    135 
    136         [Category("Behavior")]
    137         [DefaultValue(false)]
    138         public bool FocusOnMouseEnter { get; set; }
    139 
    140         public event EventHandler<RenderEventArgs> PreRender;
    141         public event EventHandler<RenderEventArgs> PostRender;
    142 
    143         public MapPanel()
    144         {
    145             InitializeComponent();
    146             DoubleBuffered = true;
    147         }
    148 
    149         public Point MapToClient(Point point)
    150         {
    151             var points = new Point[] { point };
    152             compositeTransform.TransformPoints(points);
    153             return points[0];
    154         }
    155 
    156         public Size MapToClient(Size size)
    157         {
    158             var points = new Point[] { (Point)size };
    159             compositeTransform.VectorTransformPoints(points);
    160             return (Size)points[0];
    161         }
    162 
    163         public Rectangle MapToClient(Rectangle rectangle)
    164         {
    165             var points = new Point[] { rectangle.Location, new Point(rectangle.Right, rectangle.Bottom) };
    166             compositeTransform.TransformPoints(points);
    167             return new Rectangle(points[0], new Size(points[1].X - points[0].X, points[1].Y - points[0].Y));
    168         }
    169 
    170         public Point ClientToMap(Point point)
    171         {
    172             var points = new Point[] { point };
    173             invCompositeTransform.TransformPoints(points);
    174             return points[0];
    175         }
    176 
    177         public Size ClientToMap(Size size)
    178         {
    179             var points = new Point[] { (Point)size };
    180             invCompositeTransform.VectorTransformPoints(points);
    181             return (Size)points[0];
    182         }
    183 
    184         public Rectangle ClientToMap(Rectangle rectangle)
    185         {
    186             var points = new Point[] { rectangle.Location, new Point(rectangle.Right, rectangle.Bottom) };
    187             invCompositeTransform.TransformPoints(points);
    188             return new Rectangle(points[0], new Size(points[1].X - points[0].X, points[1].Y - points[0].Y));
    189         }
    190 
    191         public void Invalidate(Map invalidateMap)
    192         {
    193             if (!fullInvalidation)
    194             {
    195                 invalidateCells.Clear();
    196                 fullInvalidation = true;
    197                 Invalidate();
    198             }
    199         }
    200 
    201         public void Invalidate(Map invalidateMap, Rectangle cellBounds)
    202         {
    203             if (fullInvalidation)
    204             {
    205                 return;
    206             }
    207 
    208             var count = invalidateCells.Count;
    209             invalidateCells.UnionWith(cellBounds.Points());
    210             if (invalidateCells.Count > count)
    211             {
    212                 var overlapCells = invalidateMap.Overlappers.Overlaps(invalidateCells).ToHashSet();
    213                 invalidateCells.UnionWith(overlapCells);
    214                 Invalidate();
    215             }
    216         }
    217 
    218         public void Invalidate(Map invalidateMap, IEnumerable<Rectangle> cellBounds)
    219         {
    220             if (fullInvalidation)
    221             {
    222                 return;
    223             }
    224 
    225             var count = invalidateCells.Count;
    226             invalidateCells.UnionWith(cellBounds.SelectMany(c => c.Points()));
    227             if (invalidateCells.Count > count)
    228             {
    229                 var overlapCells = invalidateMap.Overlappers.Overlaps(invalidateCells).ToHashSet();
    230                 invalidateCells.UnionWith(overlapCells);
    231                 Invalidate();
    232             }
    233         }
    234 
    235         public void Invalidate(Map invalidateMap, Point location)
    236         {
    237             if (fullInvalidation)
    238             {
    239                 return;
    240             }
    241 
    242             Invalidate(invalidateMap, new Rectangle(location, new Size(1, 1)));
    243         }
    244 
    245         public void Invalidate(Map invalidateMap, IEnumerable<Point> locations)
    246         {
    247             if (fullInvalidation)
    248             {
    249                 return;
    250             }
    251 
    252             Invalidate(invalidateMap, locations.Select(l => new Rectangle(l, new Size(1, 1))));
    253         }
    254 
    255         public void Invalidate(Map invalidateMap, int cell)
    256         {
    257             if (fullInvalidation)
    258             {
    259                 return;
    260             }
    261 
    262             if (invalidateMap.Metrics.GetLocation(cell, out Point location))
    263             {
    264                 Invalidate(invalidateMap, location);
    265             }
    266         }
    267 
    268         public void Invalidate(Map invalidateMap, IEnumerable<int> cells)
    269         {
    270             if (fullInvalidation)
    271             {
    272                 return;
    273             }
    274 
    275             Invalidate(invalidateMap, cells
    276                 .Where(c => invalidateMap.Metrics.GetLocation(c, out Point location))
    277                 .Select(c =>
    278                 {
    279                     invalidateMap.Metrics.GetLocation(c, out Point location);
    280                     return location;
    281                 })
    282             );
    283         }
    284 
    285         public void Invalidate(Map invalidateMap, ICellOverlapper overlapper)
    286         {
    287             if (fullInvalidation)
    288             {
    289                 return;
    290             }
    291 
    292             var rectangle = invalidateMap.Overlappers[overlapper];
    293             if (rectangle.HasValue)
    294             {
    295                 Invalidate(invalidateMap, rectangle.Value);
    296             }
    297         }
    298 
    299         protected override void OnMouseEnter(EventArgs e)
    300         {
    301             base.OnMouseEnter(e);
    302 
    303             if (FocusOnMouseEnter)
    304             {
    305                 Focus();
    306             }
    307         }
    308 
    309         protected override void OnMouseWheel(MouseEventArgs e)
    310         {
    311             Zoom += ZoomStep * Math.Sign(e.Delta);
    312         }
    313 
    314         protected override void OnClientSizeChanged(EventArgs e)
    315         {
    316             base.OnClientSizeChanged(e);
    317 
    318             UpdateCamera();
    319         }
    320 
    321         protected override void OnScroll(ScrollEventArgs se)
    322         {
    323             base.OnScroll(se);
    324 
    325             InvalidateScroll();
    326         }
    327 
    328         protected override void OnPaintBackground(PaintEventArgs e)
    329         {
    330             base.OnPaintBackground(e);
    331 
    332             e.Graphics.Clear(BackColor);
    333         }
    334 
    335         protected override void OnPaint(PaintEventArgs pe)
    336         {
    337             base.OnPaint(pe);
    338 
    339             InvalidateScroll();
    340 
    341             PreRender?.Invoke(this, new RenderEventArgs(pe.Graphics, fullInvalidation ? null : invalidateCells));
    342 
    343             if (mapImage != null)
    344             {
    345                 pe.Graphics.Transform = compositeTransform;
    346 
    347                 var oldCompositingMode = pe.Graphics.CompositingMode;
    348                 var oldCompositingQuality = pe.Graphics.CompositingQuality;
    349                 var oldInterpolationMode = pe.Graphics.InterpolationMode;
    350                 if (Quality > 1)
    351                 {
    352                     pe.Graphics.CompositingMode = CompositingMode.SourceCopy;
    353                     pe.Graphics.CompositingQuality = CompositingQuality.HighSpeed;
    354                 }
    355 
    356                 pe.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
    357                 pe.Graphics.DrawImage(mapImage, 0, 0);
    358 
    359                 pe.Graphics.CompositingMode = oldCompositingMode;
    360                 pe.Graphics.CompositingQuality = oldCompositingQuality;
    361                 pe.Graphics.InterpolationMode = oldInterpolationMode;
    362             }
    363 
    364             PostRender?.Invoke(this, new RenderEventArgs(pe.Graphics, fullInvalidation ? null : invalidateCells));
    365 
    366 #if DEVELOPER
    367             if (Globals.Developer.ShowOverlapCells)
    368             {
    369                 var invalidPen = new Pen(Color.DarkRed);
    370                 foreach (var cell in invalidateCells)
    371                 {
    372                     pe.Graphics.DrawRectangle(invalidPen, new Rectangle(cell.X * Globals.TileWidth, cell.Y * Globals.TileHeight, Globals.TileWidth, Globals.TileHeight));
    373                 }
    374             }
    375 #endif
    376 
    377             invalidateCells.Clear();
    378             fullInvalidation = false;
    379         }
    380 
    381         private void UpdateCamera()
    382         {
    383             if (mapImage == null)
    384             {
    385                 return;
    386             }
    387 
    388             if (ClientSize.IsEmpty)
    389             {
    390                 return;
    391             }
    392 
    393             updatingCamera = true;
    394 
    395             var mapAspect = (double)mapImage.Width / mapImage.Height;
    396             var panelAspect = (double)ClientSize.Width / ClientSize.Height;
    397             var cameraLocation = cameraBounds.Location;
    398 
    399             var size = Size.Empty;
    400             if (panelAspect > mapAspect)
    401             {
    402                 size.Height = mapImage.Height / zoom;
    403                 size.Width = (int)(size.Height * panelAspect);
    404             }
    405             else
    406             {
    407                 size.Width = mapImage.Width / zoom;
    408                 size.Height = (int)(size.Width / panelAspect);
    409             }
    410 
    411             var location = Point.Empty;
    412             var scrollSize = Size.Empty;
    413             if (size.Width < mapImage.Width)
    414             {
    415                 location.X = Math.Max(0, Math.Min(mapImage.Width - size.Width, cameraBounds.Left));
    416                 scrollSize.Width = mapImage.Width * ClientSize.Width / size.Width;
    417             }
    418             else
    419             {
    420                 location.X = (mapImage.Width - size.Width) / 2;
    421             }
    422 
    423             if (size.Height < mapImage.Height)
    424             {
    425                 location.Y = Math.Max(0, Math.Min(mapImage.Height - size.Height, cameraBounds.Top));
    426                 scrollSize.Height = mapImage.Height * ClientSize.Height / size.Height;
    427             }
    428             else
    429             {
    430                 location.Y = (mapImage.Height - size.Height) / 2;
    431             }
    432 
    433             cameraBounds = new Rectangle(location, size);
    434             RecalculateTransforms();
    435 
    436             if (referencePositions.HasValue)
    437             {
    438                 var mapPoint = referencePositions.Value.map;
    439                 var clientSize = referencePositions.Value.client;
    440 
    441                 cameraLocation = cameraBounds.Location;
    442                 if (scrollSize.Width != 0)
    443                 {
    444                     cameraLocation.X = Math.Max(0, Math.Min(mapImage.Width - cameraBounds.Width, (int)(mapPoint.X - (cameraBounds.Width * clientSize.Width))));
    445                 }
    446                 if (scrollSize.Height != 0)
    447                 {
    448                     cameraLocation.Y = Math.Max(0, Math.Min(mapImage.Height - cameraBounds.Height, (int)(mapPoint.Y - (cameraBounds.Height * clientSize.Height))));
    449                 }
    450                 if (!scrollSize.IsEmpty)
    451                 {
    452                     cameraBounds.Location = cameraLocation;
    453                     RecalculateTransforms();
    454                 }
    455 
    456                 referencePositions = null;
    457             }
    458 
    459             SuspendDrawing();
    460             AutoScrollMinSize = scrollSize;
    461             AutoScrollPosition = (Point)MapToClient((Size)cameraBounds.Location);
    462             lastScrollPosition = AutoScrollPosition;
    463             ResumeDrawing();
    464 
    465             updatingCamera = false;
    466 
    467             Invalidate();
    468         }
    469 
    470         private void RecalculateTransforms()
    471         {
    472             mapToViewTransform.Reset();
    473             mapToViewTransform.Translate(cameraBounds.Left, cameraBounds.Top);
    474             mapToViewTransform.Scale(cameraBounds.Width, cameraBounds.Height);
    475             mapToViewTransform.Invert();
    476 
    477             viewToPageTransform.Reset();
    478             viewToPageTransform.Scale(ClientSize.Width, ClientSize.Height);
    479 
    480             compositeTransform.Reset();
    481             compositeTransform.Multiply(viewToPageTransform);
    482             compositeTransform.Multiply(mapToViewTransform);
    483 
    484             invCompositeTransform.Reset();
    485             invCompositeTransform.Multiply(compositeTransform);
    486             invCompositeTransform.Invert();
    487         }
    488 
    489         private void InvalidateScroll()
    490         {
    491             if (updatingCamera)
    492             {
    493                 return;
    494             }
    495 
    496             if ((lastScrollPosition.X != AutoScrollPosition.X) || (lastScrollPosition.Y != AutoScrollPosition.Y))
    497             {
    498                 var delta = ClientToMap((Size)(lastScrollPosition - (Size)AutoScrollPosition));
    499                 lastScrollPosition = AutoScrollPosition;
    500 
    501                 var cameraLocation = cameraBounds.Location;
    502                 if (AutoScrollMinSize.Width != 0)
    503                 {
    504                     cameraLocation.X = Math.Max(0, Math.Min(mapImage.Width - cameraBounds.Width, cameraBounds.Left + delta.Width));
    505                 }
    506                 if (AutoScrollMinSize.Height != 0)
    507                 {
    508                     cameraLocation.Y = Math.Max(0, Math.Min(mapImage.Height - cameraBounds.Height, cameraBounds.Top + delta.Height));
    509                 }
    510                 if (!AutoScrollMinSize.IsEmpty)
    511                 {
    512                     cameraBounds.Location = cameraLocation;
    513                     RecalculateTransforms();
    514                 }
    515 
    516                 Invalidate();
    517             }
    518         }
    519 
    520         [DllImport("user32.dll")]
    521         private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
    522 
    523         private const int WM_SETREDRAW = 11;
    524 
    525         private void SuspendDrawing()
    526         {
    527             SendMessage(Handle, WM_SETREDRAW, false, 0);
    528         }
    529 
    530         private void ResumeDrawing()
    531         {
    532             SendMessage(Handle, WM_SETREDRAW, true, 0);
    533         }
    534     }
    535 }