Flow.Launcher/Flow.Launcher/ViewModel/ResultViewModel.cs

325 lines
11 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Drawing.Text;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.UserSettings;
2020-04-21 09:12:17 +00:00
using Flow.Launcher.Plugin;
2020-04-21 09:12:17 +00:00
namespace Flow.Launcher.ViewModel
{
public class ResultViewModel : BaseModel
{
private static readonly string ClassName = nameof(ResultViewModel);
2025-04-13 09:50:44 +00:00
2025-03-23 03:02:23 +00:00
private static readonly PrivateFontCollection FontCollection = new();
private static readonly Dictionary<string, string> Fonts = new();
2021-12-27 02:22:52 +00:00
public ResultViewModel(Result result, Settings settings)
{
Settings = settings;
2025-03-22 15:12:20 +00:00
if (result == null) return;
Result = result;
if (Result.Glyph is { FontFamily: not null } glyph)
{
2024-05-27 06:26:14 +00:00
// 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"))
2021-08-06 09:04:19 +00:00
{
string fontFamilyPath = glyph.FontFamily;
2021-12-27 02:22:52 +00:00
if (!Path.IsPathRooted(fontFamilyPath))
{
fontFamilyPath = Path.Combine(Result.PluginDirectory, fontFamilyPath);
}
2021-12-27 02:22:52 +00:00
2025-03-23 03:02:23 +00:00
if (Fonts.TryGetValue(fontFamilyPath, out var value))
{
Glyph = glyph with
2021-12-27 02:22:52 +00:00
{
FontFamily = value
};
2021-08-06 09:15:10 +00:00
}
else
{
2025-03-23 03:02:23 +00:00
FontCollection.AddFontFile(fontFamilyPath);
Fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{FontCollection.Families[^1].Name}";
Glyph = glyph with
{
2025-03-23 03:02:23 +00:00
FontFamily = Fonts[fontFamilyPath]
};
2021-08-06 09:15:10 +00:00
}
2021-08-06 09:04:19 +00:00
}
else
{
Glyph = glyph;
}
2021-07-31 07:44:41 +00:00
}
}
2024-05-27 06:26:14 +00:00
public Settings Settings { get; }
2022-09-13 22:34:11 +00:00
public Visibility ShowDefaultPreview => Result.PreviewPanel == null ? Visibility.Visible : Visibility.Collapsed;
2021-10-07 10:58:54 +00:00
public Visibility ShowIcon
{
get
{
// If both glyph and image icons are not available, it will then be the default icon
if (!ImgIconAvailable && !GlyphAvailable)
return Visibility.Visible;
// Although user can choose to use glyph icons, plugins may choose to supply only image icons.
// In this case we ignore the setting because otherwise icons will not display as intended
if (Settings.UseGlyphIcons && !GlyphAvailable && ImgIconAvailable)
return Visibility.Visible;
return !Settings.UseGlyphIcons && ImgIconAvailable ? Visibility.Visible : Visibility.Hidden;
}
}
2022-12-29 05:13:28 +00:00
public Visibility ShowPreviewImage
{
get
{
2022-12-29 05:13:28 +00:00
if (PreviewImageAvailable)
return Visibility.Visible;
2025-03-23 03:02:23 +00:00
// Fall back to icon
return ShowIcon;
}
}
2022-08-30 08:27:37 +00:00
public double IconRadius
{
get
{
if (Result.RoundedIcon)
return IconXY / 2;
2025-03-23 03:02:23 +00:00
2022-08-30 08:27:37 +00:00
return IconXY;
}
}
2021-10-07 10:58:54 +00:00
public Visibility ShowGlyph
{
get
{
// Although user can choose to not use glyph icons, plugins may choose to supply only glyph icons.
// In this case we ignore the setting because otherwise icons will not display as intended
if (!Settings.UseGlyphIcons && !ImgIconAvailable && GlyphAvailable)
return Visibility.Visible;
2022-12-05 22:49:21 +00:00
return Settings.UseGlyphIcons && GlyphAvailable ? Visibility.Visible : Visibility.Collapsed;
2021-10-07 10:58:54 +00:00
}
}
2025-04-11 06:58:47 +00:00
public Visibility ShowBadge
{
2025-04-11 07:51:26 +00:00
get
{
2025-04-12 12:10:24 +00:00
// If results do not allow badges, or user has disabled badges in settings,
// or badge icon is not available, then do not show badge
if (!Result.ShowBadge || !Settings.ShowBadges || !BadgeIconAvailable)
2025-04-11 08:11:39 +00:00
return Visibility.Collapsed;
2025-04-12 12:10:24 +00:00
// If user has set to show badges only for global results, and this is not a global result,
// then do not show badge
2025-04-11 08:11:39 +00:00
if (Settings.ShowBadgesGlobalOnly && !IsGlobalQuery)
return Visibility.Collapsed;
2025-04-11 07:51:26 +00:00
2025-04-11 08:11:39 +00:00
return Visibility.Visible;
2025-04-11 07:51:26 +00:00
}
2025-04-11 06:58:47 +00:00
}
2025-04-11 08:13:55 +00:00
public bool IsGlobalQuery => string.IsNullOrEmpty(Result.OriginQuery.ActionKeyword);
2025-04-11 08:11:39 +00:00
2021-10-07 10:58:54 +00:00
private bool GlyphAvailable => Glyph is not null;
2026-01-01 11:32:39 +00:00
private bool ImgIconAvailable => !string.IsNullOrEmpty(Result.IcoPathAbsolute) || Result.Icon is not null;
2025-04-11 07:51:26 +00:00
private bool BadgeIconAvailable => !string.IsNullOrEmpty(Result.BadgeIcoPath) || Result.BadgeIcon is not null;
private bool PreviewImageAvailable => !string.IsNullOrEmpty(Result.Preview.PreviewImagePath) || Result.Preview.PreviewDelegate != null;
public string ShowTitleToolTip => string.IsNullOrEmpty(Result.TitleToolTip)
2021-08-06 09:04:19 +00:00
? Result.Title
: Result.TitleToolTip;
public string ShowSubTitleToolTip => string.IsNullOrEmpty(Result.SubTitleToolTip)
2021-08-06 09:04:19 +00:00
? Result.SubTitle
: Result.SubTitleToolTip;
2025-03-23 03:02:23 +00:00
private volatile bool _imageLoaded;
2025-04-11 07:51:26 +00:00
private volatile bool _badgeImageLoaded;
2025-03-23 03:02:23 +00:00
private volatile bool _previewImageLoaded;
2025-03-23 03:02:23 +00:00
private ImageSource _image = ImageLoader.LoadingImage;
2025-04-11 07:51:26 +00:00
private ImageSource _badgeImage = ImageLoader.LoadingImage;
2025-03-23 03:02:23 +00:00
private ImageSource _previewImage = ImageLoader.LoadingImage;
public ImageSource Image
{
get
{
2025-03-23 03:02:23 +00:00
if (!_imageLoaded)
{
2025-03-23 03:02:23 +00:00
_imageLoaded = true;
_ = LoadImageAsync();
}
2025-03-23 03:02:23 +00:00
return _image;
}
2025-03-23 03:02:23 +00:00
private set => _image = value;
}
2025-04-11 07:51:26 +00:00
public ImageSource BadgeImage
{
get
{
if (!_badgeImageLoaded)
{
_badgeImageLoaded = true;
_ = LoadBadgeImageAsync();
}
return _badgeImage;
}
private set => _badgeImage = value;
}
2022-09-02 11:40:37 +00:00
public ImageSource PreviewImage
{
get
{
2025-03-23 03:02:23 +00:00
if (!_previewImageLoaded)
{
2025-03-23 03:02:23 +00:00
_previewImageLoaded = true;
_ = LoadPreviewImageAsync();
}
2025-03-23 03:02:23 +00:00
return _previewImage;
}
2025-03-23 03:02:23 +00:00
private set => _previewImage = value;
2022-09-02 11:40:37 +00:00
}
2022-11-28 02:29:32 +00:00
/// <summary>
/// Determines if to use the full width of the preview panel
/// </summary>
2022-12-05 08:01:20 +00:00
public bool UseBigThumbnail => Result.Preview.IsMedia;
2021-07-31 07:44:41 +00:00
public GlyphInfo Glyph { get; set; }
private async Task<ImageSource> LoadImageInternalAsync(string imagePath, Result.IconDelegate icon, bool loadFullImage)
{
if (string.IsNullOrEmpty(imagePath) && icon != null)
{
try
{
2025-03-23 03:02:23 +00:00
return icon();
}
catch (Exception e)
{
2025-04-13 09:50:44 +00:00
App.API.LogException(ClassName,
$"IcoPath is empty and exception when calling IconDelegate for result <{Result.Title}> of plugin <{Result.PluginDirectory}>",
e);
}
}
2025-04-02 10:25:06 +00:00
return await App.API.LoadImageAsync(imagePath, loadFullImage).ConfigureAwait(false);
}
2022-10-16 09:25:47 +00:00
private async Task LoadImageAsync()
{
2026-01-01 11:32:39 +00:00
var imagePath = Result.IcoPathAbsolute;
var iconDelegate = Result.Icon;
2025-04-11 07:51:26 +00:00
if (ImageLoader.TryGetValue(imagePath, false, out var img))
{
2025-03-23 03:02:23 +00:00
_image = img;
}
else
{
// We need to modify the property not field here to trigger the OnPropertyChanged event
Image = await LoadImageInternalAsync(imagePath, iconDelegate, false).ConfigureAwait(false);
}
}
2025-04-11 07:51:26 +00:00
private async Task LoadBadgeImageAsync()
{
var badgeImagePath = Result.BadgeIcoPath;
var badgeIconDelegate = Result.BadgeIcon;
if (ImageLoader.TryGetValue(badgeImagePath, false, out var img))
{
_badgeImage = img;
}
else
{
// We need to modify the property not field here to trigger the OnPropertyChanged event
BadgeImage = await LoadImageInternalAsync(badgeImagePath, badgeIconDelegate, false).ConfigureAwait(false);
}
}
private async Task LoadPreviewImageAsync()
2022-09-02 11:40:37 +00:00
{
2026-01-01 11:32:39 +00:00
var imagePath = Result.Preview.PreviewImagePath ?? Result.IcoPathAbsolute;
2022-12-28 13:45:30 +00:00
var iconDelegate = Result.Preview.PreviewDelegate ?? Result.Icon;
2025-04-11 07:51:26 +00:00
if (ImageLoader.TryGetValue(imagePath, true, out var img))
2022-11-26 06:51:11 +00:00
{
2025-03-23 03:02:23 +00:00
_previewImage = img;
2022-11-26 06:51:11 +00:00
}
else
{
// We need to modify the property not field here to trigger the OnPropertyChanged event
PreviewImage = await LoadImageInternalAsync(imagePath, iconDelegate, true).ConfigureAwait(false);
2022-11-26 06:51:11 +00:00
}
2022-09-02 11:40:37 +00:00
}
public void LoadPreviewImage()
{
2025-03-23 03:02:23 +00:00
if (ShowDefaultPreview == Visibility.Visible && !_previewImageLoaded && ShowPreviewImage == Visibility.Visible)
{
2025-03-23 03:02:23 +00:00
_previewImageLoaded = true;
_ = LoadPreviewImageAsync();
}
}
2025-12-19 07:56:58 +00:00
public string PreviewDescription => Result.Preview?.Description ?? Result.SubTitle;
2016-06-22 23:22:41 +00:00
public Result Result { get; }
2022-09-30 00:58:33 +00:00
public int ResultProgress
{
get
{
if (Result.ProgressBar == null)
return 0;
return Result.ProgressBar.Value;
}
}
public string QuerySuggestionText { get; set; }
public double IconXY { get; set; } = 32;
2016-02-18 14:53:41 +00:00
public override bool Equals(object obj)
{
return obj is ResultViewModel r && Result.Equals(r.Result);
}
public override int GetHashCode()
{
2016-06-22 23:22:41 +00:00
return Result.GetHashCode();
}
public override string ToString()
{
2016-06-22 23:22:41 +00:00
return Result.ToString();
2016-02-18 14:53:41 +00:00
}
}
2021-08-10 10:24:27 +00:00
}