CnC_Remastered_Collection

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

TemplateTool.cs (37982B)


      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.Controls;
     16 using MobiusEditor.Event;
     17 using MobiusEditor.Interface;
     18 using MobiusEditor.Model;
     19 using MobiusEditor.Utility;
     20 using MobiusEditor.Widgets;
     21 using System;
     22 using System.Collections.Generic;
     23 using System.Drawing;
     24 using System.Drawing.Drawing2D;
     25 using System.Linq;
     26 using System.Text.RegularExpressions;
     27 using System.Windows.Forms;
     28 
     29 namespace MobiusEditor.Tools
     30 {
     31     public class TemplateTool : ViewTool
     32     {
     33         private static readonly Regex CategoryRegex = new Regex(@"^([a-z]*)", RegexOptions.Compiled);
     34 
     35         private readonly ListView templateTypeListView;
     36         private readonly MapPanel templateTypeMapPanel;
     37         private readonly ToolTip mouseTooltip;
     38 
     39         private readonly Dictionary<int, Template> undoTemplates = new Dictionary<int, Template>();
     40         private readonly Dictionary<int, Template> redoTemplates = new Dictionary<int, Template>();
     41 
     42         private Map previewMap;
     43         protected override Map RenderMap => previewMap;
     44 
     45         private bool placementMode;
     46 
     47         private bool boundsMode;
     48         private Rectangle dragBounds;
     49         private int dragEdge = -1;
     50 
     51         private TemplateType selectedTemplateType;
     52         private TemplateType SelectedTemplateType
     53         {
     54             get => selectedTemplateType;
     55             set
     56             {
     57                 if (selectedTemplateType != value)
     58                 {
     59                     if (placementMode && (selectedTemplateType != null))
     60                     {
     61                         for (var y = 0; y < selectedTemplateType.IconHeight; ++y)
     62                         {
     63                             for (var x = 0; x < selectedTemplateType.IconWidth; ++x)
     64                             {
     65                                 mapPanel.Invalidate(map, new Point(navigationWidget.MouseCell.X + x, navigationWidget.MouseCell.Y + y));
     66                             }
     67                         }
     68                     }
     69 
     70                     selectedTemplateType = value;
     71 
     72                     templateTypeListView.BeginUpdate();
     73                     templateTypeListView.SelectedIndexChanged -= TemplateTypeListView_SelectedIndexChanged;
     74                     foreach (ListViewItem item in templateTypeListView.Items)
     75                     {
     76                         item.Selected = item.Tag == selectedTemplateType;
     77                     }
     78                     if (templateTypeListView.SelectedIndices.Count > 0)
     79                     {
     80                         templateTypeListView.EnsureVisible(templateTypeListView.SelectedIndices[0]);
     81                     }
     82                     templateTypeListView.SelectedIndexChanged += TemplateTypeListView_SelectedIndexChanged;
     83                     templateTypeListView.EndUpdate();
     84 
     85                     if (placementMode && (selectedTemplateType != null))
     86                     {
     87                         for (var y = 0; y < selectedTemplateType.IconHeight; ++y)
     88                         {
     89                             for (var x = 0; x < selectedTemplateType.IconWidth; ++x)
     90                             {
     91                                 mapPanel.Invalidate(map, new Point(navigationWidget.MouseCell.X + x, navigationWidget.MouseCell.Y + y));
     92                             }
     93                         }
     94                     }
     95 
     96                     RefreshMapPanel();
     97                 }
     98             }
     99         }
    100 
    101         private Point? selectedIcon;
    102         private Point? SelectedIcon
    103         {
    104             get => selectedIcon;
    105             set
    106             {
    107                 if (selectedIcon != value)
    108                 {
    109                     selectedIcon = value;
    110                     templateTypeMapPanel.Invalidate();
    111 
    112                     if (placementMode && (SelectedTemplateType != null))
    113                     {
    114                         for (var y = 0; y < SelectedTemplateType.IconHeight; ++y)
    115                         {
    116                             for (var x = 0; x < SelectedTemplateType.IconWidth; ++x)
    117                             {
    118                                 mapPanel.Invalidate(map, new Point(navigationWidget.MouseCell.X + x, navigationWidget.MouseCell.Y + y));
    119                             }
    120                         }
    121                     }
    122                 }
    123             }
    124         }
    125 
    126         private NavigationWidget templateTypeNavigationWidget;
    127 
    128         public TemplateTool(MapPanel mapPanel, MapLayerFlag layers, ToolStripStatusLabel statusLbl, ListView templateTypeListView, MapPanel templateTypeMapPanel, ToolTip mouseTooltip, IGamePlugin plugin, UndoRedoList<UndoRedoEventArgs> url)
    129             : base(mapPanel, layers, statusLbl, plugin, url)
    130         {
    131             previewMap = map;
    132 
    133             this.mapPanel.MouseDown += MapPanel_MouseDown;
    134             this.mapPanel.MouseUp += MapPanel_MouseUp;
    135             this.mapPanel.MouseMove += MapPanel_MouseMove;
    136             (this.mapPanel as Control).KeyDown += TemplateTool_KeyDown;
    137             (this.mapPanel as Control).KeyUp += TemplateTool_KeyUp;
    138 
    139             this.templateTypeListView = templateTypeListView;
    140             this.templateTypeListView.SelectedIndexChanged += TemplateTypeListView_SelectedIndexChanged;
    141 
    142             string templateCategory(TemplateType template)
    143             {
    144                 var m = CategoryRegex.Match(template.Name);
    145                 return m.Success ? m.Groups[1].Value : string.Empty;
    146             }
    147 
    148             var templateTypes = plugin.Map.TemplateTypes
    149                 .Where(t =>
    150                     (t.Thumbnail != null) &&
    151                     t.Theaters.Contains(plugin.Map.Theater) &&
    152                     ((t.Flag & TemplateTypeFlag.Clear) == TemplateTypeFlag.None))
    153                 .GroupBy(t => templateCategory(t)).OrderBy(g => g.Key);
    154             var templateTypeImages = templateTypes.SelectMany(g => g).Select(t => t.Thumbnail);
    155 
    156             var maxWidth = templateTypeImages.Max(t => t.Width);
    157             var maxHeight = templateTypeImages.Max(t => t.Height);
    158 
    159             var imageList = new ImageList();
    160             imageList.Images.AddRange(templateTypeImages.ToArray());
    161             imageList.ImageSize = new Size(maxWidth, maxHeight);
    162             imageList.ColorDepth = ColorDepth.Depth24Bit;
    163 
    164             this.templateTypeListView.BeginUpdate();
    165             this.templateTypeListView.LargeImageList = imageList;
    166 
    167             var imageIndex = 0;
    168             foreach (var templateTypeGroup in templateTypes)
    169             {
    170                 var group = new ListViewGroup(templateTypeGroup.Key);
    171                 this.templateTypeListView.Groups.Add(group);
    172                 foreach (var templateType in templateTypeGroup)
    173                 {
    174                     var item = new ListViewItem(templateType.DisplayName, imageIndex++)
    175                     {
    176                         Group = group,
    177                         Tag = templateType
    178                     };
    179                     this.templateTypeListView.Items.Add(item);
    180                 }
    181             }
    182             this.templateTypeListView.EndUpdate();
    183 
    184             this.templateTypeMapPanel = templateTypeMapPanel;
    185             this.templateTypeMapPanel.MouseDown += TemplateTypeMapPanel_MouseDown;
    186             this.templateTypeMapPanel.PostRender += TemplateTypeMapPanel_PostRender;
    187             this.templateTypeMapPanel.BackColor = Color.Black;
    188             this.templateTypeMapPanel.MaxZoom = 1;
    189 
    190             this.mouseTooltip = mouseTooltip;
    191 
    192             navigationWidget.MouseCellChanged += MouseoverWidget_MouseCellChanged;
    193 
    194             url.Undone += Url_Undone;
    195             url.Redone += Url_Redone;
    196 
    197             SelectedTemplateType = templateTypes.First().First();
    198 
    199             UpdateStatus();
    200         }
    201 
    202         private void Url_Redone(object sender, EventArgs e)
    203         {
    204             if (boundsMode && (map.Bounds != dragBounds))
    205             {
    206                 dragBounds = map.Bounds;
    207                 dragEdge = -1;
    208 
    209                 UpdateTooltip();
    210                 mapPanel.Invalidate();
    211             }
    212         }
    213 
    214         private void Url_Undone(object sender, EventArgs e)
    215         {
    216             if (boundsMode && (map.Bounds != dragBounds))
    217             {
    218                 dragBounds = map.Bounds;
    219                 dragEdge = -1;
    220 
    221                 UpdateTooltip();
    222                 mapPanel.Invalidate();
    223             }
    224         }
    225 
    226         private void TemplateTypeMapPanel_MouseDown(object sender, MouseEventArgs e)
    227         {
    228             if ((SelectedTemplateType == null) || ((SelectedTemplateType.IconWidth * SelectedTemplateType.IconHeight) == 1))
    229             {
    230                 SelectedIcon = null;
    231             }
    232             else
    233             {
    234                 if (e.Button == MouseButtons.Left)
    235                 {
    236                     var templateTypeMouseCell = templateTypeNavigationWidget.MouseCell;
    237                     if ((templateTypeMouseCell.X >= 0) && (templateTypeMouseCell.X < SelectedTemplateType.IconWidth))
    238                     {
    239                         if ((templateTypeMouseCell.Y >= 0) && (templateTypeMouseCell.Y < SelectedTemplateType.IconHeight))
    240                         {
    241                             if (SelectedTemplateType.IconMask[templateTypeMouseCell.X, templateTypeMouseCell.Y])
    242                             {
    243                                 SelectedIcon = templateTypeMouseCell;
    244                             }
    245                         }
    246                     }
    247                 }
    248                 else if (e.Button == MouseButtons.Right)
    249                 {
    250                     SelectedIcon = null;
    251                 }
    252             }
    253         }
    254 
    255         private void TemplateTypeMapPanel_PostRender(object sender, RenderEventArgs e)
    256         {
    257             if (SelectedIcon.HasValue)
    258             {
    259                 var selectedIconPen = new Pen(Color.Yellow, 2);
    260                 var cellSize = new Size(Globals.OriginalTileWidth / 4, Globals.OriginalTileHeight / 4);
    261                 var rect = new Rectangle(new Point(SelectedIcon.Value.X * cellSize.Width, SelectedIcon.Value.Y * cellSize.Height), cellSize);
    262                 e.Graphics.DrawRectangle(selectedIconPen, rect);
    263             }
    264 
    265             if (SelectedTemplateType != null)
    266             {
    267                 var sizeStringFormat = new StringFormat
    268                 {
    269                     Alignment = StringAlignment.Center,
    270                     LineAlignment = StringAlignment.Center
    271                 };
    272                 var sizeBackgroundBrush = new SolidBrush(Color.FromArgb(128, Color.Black));
    273                 var sizeTextBrush = new SolidBrush(Color.White);
    274 
    275                 var text = string.Format("{0} ({1}x{2})", SelectedTemplateType.DisplayName, SelectedTemplateType.IconWidth, SelectedTemplateType.IconHeight);
    276                 var textSize = e.Graphics.MeasureString(text, SystemFonts.CaptionFont) + new SizeF(6.0f, 6.0f);
    277                 var textBounds = new RectangleF(new PointF(0, 0), textSize);
    278                 e.Graphics.Transform = new Matrix();
    279                 e.Graphics.FillRectangle(sizeBackgroundBrush, textBounds);
    280                 e.Graphics.DrawString(text, SystemFonts.CaptionFont, sizeTextBrush, textBounds, sizeStringFormat);
    281             }
    282         }
    283 
    284         private void TemplateTool_KeyDown(object sender, KeyEventArgs e)
    285         {
    286             if (e.KeyCode == Keys.ShiftKey)
    287             {
    288                 EnterPlacementMode();
    289             }
    290             else if (e.KeyCode == Keys.ControlKey)
    291             {
    292                 EnterBoundsMode();
    293             }
    294         }
    295 
    296         private void TemplateTool_KeyUp(object sender, KeyEventArgs e)
    297         {
    298             if ((e.KeyCode == Keys.ShiftKey) || (e.KeyCode == Keys.ControlKey))
    299             {
    300                 ExitAllModes();
    301             }
    302         }
    303 
    304         private void TemplateTypeListView_SelectedIndexChanged(object sender, EventArgs e)
    305         {
    306             SelectedTemplateType = (templateTypeListView.SelectedItems.Count > 0) ? (templateTypeListView.SelectedItems[0].Tag as TemplateType) : null;
    307             SelectedIcon = null;
    308         }
    309 
    310         private void MapPanel_MouseDown(object sender, MouseEventArgs e)
    311         {
    312             if (boundsMode)
    313             {
    314                 dragEdge = DetectDragEdge();
    315 
    316                 UpdateStatus();
    317             }
    318             else if (placementMode)
    319             {
    320                 if (e.Button == MouseButtons.Left)
    321                 {
    322                     SetTemplate(navigationWidget.MouseCell);
    323                 }
    324                 else if (e.Button == MouseButtons.Right)
    325                 {
    326                     RemoveTemplate(navigationWidget.MouseCell);
    327                 }
    328             }
    329             else if ((e.Button == MouseButtons.Left) || (e.Button == MouseButtons.Right))
    330             {
    331                 PickTemplate(navigationWidget.MouseCell, e.Button == MouseButtons.Left);
    332             }
    333         }
    334 
    335         private void MapPanel_MouseUp(object sender, MouseEventArgs e)
    336         {
    337             if (boundsMode)
    338             {
    339                 if (dragBounds != map.Bounds)
    340                 {
    341                     var oldBounds = map.Bounds;
    342                     void undoAction(UndoRedoEventArgs ure)
    343                     {
    344                         ure.Map.Bounds = oldBounds;
    345                         ure.MapPanel.Invalidate();
    346                     }
    347 
    348                     void redoAction(UndoRedoEventArgs ure)
    349                     {
    350                         ure.Map.Bounds = dragBounds;
    351                         ure.MapPanel.Invalidate();
    352                     }
    353 
    354                     map.Bounds = dragBounds;
    355 
    356                     url.Track(undoAction, redoAction);
    357                     mapPanel.Invalidate();
    358                 }
    359 
    360                 dragEdge = -1;
    361 
    362                 UpdateStatus();
    363             }
    364             else
    365             {
    366                 if ((undoTemplates.Count > 0) || (redoTemplates.Count > 0))
    367                 {
    368                     CommitChange();
    369                 }
    370             }
    371         }
    372 
    373         private void MapPanel_MouseMove(object sender, MouseEventArgs e)
    374         {
    375             if (!placementMode && (Control.ModifierKeys == Keys.Shift))
    376             {
    377                 EnterPlacementMode();
    378             }
    379             else if (!boundsMode && (Control.ModifierKeys == Keys.Control))
    380             {
    381                 EnterBoundsMode();
    382             }
    383             else if ((placementMode || boundsMode) && (Control.ModifierKeys == Keys.None))
    384             {
    385                 ExitAllModes();
    386             }
    387 
    388             var cursor = Cursors.Default;
    389             if (boundsMode)
    390             {
    391                 switch ((dragEdge >= 0) ? dragEdge : DetectDragEdge())
    392                 {
    393                     case 0:
    394                     case 4:
    395                         cursor = Cursors.SizeNS;
    396                         break;
    397                     case 2:
    398                     case 6:
    399                         cursor = Cursors.SizeWE;
    400                         break;
    401                     case 1:
    402                     case 5:
    403                         cursor = Cursors.SizeNESW;
    404                         break;
    405                     case 3:
    406                     case 7:
    407                         cursor = Cursors.SizeNWSE;
    408                         break;
    409                 }
    410             }
    411             Cursor.Current = cursor;
    412 
    413             UpdateTooltip();
    414         }
    415 
    416         private void MouseoverWidget_MouseCellChanged(object sender, MouseCellChangedEventArgs e)
    417         {
    418             if (dragEdge >= 0)
    419             {
    420                 var endDrag = navigationWidget.MouseCell;
    421                 map.Metrics.Clip(ref endDrag, new Size(1, 1), Size.Empty);
    422 
    423                 switch (dragEdge)
    424                 {
    425                     case 0:
    426                     case 1:
    427                     case 7:
    428                         if (endDrag.Y < dragBounds.Bottom)
    429                         {
    430                             dragBounds.Height = dragBounds.Bottom - endDrag.Y;
    431                             dragBounds.Y = endDrag.Y;
    432                         }
    433                         break;
    434                 }
    435 
    436                 switch (dragEdge)
    437                 {
    438                     case 5:
    439                     case 6:
    440                     case 7:
    441                         if (endDrag.X < dragBounds.Right)
    442                         {
    443                             dragBounds.Width = dragBounds.Right - endDrag.X;
    444                             dragBounds.X = endDrag.X;
    445                         }
    446                         break;
    447                 }
    448 
    449                 switch (dragEdge)
    450                 {
    451                     case 3:
    452                     case 4:
    453                     case 5:
    454                         if (endDrag.Y > dragBounds.Top)
    455                         {
    456                             dragBounds.Height = endDrag.Y - dragBounds.Top;
    457                         }
    458                         break;
    459                 }
    460 
    461                 switch (dragEdge)
    462                 {
    463                     case 1:
    464                     case 2:
    465                     case 3:
    466                         if (endDrag.X > dragBounds.Left)
    467                         {
    468                             dragBounds.Width = endDrag.X - dragBounds.Left;
    469                         }
    470                         break;
    471                 }
    472 
    473                 mapPanel.Invalidate();
    474             }
    475             else if (placementMode)
    476             {
    477                 if (Control.MouseButtons == MouseButtons.Right)
    478                 {
    479                     RemoveTemplate(navigationWidget.MouseCell);
    480                 }
    481 
    482                 if (SelectedTemplateType != null)
    483                 {
    484                     foreach (var location in new Point[] { e.OldCell, e.NewCell })
    485                     {
    486                         for (var y = 0; y < SelectedTemplateType.IconHeight; ++y)
    487                         {
    488                             for (var x = 0; x < SelectedTemplateType.IconWidth; ++x)
    489                             {
    490                                 mapPanel.Invalidate(map, new Point(location.X + x, location.Y + y));
    491                             }
    492                         }
    493                     }
    494                 }
    495             }
    496             else if((Control.MouseButtons == MouseButtons.Left) || (Control.MouseButtons == MouseButtons.Right))
    497             {
    498                 PickTemplate(navigationWidget.MouseCell, Control.MouseButtons == MouseButtons.Left);
    499             }
    500         }
    501 
    502         private void RefreshMapPanel()
    503         {
    504             if (templateTypeNavigationWidget != null)
    505             {
    506                 templateTypeNavigationWidget.Dispose();
    507                 templateTypeNavigationWidget = null;
    508             }
    509 
    510             if (SelectedTemplateType != null)
    511             {
    512                 templateTypeMapPanel.MapImage = SelectedTemplateType.Thumbnail;
    513 
    514                 var templateTypeMetrics = new CellMetrics(SelectedTemplateType.IconWidth, SelectedTemplateType.IconHeight);
    515                 templateTypeNavigationWidget = new NavigationWidget(templateTypeMapPanel, templateTypeMetrics,
    516                     new Size(Globals.OriginalTileWidth / 4, Globals.OriginalTileHeight / 4));
    517                 templateTypeNavigationWidget.MouseoverSize = Size.Empty;
    518             }
    519             else
    520             {
    521                 templateTypeMapPanel.MapImage = null;
    522             }
    523         }
    524 
    525         private void SetTemplate(Point location)
    526         {
    527             if (SelectedTemplateType != null)
    528             {
    529                 if (SelectedIcon.HasValue)
    530                 {
    531                     if (map.Metrics.GetCell(location, out int cell))
    532                     {
    533                         if (!undoTemplates.ContainsKey(cell))
    534                         {
    535                             undoTemplates[cell] = map.Templates[location];
    536                         }
    537 
    538                         var icon = (SelectedIcon.Value.Y * SelectedTemplateType.IconWidth) + SelectedIcon.Value.X;
    539                         var template = new Template { Type = SelectedTemplateType, Icon = icon };
    540                         map.Templates[cell] = template;
    541                         redoTemplates[cell] = template;
    542                         mapPanel.Invalidate(map, cell);
    543                         plugin.Dirty = true;
    544                     }
    545                 }
    546                 else
    547                 {
    548                     for (int y = 0, icon = 0; y < SelectedTemplateType.IconHeight; ++y)
    549                     {
    550                         for (var x = 0; x < SelectedTemplateType.IconWidth; ++x, ++icon)
    551                         {
    552                             var subLocation = new Point(location.X + x, location.Y + y);
    553                             if (map.Metrics.GetCell(subLocation, out int cell))
    554                             {
    555                                 if (!undoTemplates.ContainsKey(cell))
    556                                 {
    557                                     undoTemplates[cell] = map.Templates[subLocation];
    558                                 }
    559                             }
    560                         }
    561                     }
    562 
    563                     for (int y = 0, icon = 0; y < SelectedTemplateType.IconHeight; ++y)
    564                     {
    565                         for (var x = 0; x < SelectedTemplateType.IconWidth; ++x, ++icon)
    566                         {
    567                             if (!SelectedTemplateType.IconMask[x, y])
    568                             {
    569                                 continue;
    570                             }
    571 
    572                             var subLocation = new Point(location.X + x, location.Y + y);
    573                             if (map.Metrics.GetCell(subLocation, out int cell))
    574                             {
    575                                 var template = new Template { Type = SelectedTemplateType, Icon = icon };
    576                                 map.Templates[cell] = template;
    577                                 redoTemplates[cell] = template;
    578                                 mapPanel.Invalidate(map, cell);
    579                                 plugin.Dirty = true;
    580                             }
    581                         }
    582                     }
    583                 }
    584             }
    585         }
    586 
    587         private void RemoveTemplate(Point location)
    588         {
    589             if (SelectedTemplateType != null)
    590             {
    591                 if (SelectedIcon.HasValue)
    592                 {
    593                     if (map.Metrics.GetCell(location, out int cell))
    594                     {
    595                         if (!undoTemplates.ContainsKey(cell))
    596                         {
    597                             undoTemplates[cell] = map.Templates[location];
    598                         }
    599 
    600                         map.Templates[cell] = null;
    601                         redoTemplates[cell] = null;
    602                         mapPanel.Invalidate(map, cell);
    603                         plugin.Dirty = true;
    604                     }
    605                 }
    606                 else
    607                 {
    608                     for (int y = 0, icon = 0; y < SelectedTemplateType.IconHeight; ++y)
    609                     {
    610                         for (var x = 0; x < SelectedTemplateType.IconWidth; ++x, ++icon)
    611                         {
    612                             var subLocation = new Point(location.X + x, location.Y + y);
    613                             if (map.Metrics.GetCell(subLocation, out int cell))
    614                             {
    615                                 if (!undoTemplates.ContainsKey(cell))
    616                                 {
    617                                     undoTemplates[cell] = map.Templates[subLocation];
    618                                 }
    619                             }
    620                         }
    621                     }
    622 
    623                     for (int y = 0, icon = 0; y < SelectedTemplateType.IconHeight; ++y)
    624                     {
    625                         for (var x = 0; x < SelectedTemplateType.IconWidth; ++x, ++icon)
    626                         {
    627                             var subLocation = new Point(location.X + x, location.Y + y);
    628                             if (map.Metrics.GetCell(subLocation, out int cell))
    629                             {
    630                                 map.Templates[cell] = null;
    631                                 redoTemplates[cell] = null;
    632                                 mapPanel.Invalidate(map, cell);
    633                                 plugin.Dirty = true;
    634                             }
    635                         }
    636                     }
    637                 }
    638             }
    639         }
    640 
    641         private void EnterPlacementMode()
    642         {
    643             if (placementMode || boundsMode)
    644             {
    645                 return;
    646             }
    647 
    648             placementMode = true;
    649 
    650             navigationWidget.MouseoverSize = Size.Empty;
    651 
    652             if (SelectedTemplateType != null)
    653             {
    654                 for (var y = 0; y < SelectedTemplateType.IconHeight; ++y)
    655                 {
    656                     for (var x = 0; x < SelectedTemplateType.IconWidth; ++x)
    657                     {
    658                         mapPanel.Invalidate(map, new Point(navigationWidget.MouseCell.X + x, navigationWidget.MouseCell.Y + y));
    659                     }
    660                 }
    661             }
    662 
    663             UpdateStatus();
    664         }
    665 
    666         private void EnterBoundsMode()
    667         {
    668             if (boundsMode || placementMode)
    669             {
    670                 return;
    671             }
    672 
    673             boundsMode = true;
    674             dragBounds = map.Bounds;
    675 
    676             navigationWidget.MouseoverSize = Size.Empty;
    677 
    678             if (SelectedTemplateType != null)
    679             {
    680                 for (var y = 0; y < SelectedTemplateType.IconHeight; ++y)
    681                 {
    682                     for (var x = 0; x < SelectedTemplateType.IconWidth; ++x)
    683                     {
    684                         mapPanel.Invalidate(map, new Point(navigationWidget.MouseCell.X + x, navigationWidget.MouseCell.Y + y));
    685                     }
    686                 }
    687             }
    688 
    689             UpdateTooltip();
    690             UpdateStatus();
    691         }
    692 
    693         private void ExitAllModes()
    694         {
    695             if (!placementMode && !boundsMode)
    696             {
    697                 return;
    698             }
    699 
    700             boundsMode = false;
    701             dragEdge = -1;
    702             dragBounds = Rectangle.Empty;
    703             placementMode = false;
    704 
    705             navigationWidget.MouseoverSize = new Size(1, 1);
    706 
    707             if (SelectedTemplateType != null)
    708             {
    709                 for (var y = 0; y < SelectedTemplateType.IconHeight; ++y)
    710                 {
    711                     for (var x = 0; x < SelectedTemplateType.IconWidth; ++x)
    712                     {
    713                         mapPanel.Invalidate(map, new Point(navigationWidget.MouseCell.X + x, navigationWidget.MouseCell.Y + y));
    714                     }
    715                 }
    716             }
    717 
    718             UpdateTooltip();
    719             UpdateStatus();
    720         }
    721 
    722         private void UpdateTooltip()
    723         {
    724             if (boundsMode)
    725             {
    726                 var tooltip = string.Format("X = {0}\nY = {1}\nWidth = {2}\nHeight = {3}", dragBounds.Left, dragBounds.Top, dragBounds.Width, dragBounds.Height);
    727                 var textSize = TextRenderer.MeasureText(tooltip, SystemFonts.CaptionFont);
    728                 var tooltipSize = new Size(textSize.Width + 6, textSize.Height + 6);
    729 
    730                 var tooltipPosition = mapPanel.PointToClient(Control.MousePosition);
    731                 switch (dragEdge)
    732                 {
    733                     case -1:
    734                     case 0:
    735                     case 1:
    736                     case 7:
    737                         tooltipPosition.Y -= tooltipSize.Height;
    738                         break;
    739                 }
    740                 switch (dragEdge)
    741                 {
    742                     case -1:
    743                     case 5:
    744                     case 6:
    745                     case 7:
    746                         tooltipPosition.X -= tooltipSize.Width;
    747                         break;
    748                 }
    749 
    750                 var screenPosition = mapPanel.PointToScreen(tooltipPosition);
    751                 var screen = Screen.FromControl(mapPanel);
    752                 screenPosition.X = Math.Max(0, Math.Min(screen.WorkingArea.Width - tooltipSize.Width, screenPosition.X));
    753                 screenPosition.Y = Math.Max(0, Math.Min(screen.WorkingArea.Height - tooltipSize.Height, screenPosition.Y));
    754                 tooltipPosition = mapPanel.PointToClient(screenPosition);
    755 
    756                 mouseTooltip.Show(tooltip, mapPanel, tooltipPosition.X, tooltipPosition.Y);
    757             }
    758             else
    759             {
    760                 mouseTooltip.Hide(mapPanel);
    761             }
    762         }
    763 
    764         private void PickTemplate(Point location, bool wholeTemplate)
    765         {
    766             if (map.Metrics.GetCell(location, out int cell))
    767             {
    768                 var template = map.Templates[cell];
    769                 if (template != null)
    770                 {
    771                     SelectedTemplateType = template.Type;
    772                 }
    773                 else
    774                 {
    775                     SelectedTemplateType = map.TemplateTypes.Where(t => t.Equals("clear1")).FirstOrDefault();
    776                 }
    777 
    778                 if (!wholeTemplate && ((SelectedTemplateType.IconWidth * SelectedTemplateType.IconHeight) > 1))
    779                 {
    780                     var icon = template?.Icon ?? 0;
    781                     SelectedIcon = new Point(icon % SelectedTemplateType.IconWidth, icon / SelectedTemplateType.IconWidth);
    782                 }
    783                 else
    784                 {
    785                     SelectedIcon = null;
    786                 }
    787             }
    788         }
    789 
    790         private int DetectDragEdge()
    791         {
    792             var mouseCell = navigationWidget.MouseCell;
    793             var mousePixel = navigationWidget.MouseSubPixel;
    794             var topEdge =
    795                 ((mouseCell.Y == dragBounds.Top) && (mousePixel.Y <= (Globals.PixelHeight / 4))) ||
    796                 ((mouseCell.Y == dragBounds.Top - 1) && (mousePixel.Y >= (3 * Globals.PixelHeight / 4)));
    797             var bottomEdge =
    798                 ((mouseCell.Y == dragBounds.Bottom) && (mousePixel.Y <= (Globals.PixelHeight / 4))) ||
    799                 ((mouseCell.Y == dragBounds.Bottom - 1) && (mousePixel.Y >= (3 * Globals.PixelHeight / 4)));
    800             var leftEdge =
    801                  ((mouseCell.X == dragBounds.Left) && (mousePixel.X <= (Globals.PixelWidth / 4))) ||
    802                  ((mouseCell.X == dragBounds.Left - 1) && (mousePixel.X >= (3 * Globals.PixelWidth / 4)));
    803             var rightEdge =
    804                 ((mouseCell.X == dragBounds.Right) && (mousePixel.X <= (Globals.PixelHeight / 4))) ||
    805                 ((mouseCell.X == dragBounds.Right - 1) && (mousePixel.X >= (3 * Globals.PixelHeight / 4)));
    806             if (topEdge)
    807             {
    808                 if (rightEdge)
    809                 {
    810                     return 1;
    811                 }
    812                 else if (leftEdge)
    813                 {
    814                     return 7;
    815                 }
    816                 else
    817                 {
    818                     return 0;
    819                 }
    820             }
    821             else if (bottomEdge)
    822             {
    823                 if (rightEdge)
    824                 {
    825                     return 3;
    826                 }
    827                 else if (leftEdge)
    828                 {
    829                     return 5;
    830                 }
    831                 else
    832                 {
    833                     return 4;
    834                 }
    835             }
    836             else if (rightEdge)
    837             {
    838                 return 2;
    839             }
    840             else if (leftEdge)
    841             {
    842                 return 6;
    843             }
    844             else
    845             {
    846                 return -1;
    847             }
    848         }
    849 
    850         private void CommitChange()
    851         {
    852             var undoTemplates2 = new Dictionary<int, Template>(undoTemplates);
    853             void undoAction(UndoRedoEventArgs e)
    854             {
    855                 foreach (var kv in undoTemplates2)
    856                 {
    857                     e.Map.Templates[kv.Key] = kv.Value;
    858                 }
    859                 e.MapPanel.Invalidate(e.Map, undoTemplates2.Keys);
    860             }
    861 
    862             var redoTemplates2 = new Dictionary<int, Template>(redoTemplates);
    863             void redoAction(UndoRedoEventArgs e)
    864             {
    865                 foreach (var kv in redoTemplates2)
    866                 {
    867                     e.Map.Templates[kv.Key] = kv.Value;
    868                 }
    869                 e.MapPanel.Invalidate(e.Map, redoTemplates2.Keys);
    870             }
    871 
    872             undoTemplates.Clear();
    873             redoTemplates.Clear();
    874 
    875             url.Track(undoAction, redoAction);
    876         }
    877 
    878         private void UpdateStatus()
    879         {
    880             if (placementMode)
    881             {
    882                 statusLbl.Text = "Left-Click to place template, Right-Click to clear template";
    883             }
    884             else if (boundsMode)
    885             {
    886                 if (dragEdge >= 0)
    887                 {
    888                     statusLbl.Text = "Release left button to end dragging map bounds edge";
    889                 }
    890                 else
    891                 {
    892                     statusLbl.Text = "Left-Click a map bounds edge to start dragging";
    893                 }
    894             }
    895             else
    896             {
    897                 statusLbl.Text = "Shift to enter placement mode, Ctrl to enter map bounds mode, Left-Click to pick whole template, Right-Click to pick individual template tile";
    898             }
    899         }
    900 
    901         protected override void PreRenderMap()
    902         {
    903             base.PreRenderMap();
    904 
    905             previewMap = map.Clone();
    906             if (placementMode)
    907             {
    908                 var location = navigationWidget.MouseCell;
    909                 if (SelectedTemplateType != null)
    910                 {
    911                     if (SelectedIcon.HasValue)
    912                     {
    913                         if (previewMap.Metrics.GetCell(location, out int cell))
    914                         {
    915                             var icon = (SelectedIcon.Value.Y * SelectedTemplateType.IconWidth) + SelectedIcon.Value.X;
    916                             previewMap.Templates[cell] = new Template { Type = SelectedTemplateType, Icon = icon };
    917                         }
    918                     }
    919                     else
    920                     {
    921                         int icon = 0;
    922                         for (var y = 0; y < SelectedTemplateType.IconHeight; ++y)
    923                         {
    924                             for (var x = 0; x < SelectedTemplateType.IconWidth; ++x, ++icon)
    925                             {
    926                                 if (!SelectedTemplateType.IconMask[x, y])
    927                                 {
    928                                     continue;
    929                                 }
    930 
    931                                 var subLocation = new Point(location.X + x, location.Y + y);
    932                                 if (previewMap.Metrics.GetCell(subLocation, out int cell))
    933                                 {
    934                                     previewMap.Templates[cell] = new Template { Type = SelectedTemplateType, Icon = icon };
    935                                 }
    936                             }
    937                         }
    938                     }
    939                 }
    940             }
    941         }
    942 
    943         protected override void PostRenderMap(Graphics graphics)
    944         {
    945             base.PostRenderMap(graphics);
    946 
    947             if (boundsMode)
    948             {
    949                 var bounds = Rectangle.FromLTRB(
    950                     dragBounds.Left * Globals.TileWidth,
    951                     dragBounds.Top * Globals.TileHeight,
    952                     dragBounds.Right * Globals.TileWidth,
    953                     dragBounds.Bottom * Globals.TileHeight
    954                 );
    955 
    956                 var boundsPen = new Pen(Color.Red, 8.0f);
    957                 graphics.DrawRectangle(boundsPen, bounds);
    958             }
    959             else if (placementMode)
    960             {
    961                 var location = navigationWidget.MouseCell;
    962                 if (SelectedTemplateType != null)
    963                 {
    964                     var previewPen = new Pen(Color.Green, 4.0f);
    965                     var previewBounds = new Rectangle(
    966                         location.X * Globals.TileWidth,
    967                         location.Y * Globals.TileHeight,
    968                         (SelectedIcon.HasValue ? 1 : SelectedTemplateType.IconWidth) * Globals.TileWidth,
    969                         (SelectedIcon.HasValue ? 1 : SelectedTemplateType.IconHeight) * Globals.TileHeight
    970                     );
    971                     graphics.DrawRectangle(previewPen, previewBounds);
    972                 }
    973             }
    974         }
    975 
    976         #region IDisposable Support
    977         private bool disposedValue = false;
    978 
    979         protected override void Dispose(bool disposing)
    980         {
    981             if (!disposedValue)
    982             {
    983                 if (disposing)
    984                 {
    985                     mapPanel.MouseDown -= MapPanel_MouseDown;
    986                     mapPanel.MouseUp -= MapPanel_MouseUp;
    987                     mapPanel.MouseMove -= MapPanel_MouseMove;
    988                     (mapPanel as Control).KeyDown -= TemplateTool_KeyDown;
    989                     (mapPanel as Control).KeyUp -= TemplateTool_KeyUp;
    990 
    991                     templateTypeListView.SelectedIndexChanged -= TemplateTypeListView_SelectedIndexChanged;
    992 
    993                     templateTypeMapPanel.MouseDown -= TemplateTypeMapPanel_MouseDown;
    994                     templateTypeMapPanel.PostRender -= TemplateTypeMapPanel_PostRender;
    995 
    996                     navigationWidget.MouseCellChanged -= MouseoverWidget_MouseCellChanged;
    997 
    998                     url.Undone -= Url_Undone;
    999                     url.Redone -= Url_Redone;
   1000                 }
   1001                 disposedValue = true;
   1002             }
   1003 
   1004             base.Dispose(disposing);
   1005         }
   1006         #endregion
   1007     }
   1008 }