Imports System.IO
Imports System.Threading.Tasks
Imports System.Xml.Serialization

Public Class FrmWorld
    Private dragBoxFromMouseDown As Rectangle ' Variable to track the drag rectangle
    Private currentFileName As String = ""
    Private isModified As Boolean = False

#Region " Initiate "
    Private Sub FrmWorld_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        PopulateTreeView("D:\01 - Grags\Visual Studio\Bizarria New\Data\Decor")
        ' Ensure PicCanvas can accept drops (if not already set in MapCanvas class)
        PicCanvas.AllowDrop = True

        ' Add event handler for grid size selection
        AddHandler CboDimensions.SelectedIndexChanged, AddressOf CboDimensions_SelectedIndexChanged

        ' Set default grid size
        CboDimensions.SelectedItem = "16 x 16"

        ' Add "10000 x 10000" to the combo box if not already present
        Dim defaultMapSize As String = "10000 x 10000"

        If Not CboMapSize.Items.Contains(defaultMapSize) Then
            CboMapSize.Items.Add(defaultMapSize)
        End If

        ' Set default map size
        CboMapSize.SelectedItem = defaultMapSize

        ' Handle CanvasModified event
        AddHandler PicCanvas.CanvasModified, AddressOf CanvasModified_Handler

        ' Set Context Menu in PicCanvas
        'PicCanvas.SetContextMenu(CTMMenu)
    End Sub
#End Region

#Region " Populate TreeView and FlowPanel "
    Private Async Function LoadImagesAsync(directoryInfo As DirectoryInfo) As Task
        Dim imageExtensions() As String = {".jpg", ".jpeg", ".png", ".bmp", ".gif"}
        Dim imageFiles As New List(Of FileInfo)()

        For Each extension As String In imageExtensions
            imageFiles.AddRange(directoryInfo.GetFiles($"*{extension}", SearchOption.TopDirectoryOnly))
        Next

        For Each imageFile As FileInfo In imageFiles
            Await Task.Run(Sub()
                               Dim pictureBox As New PictureBox() With {
                                   .Image = Image.FromFile(imageFile.FullName),
                                   .SizeMode = PictureBoxSizeMode.Zoom,
                                   .Width = 100,
                                   .Height = 100,
                                   .Tag = imageFile.FullName ' Store the file path
                               }
                               ' Add event handlers for drag initiation
                               AddHandler pictureBox.MouseDown, AddressOf PictureBox_MouseDown
                               AddHandler pictureBox.MouseMove, AddressOf PictureBox_MouseMove
                               AddHandler pictureBox.MouseUp, AddressOf PictureBox_MouseUp
                               FLPImages.Invoke(Sub() FLPImages.Controls.Add(pictureBox))
                           End Sub)
        Next
    End Function

    Private Sub PopulateTreeView(rootDirectory As String)
        TrvFiles.Nodes.Clear()
        Dim rootDirectoryInfo As New DirectoryInfo(rootDirectory)
        Dim rootNode As New TreeNode(rootDirectoryInfo.Name) With {.Tag = rootDirectoryInfo}
        TrvFiles.Nodes.Add(rootNode)
        AddDirectories(rootDirectoryInfo, rootNode)
    End Sub

    Private Sub AddDirectories(directoryInfo As DirectoryInfo, node As TreeNode)
        For Each directory As DirectoryInfo In directoryInfo.GetDirectories()
            Dim directoryNode As New TreeNode(directory.Name) With {.Tag = directory}
            node.Nodes.Add(directoryNode)
            AddDirectories(directory, directoryNode)
        Next
    End Sub

    Private Async Sub TrvFiles_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles TrvFiles.AfterSelect
        FLPImages.Controls.Clear()
        Dim selectedNode As TreeNode = e.Node
        If TypeOf selectedNode.Tag Is DirectoryInfo Then
            Dim directoryInfo As DirectoryInfo = CType(selectedNode.Tag, DirectoryInfo)
            Await LoadImagesAsync(directoryInfo)
        End If
    End Sub
#End Region

#Region " Mouse Events for Dragging Images "
    ' MouseDown event handler for PictureBox in FLPImages
    Private Sub PictureBox_MouseDown(sender As Object, e As MouseEventArgs)
        If e.Button = MouseButtons.Left Then
            ' Remember the point where the mouse down occurred
            Dim dragSize As Size = SystemInformation.DragSize
            ' Create a rectangle representing the area the mouse can move before a drag starts
            dragBoxFromMouseDown = New Rectangle(New Point(e.X - (dragSize.Width \ 2),
                                                           e.Y - (dragSize.Height \ 2)),
                                                 dragSize)
        Else
            ' Reset drag rectangle
            dragBoxFromMouseDown = Rectangle.Empty
        End If
    End Sub

    ' MouseMove event handler for PictureBox in FLPImages
    Private Sub PictureBox_MouseMove(sender As Object, e As MouseEventArgs)
        If (e.Button And MouseButtons.Left) = MouseButtons.Left Then
            ' Check if the mouse has moved outside the drag rectangle
            If dragBoxFromMouseDown <> Rectangle.Empty AndAlso Not dragBoxFromMouseDown.Contains(e.X, e.Y) Then
                Dim pictureBox As PictureBox = CType(sender, PictureBox)
                ' Get the image from the PictureBox
                Dim image As Image = pictureBox.Image
                If image IsNot Nothing Then
                    ' Set custom cursor
                    Dim cursor As Cursor = CreateCursorFromImage(image)
                    Cursor.Current = cursor

                    ' Use custom data format to pass the image and file path
                    Dim dataObj As New DataObject()
                    dataObj.SetData("ImageFormat", image)
                    Dim imageFilePath As String = CType(pictureBox.Tag, String)
                    dataObj.SetData("ImageFilePath", imageFilePath)
                    ' Start the drag-and-drop operation
                    pictureBox.DoDragDrop(dataObj, DragDropEffects.Copy)
                    ' Reset cursor after dragging
                    Cursor.Current = Cursors.Default
                End If
                ' Reset drag rectangle
                dragBoxFromMouseDown = Rectangle.Empty
            End If
        End If
    End Sub

    ' MouseUp event handler for PictureBox in FLPImages
    Private Sub PictureBox_MouseUp(sender As Object, e As MouseEventArgs)
        ' Reset drag rectangle
        dragBoxFromMouseDown = Rectangle.Empty
    End Sub
#End Region

    Private Sub CboDimensions_SelectedIndexChanged(sender As Object, e As EventArgs)
        Dim selectedText As String = CboDimensions.SelectedItem.ToString()
        Dim dimensions() As String = selectedText.Split("x"c)
        If dimensions.Length = 2 Then
            Dim size As Integer
            If Integer.TryParse(dimensions(0).Trim(), size) Then
                ' Update grid size in PicCanvas
                PicCanvas.GridSize = size
                ' Optionally, update existing tiles to snap to new grid
                PicCanvas.SnapAllTilesToGrid()
            End If
        End If
    End Sub

    Private Sub CboMapSize_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CboMapSize.SelectedIndexChanged
        Dim selectedText As String = CboMapSize.SelectedItem.ToString()
        Dim dimensions() As String = selectedText.Split("x"c)
        If dimensions.Length = 2 Then
            Dim width As Integer
            Dim height As Integer
            If Integer.TryParse(dimensions(0).Trim(), width) AndAlso Integer.TryParse(dimensions(1).Trim(), height) Then
                ' Update map size
                PicCanvas.SetMapSize(width, height)
            End If
        End If
    End Sub

    ' Helper function to convert Image to Cursor
    Private Function CreateCursorFromImage(image As Image) As Cursor
        Dim bitmap As New Bitmap(image)
        Dim iconHandle As IntPtr = bitmap.GetHicon()
        Return New Cursor(iconHandle)
    End Function

    ' Event handler for CanvasModified event
    Private Sub CanvasModified_Handler()
        isModified = True
    End Sub

#Region " Save and Load Functionality "
    <Serializable>
    Public Class CanvasData
        Public Property MapTiles As List(Of MapTileData)
        Public Property GridSize As Integer
        Public Property MapSize As Size
    End Class

    <Serializable>
    Public Class MapTileData
        Public Property ImageFilePath As String
        Public Property Position As Point
        Public Property Size As Size
    End Class

    Private Sub SaveCanvas(fileName As String)
        ' Create CanvasData object
        Dim canvasData As New CanvasData With {
            .GridSize = PicCanvas.GridSize,
            .MapSize = PicCanvas.AutoScrollMinSize,
            .MapTiles = New List(Of MapTileData)()
        }

        ' Map tiles
        For Each tile As MapTile In PicCanvas.Tiles
            Dim tileData As New MapTileData With {
                .ImageFilePath = tile.ImageFilePath,
                .Position = tile.Position,
                .Size = tile.Size
            }
            canvasData.MapTiles.Add(tileData)
        Next

        ' Serialize to XML
        Dim serializer As New XmlSerializer(GetType(CanvasData))

        Using writer As New StreamWriter(fileName)
            serializer.Serialize(writer, canvasData)
        End Using

        isModified = False
    End Sub

    Private Sub LoadCanvas(fileName As String)
        ' Deserialize from XML
        Dim serializer As New XmlSerializer(GetType(CanvasData))
        Using reader As New StreamReader(fileName)
            Dim canvasData As CanvasData = CType(serializer.Deserialize(reader), CanvasData)

            ' Clear existing tiles
            PicCanvas.ClearTiles()

            ' Set grid size and map size
            PicCanvas.GridSize = canvasData.GridSize
            PicCanvas.SetMapSize(canvasData.MapSize.Width, canvasData.MapSize.Height)

            ' Load tiles
            For Each tileData As MapTileData In canvasData.MapTiles
                ' Load image from file path
                If File.Exists(tileData.ImageFilePath) Then
                    Dim image As Image = Image.FromFile(tileData.ImageFilePath)
                    Dim tileImage As New Bitmap(image, tileData.Size)

                    Dim mapTile As New MapTile With {
                        .Image = tileImage,
                        .ImageFilePath = tileData.ImageFilePath,
                        .Position = tileData.Position,
                        .Size = tileData.Size
                    }

                    PicCanvas.AddTile(mapTile)
                Else
                    ' Handle missing image file
                    MessageBox.Show("Image file not found: " & tileData.ImageFilePath)
                End If
            Next
        End Using

        isModified = False
    End Sub

    Private Sub MnuNew_Click(sender As Object, e As EventArgs) Handles MnuNew.Click
        If isModified Then
            Dim result = MessageBox.Show("Do you want to save changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
            If result = DialogResult.Yes Then
                MnuSave_Click(sender, e)
            ElseIf result = DialogResult.Cancel Then
                Return
            End If
        End If

        PicCanvas.ClearTiles()
        currentFileName = ""
        isModified = False
    End Sub

    Private Sub MnuSave_Click(sender As Object, e As EventArgs) Handles MnuSave.Click
        If String.IsNullOrEmpty(currentFileName) Then
            MnuSaveAs_Click(sender, e)
        Else
            SaveCanvas(currentFileName)
        End If
    End Sub

    Private Sub MnuSaveAs_Click(sender As Object, e As EventArgs) Handles MnuSaveAs.Click
        Using sfd As New SaveFileDialog()
            sfd.Filter = "Map Files (*.map)|*.map|All Files (*.*)|*.*"
            If sfd.ShowDialog() = DialogResult.OK Then
                currentFileName = sfd.FileName
                SaveCanvas(currentFileName)
            End If
        End Using
    End Sub

    Private Sub MnuLoad_Click(sender As Object, e As EventArgs) Handles MnuLoad.Click
        If isModified Then
            Dim result = MessageBox.Show("Do you want to save changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
            If result = DialogResult.Yes Then
                MnuSave_Click(sender, e)
            ElseIf result = DialogResult.Cancel Then
                Return
            End If
        End If

        Using ofd As New OpenFileDialog()
            ofd.Filter = "Map Files (*.map)|*.map|All Files (*.*)|*.*"
            If ofd.ShowDialog() = DialogResult.OK Then
                currentFileName = ofd.FileName
                LoadCanvas(currentFileName)
            End If
        End Using
    End Sub

    Private Sub MnuExit_Click(sender As Object, e As EventArgs) Handles MnuExit.Click
        If isModified Then
            Dim result = MessageBox.Show("Do you want to save changes?", "Save Changes", MessageBoxButtons.YesNoCancel)
            If result = DialogResult.Yes Then
                MnuSave_Click(sender, e)
            ElseIf result = DialogResult.Cancel Then
                Return
            End If
        End If
        Me.Close()
    End Sub
#End Region

End Class

Imports System.Xml.Serialization

Public Class MapCanvas
    Inherits UserControl

    ' List of all tiles on the canvas
    Private _tiles As New List(Of MapTile)
    Private _gridSize As Integer = 16 ' Default grid size
    Private snappingEnabled As Boolean = True
    Private _showGrid As Boolean = True

    ' Variables for dragging and resizing
    Private selectedTile As MapTile = Nothing
    Private dragging As Boolean = False
    Private resizing As Boolean = False
    Private initialTilePosition As Point
    Private initialTileSize As Size
    Private initialMousePosition As Point

    ' Variables for selection rectangle
    Private isSelecting As Boolean = False
    Private selectionStartPoint As Point
    Private selectionEndPoint As Point
    Private selectionRectangle As Rectangle
    ' List to store selected tiles
    Private selectedTiles As New List(Of MapTile)

    ' Variables for group resizing
    Private initialSelectedTilesBounds As Rectangle
    Private initialSelectedTilesPositionsAndSizes As New List(Of Tuple(Of MapTile, Point, Size))

    ' Context Menu and Menu Items
    Private ctmMenu As ContextMenuStrip
    Private WithEvents mnuSendToBack As ToolStripMenuItem
    Private WithEvents mnuDelete As ToolStripMenuItem
    Private WithEvents mnuRotate90 As ToolStripMenuItem

    ' Event to notify when the canvas is modified
    Public Event CanvasModified()

    Public Sub New()
        Me.DoubleBuffered = True
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or
                    ControlStyles.OptimizedDoubleBuffer Or
                    ControlStyles.ResizeRedraw Or
                    ControlStyles.UserPaint, True)
        Me.UpdateStyles()
        Me.AutoScroll = True ' Enable AutoScroll

        ' Initialize the context menu
        InitializeContextMenu()
    End Sub

    Private Sub InitializeContextMenu()
        ctmMenu = New ContextMenuStrip()

        mnuSendToBack = New ToolStripMenuItem("Send to Back") With {.Name = "MnuSendToBack"}
        mnuDelete = New ToolStripMenuItem("Delete") With {.Name = "MnuDelete"}
        mnuRotate90 = New ToolStripMenuItem("Rotate 90°") With {.Name = "MnuRotate90"}

        ctmMenu.Items.AddRange(New ToolStripItem() {mnuSendToBack, mnuDelete, mnuRotate90})
    End Sub

    ' Property to adjust grid size
    Public Property GridSize As Integer
        Get
            Return _gridSize
        End Get
        Set(value As Integer)
            If value > 0 Then
                _gridSize = value
                Me.Invalidate()
            End If
        End Set
    End Property

    ' Property to show or hide the grid
    Public Property ShowGrid As Boolean
        Get
            Return _showGrid
        End Get
        Set(value As Boolean)
            _showGrid = value
            Me.Invalidate()
        End Set
    End Property

    ' Property to access the tiles
    Public ReadOnly Property Tiles As List(Of MapTile)
        Get
            Return _tiles
        End Get
    End Property

    ' Method to clear the tiles
    Public Sub ClearTiles()
        _tiles.Clear()
        Me.Invalidate()
    End Sub

    ' Method to add a tile
    Public Sub AddTile(tile As MapTile)
        _tiles.Add(tile)
        Me.Invalidate()
        RaiseEvent CanvasModified()
    End Sub

    ' Method to bring a tile to the front
    Private Sub BringTileToFront(tile As MapTile)
        If _tiles.Remove(tile) Then
            _tiles.Add(tile)
            Me.Invalidate()
        End If
    End Sub

    ' Method to send a tile to the back
    Private Sub SendTileToBack(tile As MapTile)
        If _tiles.Remove(tile) Then
            _tiles.Insert(0, tile)
            Me.Invalidate()
        End If
    End Sub

    ' Method to delete selected tiles
    Private Sub DeleteSelectedTiles()
        For Each tile In selectedTiles
            _tiles.Remove(tile)
        Next
        DeselectAllTiles()
        Me.Invalidate()
        RaiseEvent CanvasModified()
    End Sub

    ' Override OnPaint to draw everything
    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        MyBase.OnPaint(e)

        ' Draw the grid
        DrawGrid(e.Graphics)

        ' Draw the tiles
        For Each tile In _tiles
            ' Adjust tile position for scrolling
            Dim tileRect As New Rectangle(tile.Position.X + Me.AutoScrollPosition.X,
                                          tile.Position.Y + Me.AutoScrollPosition.Y,
                                          tile.Size.Width,
                                          tile.Size.Height)
            e.Graphics.DrawImage(tile.Image, tileRect)

            ' Draw selection rectangle
            If tile.Selected Then
                Using pen As New Pen(Color.Blue)
                    pen.DashStyle = Drawing2D.DashStyle.Dash
                    e.Graphics.DrawRectangle(pen, tileRect)
                End Using
            End If
        Next

        ' Draw selection rectangle if selecting
        If isSelecting Then
            Using selectionPen As New Pen(Color.Red)
                selectionPen.DashStyle = Drawing2D.DashStyle.Dash
                Dim rect As Rectangle = GetSelectionRectangle()
                ' Adjust for scrolling
                rect.Offset(Me.AutoScrollPosition)
                e.Graphics.DrawRectangle(selectionPen, rect)
            End Using
        End If

        ' Draw resize handles
        If selectedTiles.Count = 1 Then
            ' Single tile resize handles
            DrawResizeHandles(e.Graphics, selectedTiles(0))
        ElseIf selectedTiles.Count > 1 Then
            ' Group resize handles
            DrawGroupResizeHandles(e.Graphics)
        End If
    End Sub

    Private Sub DrawGrid(g As Graphics)
        If Not _showGrid Then Exit Sub

        ' Calculate the visible area
        Dim visibleRect As Rectangle = Me.DisplayRectangle
        visibleRect.Location = New Point(-Me.AutoScrollPosition.X, -Me.AutoScrollPosition.Y)

        Using pen As New Pen(Color.LightGray)
            ' Calculate starting positions
            Dim startX As Integer = visibleRect.Left - (visibleRect.Left Mod _gridSize)
            Dim startY As Integer = visibleRect.Top - (visibleRect.Top Mod _gridSize)

            ' Adjusted positions including scroll
            Dim endX As Integer = visibleRect.Right + _gridSize
            Dim endY As Integer = visibleRect.Bottom + _gridSize

            ' Draw vertical grid lines
            For x As Integer = startX To endX Step _gridSize
                g.DrawLine(pen, x + Me.AutoScrollPosition.X,
                               visibleRect.Top + Me.AutoScrollPosition.Y,
                               x + Me.AutoScrollPosition.X,
                               visibleRect.Bottom + Me.AutoScrollPosition.Y)
            Next

            ' Draw horizontal grid lines
            For y As Integer = startY To endY Step _gridSize
                g.DrawLine(pen, visibleRect.Left + Me.AutoScrollPosition.X,
                               y + Me.AutoScrollPosition.Y,
                               visibleRect.Right + Me.AutoScrollPosition.X,
                               y + Me.AutoScrollPosition.Y)
            Next
        End Using
    End Sub

    Private Sub DrawResizeHandles(g As Graphics, tile As MapTile)
        Dim handleSize As Integer = 6
        Dim tileRect As New Rectangle(tile.Position.X + Me.AutoScrollPosition.X,
                                      tile.Position.Y + Me.AutoScrollPosition.Y,
                                      tile.Size.Width,
                                      tile.Size.Height)

        Using brush As New SolidBrush(Color.White)
            Using pen As New Pen(Color.Black)
                ' Top-left corner
                g.FillRectangle(brush,
                                 tileRect.Left - handleSize \ 2,
                                 tileRect.Top - handleSize \ 2,
                                 handleSize,
                                 handleSize)
                g.DrawRectangle(pen,
                                 tileRect.Left - handleSize \ 2,
                                 tileRect.Top - handleSize \ 2,
                                 handleSize,
                                 handleSize)

                ' Top-right corner
                g.FillRectangle(brush,
                                 tileRect.Right - handleSize \ 2,
                                 tileRect.Top - handleSize \ 2,
                                 handleSize,
                                 handleSize)
                g.DrawRectangle(pen,
                                 tileRect.Right - handleSize \ 2,
                                 tileRect.Top - handleSize \ 2,
                                 handleSize,
                                 handleSize)

                ' Bottom-left corner
                g.FillRectangle(brush,
                                 tileRect.Left - handleSize \ 2,
                                 tileRect.Bottom - handleSize \ 2,
                                 handleSize,
                                 handleSize)
                g.DrawRectangle(pen,
                                 tileRect.Left - handleSize \ 2,
                                 tileRect.Bottom - handleSize \ 2,
                                 handleSize,
                                 handleSize)

                ' Bottom-right corner
                g.FillRectangle(brush,
                                 tileRect.Right - handleSize \ 2,
                                 tileRect.Bottom - handleSize \ 2,
                                 handleSize,
                                 handleSize)
                g.DrawRectangle(pen,
                                 tileRect.Right - handleSize \ 2,
                                 tileRect.Bottom - handleSize \ 2,
                                 handleSize,
                                 handleSize)
            End Using
        End Using
    End Sub

    Private Sub DrawGroupResizeHandles(g As Graphics)
        Dim handleSize As Integer = 6
        Dim bounds = GetSelectedTilesBounds()
        bounds.Offset(Me.AutoScrollPosition)

        Using brush As New SolidBrush(Color.White)
            Using pen As New Pen(Color.Black)
                ' Top-left corner
                g.FillRectangle(brush,
                                 bounds.Left - handleSize \ 2,
                                 bounds.Top - handleSize \ 2,
                                 handleSize,
                                 handleSize)
                g.DrawRectangle(pen,
                                 bounds.Left - handleSize \ 2,
                                 bounds.Top - handleSize \ 2,
                                 handleSize,
                                 handleSize)

                ' Top-right corner
                g.FillRectangle(brush,
                                 bounds.Right - handleSize \ 2,
                                 bounds.Top - handleSize \ 2,
                                 handleSize,
                                 handleSize)
                g.DrawRectangle(pen,
                                 bounds.Right - handleSize \ 2,
                                 bounds.Top - handleSize \ 2,
                                 handleSize,
                                 handleSize)

                ' Bottom-left corner
                g.FillRectangle(brush,
                                 bounds.Left - handleSize \ 2,
                                 bounds.Bottom - handleSize \ 2,
                                 handleSize,
                                 handleSize)
                g.DrawRectangle(pen,
                                 bounds.Left - handleSize \ 2,
                                 bounds.Bottom - handleSize \ 2,
                                 handleSize,
                                 handleSize)

                ' Bottom-right corner
                g.FillRectangle(brush,
                                 bounds.Right - handleSize \ 2,
                                 bounds.Bottom - handleSize \ 2,
                                 handleSize,
                                 handleSize)
                g.DrawRectangle(pen,
                                 bounds.Right - handleSize \ 2,
                                 bounds.Bottom - handleSize \ 2,
                                 handleSize,
                                 handleSize)
            End Using
        End Using
    End Sub

    ' Handle mouse events for selecting and moving tiles
    Protected Overrides Sub OnMouseDown(e As MouseEventArgs)
        MyBase.OnMouseDown(e)

        Dim currentPoint As Point = New Point(e.Location.X - Me.AutoScrollPosition.X,
                                              e.Location.Y - Me.AutoScrollPosition.Y)
        Dim clickedTile As MapTile = GetTileAtPoint(currentPoint)

        If e.Button = MouseButtons.Left Then
            ' Check if over group resize handles first
            Dim groupDirection As MapTile.ResizeDirections = MapTile.ResizeDirections.None
            If selectedTiles.Count > 1 Then
                groupDirection = GetGroupResizeDirection(currentPoint)
                If groupDirection <> MapTile.ResizeDirections.None Then
                    ' Start group resizing
                    resizing = True
                    ' Record initial mouse position
                    initialMousePosition = currentPoint

                    ' Record initial bounds of selected tiles
                    initialSelectedTilesBounds = GetSelectedTilesBounds()
                    initialSelectedTilesPositionsAndSizes = New List(Of Tuple(Of MapTile, Point, Size))
                    For Each tile In selectedTiles
                        initialSelectedTilesPositionsAndSizes.Add(Tuple.Create(tile, tile.Position, tile.Size))
                    Next

                    ' Set current resize direction
                    For Each tile In selectedTiles
                        tile.CurrentResizeDirection = groupDirection
                    Next

                    Me.Refresh()
                    Return
                End If
            End If

            ' If not over group resize handles, proceed
            If clickedTile IsNot Nothing Then
                ' Check if over resize handles of a single tile
                Dim direction As MapTile.ResizeDirections = GetResizeDirection(clickedTile, currentPoint)
                If direction <> MapTile.ResizeDirections.None Then
                    ' Start resizing single tile
                    DeselectAllTiles()
                    selectedTile = clickedTile
                    selectedTile.Selected = True
                    selectedTiles.Clear()
                    selectedTiles.Add(selectedTile)

                    resizing = True
                    selectedTile.IsResizing = True
                    selectedTile.CurrentResizeDirection = direction

                    ' Initialize resizing variables
                    initialTilePosition = selectedTile.Position
                    initialTileSize = selectedTile.Size
                    initialMousePosition = currentPoint

                    ' Bring the tile to the front
                    BringTileToFront(selectedTile)

                    Me.Refresh()
                ElseIf IsCtrlKeyPressed Then
                    ' CTRL + Click: Duplicate the tile
                    Dim duplicateTile As MapTile = clickedTile.Clone()
                    _tiles.Add(duplicateTile)
                    DeselectAllTiles()
                    selectedTile = duplicateTile
                    selectedTile.Selected = True
                    selectedTiles.Clear()
                    selectedTiles.Add(selectedTile)

                    ' Set initial positions for dragging
                    initialTilePosition = duplicateTile.Position
                    duplicateTile.InitialPosition = duplicateTile.Position
                    initialMousePosition = currentPoint

                    ' Bring the duplicated tile to the front
                    BringTileToFront(selectedTile)

                    dragging = True
                    Me.Refresh()
                Else
                    If Not selectedTiles.Contains(clickedTile) Then
                        ' Single tile selection
                        DeselectAllTiles()
                        selectedTile = clickedTile
                        selectedTile.Selected = True
                        selectedTiles.Clear()
                        selectedTiles.Add(selectedTile)
                    Else
                        selectedTile = clickedTile ' Ensure clickedTile is set as selectedTile
                    End If

                    ' Initialize variables for dragging
                    For Each tile In selectedTiles
                        tile.InitialPosition = tile.Position
                    Next
                    initialMousePosition = currentPoint

                    ' Bring the selected tile to the front
                    BringTileToFront(selectedTile)

                    dragging = True
                    Me.Refresh()
                End If
            Else
                ' No tile clicked - start selection rectangle
                isSelecting = True
                selectionStartPoint = currentPoint
                selectionEndPoint = currentPoint
                DeselectAllTiles()
                Me.Refresh()
            End If
        End If
        ' We have removed context menu handling from OnMouseDown
    End Sub

    Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
        MyBase.OnMouseMove(e)

        Dim currentPoint As Point = New Point(e.Location.X - Me.AutoScrollPosition.X,
                                              e.Location.Y - Me.AutoScrollPosition.Y)

        If resizing Then
            HandleResizing(currentPoint)
        ElseIf dragging AndAlso selectedTiles.Count > 0 Then
            HandleDragging(currentPoint)
        ElseIf isSelecting Then
            ' Update selection rectangle
            selectionEndPoint = currentPoint
            Me.Refresh()
        Else
            ' Update cursor based on hover over resize handles
            UpdateCursor(currentPoint)
        End If
    End Sub

    Private Sub UpdateCursor(currentPoint As Point)
        Dim cursorSet As Boolean = False

        ' Check if over group resize handles
        If selectedTiles.Count > 1 Then
            Dim groupDirection As MapTile.ResizeDirections = GetGroupResizeDirection(currentPoint)
            If groupDirection <> MapTile.ResizeDirections.None Then
                Me.Cursor = GetCursorForDirection(groupDirection)
                cursorSet = True
            End If
        End If

        ' Check if over resize handles of any tile
        If Not cursorSet Then
            For Each tile In _tiles
                Dim direction As MapTile.ResizeDirections = GetResizeDirection(tile, currentPoint)
                If direction <> MapTile.ResizeDirections.None Then
                    Me.Cursor = GetCursorForDirection(direction)
                    cursorSet = True
                    Exit For
                End If
            Next
        End If

        If Not cursorSet Then
            Me.Cursor = Cursors.Default
        End If
    End Sub

    Protected Overrides Sub OnMouseUp(e As MouseEventArgs)
        MyBase.OnMouseUp(e)

        If isSelecting Then
            isSelecting = False
            ' Create the selection rectangle
            selectionRectangle = GetSelectionRectangle()
            ' Select tiles within the rectangle
            SelectTilesInRectangle(selectionRectangle)
            Me.Refresh()
        Else
            dragging = False
            resizing = False
            If selectedTile IsNot Nothing Then
                selectedTile.IsResizing = False
                selectedTile.CurrentResizeDirection = MapTile.ResizeDirections.None
            End If
            For Each tile In selectedTiles
                tile.CurrentResizeDirection = MapTile.ResizeDirections.None
            Next
        End If

        ' Handle right-click to show context menu
        If e.Button = MouseButtons.Right Then
            ' Adjust current point for scrolling
            Dim currentPoint As Point = New Point(e.Location.X - Me.AutoScrollPosition.X,
                                                  e.Location.Y - Me.AutoScrollPosition.Y)
            Dim clickedTile As MapTile = GetTileAtPoint(currentPoint)

            If clickedTile IsNot Nothing Then
                ' Select the tile
                If Not selectedTiles.Contains(clickedTile) Then
                    DeselectAllTiles()
                    selectedTile = clickedTile
                    selectedTile.Selected = True
                    selectedTiles.Clear()
                    selectedTiles.Add(selectedTile)
                End If

                ' Bring the selected tile to the front for visibility (optional)
                BringTileToFront(selectedTile)

                Me.Refresh()
                ' Show context menu
                If ctmMenu IsNot Nothing Then
                    ctmMenu.Show(Me, e.Location)
                End If
            ElseIf selectedTiles.Count > 0 Then
                ' Right-click on empty space but with tiles selected
                Me.Refresh()
                ' Show context menu
                If ctmMenu IsNot Nothing Then
                    ctmMenu.Show(Me, e.Location)
                End If
            End If
        End If
    End Sub

    ' Helper method to get the tile at a specific point
    Private Function GetTileAtPoint(point As Point) As MapTile
        For i As Integer = _tiles.Count - 1 To 0 Step -1 ' Start from topmost tile
            Dim tile = _tiles(i)
            Dim rect As New Rectangle(tile.Position, tile.Size)
            If rect.Contains(point) Then
                Return tile
            End If
        Next
        Return Nothing
    End Function

    ' Enable dropping
    Protected Overrides Sub OnCreateControl()
        MyBase.OnCreateControl()
        Me.AllowDrop = True
    End Sub

    Protected Overrides Sub OnDragEnter(drgevent As DragEventArgs)
        MyBase.OnDragEnter(drgevent)
        If drgevent.Data.GetDataPresent("ImageFormat") Then
            drgevent.Effect = DragDropEffects.Copy
        Else
            drgevent.Effect = DragDropEffects.None
        End If
    End Sub

    Protected Overrides Sub OnDragDrop(drgevent As DragEventArgs)
        MyBase.OnDragDrop(drgevent)
        If drgevent.Data.GetDataPresent("ImageFormat") Then
            Dim image As Image = CType(drgevent.Data.GetData("ImageFormat"), Image)
            Dim imageFilePath As String = CType(drgevent.Data.GetData("ImageFilePath"), String)

            ' Original image size
            Dim originalSize As Size = image.Size

            ' Calculate the new size by snapping to the grid
            Dim newWidth As Integer = (CInt(originalSize.Width / _gridSize)) * _gridSize
            Dim newHeight As Integer = (CInt(originalSize.Height / _gridSize)) * _gridSize

            ' Ensure minimum size of one grid unit
            If newWidth < _gridSize Then newWidth = _gridSize
            If newHeight < _gridSize Then newHeight = _gridSize

            ' Create a resized image
            Dim resizedImage As New Bitmap(image, New Size(newWidth, newHeight))

            ' Get the drop coordinates and adjust for scroll position
            Dim point As Point = Me.PointToClient(New Point(drgevent.X, drgevent.Y))
            point.X = point.X - Me.AutoScrollPosition.X
            point.Y = point.Y - Me.AutoScrollPosition.Y

            ' Apply snapping to position if snapping is enabled and Shift is NOT pressed
            If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                point.X = (CInt(point.X / _gridSize)) * _gridSize
                point.Y = (CInt(point.Y / _gridSize)) * _gridSize
            End If

            ' Create a new tile and add it to the canvas
            Dim newTile As New MapTile With {
                .Image = resizedImage,
                .ImageFilePath = imageFilePath,
                .Position = point,
                .Size = resizedImage.Size
            }
            Me.AddTile(newTile)
        End If
    End Sub

    ' Handle dragging
    Private Sub HandleDragging(currentPoint As Point)
        Dim deltaX As Integer = currentPoint.X - initialMousePosition.X
        Dim deltaY As Integer = currentPoint.Y - initialMousePosition.Y

        ' Move all selected tiles
        For Each tile In selectedTiles
            Dim initialPos As Point = tile.InitialPosition
            Dim newPos As Point = New Point(initialPos.X + deltaX, initialPos.Y + deltaY)

            ' Apply snapping if enabled and Shift is NOT pressed
            If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                newPos.X = (CInt(newPos.X / _gridSize)) * _gridSize
                newPos.Y = (CInt(newPos.Y / _gridSize)) * _gridSize
            End If

            tile.Position = newPos
        Next

        Me.Refresh()
        RaiseEvent CanvasModified()
    End Sub

    ' Handle resizing
    Private Sub HandleResizing(currentPoint As Point)
        If selectedTiles.Count = 1 Then
            ' Single tile resizing
            Dim deltaX As Integer = currentPoint.X - initialMousePosition.X
            Dim deltaY As Integer = currentPoint.Y - initialMousePosition.Y

            Dim newSize As Size = initialTileSize
            Dim newPosition As Point = initialTilePosition

            ' Adjust size and position based on resize direction
            If selectedTile.CurrentResizeDirection.HasFlag(MapTile.ResizeDirections.Right) Then
                newSize.Width = initialTileSize.Width + deltaX
            End If
            If selectedTile.CurrentResizeDirection.HasFlag(MapTile.ResizeDirections.Bottom) Then
                newSize.Height = initialTileSize.Height + deltaY
            End If
            If selectedTile.CurrentResizeDirection.HasFlag(MapTile.ResizeDirections.Left) Then
                newSize.Width = initialTileSize.Width - deltaX
                newPosition.X = initialTilePosition.X + deltaX
            End If
            If selectedTile.CurrentResizeDirection.HasFlag(MapTile.ResizeDirections.Top) Then
                newSize.Height = initialTileSize.Height - deltaY
                newPosition.Y = initialTilePosition.Y + deltaY
            End If

            ' Ensure minimum size
            If newSize.Width <= 1 Then newSize.Width = 1
            If newSize.Height <= 1 Then newSize.Height = 1

            ' Apply snapping if enabled and Shift is NOT pressed
            If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                newSize.Width = (CInt(newSize.Width / _gridSize)) * _gridSize
                newSize.Height = (CInt(newSize.Height / _gridSize)) * _gridSize
                newPosition.X = (CInt(newPosition.X / _gridSize)) * _gridSize
                newPosition.Y = (CInt(newPosition.Y / _gridSize)) * _gridSize
            End If

            selectedTile.Size = newSize
            selectedTile.Position = newPosition
            Me.Refresh()
            RaiseEvent CanvasModified()
        Else
            ' Group resizing
            Dim deltaX As Integer = currentPoint.X - initialMousePosition.X
            Dim deltaY As Integer = currentPoint.Y - initialMousePosition.Y

            Dim newBounds As Rectangle = initialSelectedTilesBounds

            ' Adjust bounds based on resize direction
            Dim resizeDirection = selectedTiles(0).CurrentResizeDirection

            If resizeDirection.HasFlag(MapTile.ResizeDirections.Right) Then
                newBounds.Width = initialSelectedTilesBounds.Width + deltaX
            End If
            If resizeDirection.HasFlag(MapTile.ResizeDirections.Bottom) Then
                newBounds.Height = initialSelectedTilesBounds.Height + deltaY
            End If
            If resizeDirection.HasFlag(MapTile.ResizeDirections.Left) Then
                newBounds.Width = initialSelectedTilesBounds.Width - deltaX
                newBounds.X = initialSelectedTilesBounds.X + deltaX
            End If
            If resizeDirection.HasFlag(MapTile.ResizeDirections.Top) Then
                newBounds.Height = initialSelectedTilesBounds.Height - deltaY
                newBounds.Y = initialSelectedTilesBounds.Y + deltaY
            End If

            ' Ensure minimum size
            If newBounds.Width <= 1 Then newBounds.Width = 1
            If newBounds.Height <= 1 Then newBounds.Height = 1

            ' Calculate scale factors
            Dim scaleX As Double = newBounds.Width / initialSelectedTilesBounds.Width
            Dim scaleY As Double = newBounds.Height / initialSelectedTilesBounds.Height

            ' Resize and reposition each tile
            For Each item In initialSelectedTilesPositionsAndSizes
                Dim tile = item.Item1
                Dim initialPos = item.Item2
                Dim initialSize = item.Item3

                ' Calculate relative position within the group
                Dim relX As Double = (initialPos.X - initialSelectedTilesBounds.X) / initialSelectedTilesBounds.Width
                Dim relY As Double = (initialPos.Y - initialSelectedTilesBounds.Y) / initialSelectedTilesBounds.Height

                ' New position
                Dim newPosX As Double = newBounds.X + relX * newBounds.Width
                Dim newPosY As Double = newBounds.Y + relY * newBounds.Height

                ' New size
                Dim newWidth As Double = initialSize.Width * scaleX
                Dim newHeight As Double = initialSize.Height * scaleY

                ' Ensure minimum size
                If newWidth <= 1 Then newWidth = 1
                If newHeight <= 1 Then newHeight = 1

                ' Apply snapping to individual tiles if needed
                If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                    newPosX = (CInt(newPosX / _gridSize)) * _gridSize
                    newPosY = (CInt(newPosY / _gridSize)) * _gridSize
                    newWidth = (CInt(newWidth / _gridSize)) * _gridSize
                    newHeight = (CInt(newHeight / _gridSize)) * _gridSize
                End If

                tile.Position = New Point(CInt(newPosX), CInt(newPosY))
                tile.Size = New Size(CInt(newWidth), CInt(newHeight))
            Next

            Me.Refresh()
            RaiseEvent CanvasModified()
        End If
    End Sub

    ' Determine if mouse is over resize handles of a tile
    Private Function GetResizeDirection(tile As MapTile, point As Point) As MapTile.ResizeDirections
        Const handleSize As Integer = 10 ' Size of the resize handle area
        Dim rect = New Rectangle(tile.Position, tile.Size)
        Dim direction As MapTile.ResizeDirections = MapTile.ResizeDirections.None

        ' Check corners
        If New Rectangle(rect.Right - handleSize, rect.Bottom - handleSize, handleSize, handleSize).Contains(point) Then
            direction = MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Bottom ' Bottom-right corner
        ElseIf New Rectangle(rect.Left, rect.Bottom - handleSize, handleSize, handleSize).Contains(point) Then
            direction = MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Bottom ' Bottom-left corner
        ElseIf New Rectangle(rect.Right - handleSize, rect.Top, handleSize, handleSize).Contains(point) Then
            direction = MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Top ' Top-right corner
        ElseIf New Rectangle(rect.Left, rect.Top, handleSize, handleSize).Contains(point) Then
            direction = MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Top ' Top-left corner
        End If

        Return direction
    End Function

    ' Determine if mouse is over group resize handles
    Private Function GetGroupResizeDirection(point As Point) As MapTile.ResizeDirections
        If selectedTiles.Count <= 1 Then Return MapTile.ResizeDirections.None

        Const handleSize As Integer = 10 ' Size of the resize handle area
        Dim bounds = GetSelectedTilesBounds()
        Dim direction As MapTile.ResizeDirections = MapTile.ResizeDirections.None

        ' Check corners
        If New Rectangle(bounds.Right - handleSize, bounds.Bottom - handleSize, handleSize, handleSize).Contains(point) Then
            direction = MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Bottom ' Bottom-right corner
        ElseIf New Rectangle(bounds.Left - handleSize \ 2, bounds.Bottom - handleSize, handleSize, handleSize).Contains(point) Then
            direction = MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Bottom ' Bottom-left corner
        ElseIf New Rectangle(bounds.Right - handleSize, bounds.Top - handleSize \ 2, handleSize, handleSize).Contains(point) Then
            direction = MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Top ' Top-right corner
        ElseIf New Rectangle(bounds.Left - handleSize \ 2, bounds.Top - handleSize \ 2, handleSize, handleSize).Contains(point) Then
            direction = MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Top ' Top-left corner
        End If

        Return direction
    End Function

    ' Get the bounding rectangle of selected tiles
    Private Function GetSelectedTilesBounds() As Rectangle
        If selectedTiles.Count = 0 Then Return Rectangle.Empty

        Dim minX As Integer = Integer.MaxValue
        Dim minY As Integer = Integer.MaxValue
        Dim maxX As Integer = Integer.MinValue
        Dim maxY As Integer = Integer.MinValue

        For Each tile In selectedTiles
            minX = Math.Min(minX, tile.Position.X)
            minY = Math.Min(minY, tile.Position.Y)
            maxX = Math.Max(maxX, tile.Position.X + tile.Size.Width)
            maxY = Math.Max(maxY, tile.Position.Y + tile.Size.Height)
        Next

        Return New Rectangle(minX, minY, maxX - minX, maxY - minY)
    End Function

    ' Get cursor based on resize direction
    Private Function GetCursorForDirection(direction As MapTile.ResizeDirections) As Cursor
        Select Case direction
            Case MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Top,
                 MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Bottom
                Return Cursors.SizeNWSE
            Case MapTile.ResizeDirections.Right Or MapTile.ResizeDirections.Top,
                 MapTile.ResizeDirections.Left Or MapTile.ResizeDirections.Bottom
                Return Cursors.SizeNESW
            Case MapTile.ResizeDirections.Left, MapTile.ResizeDirections.Right
                Return Cursors.SizeWE
            Case MapTile.ResizeDirections.Top, MapTile.ResizeDirections.Bottom
                Return Cursors.SizeNS
            Case Else
                Return Cursors.Default
        End Select
    End Function

    ' Helper methods for selection rectangle
    Private Function GetSelectionRectangle() As Rectangle
        Dim x As Integer = Math.Min(selectionStartPoint.X, selectionEndPoint.X)
        Dim y As Integer = Math.Min(selectionStartPoint.Y, selectionEndPoint.Y)
        Dim width As Integer = Math.Abs(selectionStartPoint.X - selectionEndPoint.X)
        Dim height As Integer = Math.Abs(selectionStartPoint.Y - selectionEndPoint.Y)
        Return New Rectangle(x, y, width, height)
    End Function

    Private Sub SelectTilesInRectangle(rect As Rectangle)
        selectedTiles.Clear()
        For Each tile In _tiles
            If rect.IntersectsWith(New Rectangle(tile.Position, tile.Size)) Then
                tile.Selected = True
                selectedTiles.Add(tile)
            Else
                tile.Selected = False
            End If
        Next
    End Sub

    Private Sub DeselectAllTiles()
        For Each tile In _tiles
            tile.Selected = False
            tile.CurrentResizeDirection = MapTile.ResizeDirections.None
        Next
        selectedTiles.Clear()
    End Sub

    ' Method to set the map size
    Public Sub SetMapSize(width As Integer, height As Integer)
        Me.AutoScrollMinSize = New Size(width, height)
        Me.Invalidate()
    End Sub

    ' Method to snap all tiles to grid
    Public Sub SnapAllTilesToGrid()
        For Each tile In _tiles
            tile.Position = New Point((CInt(tile.Position.X / _gridSize)) * _gridSize,
                                      (CInt(tile.Position.Y / _gridSize)) * _gridSize)
            tile.Size = New Size((CInt(tile.Size.Width / _gridSize)) * _gridSize,
                                 (CInt(tile.Size.Height / _gridSize)) * _gridSize)
        Next
        Me.Invalidate()
    End Sub

    ' ReadOnly properties for key detection
    Private ReadOnly Property IsShiftKeyPressed As Boolean
        Get
            Return (Control.ModifierKeys And Keys.Shift) = Keys.Shift
        End Get
    End Property

    Private ReadOnly Property IsCtrlKeyPressed As Boolean
        Get
            Return (Control.ModifierKeys And Keys.Control) = Keys.Control
        End Get
    End Property

    ' Event handler for Send to Back menu item
    Private Sub mnuSendToBack_Click(sender As Object, e As EventArgs) Handles mnuSendToBack.Click
        If selectedTiles.Count > 0 Then
            For Each tile In selectedTiles
                SendTileToBack(tile)
            Next
            Me.Refresh()
        End If
    End Sub

    ' Event handler for Delete menu item
    Private Sub mnuDelete_Click(sender As Object, e As EventArgs) Handles mnuDelete.Click
        If selectedTiles.Count > 0 Then
            DeleteSelectedTiles()
        End If
    End Sub

    ' Event handler for Rotate 90° menu item
    Private Sub mnuRotate90_Click(sender As Object, e As EventArgs) Handles mnuRotate90.Click
        If selectedTiles.Count > 0 Then
            RotateSelectedTiles(90)
            Me.Refresh()
            RaiseEvent CanvasModified()
        End If
    End Sub

    ' Rotate selected tiles by specified angle
    Private Sub RotateSelectedTiles(angle As Single)
        For Each tile In selectedTiles
            ' Rotate the image
            Dim rotatedImage As Image = RotateImage(tile.Image, angle)

            ' Adjust the size
            Dim newSize As Size = rotatedImage.Size

            ' Calculate new position to maintain the center point
            Dim oldCenter As Point = New Point(tile.Position.X + tile.Size.Width / 2, tile.Position.Y + tile.Size.Height / 2)
            Dim newPosition As Point = New Point(oldCenter.X - newSize.Width / 2, oldCenter.Y - newSize.Height / 2)

            ' Apply snapping if necessary
            If snappingEnabled AndAlso Not IsShiftKeyPressed Then
                newPosition.X = (CInt(newPosition.X / _gridSize)) * _gridSize
                newPosition.Y = (CInt(newPosition.Y / _gridSize)) * _gridSize
            End If

            ' Update the tile's image, size, and position
            tile.Image = rotatedImage
            tile.Size = newSize
            tile.Position = newPosition
        Next
    End Sub

    ' Function to rotate an image by a specified angle
    Private Function RotateImage(img As Image, angle As Single) As Image
        If img Is Nothing Then
            Throw New ArgumentNullException(NameOf(img))
        End If

        ' Create a new empty bitmap to hold rotated image
        Dim rotatedBmp As New Bitmap(img.Height, img.Width)
        rotatedBmp.SetResolution(img.HorizontalResolution, img.VerticalResolution)

        ' Make a graphics object from the empty bitmap
        Using g As Graphics = Graphics.FromImage(rotatedBmp)
            ' Move rotation point to center of image
            g.TranslateTransform(rotatedBmp.Width / 2, rotatedBmp.Height / 2)
            ' Rotate
            g.RotateTransform(angle)
            ' Move image back
            g.TranslateTransform(-img.Width / 2, -img.Height / 2)
            ' Draw passed in image onto graphics object
            g.DrawImage(img, New Point(0, 0))
        End Using

        Return rotatedBmp
    End Function
End Class

Imports System.Xml.Serialization

<Serializable>
Public Class MapTile
    ' Properties
    <XmlIgnore>
    Public Property Image As Image
    Public Property ImageFilePath As String
    Public Property Position As Point
    Public Property Size As Size
    <XmlIgnore>
    Public Property Selected As Boolean = False

    ' Property for group movement
    <XmlIgnore>
    Public Property InitialPosition As Point

    ' Resizing properties
    <XmlIgnore>
    Public Property IsResizing As Boolean = False
    <XmlIgnore>
    Public Property CurrentResizeDirection As ResizeDirections = ResizeDirections.None

    ' Resize directions enum
    <Flags>
    Public Enum ResizeDirections
        None = 0
        Left = 1
        Right = 2
        Top = 4
        Bottom = 8
    End Enum

    ' Clone method
    Public Function Clone() As MapTile
        Return New MapTile With {
            .Image = New Bitmap(Me.Image),
            .ImageFilePath = Me.ImageFilePath,
            .Position = Me.Position,
            .Size = Me.Size,
            .Selected = Me.Selected
        }
    End Function
End Class


Can you make it so Rotating the middle mouse wheel zooms in and out on the Canvas? Mouse Wheel Up = Zoom In. Mouse Wheel Down =  Zoom out