1
1
using AvaGui . Models ;
2
+ using Avalonia . Controls . Selection ;
2
3
using Avalonia . Media . Imaging ;
4
+ using Avalonia . Threading ;
3
5
using OpenLoco . Dat ;
4
6
using OpenLoco . Dat . Types ;
5
7
using ReactiveUI ;
13
15
using System . Reactive . Linq ;
14
16
using System . Threading . Tasks ;
15
17
using System . Windows . Input ;
18
+ using Image = SixLabors . ImageSharp . Image ;
16
19
17
20
namespace AvaGui . ViewModels
18
21
{
@@ -36,65 +39,85 @@ public ImageTableViewModel(IHasG1Elements g1ElementProvider, IImageTableNameProv
36
39
. Subscribe ( _ => this . RaisePropertyChanged ( nameof ( Images ) ) ) ;
37
40
_ = this . WhenAnyValue ( o => o . SelectedImageIndex )
38
41
. Subscribe ( _ => this . RaisePropertyChanged ( nameof ( SelectedG1Element ) ) ) ;
39
-
40
42
_ = this . WhenAnyValue ( o => o . Images )
41
43
. Subscribe ( _ => this . RaisePropertyChanged ( nameof ( Images ) ) ) ;
44
+ _ = this . WhenAnyValue ( o => o . AnimationSpeed )
45
+ . Subscribe ( _ => UpdateAnimationSpeed ( ) ) ;
42
46
43
47
ImportImagesCommand = ReactiveCommand . Create ( ImportImages ) ;
44
48
ExportImagesCommand = ReactiveCommand . Create ( ExportImages ) ;
49
+
50
+ SelectionModel = new SelectionModel < Bitmap >
51
+ {
52
+ SingleSelect = false
53
+ } ;
54
+ SelectionModel . SelectionChanged += SelectionChanged ;
55
+
56
+ // Set up the animation timer
57
+ animationTimer = new DispatcherTimer
58
+ {
59
+ Interval = TimeSpan . FromMilliseconds ( 25 ) // Adjust animation speed as needed
60
+ } ;
61
+ animationTimer . Tick += AnimationTimer_Tick ;
62
+ animationTimer . Start ( ) ;
45
63
}
46
64
47
- public async Task ImportImages ( )
65
+ readonly DispatcherTimer animationTimer ;
66
+ int currentFrameIndex ;
67
+
68
+ public IList < Bitmap > SelectedBitmaps { get ; set ; }
69
+
70
+ [ Reactive ]
71
+ public Bitmap SelectedBitmapPreview { get ; set ; }
72
+
73
+ [ Reactive ]
74
+ public int AnimationWindowHeight { get ; set ; }
75
+
76
+ void SelectionChanged ( object sender , SelectionModelSelectionChangedEventArgs e )
48
77
{
49
- var folders = await PlatformSpecific . OpenFolderPicker ( ) ;
50
- var dir = folders . FirstOrDefault ( ) ;
51
- if ( dir == null )
78
+ var sm = ( SelectionModel < Bitmap > ) sender ;
79
+
80
+ if ( sm . SelectedIndexes . Count > 0 )
52
81
{
53
- return ;
82
+ SelectedImageIndex = sm . SelectedIndex ;
54
83
}
55
84
56
- var dirPath = dir . Path . LocalPath ;
57
- if ( Directory . Exists ( dirPath ) && Directory . EnumerateFiles ( dirPath ) . Any ( ) )
58
- {
59
- var files = Directory . GetFiles ( dirPath ) ;
60
- var sorted = files . OrderBy ( f => int . Parse ( Path . GetFileNameWithoutExtension ( f ) . Split ( '-' ) [ 0 ] ) ) ;
85
+ // ... handle selection changed
86
+ SelectedBitmaps = sm . SelectedItems . Cast < Bitmap > ( ) . ToList ( ) ;
87
+ AnimationWindowHeight = ( int ) SelectedBitmaps . Max ( x => x . Size . Height ) * 2 ;
88
+ }
61
89
62
- var g1Elements = new List < G1Element32 > ( ) ;
63
- var i = 0 ;
64
- foreach ( var file in sorted )
65
- {
66
- var img = Image . Load < Rgba32 > ( file ) ;
67
- Images [ i ] = img ;
68
- var currG1 = G1Provider . G1Elements [ i ++ ] ;
69
- currG1 . ImageData = PaletteMap . ConvertRgba32ImageToG1Data ( img , currG1 . Flags ) ; // simply overwrite existing pixel data
70
- }
90
+ [ Reactive ]
91
+ public int AnimationSpeed { get ; set ; } = 40 ;
92
+
93
+ void UpdateAnimationSpeed ( )
94
+ {
95
+ if ( animationTimer == null )
96
+ {
97
+ return ;
71
98
}
72
99
73
- this . RaisePropertyChanged ( nameof ( Bitmaps ) ) ;
74
- //this.RaisePropertyChanged(nameof(Images));
100
+ animationTimer . Interval = TimeSpan . FromMilliseconds ( 1000 / AnimationSpeed ) ;
75
101
}
76
102
77
- public async Task ExportImages ( )
103
+ private void AnimationTimer_Tick ( object ? sender , EventArgs e )
78
104
{
79
- var folders = await PlatformSpecific . OpenFolderPicker ( ) ;
80
- var dir = folders . FirstOrDefault ( ) ;
81
- if ( dir == null )
105
+ if ( SelectedBitmaps == null || SelectedBitmaps . Count == 0 )
82
106
{
83
107
return ;
84
108
}
85
109
86
- var dirPath = dir . Path . LocalPath ;
87
- if ( Directory . Exists ( dirPath ) )
110
+ if ( currentFrameIndex >= SelectedBitmaps . Count )
88
111
{
89
- var counter = 0 ;
90
- foreach ( var image in Images )
91
- {
92
- var imageName = counter ++ . ToString ( ) ; // todo: use GetImageName from winforms project
93
- var path = Path . Combine ( dir . Path . LocalPath , $ "{ imageName } .png") ;
94
- //logger.Debug($"Saving image to {path}");
95
- await image . SaveAsPngAsync ( path ) ;
96
- }
112
+ currentFrameIndex = 0 ;
97
113
}
114
+
115
+ // Update the displayed image
116
+ SelectedBitmapPreview = SelectedBitmaps [ currentFrameIndex ] ;
117
+ SelectedImageIndex = SelectionModel . SelectedIndexes [ currentFrameIndex ] ;
118
+
119
+ // Move to the next frame, looping back to the beginning if necessary
120
+ currentFrameIndex = ( currentFrameIndex + 1 ) % SelectedBitmaps . Count ;
98
121
}
99
122
100
123
[ Reactive ]
@@ -109,9 +132,11 @@ public async Task ExportImages()
109
132
[ Reactive ]
110
133
public int Zoom { get ; set ; } = 1 ;
111
134
135
+ // where the actual image data is stored
112
136
[ Reactive ]
113
137
public IList < Image < Rgba32 > > Images { get ; set ; }
114
138
139
+ // what is displaying on the ui
115
140
public IList < Bitmap ? > Bitmaps
116
141
{
117
142
get
@@ -139,11 +164,66 @@ public IList<Bitmap?> Bitmaps
139
164
[ Reactive ]
140
165
public int SelectedImageIndex { get ; set ; } = - 1 ;
141
166
167
+ [ Reactive ]
168
+ public SelectionModel < Bitmap > SelectionModel { get ; set ; }
169
+
142
170
public UIG1Element32 ? SelectedG1Element
143
171
=> SelectedImageIndex == - 1 || G1Provider . G1Elements . Count == 0
144
172
? null
145
173
: new UIG1Element32 ( SelectedImageIndex , GetImageName ( NameProvider , SelectedImageIndex ) , G1Provider . G1Elements [ SelectedImageIndex ] ) ;
146
174
175
+ public async Task ImportImages ( )
176
+ {
177
+ var folders = await PlatformSpecific . OpenFolderPicker ( ) ;
178
+ var dir = folders . FirstOrDefault ( ) ;
179
+ if ( dir == null )
180
+ {
181
+ return ;
182
+ }
183
+
184
+ var dirPath = dir . Path . LocalPath ;
185
+ if ( Directory . Exists ( dirPath ) && Directory . EnumerateFiles ( dirPath ) . Any ( ) )
186
+ {
187
+ var files = Directory . GetFiles ( dirPath ) ;
188
+ var sorted = files . OrderBy ( f => int . Parse ( Path . GetFileNameWithoutExtension ( f ) . Split ( '-' ) [ 0 ] ) ) ;
189
+
190
+ var g1Elements = new List < G1Element32 > ( ) ;
191
+ var i = 0 ;
192
+ foreach ( var file in sorted )
193
+ {
194
+ var img = Image . Load < Rgba32 > ( file ) ;
195
+ Images [ i ] = img ;
196
+ var currG1 = G1Provider . G1Elements [ i ++ ] ;
197
+ currG1 . ImageData = PaletteMap . ConvertRgba32ImageToG1Data ( img , currG1 . Flags ) ; // simply overwrite existing pixel data
198
+ }
199
+ }
200
+
201
+ this . RaisePropertyChanged ( nameof ( Bitmaps ) ) ;
202
+ }
203
+
204
+ public async Task ExportImages ( )
205
+ {
206
+ var folders = await PlatformSpecific . OpenFolderPicker ( ) ;
207
+ var dir = folders . FirstOrDefault ( ) ;
208
+ if ( dir == null )
209
+ {
210
+ return ;
211
+ }
212
+
213
+ var dirPath = dir . Path . LocalPath ;
214
+ if ( Directory . Exists ( dirPath ) )
215
+ {
216
+ var counter = 0 ;
217
+ foreach ( var image in Images )
218
+ {
219
+ var imageName = counter ++ . ToString ( ) ; // todo: use GetImageName from winforms project
220
+ var path = Path . Combine ( dir . Path . LocalPath , $ "{ imageName } .png") ;
221
+ //logger.Debug($"Saving image to {path}");
222
+ await image . SaveAsPngAsync ( path ) ;
223
+ }
224
+ }
225
+ }
226
+
147
227
public static string GetImageName ( IImageTableNameProvider nameProvider , int counter )
148
228
=> nameProvider . TryGetImageName ( counter , out var value ) && ! string . IsNullOrEmpty ( value )
149
229
? value
0 commit comments