2020-03-11 21:56:55 +00:00
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : BlockMap.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : GUI custom controls.
//
// --[ Description ] ----------------------------------------------------------
//
// Draws a block map.
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General public License for more details.
//
// You should have received a copy of the GNU General public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
2022-02-18 10:02:53 +00:00
// Copyright © 2011-2022 Natalia Portillo
2020-03-11 21:56:55 +00:00
// ****************************************************************************/
2022-03-07 07:36:44 +00:00
namespace Aaru.Gui.Controls ;
2019-04-21 16:39:51 +01:00
using System ;
2020-04-17 05:23:17 +01:00
using System.Collections.Generic ;
2019-04-21 16:39:51 +01:00
using System.Collections.Specialized ;
2020-04-17 05:23:17 +01:00
using Avalonia ;
using Avalonia.Controls ;
using Avalonia.LogicalTree ;
using Avalonia.Media ;
using Avalonia.Media.Imaging ;
using Avalonia.Platform ;
using Avalonia.Threading ;
2021-09-12 22:42:09 +01:00
using Avalonia.Visuals.Media.Imaging ;
2020-07-22 13:20:25 +01:00
using JetBrains.Annotations ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
// TODO: Partially fill clusters
// TODO: React to size changes
// TODO: Optimize block size to viewport
// TODO: Writing one more than it should
public sealed class BlockMap : ItemsControl
{
const int BLOCK_SIZE = 15 ;
public static readonly StyledProperty < ulong > BlocksProperty =
AvaloniaProperty . Register < Border , ulong > ( nameof ( Blocks ) ) ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < IBrush > SuperFastColorProperty =
AvaloniaProperty . Register < Border , IBrush > ( nameof ( SuperFastColor ) , Brushes . LightGreen ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < IBrush > FastColorProperty =
AvaloniaProperty . Register < Border , IBrush > ( nameof ( FastColor ) , Brushes . Green ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < IBrush > AverageColorProperty =
AvaloniaProperty . Register < Border , IBrush > ( nameof ( AverageColor ) , Brushes . DarkGreen ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < IBrush > SlowColorProperty =
AvaloniaProperty . Register < Border , IBrush > ( nameof ( SlowColor ) , Brushes . Yellow ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < IBrush > SuperSlowColorProperty =
AvaloniaProperty . Register < Border , IBrush > ( nameof ( SuperSlowColor ) , Brushes . Orange ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < IBrush > ProblematicColorProperty =
AvaloniaProperty . Register < Border , IBrush > ( nameof ( ProblematicColor ) , Brushes . Red ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < double > SuperFastMaxTimeProperty =
AvaloniaProperty . Register < Border , double > ( nameof ( SuperFastMaxTime ) , 3 ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < double > FastMaxTimeProperty =
AvaloniaProperty . Register < Border , double > ( nameof ( FastMaxTime ) , 10 ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < double > AverageMaxTimeProperty =
AvaloniaProperty . Register < Border , double > ( nameof ( AverageMaxTime ) , 50 ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < double > SlowMaxTimeProperty =
AvaloniaProperty . Register < Border , double > ( nameof ( SlowMaxTime ) , 150 ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public static readonly StyledProperty < double > SuperSlowMaxTimeProperty =
AvaloniaProperty . Register < Border , double > ( nameof ( SuperSlowMaxTime ) , 500 ) ;
RenderTargetBitmap _bitmap ;
ulong _clusterSize ;
ulong _maxBlocks ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
public double SuperFastMaxTime
{
get = > GetValue ( SuperFastMaxTimeProperty ) ;
set = > SetValue ( SuperFastMaxTimeProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public double FastMaxTime
{
get = > GetValue ( FastMaxTimeProperty ) ;
set = > SetValue ( FastMaxTimeProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public double AverageMaxTime
{
get = > GetValue ( AverageMaxTimeProperty ) ;
set = > SetValue ( AverageMaxTimeProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public double SlowMaxTime
{
get = > GetValue ( SlowMaxTimeProperty ) ;
set = > SetValue ( SlowMaxTimeProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public double SuperSlowMaxTime
{
get = > GetValue ( SuperSlowMaxTimeProperty ) ;
set = > SetValue ( SuperSlowMaxTimeProperty , value ) ;
}
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
public IBrush SuperFastColor
{
get = > GetValue ( SuperFastColorProperty ) ;
set = > SetValue ( SuperFastColorProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public IBrush FastColor
{
get = > GetValue ( FastColorProperty ) ;
set = > SetValue ( FastColorProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public IBrush AverageColor
{
get = > GetValue ( AverageColorProperty ) ;
set = > SetValue ( AverageColorProperty , value ) ;
}
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
public IBrush SlowColor
{
get = > GetValue ( SlowColorProperty ) ;
set = > SetValue ( SlowColorProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public IBrush SuperSlowColor
{
get = > GetValue ( SuperSlowColorProperty ) ;
set = > SetValue ( SuperSlowColorProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public IBrush ProblematicColor
{
get = > GetValue ( ProblematicColorProperty ) ;
set = > SetValue ( ProblematicColorProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
public ulong Blocks
{
get = > GetValue ( BlocksProperty ) ;
set = > SetValue ( BlocksProperty , value ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
protected override void OnPropertyChanged < T > ( [ NotNull ] AvaloniaPropertyChangedEventArgs < T > e )
{
base . OnPropertyChanged ( e ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
switch ( e . Property . Name )
{
case nameof ( Blocks ) :
if ( _maxBlocks = = 0 )
_maxBlocks = ( ulong ) ( Width / BLOCK_SIZE * ( Height / BLOCK_SIZE ) ) ;
2020-02-29 18:03:35 +00:00
2022-03-06 13:29:38 +00:00
if ( Blocks > _maxBlocks )
{
_clusterSize = Blocks / _maxBlocks ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
if ( Blocks % _maxBlocks > 0 )
_clusterSize + + ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
if ( Blocks / _clusterSize < _maxBlocks )
2020-04-17 05:23:17 +01:00
{
2022-03-06 13:29:38 +00:00
_maxBlocks = Blocks / _clusterSize ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
if ( Blocks % _clusterSize > 0 )
_maxBlocks + + ;
}
}
else
{
_clusterSize = 1 ;
_maxBlocks = Blocks ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
CreateBitmap ( ) ;
DrawGrid ( ) ;
RedrawAll ( ) ;
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
break ;
case nameof ( SuperFastMaxTime ) :
case nameof ( FastMaxTime ) :
case nameof ( AverageMaxTime ) :
case nameof ( SlowMaxTime ) :
case nameof ( SuperSlowMaxTime ) :
case nameof ( SuperFastColor ) :
case nameof ( FastColor ) :
case nameof ( AverageColor ) :
case nameof ( SlowColor ) :
case nameof ( SuperSlowColor ) :
case nameof ( ProblematicColor ) :
2019-04-21 16:39:51 +01:00
2020-04-17 05:23:17 +01:00
CreateBitmap ( ) ;
2022-03-06 13:29:38 +00:00
DrawGrid ( ) ;
RedrawAll ( ) ;
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
2021-09-12 22:42:09 +01:00
2022-03-06 13:29:38 +00:00
break ;
2020-04-17 05:23:17 +01:00
}
2022-03-06 13:29:38 +00:00
}
2019-04-21 16:39:51 +01:00
2022-03-17 23:54:41 +00:00
public override void Render ( DrawingContext context )
2022-03-06 13:29:38 +00:00
{
if ( ( int? ) _bitmap ? . Size . Height ! = ( int ) Height | |
( int? ) _bitmap ? . Size . Width ! = ( int ) Width )
2019-04-21 16:39:51 +01:00
{
2022-03-06 13:29:38 +00:00
_maxBlocks = ( ulong ) ( Width / BLOCK_SIZE * ( Height / BLOCK_SIZE ) ) ;
CreateBitmap ( ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
context . DrawImage ( _bitmap , new Rect ( 0 , 0 , Width , Height ) , new Rect ( 0 , 0 , Width , Height ) ,
BitmapInterpolationMode . HighQuality ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
base . Render ( context ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
protected override void ItemsCollectionChanged ( object sender , [ NotNull ] NotifyCollectionChangedEventArgs e )
{
base . ItemsCollectionChanged ( sender , e ) ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
switch ( e . Action )
{
case NotifyCollectionChangedAction . Add :
case NotifyCollectionChangedAction . Replace :
{
2022-11-14 01:20:28 +00:00
if ( e . NewItems is not { } items )
2022-03-06 13:29:38 +00:00
throw new ArgumentException ( "Invalid list of items" ) ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
using IDrawingContextImpl ctxi = _bitmap . CreateDrawingContext ( null ) ;
using var ctx = new DrawingContext ( ctxi , false ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
foreach ( object item in items )
2020-04-17 05:23:17 +01:00
{
2022-11-14 01:20:28 +00:00
if ( item is not ValueTuple < ulong , double > block )
2022-03-06 13:29:38 +00:00
throw new ArgumentException ( "Invalid item in list" , nameof ( Items ) ) ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
DrawCluster ( block . Item1 , block . Item2 , false , ctx ) ;
}
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
break ;
}
case NotifyCollectionChangedAction . Remove :
case NotifyCollectionChangedAction . Move :
{
2022-11-14 01:20:28 +00:00
if ( e . NewItems is not { } newItems | |
e . OldItems is not { } oldItems )
2022-03-06 13:29:38 +00:00
throw new ArgumentException ( "Invalid list of items" ) ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
using IDrawingContextImpl ctxi = _bitmap . CreateDrawingContext ( null ) ;
using var ctx = new DrawingContext ( ctxi , false ) ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
foreach ( object item in oldItems )
{
2022-11-14 01:20:28 +00:00
if ( item is not ValueTuple < ulong , double > block )
2022-03-06 13:29:38 +00:00
throw new ArgumentException ( "Invalid item in list" , nameof ( Items ) ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
DrawCluster ( block . Item1 , block . Item2 , false , ctx ) ;
2020-04-17 05:23:17 +01:00
}
2020-02-29 18:03:35 +00:00
2022-03-06 13:29:38 +00:00
foreach ( object item in newItems )
{
2022-11-14 01:20:28 +00:00
if ( item is not ValueTuple < ulong , double > block )
2022-03-06 13:29:38 +00:00
throw new ArgumentException ( "Invalid item in list" , nameof ( Items ) ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
DrawCluster ( block . Item1 , block . Item2 , false , ctx ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
break ;
2020-04-17 05:23:17 +01:00
}
2022-03-06 13:29:38 +00:00
case NotifyCollectionChangedAction . Reset :
CreateBitmap ( ) ;
DrawGrid ( ) ;
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
break ;
default : throw new ArgumentOutOfRangeException ( ) ;
2020-04-17 05:23:17 +01:00
}
2022-03-06 13:29:38 +00:00
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
void RedrawAll ( )
{
if ( Items is null )
return ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
using IDrawingContextImpl ctxi = _bitmap . CreateDrawingContext ( null ) ;
using var ctx = new DrawingContext ( ctxi , false ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
foreach ( object item in Items )
{
2022-11-14 01:20:28 +00:00
if ( item is not ValueTuple < ulong , double > block )
2022-03-06 13:29:38 +00:00
throw new ArgumentException ( "Invalid item in list" , nameof ( Items ) ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
DrawCluster ( block . Item1 , block . Item2 , false , ctx ) ;
2020-04-17 05:23:17 +01:00
}
2022-03-06 13:29:38 +00:00
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
}
void DrawCluster ( ulong block , double duration , bool clear = false , DrawingContext ctx = null )
{
if ( double . IsNegative ( duration ) | |
double . IsInfinity ( duration ) )
throw new ArgumentException ( "Duration cannot be negative or infinite" , nameof ( duration ) ) ;
bool newContext = ctx is null ;
ulong clustersPerRow = ( ulong ) Width / BLOCK_SIZE ;
ulong cluster = block / _clusterSize ;
ulong row = cluster / clustersPerRow ;
ulong column = cluster % clustersPerRow ;
ulong x = column * BLOCK_SIZE ;
ulong y = row * BLOCK_SIZE ;
var pen = new Pen ( Foreground ) ;
IBrush brush ;
if ( clear )
brush = Background ;
else if ( duration < SuperFastMaxTime )
brush = SuperFastColor ;
else if ( duration > = SuperFastMaxTime & &
duration < FastMaxTime )
brush = FastColor ;
else if ( duration > = FastMaxTime & &
duration < AverageMaxTime )
brush = AverageColor ;
else if ( duration > = AverageMaxTime & &
duration < SlowMaxTime )
brush = SlowColor ;
else if ( duration > = SlowMaxTime & &
duration < SuperSlowMaxTime )
brush = SuperSlowColor ;
else if ( duration > = SuperSlowMaxTime | |
double . IsNaN ( duration ) )
brush = ProblematicColor ;
else
brush = Background ;
if ( newContext )
2020-04-17 05:23:17 +01:00
{
2022-03-06 13:29:38 +00:00
using IDrawingContextImpl ctxi = _bitmap . CreateDrawingContext ( null ) ;
ctx = new DrawingContext ( ctxi , false ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
ctx . FillRectangle ( brush , new Rect ( x , y , BLOCK_SIZE , BLOCK_SIZE ) ) ;
ctx . DrawRectangle ( pen , new Rect ( x , y , BLOCK_SIZE , BLOCK_SIZE ) ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
if ( double . IsNaN ( duration ) )
{
ctx . DrawLine ( pen , new Point ( x , y ) , new Point ( x + BLOCK_SIZE , y + BLOCK_SIZE ) ) ;
ctx . DrawLine ( pen , new Point ( x , y + BLOCK_SIZE ) , new Point ( x + BLOCK_SIZE , y ) ) ;
2020-04-17 05:23:17 +01:00
}
2022-03-06 13:29:38 +00:00
if ( newContext )
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
protected override void ItemsChanged ( [ NotNull ] AvaloniaPropertyChangedEventArgs e )
{
if ( e . NewValue ! = null & &
2022-11-14 01:20:28 +00:00
e . NewValue is not IList < ( ulong , double ) > )
2022-03-06 13:29:38 +00:00
throw new
ArgumentException ( "Items must be a IList<(ulong, double)> with ulong being the block and double being the time spent reading it, or NaN for an error." ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
base . ItemsChanged ( e ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
CreateBitmap ( ) ;
DrawGrid ( ) ;
RedrawAll ( ) ;
Dispatcher . UIThread . Post ( InvalidateVisual , DispatcherPriority . Background ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
void CreateBitmap ( )
{
if ( _maxBlocks = = 0 )
_maxBlocks = ( ulong ) ( Width / BLOCK_SIZE * ( Height / BLOCK_SIZE ) ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
_bitmap ? . Dispose ( ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
_bitmap = new RenderTargetBitmap ( new PixelSize ( ( int ) Width , ( int ) Height ) , new Vector ( 96 , 96 ) ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
using IDrawingContextImpl ctxi = _bitmap . CreateDrawingContext ( null ) ;
using var ctx = new DrawingContext ( ctxi , false ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
ctx . FillRectangle ( Background , new Rect ( 0 , 0 , Width , Height ) ) ;
}
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
void DrawGrid ( )
{
using IDrawingContextImpl ctxi = _bitmap . CreateDrawingContext ( null ) ;
using var ctx = new DrawingContext ( ctxi , false ) ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
ulong clustersPerRow = ( ulong ) Width / BLOCK_SIZE ;
2020-04-17 05:23:17 +01:00
2022-03-07 07:36:44 +00:00
var allBlocksDrawn = false ;
2020-04-17 05:23:17 +01:00
2022-03-06 13:29:38 +00:00
for ( ulong y = 0 ; y < Height & & ! allBlocksDrawn ; y + = BLOCK_SIZE )
2021-07-13 21:59:52 -07:00
{
2022-03-06 13:29:38 +00:00
for ( ulong x = 0 ; x < Width ; x + = BLOCK_SIZE )
2021-07-13 21:59:52 -07:00
{
2022-03-07 07:36:44 +00:00
ulong currentBlockValue = y * clustersPerRow / BLOCK_SIZE + x / BLOCK_SIZE ;
2021-08-17 21:23:10 +01:00
2022-03-06 13:29:38 +00:00
if ( currentBlockValue > = _maxBlocks | |
currentBlockValue > = Blocks )
2021-07-13 21:59:52 -07:00
{
2022-03-06 13:29:38 +00:00
allBlocksDrawn = true ;
break ;
2021-07-13 21:59:52 -07:00
}
2022-03-06 13:29:38 +00:00
ctx . DrawRectangle ( new Pen ( Foreground ) , new Rect ( x , y , BLOCK_SIZE , BLOCK_SIZE ) ) ;
2021-07-13 21:59:52 -07:00
}
}
2022-03-06 13:29:38 +00:00
}
2021-07-13 21:59:52 -07:00
2022-03-06 13:29:38 +00:00
void DrawSquares ( Color [ ] colors , int borderWidth , int sideLength )
{
using IDrawingContextImpl ctxi = _bitmap . CreateDrawingContext ( null ) ;
using var ctx = new DrawingContext ( ctxi , false ) ;
2019-04-21 16:39:51 +01:00
2022-03-07 07:36:44 +00:00
int squareWidth = ( sideLength - 2 * borderWidth ) / colors . Length ;
2022-03-06 13:29:38 +00:00
int squareHeight = squareWidth ;
2022-03-07 07:36:44 +00:00
var x = 0 ;
var y = 0 ;
2019-04-21 16:39:51 +01:00
2022-03-06 13:29:38 +00:00
foreach ( Color color in colors )
{
ctx . FillRectangle ( new SolidColorBrush ( color ) , new Rect ( x , y , squareWidth , squareHeight ) ) ;
2022-03-07 07:36:44 +00:00
x + = squareWidth + 2 * borderWidth ;
2019-04-21 16:39:51 +01:00
2022-11-13 21:14:18 +00:00
if ( x < sideLength )
continue ;
x = 0 ;
y + = squareHeight + 2 * borderWidth ;
2019-04-21 16:39:51 +01:00
}
2022-03-06 13:29:38 +00:00
}
2020-02-29 18:03:35 +00:00
2022-03-06 13:29:38 +00:00
protected override void OnAttachedToLogicalTree ( LogicalTreeAttachmentEventArgs e )
{
if ( Width < 1 | |
Height < 1 | |
double . IsNaN ( Width ) | |
double . IsNaN ( Height ) )
2020-04-17 05:23:17 +01:00
{
2022-03-06 13:29:38 +00:00
base . OnAttachedToLogicalTree ( e ) ;
return ;
2020-04-17 05:23:17 +01:00
}
2022-03-06 13:29:38 +00:00
CreateBitmap ( ) ;
DrawGrid ( ) ;
base . OnAttachedToLogicalTree ( e ) ;
}
protected override void OnDetachedFromLogicalTree ( LogicalTreeAttachmentEventArgs e )
{
_bitmap . Dispose ( ) ;
_bitmap = null ;
base . OnDetachedFromLogicalTree ( e ) ;
2019-04-21 16:39:51 +01:00
}
}