mirror of
https://github.com/azaion/annotations.git
synced 2026-04-22 09:46:30 +00:00
fix editing non-timed annotations
This commit is contained in:
@@ -14,7 +14,7 @@ public class AnnotationControl : Border
|
||||
|
||||
private readonly Grid _grid;
|
||||
private readonly TextBlock _classNameLabel;
|
||||
public TimeSpan Time { get; set; }
|
||||
public TimeSpan? Time { get; set; }
|
||||
|
||||
private AnnotationClass _annotationClass = null!;
|
||||
public AnnotationClass AnnotationClass
|
||||
@@ -41,7 +41,7 @@ public class AnnotationControl : Border
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotationControl(AnnotationClass annotationClass, TimeSpan time, Action<object, MouseButtonEventArgs> resizeStart)
|
||||
public AnnotationControl(AnnotationClass annotationClass, TimeSpan? time, Action<object, MouseButtonEventArgs> resizeStart)
|
||||
{
|
||||
Time = time;
|
||||
_resizeStart = resizeStart;
|
||||
|
||||
@@ -34,13 +34,13 @@ public class CanvasEditor : Canvas
|
||||
public static readonly DependencyProperty GetTimeFuncProp =
|
||||
DependencyProperty.Register(
|
||||
nameof(GetTimeFunc),
|
||||
typeof(Func<TimeSpan>),
|
||||
typeof(Func<TimeSpan?>),
|
||||
typeof(CanvasEditor),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public Func<TimeSpan> GetTimeFunc
|
||||
public Func<TimeSpan?> GetTimeFunc
|
||||
{
|
||||
get => (Func<TimeSpan>)GetValue(GetTimeFuncProp);
|
||||
get => (Func<TimeSpan?>)GetValue(GetTimeFuncProp);
|
||||
set => SetValue(GetTimeFuncProp, value);
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ public class CanvasEditor : Canvas
|
||||
});
|
||||
}
|
||||
|
||||
public AnnotationControl CreateAnnotation(AnnotationClass annClass, TimeSpan time, CanvasLabel canvasLabel)
|
||||
public AnnotationControl CreateAnnotation(AnnotationClass annClass, TimeSpan? time, CanvasLabel canvasLabel)
|
||||
{
|
||||
var annotationControl = new AnnotationControl(annClass, time, AnnotationResizeStart)
|
||||
{
|
||||
@@ -354,7 +354,10 @@ public class CanvasEditor : Canvas
|
||||
|
||||
public void ClearExpiredAnnotations(TimeSpan time)
|
||||
{
|
||||
var expiredAnns = CurrentAnns.Where(x => Math.Abs((time - x.Time).TotalMilliseconds) > _viewThreshold.TotalMilliseconds).ToList();
|
||||
var expiredAnns = CurrentAnns.Where(x =>
|
||||
x.Time.HasValue &&
|
||||
Math.Abs((time - x.Time.Value).TotalMilliseconds) > _viewThreshold.TotalMilliseconds)
|
||||
.ToList();
|
||||
RemoveAnnotations(expiredAnns);
|
||||
}
|
||||
}
|
||||
@@ -24,19 +24,18 @@ public class FormState
|
||||
public TimeSpan? GetTime(string name)
|
||||
{
|
||||
var timeStr = name.Split("_").LastOrDefault();
|
||||
if (string.IsNullOrEmpty(timeStr))
|
||||
if (string.IsNullOrEmpty(timeStr) || timeStr.Length < 7)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
//For some reason, TimeSpan.ParseExact doesn't work on every platform.
|
||||
return new TimeSpan(
|
||||
days: 0,
|
||||
hours: int.Parse(timeStr[0..1]),
|
||||
minutes: int.Parse(timeStr[1..3]),
|
||||
seconds: int.Parse(timeStr[3..5]),
|
||||
milliseconds: int.Parse(timeStr[5..6]) * 100);
|
||||
}
|
||||
catch (Exception e) { return null; }
|
||||
//For some reason, TimeSpan.ParseExact doesn't work on every platform.
|
||||
if (!int.TryParse(timeStr[0..1], out var hours))
|
||||
return null;
|
||||
if (!int.TryParse(timeStr[1..3], out var minutes))
|
||||
return null;
|
||||
if (!int.TryParse(timeStr[3..5], out var seconds))
|
||||
return null;
|
||||
if (!int.TryParse(timeStr[5..6], out var milliseconds))
|
||||
return null;
|
||||
return new TimeSpan(0, hours, minutes, seconds, milliseconds * 100);
|
||||
}
|
||||
}
|
||||
@@ -135,31 +135,24 @@ public class YoloLabel : Label
|
||||
|
||||
var strings = s.Replace(',', '.').Split(' ');
|
||||
if (strings.Length != 5)
|
||||
return null;
|
||||
throw new Exception("Wrong labels format!");
|
||||
|
||||
try
|
||||
var res = new YoloLabel
|
||||
{
|
||||
var res = new YoloLabel
|
||||
{
|
||||
ClassNumber = int.Parse(strings[0], CultureInfo.InvariantCulture),
|
||||
CenterX = double.Parse(strings[1], CultureInfo.InvariantCulture),
|
||||
CenterY = double.Parse(strings[2], CultureInfo.InvariantCulture),
|
||||
Width = double.Parse(strings[3], CultureInfo.InvariantCulture),
|
||||
Height = double.Parse(strings[4], CultureInfo.InvariantCulture)
|
||||
};
|
||||
return res;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ClassNumber = int.Parse(strings[0], CultureInfo.InvariantCulture),
|
||||
CenterX = double.Parse(strings[1], CultureInfo.InvariantCulture),
|
||||
CenterY = double.Parse(strings[2], CultureInfo.InvariantCulture),
|
||||
Width = double.Parse(strings[3], CultureInfo.InvariantCulture),
|
||||
Height = double.Parse(strings[4], CultureInfo.InvariantCulture)
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
public static async Task<List<YoloLabel>> ReadFromFile(string filename)
|
||||
{
|
||||
var str = await File.ReadAllTextAsync(filename);
|
||||
|
||||
return str.Split(Environment.NewLine)
|
||||
return str.Split('\n')
|
||||
.Select(Parse)
|
||||
.Where(ann => ann != null)
|
||||
.ToList()!;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Azaion.Annotator.Extensions;
|
||||
@@ -26,6 +27,7 @@ public class ThumbnailDto : INotifyPropertyChanged
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
public string ImageName => Path.GetFileName(ImagePath);
|
||||
|
||||
public void UpdateImage() => _image = null;
|
||||
|
||||
|
||||
@@ -12,7 +12,22 @@
|
||||
|
||||
<Window.Resources>
|
||||
<DataTemplate x:Key="ThumbnailTemplate" DataType="{x:Type dto:ThumbnailDto}">
|
||||
<Image Source="{Binding Image}" Width="480" Height="270" Margin="5" />
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
<RowDefinition Height="32"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Source="{Binding Image}"
|
||||
Width="480"
|
||||
Height="270"
|
||||
Margin="2" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Foreground="LightGray"
|
||||
Text="{Binding ImageName}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Window.Resources>
|
||||
|
||||
@@ -78,6 +93,7 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
@@ -87,23 +103,40 @@
|
||||
</ItemsPanelTemplate>
|
||||
</StatusBar.ItemsPanel>
|
||||
<StatusBarItem Grid.Column="0" Background="Black">
|
||||
<TextBlock>Loading:</TextBlock>
|
||||
<TextBlock Name="LoadingAnnsCaption">Завантаження:</TextBlock>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="1" Background="Black">
|
||||
<controls:UpdatableProgressBar x:Name="Volume"
|
||||
Width="150"
|
||||
Height="15"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="#252525"
|
||||
BorderBrush="#252525"
|
||||
Foreground="LightBlue"
|
||||
Maximum="100"
|
||||
Minimum="0">
|
||||
</controls:UpdatableProgressBar>
|
||||
<ProgressBar x:Name="LoadingAnnsBar"
|
||||
Width="150"
|
||||
Height="15"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="#252525"
|
||||
BorderBrush="#252525"
|
||||
Foreground="LightBlue"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="0">
|
||||
</ProgressBar>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Grid.Column="2" Background="Black">
|
||||
<TextBlock Name="RefreshThumbCaption">База іконок:</TextBlock>
|
||||
</StatusBarItem>
|
||||
<Separator Grid.Column="2"/>
|
||||
<StatusBarItem Grid.Column="3" Background="Black">
|
||||
<TextBlock Name="StatusText" Text="Loading..."/>
|
||||
<ProgressBar x:Name="RefreshThumbBar"
|
||||
Width="150"
|
||||
Height="15"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="#252525"
|
||||
BorderBrush="#252525"
|
||||
Foreground="LightBlue"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="0">
|
||||
</ProgressBar>
|
||||
</StatusBarItem>
|
||||
<Separator Grid.Column="4"/>
|
||||
<StatusBarItem Grid.Column="5" Background="Black">
|
||||
<TextBlock Name="StatusText" Text=""/>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Azaion.Annotator.DTO;
|
||||
using Azaion.Annotator.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -35,7 +33,8 @@ public partial class DatasetExplorer
|
||||
Config config,
|
||||
ILogger<DatasetExplorer> logger,
|
||||
IConfigRepository configRepository,
|
||||
FormState formState)
|
||||
FormState formState,
|
||||
IGalleryManager galleryManager)
|
||||
{
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
@@ -93,6 +92,8 @@ public partial class DatasetExplorer
|
||||
SizeChanged += async (_, _) => await SaveUserSettings();
|
||||
LocationChanged += async (_, _) => await SaveUserSettings();
|
||||
StateChanged += async (_, _) => await SaveUserSettings();
|
||||
|
||||
RefreshThumbBar.Value = galleryManager.ThumbnailsPercentage;
|
||||
};
|
||||
|
||||
Closing += (sender, args) =>
|
||||
@@ -139,7 +140,12 @@ public partial class DatasetExplorer
|
||||
}
|
||||
};
|
||||
|
||||
ExplorerEditor.GetTimeFunc = () => _formState.GetTime(CurrentThumbnail!.ImagePath)!.Value;
|
||||
ExplorerEditor.GetTimeFunc = () => _formState.GetTime(CurrentThumbnail!.ImagePath);
|
||||
galleryManager.ThumbnailsUpdate += thumbnailsPercentage =>
|
||||
{
|
||||
Dispatcher.Invoke(() => RefreshThumbBar.Value = thumbnailsPercentage);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private async Task EditAnnotation()
|
||||
@@ -161,7 +167,7 @@ public partial class DatasetExplorer
|
||||
Switcher.SelectedIndex = 1;
|
||||
LvClasses.SelectedIndex = 1;
|
||||
|
||||
var time = _formState.GetTime(dto.ImagePath)!.Value;
|
||||
var time = _formState.GetTime(dto.ImagePath);
|
||||
ExplorerEditor.RemoveAllAnns();
|
||||
foreach (var ann in await YoloLabel.ReadFromFile(dto.LabelPath))
|
||||
{
|
||||
@@ -215,6 +221,9 @@ public partial class DatasetExplorer
|
||||
|
||||
private async Task ReloadThumbnails()
|
||||
{
|
||||
LoadingAnnsCaption.Visibility = Visibility.Visible;
|
||||
LoadingAnnsBar.Visibility = Visibility.Visible;
|
||||
|
||||
if (!Directory.Exists(_config.ThumbnailsDirectory))
|
||||
return;
|
||||
|
||||
@@ -227,50 +236,72 @@ public partial class DatasetExplorer
|
||||
await AddThumbnail(thumbnail);
|
||||
|
||||
if (thumbNum % 1000 == 0)
|
||||
{
|
||||
await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache));
|
||||
LoadingAnnsBar.Value = thumbNum * 100.0 / thumbnails.Length;
|
||||
}
|
||||
thumbNum++;
|
||||
}
|
||||
|
||||
LoadingAnnsCaption.Visibility = Visibility.Collapsed;
|
||||
LoadingAnnsBar.Visibility = Visibility.Collapsed;
|
||||
await File.WriteAllTextAsync(_thumbnailsCacheFile, JsonConvert.SerializeObject(LabelsCache));
|
||||
}
|
||||
|
||||
private async Task AddThumbnail(string thumbnail)
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length];
|
||||
var imageName = Path.Combine(_config.ImagesDirectory, name);
|
||||
foreach (var f in _config.ImageFormats)
|
||||
try
|
||||
{
|
||||
var curName = $"{imageName}.{f}";
|
||||
if (File.Exists(curName))
|
||||
var name = Path.GetFileNameWithoutExtension(thumbnail)[..^Config.ThumbnailPrefix.Length];
|
||||
var imageName = Path.Combine(_config.ImagesDirectory, name);
|
||||
foreach (var f in _config.ImageFormats)
|
||||
{
|
||||
imageName = curName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var labelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt");
|
||||
|
||||
if (!LabelsCache.TryGetValue(name, out var classes))
|
||||
{
|
||||
if (!File.Exists(labelPath))
|
||||
{
|
||||
_logger.LogError($"No label {labelPath} found ! Image {(!File.Exists(imageName) ? "not exists!" : "exists.")}");
|
||||
return;
|
||||
var curName = $"{imageName}.{f}";
|
||||
if (File.Exists(curName))
|
||||
{
|
||||
imageName = curName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var labels = await YoloLabel.ReadFromFile(labelPath);
|
||||
classes = labels.Select(x => x.ClassNumber).Distinct().ToList();
|
||||
LabelsCache.Add(name, classes);
|
||||
}
|
||||
var labelPath = Path.Combine(_config.LabelsDirectory, $"{name}.txt");
|
||||
|
||||
if (classes.Contains(ExplorerEditor.CurrentAnnClass.Id) || ExplorerEditor.CurrentAnnClass.Id == -1)
|
||||
{
|
||||
ThumbnailsDtos.Add(new ThumbnailDto
|
||||
if (!LabelsCache.TryGetValue(name, out var classes))
|
||||
{
|
||||
ThumbnailPath = thumbnail,
|
||||
ImagePath = imageName,
|
||||
LabelPath = labelPath
|
||||
});
|
||||
}
|
||||
if (!File.Exists(labelPath))
|
||||
{
|
||||
var imageExists = File.Exists(imageName);
|
||||
if (!imageExists)
|
||||
{
|
||||
_logger.LogError($"No label {labelPath} found ! Image {imageName} not found, removing thumbnail {thumbnail}");
|
||||
File.Delete(thumbnail);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError($"No label {labelPath} found! But Image {imageName} exists! Image moved to {_config.UnknownImages} directory!");
|
||||
File.Move(imageName, Path.Combine(_config.UnknownImages, imageName));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var labels = await YoloLabel.ReadFromFile(labelPath);
|
||||
classes = labels.Select(x => x.ClassNumber).Distinct().ToList();
|
||||
LabelsCache.Add(name, classes);
|
||||
}
|
||||
|
||||
if (classes.Contains(ExplorerEditor.CurrentAnnClass.Id) || ExplorerEditor.CurrentAnnClass.Id == -1)
|
||||
{
|
||||
ThumbnailsDtos.Add(new ThumbnailDto
|
||||
{
|
||||
ThumbnailPath = thumbnail,
|
||||
ImagePath = imageName,
|
||||
LabelPath = labelPath
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,15 @@ using Size = System.Windows.Size;
|
||||
|
||||
namespace Azaion.Annotator;
|
||||
|
||||
public delegate void ThumbnailsUpdatedEventHandler(double thumbnailsPercentage);
|
||||
|
||||
public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGalleryManager
|
||||
{
|
||||
public int ThumbnailsCount { get; set; }
|
||||
public int ImagesCount { get; set; }
|
||||
public event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
||||
private const int UPDATE_STEP = 20;
|
||||
private readonly SemaphoreSlim _updateLock = new(1);
|
||||
|
||||
public double ThumbnailsPercentage { get; set; }
|
||||
|
||||
private DirectoryInfo? _thumbnailsDirectory;
|
||||
private DirectoryInfo ThumbnailsDirectory
|
||||
@@ -30,35 +35,54 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearThumbnails()
|
||||
{
|
||||
foreach(var file in new DirectoryInfo(config.ThumbnailsDirectory).GetFiles())
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
public async Task RefreshThumbnails()
|
||||
{
|
||||
var prefixLen = Config.ThumbnailPrefix.Length;
|
||||
|
||||
var thumbnails = ThumbnailsDirectory.GetFiles()
|
||||
.Select(x => Path.GetFileNameWithoutExtension(x.Name)[..^prefixLen])
|
||||
.GroupBy(x => x)
|
||||
.Select(gr => gr.Key)
|
||||
.ToHashSet();
|
||||
ThumbnailsCount = thumbnails.Count;
|
||||
|
||||
var files = new DirectoryInfo(config.ImagesDirectory).GetFiles();
|
||||
ImagesCount = files.Length;
|
||||
|
||||
foreach (var img in files)
|
||||
await _updateLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var imgName = Path.GetFileNameWithoutExtension(img.Name);
|
||||
if (thumbnails.Contains(imgName))
|
||||
continue;
|
||||
try
|
||||
var prefixLen = Config.ThumbnailPrefix.Length;
|
||||
|
||||
var thumbnails = ThumbnailsDirectory.GetFiles()
|
||||
.Select(x => Path.GetFileNameWithoutExtension(x.Name)[..^prefixLen])
|
||||
.GroupBy(x => x)
|
||||
.Select(gr => gr.Key)
|
||||
.ToHashSet();
|
||||
var thumbnailsCount = thumbnails.Count;
|
||||
|
||||
var files = new DirectoryInfo(config.ImagesDirectory).GetFiles();
|
||||
var imagesCount = files.Length;
|
||||
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
await CreateThumbnail(img.FullName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, $"Failed to generate thumbnail for {img.Name}");
|
||||
var img = files[i];
|
||||
ThumbnailsPercentage = imagesCount == 0 ? 0 : Math.Min(100, i * 100 / (double)imagesCount);
|
||||
var imgName = Path.GetFileNameWithoutExtension(img.Name);
|
||||
if (i % UPDATE_STEP == 0)
|
||||
ThumbnailsUpdate?.Invoke(ThumbnailsPercentage);
|
||||
if (thumbnails.Contains(imgName))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
await CreateThumbnail(img.FullName);
|
||||
thumbnailsCount++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, $"Failed to generate thumbnail for {img.Name}! Error: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
ThumbnailsCount++;
|
||||
await Task.Delay(10000);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updateLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,8 +176,9 @@ public class GalleryManager(Config config, ILogger<GalleryManager> logger) : IGa
|
||||
|
||||
public interface IGalleryManager
|
||||
{
|
||||
int ThumbnailsCount { get; set; }
|
||||
int ImagesCount { get; set; }
|
||||
event ThumbnailsUpdatedEventHandler ThumbnailsUpdate;
|
||||
double ThumbnailsPercentage { get; set; }
|
||||
Task CreateThumbnail(string imgPath);
|
||||
Task RefreshThumbnails();
|
||||
void ClearThumbnails();
|
||||
}
|
||||
@@ -79,6 +79,9 @@
|
||||
<MenuItem x:Name="OpenDataExplorerItem"
|
||||
Foreground="Black"
|
||||
IsEnabled="True" Header="Відкрити браузер анотацій..." Click="OpenDataExplorerItemClick"/>
|
||||
<MenuItem x:Name="ReloadThumbnailsItem"
|
||||
Foreground="Black"
|
||||
IsEnabled="True" Header="Оновити базу іконок" Click="ReloadThumbnailsItemClick"/>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Допомога" Foreground="#FFBDBCBC" Margin="0,3,0,0">
|
||||
<MenuItem x:Name="OpenHelpWindow"
|
||||
|
||||
@@ -180,16 +180,16 @@ public partial class MainWindow
|
||||
};
|
||||
|
||||
_mediaPlayer.PositionChanged += (o, args) =>
|
||||
{
|
||||
ShowTimeAnnotations(TimeSpan.FromMilliseconds(_mediaPlayer.Time));
|
||||
};
|
||||
|
||||
VideoSlider.ValueChanged += (value, newValue) =>
|
||||
_mediaPlayer.Position = (float)(newValue / VideoSlider.Maximum);
|
||||
|
||||
VideoSlider.KeyDown += (sender, args) => _mediator.Publish(new KeyEvent(sender, args));
|
||||
VideoSlider.KeyDown += (sender, args) =>
|
||||
_mediator.Publish(new KeyEvent(sender, args));
|
||||
|
||||
Volume.ValueChanged += (_, newValue) => _mediator.Publish(new VolumeChangedEvent((int)newValue));
|
||||
Volume.ValueChanged += (_, newValue) =>
|
||||
_mediator.Publish(new VolumeChangedEvent((int)newValue));
|
||||
|
||||
SizeChanged += async (_, _) => await SaveUserSettings();
|
||||
LocationChanged += async (_, _) => await SaveUserSettings();
|
||||
@@ -401,4 +401,14 @@ public partial class MainWindow
|
||||
}
|
||||
|
||||
private void Thumb_OnDragCompleted(object sender, DragCompletedEventArgs e) => _ = SaveUserSettings();
|
||||
|
||||
private void ReloadThumbnailsItemClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show($"Видалити всі іконки і згенерувати нову базу іконок в {_config.ThumbnailsDirectory}?",
|
||||
"Підтвердження оновлення іконок", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (result != MessageBoxResult.Yes)
|
||||
return;
|
||||
_galleryManager.ClearThumbnails();
|
||||
_galleryManager.RefreshThumbnails();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user