Skip to content

Commit 2608d96

Browse files
authored
Merge pull request #221 from taooceros/fixImageFlickering
Fix image flickering
2 parents c8285aa + ac945f4 commit 2608d96

File tree

4 files changed

+83
-30
lines changed

4 files changed

+83
-30
lines changed

Flow.Launcher.Infrastructure/Image/ImageCache.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class ImageCache
2626
private const int MaxCached = 50;
2727
public ConcurrentDictionary<string, ImageUsage> Data { get; private set; } = new ConcurrentDictionary<string, ImageUsage>();
2828
private const int permissibleFactor = 2;
29-
29+
3030
public void Initialization(Dictionary<string, int> usage)
3131
{
3232
foreach (var key in usage.Keys)
@@ -44,14 +44,14 @@ public ImageSource this[string path]
4444
value.usage++;
4545
return value.imageSource;
4646
}
47-
47+
4848
return null;
4949
}
5050
set
5151
{
5252
Data.AddOrUpdate(
53-
path,
54-
new ImageUsage(0, value),
53+
path,
54+
new ImageUsage(0, value),
5555
(k, v) =>
5656
{
5757
v.imageSource = value;
@@ -66,7 +66,6 @@ public ImageSource this[string path]
6666
{
6767
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary.
6868

69-
7069
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key))
7170
{
7271
if (!(key.Equals(Constant.ErrorIcon) || key.Equals(Constant.DefaultIcon)))
@@ -80,7 +79,7 @@ public ImageSource this[string path]
8079

8180
public bool ContainsKey(string key)
8281
{
83-
var contains = Data.ContainsKey(key);
82+
var contains = Data.ContainsKey(key) && Data[key] != null;
8483
return contains;
8584
}
8685

@@ -97,5 +96,4 @@ public int UniqueImagesInCache()
9796
return Data.Values.Select(x => x.imageSource).Distinct().Count();
9897
}
9998
}
100-
10199
}

Flow.Launcher.Infrastructure/Image/ImageLoader.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public static class ImageLoader
1818
private static readonly ConcurrentDictionary<string, string> GuidToKey = new ConcurrentDictionary<string, string>();
1919
private static IImageHashGenerator _hashGenerator;
2020
private static bool EnableImageHash = true;
21+
public static ImageSource DefaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon));
22+
2123

2224
private static readonly string[] ImageExtensions =
2325
{
@@ -61,7 +63,7 @@ public static void Save()
6163
{
6264
lock (_storage)
6365
{
64-
_storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, y => y.usage));
66+
_storage.Save(ImageCache.Data.Select(x => (x.Key, x.Value.usage)).ToDictionary(x => x.Key, x => x.usage));
6567
}
6668
}
6769

@@ -211,6 +213,11 @@ private static BitmapSource GetThumbnail(string path, ThumbnailOptions option =
211213
option);
212214
}
213215

216+
public static bool CacheContainImage(string path)
217+
{
218+
return ImageCache.ContainsKey(path) && ImageCache[path] != null;
219+
}
220+
214221
public static ImageSource Load(string path, bool loadFullImage = false)
215222
{
216223
var imageResult = LoadInternal(path, loadFullImage);
@@ -221,7 +228,7 @@ public static ImageSource Load(string path, bool loadFullImage = false)
221228
string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null;
222229
if (hash != null)
223230
{
224-
231+
225232
if (GuidToKey.TryGetValue(hash, out string key))
226233
{ // image already exists
227234
img = ImageCache[key] ?? img;

Flow.Launcher/ResultListBox.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<ColumnDefinition Width="0" />
4343
</Grid.ColumnDefinitions>
4444
<Image x:Name="ImageIcon" Width="32" Height="32" HorizontalAlignment="Left"
45-
Source="{Binding Image ,IsAsync=True}" />
45+
Source="{Binding Image.Value}" />
4646
<Grid Margin="5 0 5 0" Grid.Column="1" HorizontalAlignment="Stretch">
4747
<Grid.RowDefinitions>
4848
<RowDefinition />
Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,112 @@
11
using System;
2+
using System.Threading.Tasks;
23
using System.Windows;
34
using System.Windows.Media;
4-
using System.Windows.Threading;
55
using Flow.Launcher.Infrastructure;
66
using Flow.Launcher.Infrastructure.Image;
77
using Flow.Launcher.Infrastructure.Logger;
88
using Flow.Launcher.Infrastructure.UserSettings;
99
using Flow.Launcher.Plugin;
1010

11-
1211
namespace Flow.Launcher.ViewModel
1312
{
1413
public class ResultViewModel : BaseModel
1514
{
15+
public class LazyAsync<T> : Lazy<Task<T>>
16+
{
17+
private T defaultValue;
18+
19+
private readonly Action _updateCallback;
20+
public new T Value
21+
{
22+
get
23+
{
24+
if (!IsValueCreated)
25+
{
26+
base.Value.ContinueWith(_ =>
27+
{
28+
_updateCallback();
29+
});
30+
31+
return defaultValue;
32+
}
33+
34+
if (!base.Value.IsCompleted || base.Value.IsFaulted)
35+
return defaultValue;
36+
37+
return base.Value.Result;
38+
}
39+
}
40+
public LazyAsync(Func<Task<T>> factory, T defaultValue, Action updateCallback) : base(factory)
41+
{
42+
if (defaultValue != null)
43+
{
44+
this.defaultValue = defaultValue;
45+
}
46+
47+
_updateCallback = updateCallback;
48+
}
49+
}
50+
1651
public ResultViewModel(Result result, Settings settings)
1752
{
1853
if (result != null)
1954
{
2055
Result = result;
56+
57+
Image = new LazyAsync<ImageSource>(
58+
SetImage,
59+
ImageLoader.DefaultImage,
60+
() =>
61+
{
62+
OnPropertyChanged(nameof(Image));
63+
});
2164
}
2265

2366
Settings = settings;
2467
}
2568

2669
public Settings Settings { get; private set; }
2770

28-
public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Hidden;
71+
public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Hidden;
2972

3073
public string OpenResultModifiers => Settings.OpenResultModifiers;
3174

3275
public string ShowTitleToolTip => string.IsNullOrEmpty(Result.TitleToolTip)
33-
? Result.Title
76+
? Result.Title
3477
: Result.TitleToolTip;
3578

3679
public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip)
37-
? Result.SubTitle
80+
? Result.SubTitle
3881
: Result.SubTitleToolTip;
3982

40-
public ImageSource Image
83+
public LazyAsync<ImageSource> Image { get; set; }
84+
85+
private async Task<ImageSource> SetImage()
4186
{
42-
get
87+
var imagePath = Result.IcoPath;
88+
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
4389
{
44-
var imagePath = Result.IcoPath;
45-
if (string.IsNullOrEmpty(imagePath) && Result.Icon != null)
90+
try
4691
{
47-
try
48-
{
49-
return Result.Icon();
50-
}
51-
catch (Exception e)
52-
{
53-
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
54-
imagePath = Constant.MissingImgIcon;
55-
}
92+
return Result.Icon();
93+
}
94+
catch (Exception e)
95+
{
96+
Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e);
97+
imagePath = Constant.MissingImgIcon;
5698
}
57-
99+
}
100+
101+
if (ImageLoader.CacheContainImage(imagePath))
102+
{
58103
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
59104
return ImageLoader.Load(imagePath);
60105
}
106+
else
107+
{
108+
return await Task.Run(() => ImageLoader.Load(imagePath));
109+
}
61110
}
62111

63112
public Result Result { get; }
@@ -84,6 +133,5 @@ public override string ToString()
84133
{
85134
return Result.ToString();
86135
}
87-
88136
}
89137
}

0 commit comments

Comments
 (0)