|
4 | 4 | xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
5 | 5 | xmlns:views="clr-namespace:OsuPlayer.Views" |
6 | 6 | xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" |
| 7 | + xmlns:customControls="clr-namespace:OsuPlayer.Views.CustomControls" |
7 | 8 | mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
8 | 9 | x:Class="OsuPlayer.Views.ArtistsView" |
9 | 10 | x:DataType="views:ArtistsViewModel" |
|
14 | 15 | </Design.DataContext> |
15 | 16 |
|
16 | 17 | <Grid RowDefinitions="Auto, Auto, *" Margin="0 1"> |
17 | | - <Border Grid.Row="0" Padding="8 24 8 12" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" |
18 | | - > |
19 | | - <Grid ColumnDefinitions="*, Auto"> |
20 | | - <TextBlock |
21 | | - Grid.Column="0" |
22 | | - Text="Artists" |
23 | | - FontSize="32" |
24 | | - FontWeight="Bold" |
25 | | - Margin="16 12 0 8" |
26 | | - VerticalAlignment="Center" |
27 | | - HorizontalAlignment="Left" /> |
28 | | - </Grid> |
| 18 | + <Border Grid.Row="0" Padding="8 24 8 12" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"> |
| 19 | + <TextBlock |
| 20 | + Text="Artists" |
| 21 | + FontSize="32" |
| 22 | + FontWeight="Bold" |
| 23 | + Margin="16 12 0 8" |
| 24 | + VerticalAlignment="Center" |
| 25 | + HorizontalAlignment="Left" /> |
29 | 26 | </Border> |
30 | 27 |
|
31 | 28 | <TextBox Grid.Row="1" Margin="10" Text="{Binding FilterText}" PlaceholderText="search artists..." |
32 | 29 | TextChanged="FilterText_Changed" /> |
33 | 30 |
|
34 | | - <ListBox Grid.Row="2" x:Name="ArtistListBox" |
35 | | - ItemsSource="{Binding Artists}" Background="Transparent" |
36 | | - SelectedItem="{Binding SelectedArtist}" |
37 | | - Tapped="ArtistListBox_DoubleTapped"> |
| 31 | + <!-- |
| 32 | + Virtualizing grid: the ListBox virtualizes *rows*. |
| 33 | + Each row item is an IList<ArtistEntry>; code-behind keeps ColumnCount in sync |
| 34 | + with the scroll viewer width so rows are rebuilt at the right breakpoints. |
| 35 | + --> |
| 36 | + <ListBox Grid.Row="2" |
| 37 | + x:Name="ArtistRowListBox" |
| 38 | + ItemsSource="{Binding ArtistRows}" |
| 39 | + Background="Transparent" |
| 40 | + Padding="4" |
| 41 | + SizeChanged="ArtistRowListBox_SizeChanged"> |
| 42 | + |
| 43 | + <ListBox.Styles> |
| 44 | + <!-- Remove default ListBoxItem chrome / selection highlight --> |
| 45 | + <Style Selector="ListBoxItem"> |
| 46 | + <Setter Property="Padding" Value="0" /> |
| 47 | + <Setter Property="Margin" Value="0" /> |
| 48 | + <Setter Property="Background" Value="Transparent" /> |
| 49 | + </Style> |
| 50 | + <Style Selector="ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter"> |
| 51 | + <Setter Property="Background" Value="Transparent" /> |
| 52 | + </Style> |
| 53 | + <Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter"> |
| 54 | + <Setter Property="Background" Value="Transparent" /> |
| 55 | + </Style> |
| 56 | + <Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter"> |
| 57 | + <Setter Property="Background" Value="Transparent" /> |
| 58 | + </Style> |
| 59 | + <Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator"> |
| 60 | + <Setter Property="IsVisible" Value="False" /> |
| 61 | + </Style> |
| 62 | + </ListBox.Styles> |
| 63 | + |
| 64 | + <!-- One virtualised row per list item --> |
38 | 65 | <ListBox.ItemTemplate> |
39 | | - <DataTemplate DataType="views:ArtistEntry"> |
40 | | - <Grid ColumnDefinitions="Auto, *, Auto" Margin="10 6"> |
41 | | - <avalonia:MaterialIcon Grid.Column="0" Kind="AccountMusic" Height="28" Width="28" |
42 | | - Margin="0 0 12 0" VerticalAlignment="Center" /> |
43 | | - <TextBlock Grid.Column="1" Text="{Binding Name}" FontSize="18" FontWeight="SemiBold" |
44 | | - VerticalAlignment="Center" /> |
45 | | - <TextBlock Grid.Column="2" FontSize="14" VerticalAlignment="Center" |
46 | | - Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"> |
47 | | - <TextBlock.Text> |
48 | | - <MultiBinding StringFormat="{}{0} songs"> |
49 | | - <Binding Path="SongCount" /> |
50 | | - </MultiBinding> |
51 | | - </TextBlock.Text> |
52 | | - </TextBlock> |
53 | | - </Grid> |
| 66 | + <DataTemplate x:DataType="x:Object"> |
| 67 | + <!-- Row of cards — bound via DataContext cast in code-behind via x:CompileBindings=False --> |
| 68 | + <ItemsControl ItemsSource="{Binding}" x:CompileBindings="False" Margin="0 4"> |
| 69 | + <ItemsControl.ItemsPanel> |
| 70 | + <ItemsPanelTemplate> |
| 71 | + <StackPanel Orientation="Horizontal" /> |
| 72 | + </ItemsPanelTemplate> |
| 73 | + </ItemsControl.ItemsPanel> |
| 74 | + <ItemsControl.ItemTemplate> |
| 75 | + <DataTemplate DataType="views:ArtistEntry"> |
| 76 | + <!-- Card --> |
| 77 | + <Border x:Name="ArtistCard" |
| 78 | + Width="{Binding $parent[ListBox].((views:ArtistsViewModel)DataContext).CardWidth}" |
| 79 | + Margin="4 0" |
| 80 | + CornerRadius="16" |
| 81 | + ClipToBounds="True" |
| 82 | + Cursor="Hand" |
| 83 | + Tapped="ArtistCard_Tapped"> |
| 84 | + <!-- Image + overlays stacked in a fixed-height Panel --> |
| 85 | + <Panel Height="160" Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"> |
| 86 | + |
| 87 | + <!-- Placeholder icon visible when no image loaded yet --> |
| 88 | + <avalonia:MaterialIcon Kind="AccountMusic" |
| 89 | + Width="52" Height="52" |
| 90 | + HorizontalAlignment="Center" |
| 91 | + VerticalAlignment="Center" |
| 92 | + Opacity="0.25" /> |
| 93 | + |
| 94 | + <!-- Async image: prefers cached artist image, falls back to first song cover --> |
| 95 | + <customControls:AsyncArtistImage |
| 96 | + CachedImagePath="{Binding CachedImagePath}" |
| 97 | + FirstSong="{Binding FirstSong}" /> |
| 98 | + |
| 99 | + <!-- Dark gradient from bottom up so text is readable --> |
| 100 | + <Panel> |
| 101 | + <Panel.Background> |
| 102 | + <LinearGradientBrush StartPoint="0%,100%" EndPoint="0%,0%"> |
| 103 | + <GradientStop Offset="0.1" Color="#CC000000" /> |
| 104 | + <GradientStop Offset="0.5" Color="#55000000" /> |
| 105 | + <GradientStop Offset="1.0" Color="#00000000" /> |
| 106 | + </LinearGradientBrush> |
| 107 | + </Panel.Background> |
| 108 | + </Panel> |
| 109 | + |
| 110 | + <!-- Song-count badge — top-right (hidden for single-song artists) --> |
| 111 | + <Border |
| 112 | + HorizontalAlignment="Right" |
| 113 | + VerticalAlignment="Top" |
| 114 | + Margin="0 8 8 0" |
| 115 | + Background="{DynamicResource AccentFillColorDefaultBrush}" |
| 116 | + CornerRadius="999" |
| 117 | + Padding="6 3" |
| 118 | + MinWidth="26" |
| 119 | + IsVisible="{Binding ShowSongCount}"> |
| 120 | + <TextBlock |
| 121 | + Text="{Binding SongCount}" |
| 122 | + Foreground="#FFFFFFFF" |
| 123 | + FontSize="12" |
| 124 | + FontWeight="Bold" |
| 125 | + HorizontalAlignment="Center" |
| 126 | + VerticalAlignment="Center" /> |
| 127 | + </Border> |
| 128 | + |
| 129 | + <!-- Artist name — bottom of card, over the gradient --> |
| 130 | + <TextBlock Text="{Binding Name}" |
| 131 | + FontSize="20" |
| 132 | + FontWeight="SemiBold" |
| 133 | + Foreground="White" |
| 134 | + Padding="16 8 8 12" |
| 135 | + TextTrimming="CharacterEllipsis" |
| 136 | + TextWrapping="Wrap" |
| 137 | + MaxHeight="80" |
| 138 | + VerticalAlignment="Bottom" |
| 139 | + HorizontalAlignment="Left" /> |
| 140 | + </Panel> |
| 141 | + </Border> |
| 142 | + </DataTemplate> |
| 143 | + </ItemsControl.ItemTemplate> |
| 144 | + </ItemsControl> |
54 | 145 | </DataTemplate> |
55 | 146 | </ListBox.ItemTemplate> |
56 | 147 | </ListBox> |
|
0 commit comments