UIfiedCFBase.ps1

using namespace System.Collections.Generic
using namespace System.Reflection
using namespace ConsoleFramework
using namespace ConsoleFramework.Core
using namespace ConsoleFramework.Native
using namespace ConsoleFramework.Controls
using namespace ConsoleFramework.Events
using namespace ConsoleFramework.Rendering

class CFElement : UIElement {

    CFElement() {
        $this.WrapNegatedProperty("Enable", "Disabled")
        Add-Member -InputObject $this -Name Visible -MemberType ScriptProperty -Value {
            $this.NativeUI.Visibility -eq [Visibility]::Visible
        } -SecondValue {
            if ($args[0]) {
                $this.NativeUI.Visibility = [Visibility]::Visible
            } else {
                $this.NativeUI.Visibility = [Visibility]::Collapsed
            }
        }
        $this.AddNativeUIChild = {
            param (
                [CFElement] $element
            )
            $this.NativeUI.xChildren.Add($element.NativeUI)
        }
        $this.RemoveNativeUIChild = {
            param (
                [CFElement] $element
            )
            $this.NativeUI.xChildren.Remove($element.NativeUI)
        }
        $this.ShowError = {
            param (
                [Object] $errorObject
            )
            [MessageBox]::Show("Error", $errorObject, $null)
        }
    }

    static [void] RenderText([string] $text, $buffer, $x, $y, $attrs) {
        $chars = $text.ToCharArray()
        0..($chars.Length - 1) | ForEach-Object {
            $buffer.SetPixel($_ + $x, $y, $chars[$_], $attrs)
        }
    }
}

class CFHost : UIHost {

    [void] ShowFrame([ScriptBlock] $frameScriptBlock) {
        $window = Invoke-Command $frameScriptBlock
        $Global:SyncHash = [HashTable]::Synchronized(@{
            Window = $window
            Errors = @()
        })
        $winhost = [WindowsHost]::new()
        $winhost.Show($window.NativeUI)
        [ConsoleFramework.ConsoleApplication]::Instance.run($winhost)
    }

}

class CFWindow : WindowBase {

    CFWindow() {
        $this.SetNativeUI([Window]::new())
        $this.WrapProperty("Caption", "Title")
        $this.AddScriptBlockProperty("Loaded")
        $this.AddNativeUIChild = {
            param (
                [CFElement] $element
            )
            $this.NativeUI.Content = $element.NativeUI
            $this.NativeUI.Created()
        }
    }

    [void] ShowDialog() {
    }

    [void] OnLoaded() {
        Invoke-Command -ScriptBlock $this._Loaded -ArgumentList $this
    }

}

class CFCustomPanel : Panel {
    static [color] $DefaultBackgroundColor    = [color]::Gray
           [color] $ForegroundColor           = [color]::Black
           [color] $BackgroundColor           = [color]::Gray
           [char]  $Pattern                   = ' '
    
    [void] Render([RenderingBuffer] $buffer) {
        if ($this.BackgroundColor -eq [color]::Gray) {
            $this.BackgroundColor = [CFCustomPanel]::DefaultBackgroundColor
        }
        $buffer.FillRectangle( 0, 0, $this.ActualWidth, $this.ActualHeight, $this.Pattern, [colors]::Blend($this.ForegroundColor, $this.BackgroundColor));
        #for ([int] $x = 0; $x -lt $this.ActualWidth; $x++) {
        # for ([int] $y = 0; $y -lt $this.ActualHeight; $y++) {
        # $buffer.SetPixel($x, $y, $this.Pattern, [colors]::Blend($this.ForegroundColor, $this.BackgroundColor));
        # $buffer.SetOpacity( $x, $y, 4 );
        # }
        #}
    }
}

class CFStackPanel : CFElement {

    CFStackPanel() {
        $this.SetNativeUI([CFCustomPanel]::new())
        $this.WrapProperty("Orientation", "Orientation")
    }
}

class CFLabel : CFElement {

    CFLabel() {
        $textBlock = [TextBlock]::new()
        $this.SetNativeUI($textBlock)
        $this.WrapProperty("Caption", "Text")
    }
}

class CFIcon : CFLabel {
    hidden  [String] $KindName

    CFIcon() {
        Add-Member -InputObject $this -Name Kind -MemberType ScriptProperty -Value {
            $this.KindName
        } -SecondValue {
            $this.KindName = $args[0]
            $this.RefreshCaption()
        }
    }

    [void] RefreshCaption() {
        $this.Caption = [IconStrinfy]::ToIconString($this.KindName)
    }
}

class CFCustomButton : Button {
    [string]   $Style = "Default"
    [color]    $ForegroundColor = [color]::White
    [color]    $BackgroundColor = [color]::Magenta
    [char]     $Pattern         = ' '

    [Size] MeasureOverride([Size] $availableSize) {
        switch ($this.Style) {
            "Pill"     {
                return [Size]::new(($this.Caption.Length + 2), 1)
            }
            "Flat"     {
                return [Size]::new(($this.Caption.Length + 2), 1)
            }
            "Primary"  {
                return [Size]::new(($this.Caption.Length + 2), 1)
            }
            default {
                if (-not [System.string]::IsNullOrEmpty($this.Caption)) {
                    if ($this.MaxWidth -ge 20) {
                        $currentWidth = $this.Caption.Length + 5
                    } else {
                        $currentWidth = $this.MaxWidth
                    }
                    if ($this.MaxHeight -ge 20) {
                        $currentHeight = 2
                    } else {
                        $currentHeight = $this.MaxHeight
                    }
                    [Size] $minButtonSize = [Size]::new($currentWidth, $currentHeight)
                    return $minButtonSize;
                } else {
                    return [Size]::new(8, 2);
                }
            }
        }
        return $null
    }

    [void] Render([RenderingBuffer] $buffer) {
        switch ($this.Style) {
            "Pill"      { $thiS.RenderPill($buffer)       }
            "Flat"      { $thiS.RenderFlat($buffer)       }
            "Primary"   { $thiS.RenderPrimary($buffer)    }
            default     { $thiS.RenderDefault($buffer)    }
        }
    }

    [void] RenderDefault([RenderingBuffer] $buffer) {
        ([Button] $this).Render($buffer)
    }

    [void] RenderPill([RenderingBuffer] $buffer) {
        $buffer.FillRectangle( 0, 0, $this.ActualWidth, $this.ActualHeight, $this.Pattern, [colors]::Blend($this.ForegroundColor, $this.BackgroundColor))
        $chars = $this.Caption.ToCharArray()
        $buffer.SetPixel(0, 0, [IconStrinfy]::ToIconString("left_semi_circle"), [Colors]::Blend($this.BackgroundColor, $this.ForegroundColor))
        $buffer.SetOpacityRect(0, 0, 1, 1, 3);
        0..($chars.Length - 1) | ForEach-Object {
            $buffer.SetPixel($_ + 1, 0, $chars[$_], [Colors]::Blend($this.GetForegroundColor(), $this.BackgroundColor))
        }
        $buffer.SetPixel($chars.Length + 1, 0, [IconStrinfy]::ToIconString("right_semi_circle"), [Colors]::Blend($this.BackgroundColor, $this.ForegroundColor))
        $buffer.SetOpacityRect($this.ActualWidth - 1, 0, 1, 1, 3);

        $this.Margin       = [Thickness]::new(0, 0, 1, 1)
    }

    [void] RenderPrimary([RenderingBuffer] $buffer) {
        $buffer.FillRectangle( 0, 0, $this.ActualWidth, $this.ActualHeight, $this.Pattern, [colors]::Blend($this.ForegroundColor, $this.BackgroundColor))
        $chars = $this.Caption.ToCharArray()
        $buffer.SetPixel(0, 0, ' ', [Colors]::Blend($this.ForegroundColor, $this.BackgroundColor))
        #$buffer.SetOpacityRect(0, 0, 1, 1, 3);
        0..($chars.Length - 1) | ForEach-Object {
            $buffer.SetPixel($_ + 1, 0, $chars[$_], [Colors]::Blend($this.GetForegroundColor(), $this.BackgroundColor))
        }
        $buffer.SetPixel($chars.Length + 1, 0, ' ', [Colors]::Blend($this.ForegroundColor, $this.BackgroundColor))
        #$buffer.SetOpacityRect($this.ActualWidth - 1, 0, 1, 1, 3);

        $this.Margin       = [Thickness]::new(0, 0, 1, 1)
    }

    [void] RenderFlat([RenderingBuffer] $buffer) {
        $buffer.FillRectangle( 0, 0, $this.ActualWidth, $this.ActualHeight, $this.Pattern, [colors]::Blend($this.ForegroundColor, $this.BackgroundColor))
        $chars = $this.Caption.ToCharArray()
        0..($chars.Length - 1) | ForEach-Object {
            $buffer.SetPixel($_ + 1, 0, $chars[$_], [Colors]::Blend($this.GetForegroundColor(), $this.BackgroundColor))
        }
        $buffer.SetOpacityRect(0, 0, $this.ActualWidth, $this.ActualHeight, 3);

        $this.Margin       = [Thickness]::new(0, 0, 1, 1)
    }

    [Color] GetForegroundColor() {
        if ($this.Disabled) {
            return [Color]::Gray
        } else {
            if ($this.Pressed -or $this.PressedUsingKeyboard) {
                return [Color]::Black
            } else {
                if ($this.HasFocus) {
                    return [Color]::DarkGray
                } else {
                    return $this.ForegroundColor
                }
            }
        }
    }
}

class CFButton : CFElement {
    hidden [String]         $CaptionText   = ""
    hidden [String]         $IconText      = ""
           [IconPosition]   $IconPosition  = [IconPosition]::Left

    CFButton() {
        $this.SetNativeUI([CFCustomButton]::new())
        Add-Member -InputObject $this -Name Caption -MemberType ScriptProperty -Value {
            $this.CaptionText
        } -SecondValue {
            $this.CaptionText = $args[0]
            $this.RefreshCaption()
        }
        Add-Member -InputObject $this -Name Icon -MemberType ScriptProperty -Value {
            $this.IconText
        } -SecondValue {
            if ($args[0] -ne $null) {
                $this.IconText = [IconStrinfy]::ToIconString($args[0].Kind)
                $this.RefreshCaption()
            } else {
                $this.IconText = ""
                $this.RefreshCaption()
            }
        }
        $this.AddScriptBlockProperty("Action")
        $this.NativeUI.Add_OnClick({ $this.Control.OnAction() })
    }

    [void] RefreshCaption() {
        if ($this.IconPosition -eq [IconPosition]::Left) {
            $this.NativeUI.Caption = ($this.IconText + " " + $this.CaptionText).Trim()
        } else {
            $this.NativeUI.Caption = ($this.CaptionText + " " + $this.IconText).Trim()
        }
    }

    [void] OnAction() {
        $this.InvokeTrappableCommand($this._Action, $this)
    }
}

class CFCustomTextBox : TextBox {
    [string]   $Style = "Default"

    [void] Render([RenderingBuffer] $buffer) {
        switch ($this.Style) {
            "Flat"      { $thiS.RenderFlat($buffer)       }
            default     { $thiS.RenderDefault($buffer)    }
        }
    }

    [void] RenderDefault([RenderingBuffer] $buffer) {
        ([TextBox] $this).Render($buffer)
    }

    [void] RenderFlat([RenderingBuffer] $buffer) {
        $displayOffset = $this.GetDisplayOffset()
        [Attr] $attr = [Colors]::Blend([Color]::Magenta, [Color]::White)
        $buffer.FillRectangle(0, 0, $this.ActualWidth, $this.ActualHeight, '_', $attr)
        if ($null -ne $this.Text) {
            for ($i = $displayOffset; $i -lt $this.text.Length; $i++) {
                if (($i - $displayOffset) -lt ($this.ActualWidth - 2) -and ($i - $displayOffset) -ge 0) {
                    $buffer.SetPixel(1 + ($i - $displayOffset), 0, $this.Text.ToCharArray()[$i], [Colors]::Blend([Color]::Black, [Color]::White))
                }
            }
        }
        if ($displayOffset -gt 0) {
            $buffer.SetPixel(0, 0, '<', $attr)
        }
        if (-not [String]::IsNullOrEmpty($this.Text) -and $this.ActualWidth - 2 + $displayOffset -lt $this.Text.Length) {
            $buffer.SetPixel($this.ActualWidth - 1, 0, '>', $attr)
        }
    }

    [int] GetDisplayOffset() {
        $prop = $this.GetType().BaseType.GetField("displayOffset", [BindingFlags]::NonPublic -bor [BindingFlags]::Instance)
        return $prop.GetValue($this)
    }
}

class CFTextBox : CFElement {

    CFTextBox() {
        $this.SetNativeUI([CFCustomTextBox]::new())
        $this.NativeUI.Size = 10
        $this.WrapProperty("Text", "Text")
        $this.AddScriptBlockProperty("Change")
        $this.NativeUI.Add_PropertyChanged({
            param (
                [System.Object] $sender,
                [System.ComponentModel.PropertyChangedEventArgs] $eventArgs
            )
            if ($this.Control.NativeUI.HasFocus -and $eventArgs.PropertyName -eq "Text") {
                $this.Control.OnChange()
            }
        })
    }

    [void] OnChange() {
        $this.InvokeTrappableCommand($this._Change, $this)
    }

}

class CFCustomCheckBox : CheckBox {
    [string]   $Style = "Default"

    [Size] MeasureOverride([Size] $availableSize) {
        switch ($this.Style) {
            "Flat"  {
                return [Size]::new(($this.Caption.Length + 2), 1)
            }
            default {
                return [Size]::new(($this.Caption.Length + 4), 1)
            }
        }
        return $null
    }

    [void] Render([RenderingBuffer] $buffer) {
        switch ($this.Style) {
            "Flat"   { $thiS.RenderFlat($buffer)    }
            default  { $thiS.RenderDefault($buffer) }
        }
    }

    [void] RenderDefault([RenderingBuffer] $buffer) {
        ([CheckBox] $this).Render($buffer)
    }

    [void] RenderFlat([RenderingBuffer] $buffer) {
        $buttonAttrs = [Colors]::Blend([Color]::Magenta, [Color]::White);
        if ($this.Checked) {
            $buffer.SetPixel(0, 0, [IconStrinfy]::ToIconString("check_box"), $buttonAttrs)
        } else {
            $buffer.SetPixel(0, 0, [IconStrinfy]::ToIconString("check_box_outlined_blank"), $buttonAttrs)
        }
        $buffer.SetPixel(1, 0, " " , $buttonAttrs)
        $chars = $this.Caption.ToCharArray()
        0..($chars.Length - 1) | ForEach-Object {
            $buffer.SetPixel($_ + 2, 0, $chars[$_], [Colors]::Blend($this.GetForegroundColor(), [Color]::White))
        }
        #$buffer.SetOpacityRect(0, 0, $this.ActualWidth, $this.ActualHeight, 3)
    }

    [Color] GetForegroundColor() {
        if ($this.Disabled) {
            return [Color]::Gray
        } else {
            if ($this.HasFocus) {
                return [Color]::DarkGray
            } else {
                return [Color]::Black
            }
        }
    }

}

class CFCheckBox : CFElement {

    CFCheckBox() {
        $this.SetNativeUI([CFCustomCheckBox]::new())
        $this.WrapProperty("Caption", "Caption")
        $this.WrapProperty("IsChecked", "Checked")
        $this.AddScriptBlockProperty("Click")
        $this.NativeUI.Add_OnClick({ $this.Control.OnClick() })
    }

    [void] OnClick() {
        $this.InvokeTrappableCommand($this._Click, $this)
    }

}

class CFCustomRadioButton : RadioButton {
    [string]   $Style = "Default"

    [Size] MeasureOverride([Size] $availableSize) {
        switch ($this.Style) {
            "Flat"  {
                return [Size]::new(($this.Caption.Length + 2), 1)
            }
            default {
                return [Size]::new(($this.Caption.Length + 4), 1)
            }
        }
        return $null
    }

    [void] Render([RenderingBuffer] $buffer) {
        switch ($this.Style) {
            "Flat"   { $thiS.RenderFlat($buffer)    }
            default  { $thiS.RenderDefault($buffer) }
        }
    }

    [void] RenderDefault([RenderingBuffer] $buffer) {
        ([RadioButton] $this).Render($buffer)
    }

    [void] RenderFlat([RenderingBuffer] $buffer) {
        $buttonAttrs = [Colors]::Blend([Color]::Magenta, [Color]::White);
        if ($this.Checked) {
            $buffer.SetPixel(0, 0, [IconStrinfy]::ToIconString("radio_button_checked"), $buttonAttrs)
        } else {
            $buffer.SetPixel(0, 0, [IconStrinfy]::ToIconString("radio_button_unchecked"), $buttonAttrs)
        }
        $buffer.SetPixel(1, 0, " " , $buttonAttrs)
        $chars = $this.Caption.ToCharArray()
        0..($chars.Length - 1) | ForEach-Object {
            $buffer.SetPixel($_ + 2, 0, $chars[$_], [Colors]::Blend($this.GetForegroundColor(), [Color]::White))
        }
        #$buffer.SetOpacityRect(0, 0, $this.ActualWidth, $this.ActualHeight, 3)
    }

    [Color] GetForegroundColor() {
        if ($this.Disabled) {
            return [Color]::Gray
        } else {
            if ($this.HasFocus) {
                return [Color]::DarkGray
            } else {
                return [Color]::Black
            }
        }
    }
}

class CFRadioButton : CFElement {

    CFRadioButton() {
        $this.SetNativeUI([CFCustomRadioButton]::new())
        $this.WrapProperty("Caption", "Caption")
        $this.WrapProperty("IsChecked", "Checked")
        $this.AddScriptBlockProperty("Click")
        $this.NativeUI.Add_OnClick({ $this.Control.OnClick() })
    }

    [void] OnClick() {
        $this.InvokeTrappableCommand($this._Click, $this)
    }

}

class CFRadioGroup : CFElement {

    CFRadioGroup() {
        $this.SetNativeUI([RadioGroup]::new())
    }

}

class CFList : CFStackPanel {
    [List[ListItem]] $Items = [List[ListItem]]::new()

    CFList() {
        $this.Orientation   = [Orientation]::Horizontal
    }

    [void] AddColumn([CFListColumn] $listColumn) {
        $column = [CFStackPanel]::new()
        $column.Orientation           = [Orientation]::Vertical
        $column.NativeUI.Margin       = [Thickness]::new(0, 0, 1, 0)
        $title = [CFLabel]::new()
        $title.NativeUI.Color = [Color]::DarkGray
        $title.Caption = $listColumn.Title
        $column.AddChild($title)
        $this.AddChild($column)
    }

    [void] AddItem([ListItem] $listItem) {
        $this.Items.Add($listItem)
        $columnIndex = 0
        $this.Children | ForEach-Object {
            $column = $_
            $cell = $listItem.Children.Item($columnIndex)
            $column.AddChild($cell)
            $columnIndex++
        }
    }

    [void] RemoveItem([ListItem] $listItem) {
        $this.Items.Remove($listItem)
        $columnIndex = 0
        $this.Children | ForEach-Object {
            $column = $_
            $cell = $listItem.Children.Item($columnIndex)
            $column.RemoveChild($cell)
            $columnIndex++
        }
    }

    [void] Clear() {
        $this.Items.ToArray() | ForEach-Object {
            $this.RemoveItem($_)
        }
    }

}

class CFListColumn {
    [String] $Name
    [String] $Title
}

class CFTabItem : CFElement {
    [String] $Caption   = ""

    CFTabItem() {
        $this.SetNativeUI([CFCustomPanel]::new())
    }

}

class CFCustomTabControl : TabControl {
    [string]   $Style = "Default"

    [void] Render([RenderingBuffer] $buffer) {
        switch ($this.Style) {
            "Flat"   { $thiS.RenderFlat($buffer)    }
            default  { $thiS.RenderDefault($buffer) }
        }
    }

    [void] RenderDefault([RenderingBuffer] $buffer) {
        ([TabControl] $this).Render($buffer)
    }

    [void] RenderFlat([RenderingBuffer] $buffer) {
        $attr = [Colors]::Blend( [Color]::Black, [Color]::DarkGreen )
        $inactiveAttr = [Colors]::Blend( [Color]::DarkGray, [Color]::DarkGreen )

        # Transparent background for borders
        $buffer.SetOpacityRect( 0, 0, $this.ActualWidth, $this.ActualHeight, 3 )
        $buffer.FillRectangle( 0, 0, $this.ActualWidth, $this.ActualHeight, ' ', $attr )
        # Transparent child content part
        if ( $this.ActualWidth -gt 2 -and $this.ActualHeight -gt 3 ) {
            $buffer.SetOpacityRect( 1, 3, $this.ActualWidth - 2, $this.ActualHeight - 4, 2 )
        }
        # Transparent child content part
        if ( $this.ActualWidth -gt 2 -and $this.ActualHeight -gt 3 ) {
            $buffer.SetOpacityRect( 1, 3, $this.ActualWidth - 2, $this.ActualHeight - 4, 2 )
        }
        #$this.renderBorderSafe( $buffer, 0, 2, [Math]::Max( $this.getTabHeaderWidth( ) - 1, $this.ActualWidth - 1 ), $this.ActualHeight - 1 )
        # Start to render header
        $buffer.FillRectangle( 0, 0, $this.ActualWidth, [Math]::Min( 2, $this.ActualHeight ), ' ', $attr )
        
        $x = 0
        for ($tab = 0; $tab -lt $this.tabDefinitions.Count; $x += $this.TabDefinitions[ $tab++ ].Title.Length + 3 ) {
            $tabDefinition = $this.TabDefinitions[ $tab ];
            $titleAttr = if ($this.activeTabIndex -eq $tab) { $attr } else { $inactiveAttr }
            $buffer.RenderStringSafe( " " + $tabDefinition.Title.ToUpperInvariant() + " ", $x + 1, 1, $titleAttr )
            if ($this.activeTabIndex -eq $tab) {
                $pattern = ([string] ([char] 0xf068)) * ($tabDefinition.Title.Length + 2)
                $buffer.RenderStringSafe( $pattern, $x + 1, 2, [Colors]::Blend( [Color]::Green, [Color]::DarkGreen ) )
            }
        }
    }
}

class CFTabControl : CFElement {

    CFTabControl() {
        $this.SetNativeUI([CFCustomTabControl]::new())
        $this.AddNativeUIChild = {
            param (
                [CFElement] $element
            )
            $tabDefinition = [TabDefinition]::new()
            $tabDefinition.Title = $element.Caption
            $this.NativeUI.TabDefinitions.Add($tabDefinition)
            $this.NativeUI.Controls.Add($element.NativeUI)
        }
    }
}

class CFModal : CFElement {
    $Window

    CFModal() {
        $this.Window = [Window]::new()
        $this.SetNativeUI([CFCustomPanel]::new())
        $this.WrapProperty("Title", "Title", "Window")
        $this.AddNativeUIChild = {
            param (
                [CFElement] $element
            )
            $this.Window.Content = $element.NativeUI
        }
    }

    [void] Show() {
        [WindowsHost] $windowsHost = [WindowsHost] [ConsoleFramework.ConsoleApplication]::Instance.RootControl
        $windowsHost.ShowModal($this.Window)
    }

    [void] Hide() {
        $this.Window.Close()
    }

}

class CFTimer : CFElement {
    [System.Timers.Timer] $Timer
    [Double] $Interval = 1000
    
    CFTimer() {
        $label = [TextBlock]::new()
        $label.Visibility = [Visibility]::Collapsed
        $this.SetNativeUI($label)
        $this.AddScriptBlockProperty("Elapsed")
        $this.Timer = New-Object System.Timers.Timer
        Register-ObjectEvent -InputObject $this.Timer -EventName Elapsed -MessageData $this -Action {
            $this = $event.MessageData
            $this.Control.OnElapsed()
        }
    }

    [void] OnElapsed() {
        Invoke-Command -ScriptBlock $this._Elapsed -ArgumentList $this
    }
    
    [void] Start() {
        $this.Timer.Interval = $this.Interval
        $this.Timer.Start()
    }

    [void] Stop() {
        $this.Timer.Stop()
    }
}

class CFDatePicker : CFElement {

    CFDatePicker() {
        $textBox = [CFCustomTextBox]::new()
        $textBox.Size = 10
        $textBox.MaxLenght = 10
        $this.SetNativeUI($textBox)
        Add-Member -InputObject $this -Name Value -MemberType ScriptProperty -Value {
            $this.GetTextDateTime()
        } -SecondValue {
            $this.NativeUI.Text = $args[0].ToShortDateString()
        }
        $this.AddScriptBlockProperty("Change")
        $this.NativeUI.Add_PropertyChanged({
            param (
                [System.Object] $sender,
                [System.ComponentModel.PropertyChangedEventArgs] $eventArgs
            )
            if ($this.Control.NativeUI.HasFocus -and $eventArgs.PropertyName -eq "Text") {
                $this.Control.OnChange()
            }
        })
        $this.AddScriptBlockProperty("LostFocus")
        $this.NativeUI.Add_LostKeyboardFocus({
            param (
                $sender,
                $eventArgs
            )
            $this.Control.OnLostFocus()
        })
    }

    [void] OnChange() {
        $this.InvokeTrappableCommand($this._Change, $this)
    }

    [void] OnLostFocus() {
        $this.Value = $this.GetTextDateTime()
        $this.InvokeTrappableCommand($this._LostFocus, $this)
    }

    hidden [DateTime] GetTextDateTime() {
        [DateTime] $dateTime = [DateTime]::Today
        if (-not [DateTime]::TryParse($this.NativeUI.Text, [ref] $dateTime)) {
            return [DateTime]::Today
        } else {
            return $dateTime
        }
    }

}

class CFTimePicker : CFElement {

    CFTimePicker() {
        $textBox = [CFCustomTextBox]::new()
        $textBox.Size = 5
        $textBox.MaxLenght = 5
        $this.SetNativeUI($textBox)
        Add-Member -InputObject $this -Name Value -MemberType ScriptProperty -Value {
            $this.GetTextTime()
        } -SecondValue {
            $this.NativeUI.Text = $args[0]
        }
        $this.AddScriptBlockProperty("Change")
        $this.NativeUI.Add_PropertyChanged({
            param (
                [System.Object] $sender,
                [System.ComponentModel.PropertyChangedEventArgs] $eventArgs
            )
            if ($this.Control.NativeUI.HasFocus -and $eventArgs.PropertyName -eq "Text") {
                $this.Control.OnChange()
            }
        })
        $this.AddScriptBlockProperty("LostFocus")
        $this.NativeUI.Add_LostKeyboardFocus({
            param (
                $sender,
                $eventArgs
            )
            $this.Control.OnLostFocus()
        })
    }

    [void] OnChange() {
        $this.InvokeTrappableCommand($this._Change, $this)
    }

    [void] OnLostFocus() {
        $this.Value = $this.GetTextTime()
        $this.InvokeTrappableCommand($this._LostFocus, $this)
    }

    hidden [String] GetTextTime() {
        [DateTime] $dateTime = [DateTime]::Today
        if (-not [DateTime]::TryParse("2000-01-01 " + $this.NativeUI.Text, [ref] $dateTime)) {
            return "00:00"
        } else {
            return $dateTime.ToShortTimeString()
        }
    }

}

class CFBrowser : CFStackPanel {
    [HashTable[]]            $Data            = [HashTable[]] @()
    [int]                    $PageRows        = 10
    [int]                    $CurrentPage     = 0
    [Boolean]                $IsEditable      = $true
    [HashTable]              $CurrentRow

    #region Components Declaration

    hidden [CFListColumn[]] $Columns         = [CFListColumn[]] @()
    hidden [CFListColumn]   $EditionColumn
    hidden [CFList]         $List            = [CFList]::new()
    hidden [CFStackPanel]   $ButtonPanel     = [CFStackPanel]::new()
    hidden [CFButton]       $FirstButton     = [CFButton]::new()
    hidden [CFButton]       $PreviousButton  = [CFButton]::new()
    hidden [CFButton]       $NextButton      = [CFButton]::new()
    hidden [CFButton]       $LastButton      = [CFButton]::new()
    hidden [CFButton]       $AddNewButton    = [CFButton]::new()

    #endregion
    
    CFBrowser() {
        $this.AddScriptBlockProperty("AddNew")
        $this.AddScriptBlockProperty("Edit")
        $this.AddScriptBlockProperty("Delete")
        $this.AddChild($this.List)
        $this.AddButtons()
    }

    #region Components Creation

    hidden [void] AddButtons() {
        $this.ButtonPanel = [CFStackPanel]::new()
        $this.ButtonPanel.Orientation = "Horizontal"

        $this.FirstButton.Action                 = { $this.Parent.Parent.OnMoveFirst()     }
        $this.PreviousButton.Action              = { $this.Parent.Parent.OnMovePrevious()  }
        $this.NextButton.Action                  = { $this.Parent.Parent.OnMoveNext()      }
        $this.LastButton.Action                  = { $this.Parent.Parent.OnMoveLast()      }
        $this.AddNewButton.Action                = { $this.Parent.Parent.OnAddNew()        }

        $this.ButtonPanel.AddChild($this.FirstButton)
        $this.ButtonPanel.AddChild($this.PreviousButton)
        $this.ButtonPanel.AddChild($this.NextButton)
        $this.ButtonPanel.AddChild($this.LastButton)
        $this.ButtonPanel.AddChild($this.AddNewButton)

        $this.StyleComponents()

        $this.AddChild($this.ButtonPanel)
    }

    [void] AddColumn([CFListColumn] $listColumn) {
        $this.Columns += $listColumn
        $this.List.AddColumn($listColumn)
    }

    [void] CreateList() {
        $this.List.Clear()
        $this.CreateEditable()
        0..($this.PageRows - 1) | ForEach-Object {
            $listItem = $this.GetInitialListItem($_)
            $this.List.AddItem($listItem)
        }
    }

    hidden [ListItem] GetInitialListItem([int] $rowIndex) {
        $hash = $this.GetInitialHash()
        $listItem = [ListItem]::new()
        $this.Columns | ForEach-Object {
            $column = $_
            if ($column -eq $this.EditionColumn -and $this.IsEditable) {
                $this.AddEditionButtons($hash, $listItem, $rowIndex)
            } else {
                $this.AddCell($hash, $column.Name, $listItem, $rowIndex)
            }
        }
        return $listItem
    }

    hidden [HashTable] GetInitialHash() {
        $hash = @{}
        $this.Columns | ForEach-Object {
            $column = $_
            $hash += @{ "$($column.Name)" = "" }
        }
        return $hash
    }

    hidden [void] AddEditionButtons([HashTable] $hash, [ListItem] $listItem, [int] $rowIndex) {
        $editionPanel = [CFStackPanel]::new()
        $editionPanel.Orientation = "Horizontal"
        $listItem.AddChild($editionPanel)

        $editButton = [CFButton]::new()
        Add-Member -InputObject $editButton -MemberType NoteProperty -Name CurrentRow -Value $hash
        $editButton.Action = {
            $this.Parent.Parent.Parent.Parent.CurrentRow = $this.CurrentRow
            $this.Parent.Parent.Parent.Parent.OnEdit()
        }
        $editionPanel.AddChild($editButton)

        $deleteButton = [CFButton]::new()
        Add-Member -InputObject $deleteButton -MemberType NoteProperty -Name CurrentRow -Value $hash
        $deleteButton.Action = {
            $this.Parent.Parent.Parent.Parent.CurrentRow = $this.CurrentRow
            $this.Parent.Parent.Parent.Parent.OnDelete()
        }
        $editionPanel.AddChild($deleteButton)
        $this.StyleEditionButtons($editButton, $deleteButton, $rowIndex)
    }

    hidden [void] AddCell([HashTable] $hash, [string] $columnName, [ListItem] $listItem, [int] $rowIndex) {
        $itemLabel = [CFLabel]::new()
        $itemLabel.Caption = $hash."$columnName"
        $listItem.AddChild($itemLabel)
        $this.StyleCell($itemLabel, $rowIndex)
    }

    hidden [void] CreateEditable() {
        if ($this.EditionColumn -eq $null -and $this.IsEditable) {
            $this.CreateEditionColumn()
        }
        $this.AddNewButton.Visible = $this.IsEditable
    }

    hidden [void] CreateEditionColumn() {
        $this.EditionColumn = New-Object CFListColumn -Property @{Name  = "_Edition"; Title = "_"}
        $this.AddColumn($this.EditionColumn)
    }

    #endregion

    #region Data show

    [void] Refresh() {
        # Fill Rows
        $rowIndex = 0
        $selectedData = $this.GetSelectedData()
        $selectedData | ForEach-Object {
            $hash = $_
            $columnIndex = 0
            $this.Columns | Select-Object -First ($this.Columns.Count) | ForEach-Object {
                $column = $_
                if ($this.EditionColumn -ne $column) {
                    $this.List.Children.Item($columnIndex).Children.Item($rowIndex + 1).Caption = $hash."$($column.Name)"
                } else {
                    $buttons = $this.List.Children.Item($columnIndex).Children.Item($rowIndex + 1).Children
                    $buttons.Item(0).CurrentRow = $hash
                    $buttons.Item(1).CurrentRow = $hash
                    $buttons.Item(0).Visible    = $true
                    $buttons.Item(1).Visible    = $true
                }
                $columnIndex++
            }
            $rowIndex++
        }
        # EmptyRows
        for ($rowIndex = $selectedData.Count + 1; $rowIndex -le $this.PageRows; $rowIndex++) {
            $columnIndex = 0
            $this.Columns | Select-Object -First ($this.Columns.Count) | ForEach-Object {
                $column = $_
                if ($this.EditionColumn -ne $column) {
                    $this.List.Children.Item($columnIndex).Children.Item($rowIndex).Caption = ""
                } else {
                    $buttons = $this.List.Children.Item($columnIndex).Children.Item($rowIndex).Children
                    $buttons.Item(0).Visible    = $false
                    $buttons.Item(1).Visible    = $false
                }
                $columnIndex++
            }
        }
    }

    hidden [HashTable[]] GetSelectedData() {
        return $this.Data | Select-Object -Skip ($this.CurrentPage * $this.PageRows) -First $this.PageRows
    }

    hidden [int] GetLastPage() {
        $lastPage =  [Math]::Truncate($this.Data.Count / $this.PageRows)
        if (($this.Data.Count % $this.PageRows) -eq 0) {
            $lastPage--
        }
        return $lastPage
    }

    #endregion

    #region Style

    [void] StyleComponents() {
        $this.FirstButton.Caption        = "|<"
        $this.PreviousButton.Caption     = "<"
        $this.NextButton.Caption         = ">"
        $this.LastButton.Caption         = ">|"
        $this.AddNewButton.Caption       = "+"

        $this.FirstButton.NativeUI.MaxWidth     = 7
        $this.PreviousButton.NativeUI.MaxWidth  = 7
        $this.NextButton.NativeUI.MaxWidth      = 7
        $this.LastButton.NativeUI.MaxWidth      = 7
        $this.AddNewButton.NativeUI.MaxWidth    = 7

        $this.AddNewButton.NativeUI.MaxHeight   = 4
        $this.AddNewButton.NativeUI.Margin      = [Thickness]::new(6, 0, 0, 0)
    }

    [void] StyleCell($cell, [int] $rowIndex) {
        if ($this.IsEditable) {
            $cell.NativeUI.Margin       = [Thickness]::new(0, 0, 0, 1)
        }
    }

    [void] StyleEditionButtons([CFButton] $editButton, [CFButton] $deleteButton, [int] $rowIndex) {
        $editButton.Caption       = "/"
        $deleteButton.Caption     = "-"

        $editButton.NativeUI.MaxWidth     = 5
        $deleteButton.NativeUI.MaxWidth   = 5
    }

    #endregion

    #region Move Events
    
    hidden [void] OnMoveFirst() {
        $this.CurrentPage = 0
        $this.Refresh()
    }
    
    hidden [void] OnMovePrevious() {
        if ($this.CurrentPage -gt 0) {
            $this.CurrentPage--
        }
        $this.Refresh()
    }
    
    hidden [void] OnMoveNext() {
        if ($this.CurrentPage -lt $this.GetLastPage()) {
            $this.CurrentPage++
        }
        $this.Refresh()
    }
    
    hidden [void] OnMoveLast() {
        $this.CurrentPage = $this.GetLastPage()
        $this.Refresh()
    }

    #endregion
    
    #region CRUD Events

    hidden [void] OnAddNew() {
        $this.InvokeTrappableCommand($this._AddNew, $this)
    }
    
    hidden [void] OnEdit() {
        $this.InvokeTrappableCommand($this._Edit, $this)
    }
    
    hidden [void] OnDelete() {
        $this.InvokeTrappableCommand($this._Delete, $this)
    }

    #endregion

}

class CFCustomMenuItem : MenuItem {
    [Color] $BackgroundColor           = [color]::DarkGreen
    
    [void] Render([RenderingBuffer] $buffer) {
        if ($this.HasFocus -or $this.expanded) {
            $captionAttrs = [Colors]::Blend([Color]::Black,   $this.BackgroundColor )
            $specialAttrs = [Colors]::Blend([Color]::DarkRed, $this.BackgroundColor )
        } else {
            $captionAttrs = [Colors]::Blend([Color]::Black,   [Color]::Gray )
            $specialAttrs = [Colors]::Blend([Color]::DarkRed, [Color]::Gray )
        }
        if ( $this.disabled ) {
            $captionAttrs = [Colors]::Blend( [Color]::DarkGray, [Color]::Gray )
        }
        $buffer.FillRectangle( 0, 0, $this.ActualWidth, $this.ActualHeight, ' ', $captionAttrs )
        if ( $null -ne $this.Title ) {
            $attrs = if ($this.Disabled) { $specialAttrs } else { $captionAttrs }
            #$this.RenderString( $this.Title, $buffer, 1, 0, $this.ActualWidth, $this.captionAttrs)
            [CFElement]::RenderText($this.Title, $buffer, 1, 0, $attrs)
        }
        if ( $null -ne $this.TitleRight ) {
            #$this.RenderString( $this.TitleRight, $buffer, $this.ActualWidth - $this.TitleRight.Length - 1, 0, $this.TitleRight.Length, $captionAttrs )
            [CFElement]::RenderText($this.TitleRight, $buffer, $this.ActualWidth - $this.TitleRight.Length - 1, 0, $captionAttrs)
        }
    }
}

class CFMenuItem : CFElement {

    CFMenuItem() {
        $this.SetNativeUI([CFCustomMenuItem]::new())
        $this.WrapProperty("Caption", "Title")
        $this.AddScriptBlockProperty("Action")
        $this.NativeUI.Add_Click({ $this.Control.OnAction() })
    }

    [void] OnAction() {
        $this.InvokeTrappableCommand($this._Action, $this)
    }
}

class CFDropDownMenu : CFButton {

    CFDropDownMenu() {
        $this.Icon         = [CFIcon] @{ Kind = "chevron_down" }
        $this.IconPosition = [IconPosition]::Right

        $this.NativeUI.ContextMenu = [ContextMenu] @{ PopupShadow = $true }
        $this.AddNativeUIChild = {
            param (
                [CFElement] $element
            )
            $this.NativeUI.ContextMenu.Items.Add($element.NativeUI)
        }
        
        $this.Action = {
            param($this)
            [WindowsHost] $windowsHost = [WindowsHost] [ConsoleFramework.ConsoleApplication]::Instance.RootControl
            $point = [Control]::TranslatePoint($this.NativeUI, [Point]::new(0, 0), $windowsHost)
            $this.NativeUI.ContextMenu.OpenMenu($windowsHost, $point)
        }
    }
    
}

class CFAutoComplete : CFTextBox {

    CFAutoComplete() {
        $this.NativeUI.ContextMenu = [ContextMenu]::new()

        $this.NativeUI.Add_KeyDown({
            if ($_.wVirtualScanCode -eq 80) {
                $this.Control.ShowDropDown()
            }
        })

        $this.AddScriptBlockProperty("ItemsRequested")

        $this.CreateMenuItems()
    }

    [void] CreateMenuItems() {
        0..19 | ForEach-Object {
            [MenuItem] $menuItem = [CFCustomMenuItem] @{ Title = $_ }
            $this.StyleMenuItem($menuItem)
            Add-Member -InputObject $menuItem -MemberType NoteProperty -Name AutoCompleteTextBox -Value $this
            Add-Member -InputObject $menuItem -MemberType NoteProperty -Name AutoCompleteId      -Value $_
            $menuItem.Add_Click({
                $this.AutoCompleteTextBox.Text = $this.AutoCompleteId
                $this.AutoCompleteTextBox.SetCursor()
            })
            $this.NativeUI.ContextMenu.Items.Add($menuItem)
        }
    }

    [void] StyleMenuItem($menuItem) {
    }

    [void] SetCursor() {
        $nativeUI = [TextBox]$this.NativeUI
        $position = $this.Text.Length
        $prop = $nativeUI.GetType().GetProperty("CursorPosition", [BindingFlags]::NonPublic -bor [BindingFlags]::Instance)
        $prop.SetValue($nativeUI, [Point]::new($position, 0), $null)
        $prop = $nativeUI.GetType().BaseType.GetField("cursorPosition", [BindingFlags]::NonPublic -bor [BindingFlags]::Instance)
        $prop.SetValue($nativeUI, $position)
        $nativeUI.Invalidate()
    }

    [void] ShowDropDown() {
        $this.ClearDropDown()
        $this.AddItems()
        [WindowsHost] $windowsHost = [WindowsHost] [ConsoleFramework.ConsoleApplication]::Instance.RootControl
        $point = [Control]::TranslatePoint($this.NativeUI, [Point]::new(0, 0), $windowsHost)
        $this.NativeUI.ContextMenu.OpenMenu($windowsHost, $point)
    }
    
    [void] ClearDropDown() {
        $this.NativeUI.ContextMenu.Items | ForEach-Object {
            $_.Visibility = [Visibility]::Collapsed
        }
    }
    
    [void] AddItems() {
        $this.OnItemsRequested()
    }
    
    [void] OnItemsRequested() {
        [AutoCompleteItem[]] $items = Invoke-Command -ScriptBlock $this._ItemsRequested -ArgumentList $this | Select-Object -First 20
        0..($items.Count - 1) | ForEach-Object {
            $this.NativeUI.ContextMenu.Items.Item($_).Title            = $items[$_].Text
            $this.NativeUI.ContextMenu.Items.Item($_).AutoCompleteId   = $items[$_].Id
            $this.NativeUI.ContextMenu.Items.Item($_).Visibility       = [Visibility]::Visible
        }
    }

}

class CFCard : CFElement {
    hidden  [GroupBox]          $CardGroupBox       = [GroupBox]::new()
    hidden  [CFCustomPanel]     $BodyPanel          = [CFCustomPanel]::new()
    hidden                      $CurrentIcon        = [CFIcon]::new()
    hidden  [String]            $Title              = ""

    CFCard() {
        $this.SetNativeUI($this.CardGroupBox)
        $this.CardGroupBox.Content = $this.BodyPanel

        Add-Member -InputObject $this -Name Caption -MemberType ScriptProperty -Value {
            $this.Title
        } -SecondValue {
            $this.Title = $args[0]
            $this.Render()
        }
        Add-Member -InputObject $this -Name Icon -MemberType ScriptProperty -Value {
            $this.CurrentIcon
        } -SecondValue {
            $this.CurrentIcon = $args[0]
            $this.Render()
        }
        $this.AddNativeUIChild = {
            param (
                [CFElement] $element
            )
            $this.BodyPanel.xChildren.Add($element.NativeUI) | Out-Null
        }
        $this.Render()
    }

    [void] Render() {
        if ($this.CurrentIcon -ne $null) {
            $this.CardGroupBox.Title = $this.CurrentIcon.Caption + " " + $this.Title
        } else {
            $this.CardGroupBox.Title = $this.Title
        }
        $this.StyleComponents()
    }

    [void] StyleComponents() {
        $this.BodyPanel.Margin          = [Thickness]::new(1, 1, 1, 0)
    }
}

#region UI Images

class ImageCache {
    static hidden  [hashtable]   $Cache    = @{}

    static [string] GetCachePath([string] $imageSource) {
        if ($null -eq [ImageCache]::Cache."$imageSource") {
            if ($imageSource.ToUpperInvariant().StartsWith("HTTP")) {
                [ImageCache]::CacheWebImage($imageSource)
            } else {
                [ImageCache]::CacheFileImage($imageSource)
            }
        }
        return [ImageCache]::Cache."$imageSource"
    }

    static hidden [void] CacheWebImage([string] $url) {
        $fileName     = (Get-Random -Minimum 1000000 -Maximum 9999999).ToString()
        $extension    = $url.Split(".") | Select-Object -Last 1
        $outputPath   = $env:TMP + [System.IO.Path]::DirectorySeparatorChar + $fileName + "." + $extension
        Invoke-WebRequest $url -OutFile $outputPath | Out-Null
        [ImageCache]::Cache += @{ "$url" = $outputPath } 
    }

    static hidden [void] CacheFileImage([string] $path) {
        [ImageCache]::Cache += @{ "$path" = $path } 
    }
}

Function Get-ClosestConsoleColor {
    param (
        [System.Drawing.Color]   $PixelColor
    ) 
    $Colors = @{ 
        FF000000 =   [Color]::Black          
        FF000080 =   [Color]::DarkBlue       
        FF008000 =   [Color]::DarkGreen      
        FF008080 =   [Color]::DarkCyan       
        FF800000 =   [Color]::DarkRed        
        FF800080 =   [Color]::DarkMagenta    
        FF808000 =   [Color]::DarkYellow     
        FFC0C0C0 =   [Color]::Gray           
        FF808080 =   [Color]::DarkGray       
        FF0000FF =   [Color]::Blue           
        FF00FF00 =   [Color]::Green          
        FF00FFFF =   [Color]::Cyan           
        FFFF0000 =   [Color]::Red            
        FFFF00FF =   [Color]::Magenta        
        FFFFFF00 =   [Color]::Yellow          
        FFFFFFFF =   [Color]::White                  
    }
    $selectedColor = [Color]::Black
    $selectedDiff  = 32000
    foreach ($item in $Colors.Keys) {
        $diffR        = [Int] ("0x" + $item.Substring(2, 2)) - $PixelColor.R
        $diffG        = [Int] ("0x" + $item.Substring(4, 2)) - $PixelColor.G
        $diffB        = [Int] ("0x" + $item.Substring(6, 2)) - $PixelColor.B
        $diffTotal    = [Math]::Abs($diffR) + [Math]::Abs($diffG) + [Math]::Abs($diffB)
        if ($diffTotal -lt $selectedDiff) {
            $selectedColor = $Colors."$item"
            $selectedDiff  = $diffTotal
        }
    }
    $selectedColor
}

Function Get-ClosestConsoleChar {
    param(
        [Parameter(Mandatory)]
        [System.Drawing.Color]   $PixelColor,
        [char[]]                 $Characters   = "█▓▒░ ".ToCharArray() # "$#H&@*+;:-,. ".ToCharArray()
    )
    $c = $characters.count
    $brightness = $PixelColor.GetBrightness() 
    [int]$offset = [Math]::Floor($brightness * $c) 
    $ch = $characters[$offset] 
    if (-not $ch) {
        $ch = $characters[-1]
    }
    $ch
}

#endregion

class CFImagePanel : Panel {
    hidden   [int]             $ImageWidth      = 0
    hidden   [int]             $ImageHeight     = 0
    hidden   [string]          $ImageSource     = ""
    hidden   [Drawing.Image]   $TargetImage     = $null
    hidden   [float]           $Ratio           = 1.5

    CFImagePanel() {
        Add-Member -InputObject $this -Name Source -MemberType ScriptProperty -Value {
            $this.ImageSource
        } -SecondValue {
            if ($this.ImageSource -ne $args[0]) {
                $this.ImageSource = $args[0]
                $this.RefreshImage()
            }
        }
        Add-Member -InputObject $this -Name TargetWidth -MemberType ScriptProperty -Value {
            $this.ImageWidth
        } -SecondValue {
            if ($this.ImageWidth -ne $args[0]) {
                $this.ImageWidth = $args[0]
                $this.RefreshImage()
            }
        }
    }

    [void] RefreshImage() {
        if ($this.ImageWidth -ne 0 -and $this.ImageSource -ne "") {
            $path                = [ImageCache]::GetCachePath($this.Source)
            $image               = [Drawing.Image]::FromFile($Path)
            $this.ImageHeight    = [int] ($image.Height / ($image.Width / $this.ImageWidth) / $this.Ratio)
            if ($this.TargetImage -ne $null) {
                $this.TargetImage.Dispose()
            }
            $this.TargetImage    = new-object Drawing.Bitmap($image ,$this.ImageWidth, $this.ImageHeight) 
            $image.Dispose() | Out-Null
            $this.Invalidate()
        }
    }

    [Size] MeasureOverride([Size] $availableSize) {
        return [Size]::new($this.ImageWidth, $this.ImageHeight)
    }

    [void] Render([RenderingBuffer] $buffer) {
        $bitmap = $this.TargetImage 
    
        for ([int]$y=0; $y -lt $bitmap.Height; $y++) { 
            for ([int]$x=0; $x -lt $bitmap.Width; $x++) {
                $pixelColor = $bitmap.GetPixel($x, $y)
                $character  = Get-ClosestConsoleChar -PixelColor $pixelColor
                #$character = "█"
                #$color = Get-ClosestConsoleColor -PixelColor $pixelColor
                $color      = [Color]::Black
                $buffer.SetPixel($x, $y, $character, [Colors]::Blend($color, [Color]::White))
            }
        }
    }
}

class CFImage : CFElement {
    hidden [int] $TargetWidth   = 0

    CFImage() {
        $imagePanel = [CFImagePanel]::new()
        $this.SetNativeUI($imagePanel)
        $this.WrapProperty("Source", "Source")
        Add-Member -InputObject $this -Name Width -MemberType ScriptProperty -Value {
            $this.TargetWidth
        } -SecondValue {
            $this.TargetWidth              = $args[0]
            $this.NativeUI.TargetWidth     = ([int] $args[0] / 10) # Conversion to character length
        }
    }
}