'//----------------------------------------------------------------------
'//【テキストを回転表示させるユーザーコントロール】- RotateText
'//
'//
'//                                                       by Masato Hirai         
'//----------------------------------------------------------------------
Imports System
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Text
Imports System.Windows.Forms
Imports System.ComponentModel

Public Class TextSpin : Inherits UserControl

#Region " Windows フォーム デザイナで生成されたコード "

  Public Sub New()
    MyBase.New()
    InitializeComponent()
    lastFont = Me.Font
    textBrush = New SolidBrush(Me.ForeColor)
    Me.SetStyle(ControlStyles.ResizeRedraw _
             Or ControlStyles.AllPaintingInWmPaint _
             Or ControlStyles.UserPaint, True)
    Me.UpdateStyles()
    fmt = New StringFormat
    fmt.Alignment = StringAlignment.Center
    fmt.LineAlignment = StringAlignment.Center
  End Sub

    'UserControl はコンポーネント一覧を消去するために dispose をオーバーライドします。
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
      If disposing Then
          If Not (components Is Nothing) Then
              components.Dispose()
          End If
      End If
      MyBase.Dispose(disposing)
    End Sub

    ' Windows フォーム デザイナで必要です。
    Private components As System.ComponentModel.IContainer

    ' メモ : 以下のプロシージャは、Windows フォーム デザイナで必要です。
    'Windows フォーム デザイナを使って変更してください。  
    ' コード エディタを使って変更しないでください。
  Private WithEvents Timer1 As System.Timers.Timer
  Private WithEvents Timer2 As System.Timers.Timer
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Me.Timer1 = New System.Timers.Timer
Me.Timer2 = New System.Timers.Timer
CType(Me.Timer1, System.ComponentModel.ISupportInitialize).BeginInit()
CType(Me.Timer2, System.ComponentModel.ISupportInitialize).BeginInit()
'
'Timer1
'
Me.Timer1.Interval = 25
Me.Timer1.SynchronizingObject = Me
'
'Timer2
'
Me.Timer2.Interval = 1000
Me.Timer2.SynchronizingObject = Me
'
'TextSpin
'
Me.Name = "TextSpin"
CType(Me.Timer1, System.ComponentModel.ISupportInitialize).EndInit()
CType(Me.Timer2, System.ComponentModel.ISupportInitialize).EndInit()

    End Sub

#End Region

  Private textBrush As Brush
  Private fps As String = "0 fps"
  Private frames As Integer = 0
  Private txtOpacity As Integer = 255
  Private pendingPaint As Boolean = False
  Private fpsRect As Rectangle
  Private fpsBrush As Brush
  Private img As Image
  Private Shared fmt As StringFormat
  Private textSize As SizeF
  Private rotate As Integer = 0
  Private lastText As String
  Private lastFont As Font
  Private lastBounds As Rectangle
  Private nextRegion As Region
  Private lastRegion As Region
  Private invalidRegion As Region
  Private _RotateString As String = "Hello World"

  '//
  '// OnPaint イベント
  '//
  Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
    MyBase.OnPaint(e)
    If Not img Is Nothing Then
      e.Graphics.DrawImage(img, Me.ClientRectangle)
    End If
    ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle, Border3DStyle.Sunken)
    DrawEffect(e.Graphics, ClientRectangle, _RotateString, Me.textBrush, Me.Font, False)
    DrawFps(e.Graphics)
    frames += 1
    pendingPaint = False
  End Sub
  '//
  '// 表示テキストのプロパティ
  '//
  <DefaultValue("Hello World!")> _
  Public Overloads Property RotateString() As String
    Get
      Return _RotateString
    End Get
    Set(ByVal Value As String)
      _RotateString = Value
      Invalidate()
    End Set
  End Property
  '//
  '// テキストの透過度のプロパティ
  '//
  <TypeConverter(GetType(OpacityConverter))> _
  Public Property TextOpacity() As Double
    Get
      Return CDbl(txtOpacity / 255)
    End Get
    Set(ByVal Value As Double)
      txtOpacity = (Value * 255)
      UpdateDefaultBrush()
    End Set
  End Property
  '//
  '// 背景用 Imageプロパティ
  '//
  Public Property [Image]() As System.Drawing.Image
    Get
      Return img
    End Get
    Set(ByVal Value As System.Drawing.Image)
      img = Value
      If Not img Is Nothing Then
        Dim bm As Bitmap = Image
        Dim newImage As New Bitmap(bm.Width, bm.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
        Dim g As Graphics = Graphics.FromImage(newImage)
        g.DrawImage(bm, New Rectangle(0, 0, bm.Width, bm.Height))
        g.Dispose()
        img = newImage
        SetStyle(ControlStyles.Opaque, True)
        Invalidate()
      End If
    End Set
  End Property
  '//
  '// DoubleBuffer ON/OFF用のプロパティ
  '//
  <DefaultValue(False)> _
  Public Property DoubleBuffer() As Boolean
    Get
      Return Me.GetStyle(ControlStyles.DoubleBuffer)
    End Get
    Set(ByVal Value As Boolean)
      Me.SetStyle(ControlStyles.DoubleBuffer, Value)
      Me.UpdateStyles()
    End Set
  End Property
  '//
  '// Startメソッド（回転の開始)
  '//
  Public Sub Start()
    Timer1.Enabled = True
    Timer2.Enabled = True
  End Sub
  '//
  '// Stopメソッド（回転の停止)
  '//
  Public Sub [Stop]()
    Timer1.Enabled = False
    Timer2.Enabled = False
  End Sub
  '//
  '// テキストの描画
  '//
  Private Function DrawEffect(ByVal g As Graphics, ByVal bounds As Rectangle, ByVal txt As String, ByVal textBrush As Brush, ByVal f As Font, ByVal computeOnly As Boolean) As Region
    Dim newCompute As Boolean = True
    textSize = g.MeasureString(txt, f, bounds.Width, fmt)
    lastText = txt
    lastFont = f
    If bounds.Equals(lastBounds) And Not (nextRegion Is Nothing) Then
      newCompute = False
    End If
    lastBounds = bounds
    Dim r As Region = Nothing
    If newCompute Then
      Dim gp As New GraphicsPath
      Dim sc As Single = g.MeasureString("W", f).Height

      gp.AddString(txt, f.FontFamily, CInt(f.Style), sc, bounds, fmt)
      Dim gpBounds As RectangleF = gp.GetBounds()
      Dim m As New Matrix
      m.RotateAt(rotate, New PointF(bounds.Width / 2, bounds.Height / 2))
      gp.Transform(m)
      r = New Region(gp)
    Else
      r = nextRegion
    End If
    If Not computeOnly Then
      g.FillRegion(textBrush, r)
      lastRegion = r
    Else
      nextRegion = r
    End If
    Return r
  End Function
  '//
  '// FPSの描画
  '//
  Private Sub DrawFps(ByVal g As Graphics)
    If fpsBrush Is Nothing Then
      fpsBrush = New SolidBrush(Color.FromArgb(60, Color.Black))
    End If
    fpsRect = New Rectangle(Width - 155, Height - 50, 150, 45)
    Dim r As New RectangleF(Width - 155, Height - 50, 150, 45)
    g.FillRectangle(fpsBrush, r)
    ControlPaint.DrawBorder3D(g, fpsRect, Border3DStyle.Raised)
    r.Offset(2, 4)
    Dim f As New Font("Arial", 24, FontStyle.Bold)
    g.DrawString(fps, f, Brushes.Yellow, r)
  End Sub
  '//
  '// 描画更新用
  '//
  Private Sub DoInvalidate()
    invalidRegion = AdvanceEffect()
    If Not invalidRegion Is Nothing Then
      Invalidate(invalidRegion)
    Else
      Invalidate()
    End If
  End Sub
  '//
  '// 回転角の更新
  '//
  Private Function AdvanceEffect() As Region
    If rotate < 360 Then
      rotate += 1
    Else
      rotate = 0
    End If
    nextRegion = Nothing
    Return lastRegion
  End Function
  '//
  '// 表示更新用のタイマー
  '//
  Private Sub Timer1_Elapsed(ByVal sender As System.Object, ByVal e As System.Timers.ElapsedEventArgs) Handles Timer1.Elapsed
    If Not pendingPaint Then
      pendingPaint = True
      DoInvalidate()
    End If
  End Sub
  '//
  '// FPS更新用のタイマー
  '//
  Private Sub Timer2_Elapsed(ByVal sender As System.Object, ByVal e As System.Timers.ElapsedEventArgs) Handles Timer2.Elapsed
    fps = frames & " fps"
    Invalidate(fpsRect)
    frames = 0
  End Sub

  Protected Overrides Sub OnVisibleChanged(ByVal e As EventArgs)
    MyBase.OnVisibleChanged(e)
  End Sub
  Private Sub UpdateDefaultBrush()
    textBrush = New SolidBrush(Color.FromArgb(txtOpacity, Me.ForeColor))
  End Sub
  Protected Overrides Sub OnForeColorChanged(ByVal e As System.EventArgs)
    MyBase.OnForeColorChanged(e)
    UpdateDefaultBrush()
    Invalidate()
  End Sub
  Protected Overrides Sub OnFontChanged(ByVal e As System.EventArgs)
    MyBase.OnFontChanged(e)
    Invalidate()
  End Sub

End Class
