Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="400"
x:Class="WheelWizard.Views.BehaviorComponent.MiiImageLoaderWithHover"
xmlns:behaviorComp="clr-namespace:WheelWizard.Views.BehaviorComponent"
xmlns:components="clr-namespace:WheelWizard.Views.Components"
xmlns:conv="clr-namespace:WheelWizard.Views.Converters"
x:DataType="behaviorComp:MiiImageLoaderWithHover" x:Name="Self"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<behaviorComp:AspectGrid x:Name="MiiImageContainer" ClipToBounds="True" ColumnDefinitions="*,3*,*"
RowDefinitions="*,3*,*"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<components:LoadingIcon HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="MiiLoadingIcon"
Foreground="{Binding LoadingColor, ElementName=Self}" Grid.Column="1" Grid.Row="1" >
<components:LoadingIcon.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="MiiLoaded" ElementName="Self"
Converter="{x:Static BoolConverters.Not}"/>
<Binding Path="GeneratedImages" ElementName="Self"
Converter="{x:Static conv:CollectionConverters.FirstIsNull}" />
</MultiBinding>
</components:LoadingIcon.IsVisible>
</components:LoadingIcon>

<Path HorizontalAlignment="Center" VerticalAlignment="Bottom" Stretch="Uniform"
Grid.ColumnSpan="3" Grid.Column="0" Grid.RowSpan="2" Grid.Row="1"
Fill="{Binding FallBackColor, ElementName=Self}" Data="{StaticResource User}">
<Path.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="GeneratedImages" ElementName="Self"
Converter="{x:Static conv:CollectionConverters.FirstIsNull}" />
<Binding Path="MiiLoaded" ElementName="Self"/>
</MultiBinding>
</Path.IsVisible>
</Path>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="{Binding ImageOnlyMargin, ElementName=Self}" Grid.ColumnSpan="3" Grid.Column="0" Grid.Row="0" Grid.RowSpan="3">

<!-- Normal expression -->
<Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Source="{Binding GeneratedImages, ElementName=Self, Converter={x:Static conv:CollectionConverters.First }}"
IsVisible="{Binding ShowNormalImage, ElementName=Self}"/>

<!-- Hover expression -->
<Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Source="{Binding GeneratedImages, ElementName=Self, Converter={x:Static conv:CollectionConverters.Second }}"
IsVisible="{Binding ShowHoverImage, ElementName=Self}"/>
</Grid>

</behaviorComp:AspectGrid>
</UserControl>

Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System.ComponentModel;
using Avalonia;
using Avalonia.Media;
using WheelWizard.MiiImages;
using WheelWizard.MiiImages.Domain;
using WheelWizard.WiiManagement.MiiManagement.Domain.Mii;

namespace WheelWizard.Views.BehaviorComponent;

public partial class MiiImageLoaderWithHover : BaseMiiImage
{
public static readonly StyledProperty<bool> IsHoveredProperty = AvaloniaProperty.Register<MiiImageLoaderWithHover, bool>(
nameof(IsHovered),
false
);

public bool IsHovered
{
get => GetValue(IsHoveredProperty);
set => SetValue(IsHoveredProperty, value);
}

public static readonly StyledProperty<bool> ShowNormalImageProperty = AvaloniaProperty.Register<MiiImageLoaderWithHover, bool>(
nameof(ShowNormalImage),
true
);

public bool ShowNormalImage
{
get => GetValue(ShowNormalImageProperty);
private set => SetValue(ShowNormalImageProperty, value);
}

public static readonly StyledProperty<bool> ShowHoverImageProperty = AvaloniaProperty.Register<MiiImageLoaderWithHover, bool>(
nameof(ShowHoverImage),
false
);

public bool ShowHoverImage
{
get => GetValue(ShowHoverImageProperty);
private set => SetValue(ShowHoverImageProperty, value);
}

private void UpdateImageVisibility()
{
var hasHoverImage = GeneratedImages.Count > 1 && GeneratedImages[1] != null;

if (IsHovered && hasHoverImage)
{
ShowNormalImage = false;
ShowHoverImage = true;
}
else
{
ShowNormalImage = true;
ShowHoverImage = false;
}
}

public static readonly StyledProperty<IBrush> LoadingColorProperty = AvaloniaProperty.Register<MiiImageLoaderWithHover, IBrush>(
nameof(LoadingColor),
new SolidColorBrush(ViewUtils.Colors.Neutral900)
);

public IBrush LoadingColor
{
get => GetValue(LoadingColorProperty);
set => SetValue(LoadingColorProperty, value);
}

public static readonly StyledProperty<IBrush> FallBackColorProperty = AvaloniaProperty.Register<MiiImageLoaderWithHover, IBrush>(
nameof(FallBackColor),
new SolidColorBrush(ViewUtils.Colors.Neutral700)
);

public IBrush FallBackColor
{
get => GetValue(FallBackColorProperty);
set => SetValue(FallBackColorProperty, value);
}

public static readonly StyledProperty<Thickness> ImageOnlyMarginProperty = AvaloniaProperty.Register<
MiiImageLoaderWithHover,
Thickness
>(nameof(ImageOnlyMargin), enableDataValidation: true);

public Thickness ImageOnlyMargin
{
get => GetValue(ImageOnlyMarginProperty);
set => SetValue(ImageOnlyMarginProperty, value);
}

public static readonly StyledProperty<MiiImageSpecifications> ImageVariantProperty = AvaloniaProperty.Register<
MiiImageLoaderWithHover,
MiiImageSpecifications
>(nameof(ImageVariant), MiiImageVariants.OnlinePlayerSmall, coerce: CoerceVariant);

public MiiImageSpecifications ImageVariant
{
get => GetValue(ImageVariantProperty);
set => SetValue(ImageVariantProperty, value);
}

public static readonly StyledProperty<MiiImageSpecifications?> HoverVariantProperty = AvaloniaProperty.Register<
MiiImageLoaderWithHover,
MiiImageSpecifications?
>(nameof(HoverVariant), coerce: CoerceHoverVariant);

public MiiImageSpecifications? HoverVariant
{
get => GetValue(HoverVariantProperty);
set => SetValue(HoverVariantProperty, value);
}

private static MiiImageSpecifications? CoerceHoverVariant(AvaloniaObject o, MiiImageSpecifications? value)
{
var loader = (MiiImageLoaderWithHover)o;
// Reload both variants when hover variant changes (if Mii is already set)
// If Mii is not set yet, OnMiiChanged will handle the reload
if (loader.Mii != null && value != null)
{
loader.ReloadBothVariants();
}
return value;
}

private static MiiImageSpecifications CoerceVariant(AvaloniaObject o, MiiImageSpecifications value)
{
((MiiImageLoaderWithHover)o).OnVariantChanged(value);
return value;
}

public MiiImageLoaderWithHover()
{
InitializeComponent();
PropertyChanged += MiiImageLoaderWithHover_PropertyChanged;
GeneratedImages.CollectionChanged += (s, e) => UpdateImageVisibility();
MiiImageLoaded += (s, e) => UpdateImageVisibility();
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

if (change.Property == IsHoveredProperty)
{
UpdateImageVisibility();
}
}

private void MiiImageLoaderWithHover_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(GeneratedImages))
{
UpdateImageVisibility();
}
}

private void OnVariantChanged(MiiImageSpecifications newSpecifications)
{
ReloadBothVariants();
}

protected override void OnMiiChanged(Mii? newMii)
{
// Always reload both variants when Mii changes
// This ensures both images are loaded even if hover variant is set later
ReloadBothVariants();
}

public void ReloadBothVariants()
{
if (Mii == null)
return;

var variants = new List<MiiImageSpecifications>();

// Always load the normal variant first
variants.Add(ImageVariant);

// If hover variant is set, load it as the second image
if (HoverVariant != null)
{
variants.Add(HoverVariant);
}

ReloadImages(Mii, variants);
}
}
17 changes: 11 additions & 6 deletions WheelWizard/Views/Components/WhWzLibrary/MiiBlock.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@
IsVisible="{TemplateBinding Mii, Converter={x:Static ObjectConverters.IsNull} }"
Width="1000" Foreground="{StaticResource Neutral500}" />

<behaviorComponent:MiiImageLoader HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Mii, Converter={x:Static ObjectConverters.IsNotNull} }"
Mii="{TemplateBinding Mii}" ImageOnlyMargin="-4,-9,-4,0"
FallBackColor="{StaticResource Neutral500}"
LoadingColor="{StaticResource Neutral950}"
ImageVariant="{x:Static miiVars:MiiImageVariants.MiiBlockProfile}" />
<behaviorComponent:MiiImageLoaderWithHover x:Name="PART_MiiImageLoader" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{TemplateBinding Mii, Converter={x:Static ObjectConverters.IsNotNull} }"
Mii="{TemplateBinding Mii}" ImageOnlyMargin="-4,-9,-4,0"
FallBackColor="{StaticResource Neutral500}"
LoadingColor="{StaticResource Neutral950}"
ImageVariant="{x:Static miiVars:MiiImageVariants.MiiBlockProfile}"
ReloadMethod="KeepAllUntilNew" />

<PathIcon Data="{StaticResource StarIcon}" Height="17"
IsVisible="{TemplateBinding IsFavorite}" HorizontalAlignment="Left" VerticalAlignment="Top"
Expand Down Expand Up @@ -85,6 +86,10 @@
<Setter Property="Opacity" Value="1" />
</Style>

<Style Selector="^:pointerover /template/ behaviorComponent|MiiImageLoaderWithHover#PART_MiiImageLoader">
<Setter Property="IsHovered" Value="True" />
</Style>

<Style Selector="^[IsFavorite=True] /template/ Border#PART_MainBorder">
<Setter Property="BorderBrush" Value="{StaticResource Warning500}"></Setter>
<Style Selector="^ PathIcon#PART_AddIcon">
Expand Down
69 changes: 69 additions & 0 deletions WheelWizard/Views/Components/WhWzLibrary/MiiBlock.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using WheelWizard.MiiImages;
using WheelWizard.MiiImages.Domain;
using WheelWizard.Views.BehaviorComponent;
using WheelWizard.WiiManagement;
using WheelWizard.WiiManagement.MiiManagement;
using WheelWizard.WiiManagement.MiiManagement.Domain.Mii;
Expand All @@ -11,6 +15,7 @@ namespace WheelWizard.Views.Components;
public class MiiBlock : RadioButton
{
private static ContextMenu? s_oldMenu;
private MiiImageLoaderWithHover? _miiImageLoader;

public static readonly StyledProperty<Mii?> MiiProperty = AvaloniaProperty.Register<MiiBlock, Mii?>(nameof(Mii));

Expand Down Expand Up @@ -44,6 +49,41 @@ public bool IsGlobal
private set => SetValue(IsGlobalProperty, value);
}

protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

_miiImageLoader = e.NameScope.Find<MiiImageLoaderWithHover>("PART_MiiImageLoader");

// Set hover variant immediately
if (_miiImageLoader != null)
{
// Create hover variant with smile expression
var hoverVariant = MiiImageVariants.MiiBlockProfile.Clone();
hoverVariant.Name = "MiiBlockProfileHover";
hoverVariant.Expression = MiiImageSpecifications.FaceExpression.smile;
_miiImageLoader.HoverVariant = hoverVariant;

// If Mii is already set, trigger reload
if (Mii != null)
{
_miiImageLoader.ReloadBothVariants();
}
}
}

private void SetupHoverVariant()
{
if (_miiImageLoader != null)
{
// Create hover variant with smile expression
var hoverVariant = MiiImageVariants.MiiBlockProfile.Clone();
hoverVariant.Name = "MiiBlockProfileHover";
hoverVariant.Expression = MiiImageSpecifications.FaceExpression.smile;
_miiImageLoader.HoverVariant = hoverVariant;
}
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
Expand All @@ -54,12 +94,41 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
MiiName = mii?.Name.ToString();
IsFavorite = mii?.IsFavorite ?? false;
IsGlobal = mii?.IsGlobal() ?? false;

// Ensure hover variant is set when Mii changes
if (_miiImageLoader != null)
{
SetupHoverVariant();
// Trigger reload if Mii is set
if (mii != null)
{
_miiImageLoader.ReloadBothVariants();
}
}
}

Tag = MiiName ?? String.Empty;
ClipToBounds = string.IsNullOrWhiteSpace(MiiName);
}

protected override void OnPointerEntered(PointerEventArgs e)
{
base.OnPointerEntered(e);
if (_miiImageLoader != null)
{
_miiImageLoader.IsHovered = true;
}
}

protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
if (_miiImageLoader != null)
{
_miiImageLoader.IsHovered = false;
}
}

protected override void OnPointerPressed(PointerPressedEventArgs e)
{
e.Handled = true;
Expand Down
Loading