diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index b0eebd2df5d..56f421e30cc 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -29,6 +29,7 @@ public static class Constant public static readonly string DefaultIcon = Path.Combine(ImagesDirectory, "app.png"); public static readonly string ErrorIcon = Path.Combine(ImagesDirectory, "app_error.png"); public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png"); + public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png"); public static string PythonPath; diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 13022137960..deb858a79c8 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -21,8 +22,10 @@ public static class ImageLoader private static readonly ConcurrentDictionary GuidToKey = new(); private static IImageHashGenerator _hashGenerator; private static readonly bool EnableImageHash = true; - public static ImageSource DefaultImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); - public const int SmallIconSize = 32; + public static ImageSource MissingImage { get; } = new BitmapImage(new Uri(Constant.MissingImgIcon)); + public static ImageSource LoadingImage { get; } = new BitmapImage(new Uri(Constant.LoadingImgIcon)); + public const int SmallIconSize = 64; + public const int FullIconSize = 256; private static readonly string[] ImageExtensions = @@ -99,6 +102,7 @@ private enum ImageType Folder, Data, ImageFile, + FullImageFile, Error, Cache } @@ -111,7 +115,7 @@ private static async ValueTask LoadInternalAsync(string path, bool { if (string.IsNullOrEmpty(path)) { - return new ImageResult(DefaultImage, ImageType.Error); + return new ImageResult(MissingImage, ImageType.Error); } if (ImageCache.ContainsKey(path, loadFullImage)) @@ -201,6 +205,7 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag if (loadFullImage) { image = LoadFullImage(path); + type = ImageType.FullImageFile; } else { @@ -215,7 +220,7 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag else { type = ImageType.File; - image = GetThumbnail(path, ThumbnailOptions.None); + image = GetThumbnail(path, ThumbnailOptions.None, loadFullImage ? FullIconSize : SmallIconSize); } } else @@ -232,12 +237,12 @@ private static ImageResult GetThumbnailResult(ref string path, bool loadFullImag return new ImageResult(image, type); } - private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly) + private static BitmapSource GetThumbnail(string path, ThumbnailOptions option = ThumbnailOptions.ThumbnailOnly, int size = SmallIconSize) { return WindowsThumbnailProvider.GetThumbnail( path, - Constant.ThumbnailSize, - Constant.ThumbnailSize, + size, + size, option); } @@ -254,6 +259,10 @@ public static async ValueTask LoadAsync(string path, bool loadFullI if (imageResult.ImageType != ImageType.Error && imageResult.ImageType != ImageType.Cache) { // we need to get image hash string hash = EnableImageHash ? _hashGenerator.GetHashFromImage(img) : null; + if (imageResult.ImageType == ImageType.FullImageFile) + { + path = $"{path}_{ImageType.FullImageFile}"; + } if (hash != null) { @@ -263,6 +272,7 @@ public static async ValueTask LoadAsync(string path, bool loadFullI } else { // new guid + GuidToKey[hash] = path; } } @@ -279,9 +289,33 @@ private static BitmapImage LoadFullImage(string path) BitmapImage image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad; - image.UriSource = new Uri(path); + image.UriSource = new Uri(path); image.CreateOptions = BitmapCreateOptions.IgnoreColorProfile; image.EndInit(); + + if (image.PixelWidth > 320) + { + BitmapImage resizedWidth = new BitmapImage(); + resizedWidth.BeginInit(); + resizedWidth.CacheOption = BitmapCacheOption.OnLoad; + resizedWidth.UriSource = new Uri(path); + resizedWidth.CreateOptions = BitmapCreateOptions.IgnoreColorProfile; + resizedWidth.DecodePixelWidth = 320; + resizedWidth.EndInit(); + + if (resizedWidth.PixelHeight > 320) + { + BitmapImage resizedHeight = new BitmapImage(); + resizedHeight.BeginInit(); + resizedHeight.CacheOption = BitmapCacheOption.OnLoad; + resizedHeight.UriSource = new Uri(path); + resizedHeight.CreateOptions = BitmapCreateOptions.IgnoreColorProfile; + resizedHeight.DecodePixelHeight = 320; + resizedHeight.EndInit(); + return resizedHeight; + } + return resizedWidth; + } return image; } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 3561c6ffe55..09fad990bdd 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -146,6 +146,7 @@ public CustomBrowserViewModel CustomBrowser /// when false Alphabet static service will always return empty results /// public bool ShouldUsePinyin { get; set; } = false; + public bool AlwaysPreview { get; set; } = false; [JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))] public SearchPrecisionScore QuerySearchPrecision { get; private set; } = SearchPrecisionScore.Regular; diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index f2d9323ef83..912a23a6f47 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using System.Windows.Controls; using System.Windows.Media; namespace Flow.Launcher.Plugin @@ -203,6 +204,11 @@ public override string ToString() /// public string SubTitleToolTip { get; set; } + /// + /// Customized Preview Panel + /// + public Lazy PreviewPanel { get; set; } + /// /// Run this result, asynchronously /// @@ -223,5 +229,30 @@ public ValueTask ExecuteAsync(ActionContext context) /// /// #26a0da (blue) public string ProgressBarColor { get; set; } = "#26a0da"; + + public PreviewInfo Preview { get; set; } = PreviewInfo.Default; + + /// + /// Info of the preview image. + /// + public record PreviewInfo + { + /// + /// Full image used for preview panel + /// + public string PreviewImagePath { get; set; } + /// + /// Determines if the preview image should occupy the full width of the preveiw panel. + /// + public bool IsMedia { get; set; } + public string Description { get; set; } + + public static PreviewInfo Default { get; } = new() + { + PreviewImagePath = null, + Description = null, + IsMedia = false, + }; + } } } diff --git a/Flow.Launcher/Images/loading.png b/Flow.Launcher/Images/loading.png new file mode 100644 index 00000000000..1600b5967ed Binary files /dev/null and b/Flow.Launcher/Images/loading.png differ diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 01be5ebc81d..fe2e4a8e45a 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -65,6 +65,8 @@ Changes minimum match score required for results. Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. + Always Preview + Always open preview panel when Flow starts. Press F1 to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 49854ed8194..baf96e01c0a 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -13,7 +13,6 @@ Title="Flow Launcher" MinWidth="{Binding MainWindowWidth, Mode=OneWay}" MaxWidth="{Binding MainWindowWidth, Mode=OneWay}" - d:DataContext="{d:DesignInstance vm:MainViewModel}" AllowDrop="True" AllowsTransparency="True" Background="Transparent" @@ -306,6 +305,7 @@ @@ -323,55 +323,151 @@ Y1="0" Y2="0" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index b802cb4579d..28c631d11c9 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -59,7 +59,7 @@ public MainWindow(Settings settings, MainViewModel mainVM) _settings = settings; InitializeComponent(); - InitializePosition(); + InitializePosition(); animationSound.Open(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav")); } @@ -106,6 +106,7 @@ private void OnLoaded(object sender, RoutedEventArgs _) WindowsInteropHelper.DisableControlBox(this); InitProgressbarAnimation(); InitializePosition(); + PreviewReset(); // since the default main window visibility is visible // so we need set focus during startup QueryTextBox.Focus(); @@ -123,6 +124,7 @@ private void OnLoaded(object sender, RoutedEventArgs _) animationSound.Play(); } UpdatePosition(); + PreviewReset(); Activate(); QueryTextBox.Focus(); _settings.ActivateTimes++; @@ -618,12 +620,45 @@ private void OnKeyDown(object sender, KeyEventArgs e) } } break; + case Key.F1: + PreviewToggle(); + e.Handled = true; + break; + default: break; } } + public void PreviewReset() + { + if (_settings.AlwaysPreview == true) + { + ResultArea.SetValue(Grid.ColumnSpanProperty, 1); + Preview.Visibility = Visibility.Visible; + } + else + { + ResultArea.SetValue(Grid.ColumnSpanProperty, 2); + Preview.Visibility = Visibility.Collapsed; + } + } + public void PreviewToggle() + { + + if (Preview.Visibility == Visibility.Collapsed) + { + ResultArea.SetValue(Grid.ColumnSpanProperty, 1); + Preview.Visibility = Visibility.Visible; + } + else + { + ResultArea.SetValue(Grid.ColumnSpanProperty, 2); + Preview.Visibility = Visibility.Collapsed; + } + } + private void MoveQueryTextToEnd() { // QueryTextBox seems to be update with a DispatcherPriority as low as ContextIdle. diff --git a/Flow.Launcher/ResultListBox.xaml b/Flow.Launcher/ResultListBox.xaml index d44830f5251..b2b96aa0025 100644 --- a/Flow.Launcher/ResultListBox.xaml +++ b/Flow.Launcher/ResultListBox.xaml @@ -58,6 +58,7 @@ diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml index f7955dd2251..5f26a74cb11 100644 --- a/Flow.Launcher/SettingWindow.xaml +++ b/Flow.Launcher/SettingWindow.xaml @@ -731,6 +731,22 @@ + + + + + + + + +  + + + + diff --git a/Flow.Launcher/Themes/Base.xaml b/Flow.Launcher/Themes/Base.xaml index 94740a73031..398e90d7304 100644 --- a/Flow.Launcher/Themes/Base.xaml +++ b/Flow.Launcher/Themes/Base.xaml @@ -356,7 +356,23 @@ - @@ -384,7 +400,63 @@ + + + + + + + + + + + + + diff --git a/Flow.Launcher/Themes/BlurBlack Darker.xaml b/Flow.Launcher/Themes/BlurBlack Darker.xaml index 88c48afeee3..67a47f4cf24 100644 --- a/Flow.Launcher/Themes/BlurBlack Darker.xaml +++ b/Flow.Launcher/Themes/BlurBlack Darker.xaml @@ -37,7 +37,6 @@ x:Key="WindowBorderStyle" BasedOn="{StaticResource BaseWindowBorderStyle}" TargetType="{x:Type Border}"> - @@ -48,6 +47,15 @@ + + + + + + diff --git a/Flow.Launcher/Themes/BlurBlack.xaml b/Flow.Launcher/Themes/BlurBlack.xaml index f74578a741a..0f126429242 100644 --- a/Flow.Launcher/Themes/BlurBlack.xaml +++ b/Flow.Launcher/Themes/BlurBlack.xaml @@ -45,6 +45,15 @@ + + + + + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/BlurWhite.xaml b/Flow.Launcher/Themes/BlurWhite.xaml index 2d42d49bcf1..4406724b8ec 100644 --- a/Flow.Launcher/Themes/BlurWhite.xaml +++ b/Flow.Launcher/Themes/BlurWhite.xaml @@ -155,4 +155,28 @@ + + + + diff --git a/Flow.Launcher/Themes/Bullet Light.xaml b/Flow.Launcher/Themes/Bullet Light.xaml index 0dc3b33d22a..1f776a2eee4 100644 --- a/Flow.Launcher/Themes/Bullet Light.xaml +++ b/Flow.Launcher/Themes/Bullet Light.xaml @@ -87,7 +87,7 @@ x:Key="SeparatorStyle" BasedOn="{StaticResource BaseSeparatorStyle}" TargetType="{x:Type Rectangle}"> - + @@ -156,6 +156,7 @@ + @@ -181,4 +182,28 @@ TargetType="{x:Type TextBlock}"> + + + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Circle Light.xaml b/Flow.Launcher/Themes/Circle Light.xaml index 7e14a29a6c5..e52e3a9570f 100644 --- a/Flow.Launcher/Themes/Circle Light.xaml +++ b/Flow.Launcher/Themes/Circle Light.xaml @@ -69,7 +69,7 @@ x:Key="SeparatorStyle" BasedOn="{StaticResource BaseSeparatorStyle}" TargetType="{x:Type Rectangle}"> - + @@ -163,4 +163,28 @@ TargetType="{x:Type TextBlock}"> - \ No newline at end of file + + + + + diff --git a/Flow.Launcher/Themes/Circle System.xaml b/Flow.Launcher/Themes/Circle System.xaml index b00f03e7601..2b2ce7ca33d 100644 --- a/Flow.Launcher/Themes/Circle System.xaml +++ b/Flow.Launcher/Themes/Circle System.xaml @@ -70,7 +70,7 @@ x:Key="SeparatorStyle" BasedOn="{StaticResource BaseSeparatorStyle}" TargetType="{x:Type Rectangle}"> - + @@ -164,4 +164,28 @@ TargetType="{x:Type TextBlock}"> + + + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Cyan Dark.xaml b/Flow.Launcher/Themes/Cyan Dark.xaml index c79044f0051..60bc090022f 100644 --- a/Flow.Launcher/Themes/Cyan Dark.xaml +++ b/Flow.Launcher/Themes/Cyan Dark.xaml @@ -164,7 +164,7 @@ 0 0 - 0 0 0 0 + 0 0 0 4 + + + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Darker Glass.xaml b/Flow.Launcher/Themes/Darker Glass.xaml index a33f98b0928..89b6dfa0181 100644 --- a/Flow.Launcher/Themes/Darker Glass.xaml +++ b/Flow.Launcher/Themes/Darker Glass.xaml @@ -5,7 +5,7 @@ - + 0 0 0 8 + + + + + diff --git a/Flow.Launcher/Themes/Darker.xaml b/Flow.Launcher/Themes/Darker.xaml new file mode 100644 index 00000000000..d1abbe9789f --- /dev/null +++ b/Flow.Launcher/Themes/Darker.xaml @@ -0,0 +1,100 @@ + + + + + 0 0 0 8 + + + + + + + + #4d4d4d + + + + + + diff --git a/Flow.Launcher/Themes/Discord Dark.xaml b/Flow.Launcher/Themes/Discord Dark.xaml index 74c1719c415..5315c7644a1 100644 --- a/Flow.Launcher/Themes/Discord Dark.xaml +++ b/Flow.Launcher/Themes/Discord Dark.xaml @@ -5,6 +5,7 @@ + 0 0 0 6 + + + + diff --git a/Flow.Launcher/Themes/Dracula.xaml b/Flow.Launcher/Themes/Dracula.xaml index c01b67c740d..ce335072862 100644 --- a/Flow.Launcher/Themes/Dracula.xaml +++ b/Flow.Launcher/Themes/Dracula.xaml @@ -5,6 +5,7 @@ + 0 0 0 6 + + + + diff --git a/Flow.Launcher/Themes/Gray.xaml b/Flow.Launcher/Themes/Gray.xaml index eb8b48f4379..d8d344e216d 100644 --- a/Flow.Launcher/Themes/Gray.xaml +++ b/Flow.Launcher/Themes/Gray.xaml @@ -70,7 +70,7 @@ TargetType="{x:Type Rectangle}"> - + - \ No newline at end of file + + + + + + diff --git a/Flow.Launcher/Themes/League.xaml b/Flow.Launcher/Themes/League.xaml index 9f4a9a62858..7fbe56187ad 100644 --- a/Flow.Launcher/Themes/League.xaml +++ b/Flow.Launcher/Themes/League.xaml @@ -137,4 +137,29 @@ TargetType="{x:Type TextBlock}"> + + + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Midnight.xaml b/Flow.Launcher/Themes/Midnight.xaml index b52fe87e14c..91ff620d54e 100644 --- a/Flow.Launcher/Themes/Midnight.xaml +++ b/Flow.Launcher/Themes/Midnight.xaml @@ -163,4 +163,29 @@ TargetType="{x:Type TextBlock}"> - \ No newline at end of file + + + + + diff --git a/Flow.Launcher/Themes/Nord Darker.xaml b/Flow.Launcher/Themes/Nord Darker.xaml index 840e44b3c10..d9ddb307664 100644 --- a/Flow.Launcher/Themes/Nord Darker.xaml +++ b/Flow.Launcher/Themes/Nord Darker.xaml @@ -3,6 +3,7 @@ + 0 0 0 8 + + + + + diff --git a/Flow.Launcher/Themes/Pink.xaml b/Flow.Launcher/Themes/Pink.xaml index 96dae254560..dc97e432055 100644 --- a/Flow.Launcher/Themes/Pink.xaml +++ b/Flow.Launcher/Themes/Pink.xaml @@ -2,6 +2,7 @@ + 0 0 0 4 + + + + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Sublime.xaml b/Flow.Launcher/Themes/Sublime.xaml index 417bd723ed8..6df69ad3e72 100644 --- a/Flow.Launcher/Themes/Sublime.xaml +++ b/Flow.Launcher/Themes/Sublime.xaml @@ -5,6 +5,7 @@ + 0 0 0 8 + + + + diff --git a/Flow.Launcher/Themes/Ubuntu.xaml b/Flow.Launcher/Themes/Ubuntu.xaml index ea10c0e8202..33f232699a4 100644 --- a/Flow.Launcher/Themes/Ubuntu.xaml +++ b/Flow.Launcher/Themes/Ubuntu.xaml @@ -169,7 +169,7 @@ 0 0 0 0 0 - 0 0 0 0 + 0 0 0 8 + + + + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Win10Light.xaml b/Flow.Launcher/Themes/Win10Light.xaml index 12ba01f71b2..5f837bdb393 100644 --- a/Flow.Launcher/Themes/Win10Light.xaml +++ b/Flow.Launcher/Themes/Win10Light.xaml @@ -5,6 +5,7 @@ + 0 0 0 4 + + + + diff --git a/Flow.Launcher/Themes/Win11Dark.xaml b/Flow.Launcher/Themes/Win11Dark.xaml index 4660eae8fff..5abb96cce0d 100644 --- a/Flow.Launcher/Themes/Win11Dark.xaml +++ b/Flow.Launcher/Themes/Win11Dark.xaml @@ -5,6 +5,7 @@ + 0 0 0 8 + + + + diff --git a/Flow.Launcher/Themes/Win11Light.xaml b/Flow.Launcher/Themes/Win11Light.xaml index 4c0769d4d97..e2c37236cbf 100644 --- a/Flow.Launcher/Themes/Win11Light.xaml +++ b/Flow.Launcher/Themes/Win11Light.xaml @@ -5,6 +5,7 @@ + 0 0 0 8 + + + + diff --git a/Flow.Launcher/Themes/Win11System.xaml b/Flow.Launcher/Themes/Win11System.xaml index 42f0579a70b..df09249beb6 100644 --- a/Flow.Launcher/Themes/Win11System.xaml +++ b/Flow.Launcher/Themes/Win11System.xaml @@ -6,6 +6,7 @@ + 0 0 0 8 + + + + diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 9871ceb93f1..f371f32d698 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -36,7 +36,7 @@ public ImageSource Image { get { - if (_image == ImageLoader.DefaultImage) + if (_image == ImageLoader.MissingImage) LoadIconAsync(); return _image; @@ -69,7 +69,7 @@ public Control SettingControl ? new Control() : settingProvider.CreateSettingPanel() : null; - private ImageSource _image = ImageLoader.DefaultImage; + private ImageSource _image = ImageLoader.MissingImage; public Visibility ActionKeywordsVisibility => PluginPair.Metadata.ActionKeywords.Count == 1 ? Visibility.Visible : Visibility.Collapsed; public string InitilizaTime => PluginPair.Metadata.InitTime + "ms"; diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 2d61f6cabd3..0fadeccdf97 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -19,47 +19,49 @@ public class ResultViewModel : BaseModel public ResultViewModel(Result result, Settings settings) { - if (result != null) + Settings = settings; + + if (result == null) { - Result = result; + return; + } + Result = result; - if (Result.Glyph is { FontFamily: not null } glyph) + if (Result.Glyph is { FontFamily: not null } glyph) + { + // Checks if it's a system installed font, which does not require path to be provided. + if (glyph.FontFamily.EndsWith(".ttf") || glyph.FontFamily.EndsWith(".otf")) { - // Checks if it's a system installed font, which does not require path to be provided. - if (glyph.FontFamily.EndsWith(".ttf") || glyph.FontFamily.EndsWith(".otf")) - { - string fontFamilyPath = glyph.FontFamily; + string fontFamilyPath = glyph.FontFamily; - if (!Path.IsPathRooted(fontFamilyPath)) - { - fontFamilyPath = Path.Combine(Result.PluginDirectory, fontFamilyPath); - } + if (!Path.IsPathRooted(fontFamilyPath)) + { + fontFamilyPath = Path.Combine(Result.PluginDirectory, fontFamilyPath); + } - if (fonts.ContainsKey(fontFamilyPath)) - { - Glyph = glyph with - { - FontFamily = fonts[fontFamilyPath] - }; - } - else + if (fonts.ContainsKey(fontFamilyPath)) + { + Glyph = glyph with { - fontCollection.AddFontFile(fontFamilyPath); - fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{fontCollection.Families[^1].Name}"; - Glyph = glyph with - { - FontFamily = fonts[fontFamilyPath] - }; - } + FontFamily = fonts[fontFamilyPath] + }; } else { - Glyph = glyph; + fontCollection.AddFontFile(fontFamilyPath); + fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{fontCollection.Families[^1].Name}"; + Glyph = glyph with + { + FontFamily = fonts[fontFamilyPath] + }; } } + else + { + Glyph = glyph; + } } - Settings = settings; } private Settings Settings { get; } @@ -67,6 +69,10 @@ public ResultViewModel(Result result, Settings settings) public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Collapsed; + public Visibility ShowDefaultPreview => Result.PreviewPanel == null ? Visibility.Visible : Visibility.Collapsed; + + public Visibility ShowCustomizedPreview => Result.PreviewPanel == null ? Visibility.Collapsed : Visibility.Visible; + public Visibility ShowIcon { get @@ -106,7 +112,7 @@ public Visibility ShowGlyph if (!Settings.UseGlyphIcons && !ImgIconAvailable && GlyphAvailable) return Visibility.Visible; - return Settings.UseGlyphIcons && GlyphAvailable ? Visibility.Visible : Visibility.Hidden; + return Settings.UseGlyphIcons && GlyphAvailable ? Visibility.Visible : Visibility.Collapsed; } } @@ -125,8 +131,10 @@ public Visibility ShowGlyph : Result.SubTitleToolTip; private volatile bool ImageLoaded; + private volatile bool PreviewImageLoaded; - private ImageSource image = ImageLoader.DefaultImage; + private ImageSource image = ImageLoader.LoadingImage; + private ImageSource previewImage = ImageLoader.LoadingImage; public ImageSource Image { @@ -143,37 +151,76 @@ public ImageSource Image private set => image = value; } + public ImageSource PreviewImage + { + get + { + if (!PreviewImageLoaded) + { + PreviewImageLoaded = true; + _ = LoadPreviewImageAsync(); + } + + return previewImage; + } + private set => previewImage = value; + } + + /// + /// Determines if to use the full width of the preview panel + /// + public bool UseBigThumbnail => Result.Preview.IsMedia; + public GlyphInfo Glyph { get; set; } - private async Task LoadImageAsync() + private async Task LoadImageInternalAsync(string imagePath, Result.IconDelegate icon, bool loadFullImage) { - var imagePath = Result.IcoPath; - if (string.IsNullOrEmpty(imagePath) && Result.Icon != null) + if (string.IsNullOrEmpty(imagePath) && icon != null) { try { - image = Result.Icon(); - return; + var image = await Task.Run(() => icon()).ConfigureAwait(false); + return image; } catch (Exception e) { Log.Exception( - $"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", + $"|ResultViewModel.LoadImageInternalAsync|IcoPath is empty and exception when calling IconDelegate for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); } } - var loadFullImage = (Path.GetExtension(imagePath) ?? "").Equals(".url", StringComparison.OrdinalIgnoreCase); + return await ImageLoader.LoadAsync(imagePath, loadFullImage).ConfigureAwait(false); + } - if (ImageLoader.CacheContainImage(imagePath)) + private async Task LoadImageAsync() + { + var imagePath = Result.IcoPath; + var iconDelegate = Result.Icon; + if (ImageLoader.CacheContainImage(imagePath, false)) { - // will get here either when icoPath has value\icon delegate is null\when had exception in delegate - image = await ImageLoader.LoadAsync(imagePath, loadFullImage); - return; + image = await LoadImageInternalAsync(imagePath, iconDelegate, false).ConfigureAwait(false); } + else + { + // We need to modify the property not field here to trigger the OnPropertyChanged event + Image = await LoadImageInternalAsync(imagePath, iconDelegate, false).ConfigureAwait(false); + } + } - // We need to modify the property not field here to trigger the OnPropertyChanged event - Image = await ImageLoader.LoadAsync(imagePath, loadFullImage).ConfigureAwait(false); + private async Task LoadPreviewImageAsync() + { + var imagePath = string.IsNullOrEmpty(Result.Preview.PreviewImagePath) ? Result.IcoPath : Result.Preview.PreviewImagePath; + var iconDelegate = Result.Icon; + if (ImageLoader.CacheContainImage(imagePath, true)) + { + previewImage = await LoadImageInternalAsync(imagePath, iconDelegate, true).ConfigureAwait(false); + } + else + { + // We need to modify the property not field here to trigger the OnPropertyChanged event + PreviewImage = await LoadImageInternalAsync(imagePath, iconDelegate, true).ConfigureAwait(false); + } } public Result Result { get; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 8a12f130688..4ddc75cfe26 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -3,7 +3,6 @@ using Flow.Launcher.Plugin.SharedCommands; using System; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -207,11 +206,17 @@ internal static Result CreateOpenCurrentFolderResult(string path, bool windowsIn internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false) { + Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) ? new Result.PreviewInfo { + IsMedia = true, + PreviewImagePath = filePath, + } : Result.PreviewInfo.Default; + var result = new Result { Title = Path.GetFileName(filePath), SubTitle = Path.GetDirectoryName(filePath), IcoPath = filePath, + Preview = preview, AutoCompleteText = GetPathWithActionKeyword(filePath, ResultType.File), TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData, Score = score, @@ -266,6 +271,20 @@ internal static Result CreateFileResult(string filePath, Query query, int score }; return result; } + + public static bool IsMedia(string extension) + { + if (string.IsNullOrEmpty(extension)) + { + return false; + } + else + { + return MediaExtensions.Contains(extension.ToLowerInvariant()); + } + } + + public static readonly string[] MediaExtensions = { ".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4" }; } public enum ResultType diff --git a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs index 627502ea1c5..28641dd003c 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Programs/UWP.cs @@ -96,8 +96,9 @@ public void InitAppsInPackage(Package package) var visualElement = appNode.SelectSingleNode($"*[local-name()='VisualElements']", namespaceManager); var logoUri = visualElement?.Attributes[logoName]?.Value; app.LogoPath = app.LogoPathFromUri(logoUri, (64, 64)); - var previewUri = visualElement?.Attributes[bigLogoName]?.Value; - app.PreviewImagePath = app.LogoPathFromUri(previewUri, (128, 128)); + // use small logo or may have a big margin + var previewUri = visualElement?.Attributes[logoName]?.Value; + app.PreviewImagePath = app.LogoPathFromUri(previewUri, (256, 256)); } } } @@ -405,6 +406,12 @@ public Result Result(string query, IPublicAPI api) Title = title, SubTitle = Main._settings.HideAppsPath ? string.Empty : Location, IcoPath = LogoPath, + Preview = new Result.PreviewInfo + { + IsMedia = false, + PreviewImagePath = PreviewImagePath, + Description = Description + }, Score = matchResult.Score, TitleHighlightData = matchResult.MatchData, ContextData = this, @@ -549,6 +556,7 @@ string TryToFindLogo(string uri, string path, int px) // select like logo.[xxx_yyy].png // https://learn.microsoft.com/en-us/windows/uwp/app-resources/tailor-resources-lang-scale-contrast + // todo select from file name like pt run var selected = logos.FirstOrDefault(); var closest = selected; int min = int.MaxValue; diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs index b136e3b8b46..179745e2d2f 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Main.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -192,4 +192,4 @@ public string GetTranslatedPluginDescription() public event ResultUpdatedEventHandler ResultsUpdated; } -} \ No newline at end of file +}