Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Add ImageGalleryControl #207

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions FoliCon/Modules/Convertor/ImageCacheConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ public object Convert(object value, Type targetType, object parameter, CultureIn
{
return null;
}

var uri = new Uri(path, UriKind.Absolute);
try
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(path, UriKind.Absolute);
image.UriSource = uri;
image.EndInit();
image.Freeze(); // Improve performance and thread safety
return image;
Expand Down
60 changes: 60 additions & 0 deletions FoliCon/Modules/Extension/BindingPathExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using FoliCon.Modules.Convertor;
using Image = System.Windows.Controls.Image;

namespace FoliCon.Modules.Extension;

public static class BindingPathExtensions
{
private static readonly ImageCacheConverter ImageCacheConverter = new ();

public static readonly DependencyProperty BindingPathProperty =
DependencyProperty.RegisterAttached(
"BindingPath",
typeof(string),
typeof(BindingPathExtensions),
new PropertyMetadata("", BindingPathPropertyChanged));

public static string GetBindingPath(DependencyObject obj)
{
return (string)obj.GetValue(BindingPathProperty);
}

public static void SetBindingPath(DependencyObject obj, string value)
{
obj.SetValue(BindingPathProperty, value);
}

private static void BindingPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var img = d as Image;
var bindingPath = (string)e.NewValue;
var binding = new Binding();

var userControl = FindAncestor<ImageGalleryControl>(img);
if (userControl is { UseCacheConverter: true })
{
binding.Converter = ImageCacheConverter;
}
// Only set Binding.Path if it's not direct DataContext binding
if (bindingPath != ".")
{
binding.Path = new PropertyPath(bindingPath);
}
img?.SetBinding(Image.SourceProperty, binding);
}

private static T FindAncestor<T>(DependencyObject dependencyObject) where T : class
{
var current = dependencyObject;
do
{
if (current is T typed)
{
return typed;
}
current = VisualTreeHelper.GetParent(current);
}
while (current != null);
return null;
}
}
22 changes: 11 additions & 11 deletions FoliCon/ViewModels/PosterPickerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@ public class PosterPickerViewModel : BindableBase, IDialogAware

public ObservableCollection<DArtImageList> ImageUrl { get; set; }
public DelegateCommand StopSearchCommand { get; set; }
public DelegateCommand<object> PickCommand { get; set; }
public DelegateCommand<object> OpenImageCommand { get; set; }
public DelegateCommand<DArtImageList> PickCommand { get; set; }
public DelegateCommand<DArtImageList> OpenImageCommand { get; set; }
#endregion
public PosterPickerViewModel()
{
ImageUrl = new ObservableCollection<DArtImageList>();
ImageUrl = [];
StopSearchCommand = new DelegateCommand(delegate { StopSearch = true; });
PickCommand = new DelegateCommand<object>(PickMethod);
OpenImageCommand = new DelegateCommand<object>(OpenImageMethod);
PickCommand = new DelegateCommand<DArtImageList>(PickMethod);
OpenImageCommand = new DelegateCommand<DArtImageList>(OpenImageMethod);
}

private void OpenImageMethod(object parameter)
private void OpenImageMethod(DArtImageList selectedImage)
{
Logger.Info("Open Image Method called with parameter: {Parameter}, MediaType: {MediaType}",parameter, Result.MediaType );
var link = (string)parameter;
Logger.Info("Open Image Method called with parameter: {Parameter}, MediaType: {MediaType}",selectedImage, Result.MediaType );
var link = selectedImage.Url;
link = Result.MediaType == MediaTypes.Game
? $"https://{ImageHelper.GetImageUrl(link, ImageSize.HD720)[2..]}"
: link;
Expand Down Expand Up @@ -258,10 +258,10 @@ private async void LoadImages(Artwork[] images)
}
IsBusy = false;
}
private void PickMethod(object parameter)
private void PickMethod(DArtImageList pickedImage)
{
Logger.Info("Pick Method called with parameter: {Parameter}", parameter);
var link = (string)parameter;
Logger.Info("Pick Method called with parameter: {Parameter}", pickedImage);
var link = pickedImage.Url;
var result = _isPickedById
? Result.MediaType == MediaTypes.Game ? Result.Result[0] : Result.Result
: Result.MediaType == MediaTypes.Game ? Result.Result[PickedIndex] : Result.Result.Results[PickedIndex];
Expand Down
50 changes: 50 additions & 0 deletions FoliCon/Views/ImageGalleryControl.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<UserControl x:Class="FoliCon.Views.ImageGalleryControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:FoliCon.Views"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:ui="clr-namespace:FoliCon.Modules.UI"
xmlns:extension="clr-namespace:FoliCon.Modules.Extension"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<hc:BusyIndicator IsBusy="{Binding IsBusy}"
BusyContentTemplate="{Binding CustomBusyContentTemplate, RelativeSource={RelativeSource AncestorType={x:Type local:ImageGalleryControl}}}">
<hc:BusyIndicator.ProgressBarStyle>
<Style TargetType="ProgressBar">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</hc:BusyIndicator.ProgressBarStyle>

<Grid Margin="12,12,12,12">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<hc:ScrollViewer x:Name="ScrollViewer" Grid.Row="0" HorizontalScrollBarVisibility="Disabled"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ScrollChanged="ScrollViewer_ScrollChanged">
<ItemsControl ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Tag="{Binding}" Height="128" Width="128" extension:BindingPathExtensions.BindingPath="{Binding BindingPath, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:ImageGalleryControl}}}" Unloaded="FrameworkElement_OnUnloaded"
RenderOptions.BitmapScalingMode="HighQuality" Margin="5, 5, 5, 5">
<b:Interaction.Behaviors>
<ui:ClickBehavior CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Image}}}"
DoubleClickCommand="{Binding Path=DoubleClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
ClickCommand="{Binding Path=ClickCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
</b:Interaction.Behaviors>
</Image>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</hc:ScrollViewer>
</Grid>
</hc:BusyIndicator>
</UserControl>
108 changes: 108 additions & 0 deletions FoliCon/Views/ImageGalleryControl.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Collections;
using Image = System.Windows.Controls.Image;

namespace FoliCon.Views;

public partial class ImageGalleryControl
{
public ImageGalleryControl()
{
InitializeComponent();
}

#region Variables

private bool _autoScroll = true;

public static readonly DependencyProperty CustomBusyContentTemplateProperty =
DependencyProperty.Register(nameof(CustomBusyContentTemplate), typeof(DataTemplate), typeof(ImageGalleryControl), new PropertyMetadata(null));

public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(ImageGalleryControl), new PropertyMetadata(null));

public static readonly DependencyProperty DoubleClickCommandProperty =
DependencyProperty.Register(nameof(DoubleClickCommand), typeof(ICommand), typeof(ImageGalleryControl), new PropertyMetadata(null));

public static readonly DependencyProperty ClickCommandProperty =
DependencyProperty.Register(nameof(ClickCommand), typeof(ICommand), typeof(ImageGalleryControl), new PropertyMetadata(null));

public static readonly DependencyProperty BindingPathProperty =
DependencyProperty.Register(
nameof(BindingPath),
typeof(string),
typeof(ImageGalleryControl),
new PropertyMetadata("Url") // Default binding path
);

public static readonly DependencyProperty UseCacheConverterProperty =
DependencyProperty.Register(nameof(UseCacheConverter), typeof(bool),
typeof(ImageGalleryControl),
new PropertyMetadata(false));

#endregion

#region Properties

public DataTemplate CustomBusyContentTemplate
{
get => GetValue(CustomBusyContentTemplateProperty) as DataTemplate;
set => SetValue(CustomBusyContentTemplateProperty, value);
}

public IEnumerable ItemsSource
{
get => GetValue(ItemsSourceProperty) as IEnumerable;
set => SetValue(ItemsSourceProperty, value);
}

public ICommand DoubleClickCommand
{
get => GetValue(DoubleClickCommandProperty) as ICommand;
set => SetValue(DoubleClickCommandProperty, value);
}

public ICommand ClickCommand
{
get => GetValue(ClickCommandProperty) as ICommand;
set => SetValue(ClickCommandProperty, value);
}

public string BindingPath
{
get => GetValue(BindingPathProperty) as string;
set => SetValue(BindingPathProperty, value);
}

public bool UseCacheConverter
{
get => GetValue(UseCacheConverterProperty) as bool? ?? false;
set => SetValue(UseCacheConverterProperty, value);
}
#endregion

#region Methods

private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{

if (e.ExtentHeightChange == 0)
{
_autoScroll = ScrollViewer.VerticalOffset == ScrollViewer.ScrollableHeight;
}


if (_autoScroll && e.ExtentHeightChange != 0)
{
ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight);
}
}

private void FrameworkElement_OnUnloaded(object sender, RoutedEventArgs e)
{
var image = (Image)sender;
image.Source = null;
}

#endregion

}
67 changes: 17 additions & 50 deletions FoliCon/Views/ManualExplorer.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
xmlns:viewModels="clr-namespace:FoliCon.ViewModels"
xmlns:langs="clr-namespace:FoliCon.Properties.Langs"
xmlns:extension="clr-namespace:FoliCon.Modules.Extension"
xmlns:views="clr-namespace:FoliCon.Views"
prism:ViewModelLocator.AutoWireViewModel="True"
d:DataContext="{d:DesignInstance viewModels:ManualExplorerViewModel }"
x:Name="Root">
<UserControl.Resources>
<convertor:ImageCacheConverter x:Key="ImageCacheConverter"/>
<hc:BindingProxy x:Key="Proxy" Value="{Binding}" />
</UserControl.Resources>
<prism:Dialog.WindowStyle>
<Style TargetType="Window">
Expand All @@ -25,66 +26,32 @@
<Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterOwner" />
</Style>
</prism:Dialog.WindowStyle>
<hc:BusyIndicator IsBusy="{Binding IsBusy}">
<hc:BusyIndicator.BusyContentTemplate>
<views:ImageGalleryControl ItemsSource="{Binding Directory}" BindingPath="." UseCacheConverter="True"
ClickCommand="{Binding OpenImageCommand}"
DoubleClickCommand="{Binding PickCommand}">
<views:ImageGalleryControl.CustomBusyContentTemplate>
<DataTemplate>
<StackPanel Margin="4">
<StackPanel Margin="4">
<TextBlock>
<TextBlock FontWeight="Bold">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}({1}) {2}/{3}">
<Binding Path="DataContext.ProgressInfo.Text" ElementName="Root"/>
<Binding Path="DataContext.DArtDownloadResponse.FileSizeHumanReadable" ElementName="Root"/>
<Binding Path="DataContext.ProgressInfo.Current" ElementName="Root"/>
<Binding Path="DataContext.ProgressInfo.Total" ElementName="Root"/>
<Binding Path="Value.ProgressInfo.Text" Source="{StaticResource Proxy}"/>
<Binding Path="Value.DArtDownloadResponse.FileSizeHumanReadable" Source="{StaticResource Proxy}"/>
<Binding Path="Value.ProgressInfo.Current" Source="{StaticResource Proxy}"/>
<Binding Path="Value.ProgressInfo.Total" Source="{StaticResource Proxy}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<ProgressBar Style="{StaticResource ProgressBarInfo}" Value="{Binding DataContext.ProgressInfo.Current, ElementName=Root}"
Maximum="{Binding DataContext.ProgressInfo.Total, ElementName=Root}" Height="15"/>
<ProgressBar Style="{StaticResource ProgressBarInfo}" Value="{Binding Value.ProgressInfo.Current, Source={StaticResource Proxy}}"
Maximum="{Binding Value.ProgressInfo.Total, Source={StaticResource Proxy}}" Height="15"/>
</StackPanel>
<Grid>
<Button Content="{extension:Lang Key={x:Static langs:LangKeys.Cancel}}" Command="{Binding DataContext.CancelCommand,
ElementName=Root}" HorizontalAlignment="Center" Margin="2 0 0 0"/>
<Button Content="{extension:Lang Key={x:Static langs:LangKeys.Cancel}}" Command="{Binding Value.CancelCommand,
Source={StaticResource Proxy}}" HorizontalAlignment="Center" Margin="2 0 0 0"/>
</Grid>
</StackPanel>
</DataTemplate>
</hc:BusyIndicator.BusyContentTemplate>
<hc:BusyIndicator.ProgressBarStyle>
<Style TargetType="ProgressBar">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</hc:BusyIndicator.ProgressBarStyle>

<Grid Margin="12,12,12,12">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<hc:ScrollViewer x:Name="ScrollViewer" Grid.Row="0" HorizontalScrollBarVisibility="Disabled"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ScrollChanged="ScrollViewer_ScrollChanged">
<ItemsControl ItemsSource="{Binding Directory}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Tag="{Binding}" Height="128" Width="128" Source="{Binding Converter={StaticResource ImageCacheConverter}}" Unloaded="FrameworkElement_OnUnloaded"
RenderOptions.BitmapScalingMode="HighQuality" Margin="5, 5, 5, 5">
<i:Interaction.Behaviors>
<ui:ClickBehavior CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Image}}}"
DoubleClickCommand="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.(viewModels:ManualExplorerViewModel.PickCommand)}"
ClickCommand="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.(viewModels:ManualExplorerViewModel.OpenImageCommand)}"/>
</i:Interaction.Behaviors>
</Image>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</hc:ScrollViewer>
</Grid>
</hc:BusyIndicator>
</views:ImageGalleryControl.CustomBusyContentTemplate>
</views:ImageGalleryControl>
</UserControl>
Loading