﻿Imports System.Drawing.Drawing2D
Imports System.ComponentModel
Imports System.Drawing.Imaging
Imports System.Drawing.Design

<DefaultEvent("TextChanged")> _
Public Class ImageTextBox
    Inherits Control

    '//constants
    Private Const CornerRadius As Integer = 24

    '//events
    Public Shadows Event TextChanged As EventHandler

    '//fields
    Private components As IContainer
    Private WithEvents textBox As OverlayTextBox

    '//properties
    'Protected ReadOnly Property Image As Image
    '    Get
    '        If Me.ImageList IsNot Nothing Then
    '            If Me.ImageKey IsNot Nothing Then
    '                Return Me.ImageList.Images(Me.ImageKey)
    '            End If
    '        End If
    '        Return Nothing
    '    End Get
    'End Property

    Private _overlayText As String
    <DefaultValue(CStr(Nothing))> _
    Public Property OverlayText As String
        Get
            Return Me._overlayText
        End Get
        Set(ByVal value As String)
            If Not Object.Equals(Me._overlayText, value) Then
                Me._overlayText = value

                If Me.textBox IsNot Nothing Then
                    Me.textBox.OverlayText = value
                End If
            End If
        End Set
    End Property

    'Private _imageKey As String
    '<DefaultValue(CStr(Nothing))> _
    '<TypeConverter(GetType(ImageKeyConverter))> _
    '<Localizable(True)> _
    '<Editor("System.Windows.Forms.Design.ImageIndexEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", GetType(UITypeEditor))> _
    '<RefreshProperties(RefreshProperties.Repaint)> _
    'Public Property ImageKey As String
    '    Get
    '        Return Me._imageKey
    '    End Get
    '    Set(ByVal value As String)
    '        If Not Object.Equals(Me._imageKey, value) Then
    '            Me._imageKey = value
    '            MyBase.Invalidate()
    '        End If
    '        Me.PerformContentLayout()
    '    End Set
    'End Property

    Private _Image As System.Drawing.Image
    <Localizable(True)> _
    <DefaultValue(CStr(Nothing))> _
    <RefreshProperties(RefreshProperties.Repaint)> _
    Public Property Image() As System.Drawing.Image
        Get
            Return _Image
        End Get
        Set(ByVal value As System.Drawing.Image)
            _Image = value
            Me.PerformContentLayout()
        End Set
    End Property

    Public Overrides Property Text As String
        Get
            If Me.textBox Is Nothing Then
                Return String.Empty
            End If
            Return Me.textBox.Text
        End Get
        Set(ByVal value As String)
            If Me.textBox IsNot Nothing Then
                Me.textBox.Text = value
            End If
        End Set
    End Property

    'Private _imageList As System.Windows.Forms.ImageList = Nothing
    '<DefaultValue(GetType(ImageList), Nothing)> _
    'Public Property ImageList() As System.Windows.Forms.ImageList
    '    Get
    '        Return Me._imageList
    '    End Get
    '    Set(ByVal value As System.Windows.Forms.ImageList)
    '        Me._imageList = value
    '        Me.PerformContentLayout()
    '    End Set
    'End Property

    Public Overrides Property MinimumSize As System.Drawing.Size
        Get
            Return New Size(0, ImageTextBox.CornerRadius)
        End Get
        Set(ByVal value As System.Drawing.Size)
            MyBase.MinimumSize = value
        End Set
    End Property

    Public Overrides Property Font() As System.Drawing.Font
        Get
            Return If(Me.textBox IsNot Nothing, Me.textBox.Font, New Font("Microsoft Sans Serif", 8.25!, FontStyle.Regular))
        End Get
        Set(ByVal value As System.Drawing.Font)
            If Me.textBox IsNot Nothing Then Me.textBox.Font = value
        End Set
    End Property

    Public Overrides Property ForeColor() As System.Drawing.Color
        Get
            Return If(Me.textBox IsNot Nothing, Me.textBox.ForeColor, SystemColors.WindowText)
        End Get
        Set(ByVal value As System.Drawing.Color)
            If Me.textBox IsNot Nothing Then Me.textBox.ForeColor = value
        End Set
    End Property

    Public Overrides Property BackColor() As System.Drawing.Color
        Get
            Return If(Me.textBox IsNot Nothing, Me.textBox.BackColor, SystemColors.Window)
        End Get
        Set(ByVal value As System.Drawing.Color)
            If Me.textBox IsNot Nothing Then Me.textBox.BackColor = value
        End Set
    End Property

    Protected Overrides ReadOnly Property DefaultSize As System.Drawing.Size
        Get
            Return New Size(100, ImageTextBox.CornerRadius)
        End Get
    End Property

    '//constructors
    Public Sub New()
        Me.components = New Container()

        MyBase.SetStyle(ControlStyles.ResizeRedraw Or ControlStyles.AllPaintingInWmPaint Or _
                        ControlStyles.UserPaint Or ControlStyles.OptimizedDoubleBuffer Or _
                        ControlStyles.SupportsTransparentBackColor, True)
    End Sub

    '//methods
    Public Sub PerformContentLayout()
        MyBase.Invalidate()

        Me.LayoutTextBox()
    End Sub

    Private Sub LayoutTextBox()
        If Me.textBox IsNot Nothing Then
            With textBox
                .AutoSize = False
                .Anchor = AnchorStyles.Top Or AnchorStyles.Bottom Or _
                              AnchorStyles.Left Or AnchorStyles.Right
                .BorderStyle = BorderStyle.None

                Dim bounds = Me.GetTextRectangle()

                '//some adjustments to line up the actual 
                '//text of the text box better
                bounds.Offset(0, 2)

                .Bounds = bounds
            End With
        End If
    End Sub

    Private Function GetContentRectangle() As Rectangle
        Return New Rectangle(ImageTextBox.CornerRadius \ 2, 2, _
                             Me.Width - (ImageTextBox.CornerRadius \ 2) * 2, Me.Height - 4)
    End Function

    Private Function GetTextRectangle() As Rectangle
        Dim imageRect = Me.GetImageRectangle()
        Dim content = Me.GetContentRectangle()
        Dim textRect = imageRect

        textRect.Height = 16

        If Me.Image IsNot Nothing Then
            textRect.X = imageRect.Right
            textRect.Width = content.Width - imageRect.Width + (imageRect.Width \ 2)
        Else
            textRect = DrawingUtils.AlignRectangle(ContentAlignment.MiddleLeft, textRect, content)
        End If

        Return textRect
    End Function

    Private Function GetImageRectangle() As Rectangle
        Dim content = Me.GetContentRectangle()
        If Me.Image IsNot Nothing Then
            Dim imageRect = DrawingUtils.AlignRectangle(ContentAlignment.MiddleLeft, _
                                                        New Rectangle(0, 0, 16, 16), content)

            '//image rectangle is offset in the middle of the arc
            imageRect.X -= (imageRect.Width \ 2)

            Return imageRect
        End If
        Return content
    End Function

    Protected Overridable Function InitializeTextBox() As OverlayTextBox
        If Me.textBox Is Nothing Then
            Me.textBox = New OverlayTextBox(Me)
            Me.textBox.OverlayText = Me.OverlayText
            Me.components.Add(Me.textBox)

            Me.LayoutTextBox()
        End If
        Return Me.textBox
    End Function

    Protected Overrides Sub InitLayout()
        MyBase.InitLayout()
        MyBase.Controls.Add(Me.InitializeTextBox())
    End Sub

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        Try
            If disposing Then
                If _Image IsNot Nothing Then _Image.Dispose()
                If Me.components IsNot Nothing Then
                    Me.components.Dispose()
                End If
                Me.components = Nothing
            End If
            _Image = Nothing
        Finally
            MyBase.Dispose(disposing)
        End Try
    End Sub

    Protected Overrides Sub OnSizeChanged(ByVal e As System.EventArgs)
        MyBase.OnSizeChanged(e)

        Me.LayoutTextBox()
    End Sub

    Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
        RaiseEvent TextChanged(Me, e)
    End Sub

    Protected Overridable Overloads Sub OnTextChanged(ByVal sender As Object, ByVal e As EventArgs) Handles textBox.TextChanged
        Me.OnTextChanged(EventArgs.Empty)
    End Sub

    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)

        'e.Graphics.DrawRectangle(Pens.Black, Me.GetContentRectangle())
        'e.Graphics.DrawRectangle(Pens.Black, Me.GetImageRectangle())
        'e.Graphics.DrawRectangle(Pens.Black, Me.GetTextRectangle())

        If Me.Image IsNot Nothing Then
            e.Graphics.DrawImage(Me.Image, Me.GetImageRectangle(), 0, 0, 16, 16, GraphicsUnit.Pixel)
        End If
    End Sub

    Protected Overrides Sub OnPaintBackground(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaintBackground(e)

        e.Graphics.SmoothingMode = SmoothingMode.HighQuality

        Dim client = MyBase.ClientRectangle
        Dim container = e.Graphics.BeginContainer()
        Dim translateRect = MyBase.Bounds

        e.Graphics.TranslateTransform(-MyBase.Left, -MyBase.Top)

        Using pe = New PaintEventArgs(e.Graphics, translateRect)
            MyBase.InvokePaintBackground(MyBase.Parent, pe)
            MyBase.InvokePaint(MyBase.Parent, pe)

            e.Graphics.ResetTransform()
            e.Graphics.EndContainer(container)
        End Using

        Using path = DrawingUtils.RoundRectangle(client, ImageTextBox.CornerRadius)
            e.Graphics.FillPath(SystemBrushes.Window, path)
            e.Graphics.DrawPath(SystemPens.ControlDark, path)
        End Using

    End Sub

    '//nested types
    Protected Class OverlayTextBox
        Inherits TextBox

        '//fields
        Private owner As ImageTextBox

        '//properties
        Private _overlayText As String
        Public Property OverlayText As String
            Get
                Return Me._overlayText
            End Get
            Set(ByVal value As String)
                Me._overlayText = value
                MyBase.Invalidate()
            End Set
        End Property

        '//constructors
        Public Sub New(ByVal owner As ImageTextBox)
            MyBase.New()

            MyBase.Font = owner.Font
            MyBase.SetStyle(ControlStyles.UserPaint Or _
                            ControlStyles.OptimizedDoubleBuffer, True)
            MyBase.Invalidate()

            Me.owner = owner
        End Sub

        '//methods
        Protected Overrides Sub OnMouseLeave(ByVal e As System.EventArgs)
            MyBase.OnMouseLeave(e)
            MyBase.Invalidate()
        End Sub

        Protected Overrides Sub OnMouseEnter(ByVal e As System.EventArgs)
            MyBase.OnMouseEnter(e)
            MyBase.Invalidate()
        End Sub

        Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
            MyBase.OnTextChanged(e)

            MyBase.SetStyle(ControlStyles.UserPaint, Not MyBase.TextLength > 0)
            MyBase.Font = Me.owner.Font
            MyBase.Invalidate()
        End Sub

        Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
            MyBase.OnPaint(e)

            If Not String.IsNullOrEmpty(Me.OverlayText) Then
                e.Graphics.DrawString(Me.OverlayText, Me.owner.Font, _
                                      SystemBrushes.ControlDark, MyBase.ClientRectangle)
            End If
        End Sub

    End Class

End Class

Friend Class DrawingUtils

    '//properties
    Private Shared _disabledImageAttributes As ImageAttributes
    Public Shared ReadOnly Property DisabledImageAttributes As ImageAttributes
        Get

            If DrawingUtils._disabledImageAttributes Is Nothing Then
                DrawingUtils._disabledImageAttributes = New ImageAttributes()

                Dim matrix()() As Single = New Single(4)() {}

                matrix(0) = New Single() {0.2125!, 0.2125!, 0.2125!, 0.0!, 0.0!}
                matrix(1) = New Single() {0.2577!, 0.2577!, 0.2577!, 0.0!, 0.0!}
                matrix(2) = New Single() {0.0361!, 0.0361!, 0.0361!, 0.0!, 0.0!}
                matrix(3) = New Single() {0.0!, 0.0!, 0.0!, 1.0!, 0.0!}
                matrix(4) = New Single() {0.38!, 0.38!, 0.38!, 0.0!, 1.0!}

                Dim colorMatrix As New ColorMatrix(matrix)

                DrawingUtils._disabledImageAttributes.ClearColorKey()
                DrawingUtils._disabledImageAttributes.SetColorMatrix(colorMatrix)
            End If

            Return DrawingUtils._disabledImageAttributes
        End Get
    End Property

    '//methods
    Public Shared Function AlignRectangle(ByVal alignment As ContentAlignment, _
                                          ByVal contentRect As Rectangle, _
                                          ByVal layoutRect As Rectangle) As Rectangle

        Select Case alignment
            Case ContentAlignment.BottomCenter
                Return New Rectangle(layoutRect.X + ((layoutRect.Width - contentRect.Width) \ 2), layoutRect.Bottom - contentRect.Height, contentRect.Width, contentRect.Height)
            Case ContentAlignment.BottomLeft
                Return New Rectangle(layoutRect.Left, layoutRect.Bottom - contentRect.Height, contentRect.Width, contentRect.Height)
            Case ContentAlignment.BottomRight
                Return New Rectangle(layoutRect.Right - contentRect.Width, layoutRect.Bottom - contentRect.Height, contentRect.Width, contentRect.Height)
            Case ContentAlignment.MiddleCenter
                Return New Rectangle(layoutRect.X + ((layoutRect.Width - contentRect.Width) \ 2), layoutRect.Y + ((layoutRect.Height - contentRect.Height) \ 2), contentRect.Width, contentRect.Height)
            Case ContentAlignment.MiddleLeft
                Return New Rectangle(layoutRect.Left, layoutRect.Y + ((layoutRect.Height - contentRect.Height) \ 2), contentRect.Width, contentRect.Height)
            Case ContentAlignment.MiddleRight
                Return New Rectangle(layoutRect.Right - contentRect.Width, layoutRect.Y + ((layoutRect.Height - contentRect.Height) \ 2), contentRect.Width, contentRect.Height)
            Case ContentAlignment.TopCenter
                Return New Rectangle(layoutRect.X + ((layoutRect.Width - contentRect.Width) \ 2), layoutRect.Y, contentRect.Width, contentRect.Height)
            Case ContentAlignment.TopLeft
                Return New Rectangle(layoutRect.X, layoutRect.Y, contentRect.Width, contentRect.Height)
            Case ContentAlignment.TopRight
                Return New Rectangle(layoutRect.Right - contentRect.Width, layoutRect.Y, contentRect.Width, contentRect.Height)
            Case Else
                Return Rectangle.Empty
        End Select

    End Function

    Public Shared Function RoundRectangle(ByVal r As Rectangle, _
                                          ByVal radius As Integer) As GraphicsPath

        'make sure the Path fits inside the rectangle
        r.Inflate(-1, -1)

        'scale the radius if it's too large to fit.
        If (radius > (r.Width)) Then
            radius = r.Width
        End If
        If (radius > (r.Height)) Then
            radius = r.Height
        End If

        Dim path = New GraphicsPath()

        If radius <= 0 Then
            path.AddRectangle(r)
        Else
            path.AddArc(r.Left, r.Top, radius, radius, 180, 90)
            path.AddArc(r.Right - radius, r.Top, radius, radius, 270, 90)
            path.AddArc(r.Right - radius, r.Bottom - radius, radius, radius, 0, 90)
            path.AddArc(r.Left, r.Bottom - radius, radius, radius, 90, 90)
        End If

        path.CloseFigure()

        Return path
    End Function

End Class