LANCommander/LANCommander.Launcher/Views/GameDetailView.axaml
Pat Hartl 38db011de8 Show library before it's fully imported
Loads the user's library from the /Library/Games endpoint and displays it immediately. This route returns a full data set for the user's games. Images are rendered directly from the server. The import still runs in the background, preserving any offline functionality.
2026-07-04 03:33:13 -05:00

357 lines
20 KiB
XML

<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:vm="using:LANCommander.Launcher.ViewModels"
xmlns:vmComponents="using:LANCommander.Launcher.ViewModels.Components"
xmlns:components="using:LANCommander.Launcher.Views.Components"
xmlns:controls="using:LANCommander.Launcher.Controls"
x:Class="LANCommander.Launcher.Views.GameDetailView"
x:DataType="vm:GameDetailViewModel"
>
<UserControl.Styles>
<Style Selector="Panel:focus-visible > Border#FocusBorder">
<Setter Property="IsVisible" Value="True" />
</Style>
</UserControl.Styles>
<ScrollViewer Classes="TitlebarOffset"
HorizontalScrollBarVisibility="Disabled"
GotFocus="OnScrollViewerGotFocus">
<Grid RowDefinitions="Auto,*">
<!-- ══ Hero Section (extends into title bar) ═══════════════════════ -->
<Border Grid.Row="0"
Height="340"
Background="Black">
<Panel>
<!-- Blurred backdrop strip behind titlebar -->
<Border VerticalAlignment="Top"
Height="60"
ClipToBounds="True"
IsHitTestVisible="False"
IsVisible="{Binding BackgroundPath, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<Panel>
<Image controls:AsyncImage.Source="{Binding BackgroundPath}"
controls:AsyncImage.DecodeWidth="1600"
Stretch="UniformToFill">
<Image.Effect>
<BlurEffect Radius="24" />
</Image.Effect>
</Image>
<Border Background="#33000000" />
</Panel>
</Border>
<!-- Background image -->
<Image controls:AsyncImage.Source="{Binding BackgroundPath}"
controls:AsyncImage.DecodeWidth="1600"
Stretch="UniformToFill"
IsVisible="{Binding BackgroundPath, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<!-- Top fade gradient -->
<Border VerticalAlignment="Top"
Height="120"
IsHitTestVisible="False">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Color="#55000000" Offset="0" />
<GradientStop Color="#00000000" Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
<!-- Bottom fade gradient -->
<Border VerticalAlignment="Bottom"
Height="128"
IsHitTestVisible="False">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Color="#00000000" Offset="0" />
<GradientStop Color="#FF000000" Offset="1" />
</LinearGradientBrush>
</Border.Background>
</Border>
<!-- Back button (overlaid at top-left, below title bar) -->
<IconButton Command="{Binding GoBackCommand}"
Text="{Binding BackLabel}"
Classes="Glass Dark"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="16, 48, 0, 0"
IconType="Regular"
IconValue="CaretLeft"
Size="16"
ZIndex="2"
IsHitTestVisible="True" />
<!-- Logo (bottom-left) -->
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="48,0,0,64"
ClipToBounds="True">
<Viewbox Stretch="Uniform"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
MaxWidth="{Binding Bounds.Width, RelativeSource={RelativeSource AncestorType=Border}, Converter={StaticResource MultiplyConverter}, ConverterParameter=0.33}"
MaxHeight="{Binding Bounds.Height, RelativeSource={RelativeSource AncestorType=Border}, Converter={StaticResource MultiplyConverter}, ConverterParameter=0.5}">
<Image controls:AsyncImage.Source="{Binding LogoPath}"
Stretch="Uniform"
HorizontalAlignment="Left"
VerticalAlignment="Bottom" />
</Viewbox>
</Border>
<!-- Title fallback when no logo (bottom-left) -->
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="24,0,0,28"
Text="{Binding Title}"
FontSize="32"
FontWeight="Bold"
Foreground="White"
IsVisible="{Binding LogoPath, Converter={x:Static StringConverters.IsNullOrEmpty}}" />
</Panel>
</Border>
<!-- ══ Game Details Content ══════════════════════════════════════════ -->
<Grid Grid.Row="1" ColumnDefinitions="*,Auto">
<!-- Left: Action Bar, Metadata -->
<StackPanel x:Name="LeftContent" Grid.Column="0" Margin="24,24,0,24" Spacing="16">
<!-- Game Action Bar Component -->
<components:GameActionBarView DataContext="{Binding ActionBar}" />
<!-- Description -->
<controls:MarkdownTextBlock Text="{Binding Description}"
IsVisible="{Binding Description, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<!-- Screenshots / Videos carousel -->
<StackPanel x:Name="MediaSection" Spacing="8" IsVisible="{Binding HasMedia}">
<TextBlock Text="Media" FontWeight="SemiBold" FontSize="14" Opacity="0.7" />
<CarouselControl ItemsSource="{Binding MediaItems}"
ItemWidth="384"
VisibleItems="2"
Gap="12">
<CarouselControl.ItemTemplate>
<DataTemplate x:DataType="vmComponents:GameMediaItemViewModel">
<Panel Width="384" Height="216"
Focusable="True"
Tapped="MediaItem_Tapped"
KeyDown="MediaItem_KeyDown"
Cursor="Hand">
<!-- Skeleton placeholder -->
<Border CornerRadius="4"
Background="#15FFFFFF"
IsVisible="{Binding IsSkeleton}" />
<!-- Screenshot image -->
<Border CornerRadius="4" ClipToBounds="True"
IsVisible="{Binding !IsVideo}">
<Image Source="{Binding ImageSource}"
Stretch="UniformToFill" />
</Border>
<!-- Video: muted autoplay, click to open lightbox.
A ContentControl whose Content is the video path (null
for screenshots) only realizes its template when non-null,
so screenshots never instantiate an InlineVideoPlayer/libvlc. -->
<ContentControl Content="{Binding VideoPath}">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="sys:String">
<Border CornerRadius="4" ClipToBounds="True"
Background="Black">
<InlineVideoPlayer VideoPath="{Binding}" />
</Border>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
<!-- Focus indicator -->
<Border Name="FocusBorder"
CornerRadius="4"
BorderThickness="2"
BorderBrush="#994096FF"
IsVisible="False" />
</Panel>
</DataTemplate>
</CarouselControl.ItemTemplate>
</CarouselControl>
</StackPanel>
</StackPanel>
<!-- Right: Cover Image + Metadata -->
<StackPanel x:Name="MetadataPanel" Grid.Column="1"
Width="280"
Margin="16,-100,24,24"
Spacing="16"
VerticalAlignment="Top"
ZIndex="1">
<!-- Cover Image -->
<components:Cover Source="{Binding CoverPath}"
MimeType="{Binding CoverMimeType}"
Title="{Binding Title}"
AlwaysAnimate="True" />
<!-- Players -->
<StackPanel Classes="Badges"
IsVisible="{Binding HasPlayerInfo}">
<TextBlock Text="Players" Classes="Header" />
<WrapPanel Orientation="Horizontal">
<Border Margin="0,0,6,6" Padding="10,5" CornerRadius="4" Background="#22FFFFFF"
IsVisible="{Binding Singleplayer}">
<TextBlock Text="Singleplayer" FontSize="12" />
</Border>
<ItemsControl ItemsSource="{Binding MultiplayerModeDetails}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="sys:String">
<Border Margin="0,0,6,6"
Padding="10,5"
CornerRadius="4"
Background="#22FFFFFF">
<TextBlock Text="{Binding}" FontSize="12" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</WrapPanel>
</StackPanel>
<!-- Developers -->
<StackPanel Classes="Badges"
IsVisible="{Binding Developers, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Text="Developers" Classes="Header" />
<ItemsControl ItemsSource="{Binding DeveloperList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="sys:String">
<Button Classes="Badge"
Command="{Binding $parent[UserControl].((vm:GameDetailViewModel)DataContext).SearchForCommand}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding}" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Publishers -->
<StackPanel Classes="Badges"
IsVisible="{Binding Publishers, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Text="Publishers" Classes="Header" />
<ItemsControl ItemsSource="{Binding PublisherList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="sys:String">
<Button Classes="Badge"
Command="{Binding $parent[UserControl].((vm:GameDetailViewModel)DataContext).SearchForCommand}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding}" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Genres -->
<StackPanel Classes="Badges"
IsVisible="{Binding Genres, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Text="Genres" Classes="Header" />
<ItemsControl ItemsSource="{Binding GenreList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="sys:String">
<Button Classes="Badge"
Command="{Binding $parent[UserControl].((vm:GameDetailViewModel)DataContext).SearchForCommand}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding}" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Tags — limited to 5, expandable -->
<StackPanel Classes="Badges"
IsVisible="{Binding Tags, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Text="Tags" Classes="Header" />
<WrapPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding VisibleTagList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="sys:String">
<Button Classes="Badge"
Command="{Binding $parent[UserControl].((vm:GameDetailViewModel)DataContext).SearchForCommand}"
CommandParameter="{Binding}">
<TextBlock Text="{Binding}" />
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Classes="Badge Primary"
IsVisible="{Binding HasMoreTags}"
Command="{Binding ToggleTagsExpandedCommand}">
<TextBlock Text="{Binding ShowMoreTagsLabel}" />
</Button>
</WrapPanel>
</StackPanel>
<!-- Tools -->
<StackPanel Classes="Badges"
IsVisible="{Binding HasTools}">
<TextBlock Text="Tools" Classes="Header" />
<ItemsControl ItemsSource="{Binding Tools}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:ToolItemViewModel">
<Border Margin="0,0,0,6"
Padding="10,5"
CornerRadius="4"
Background="#22FFFFFF">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0"
Text="{Binding Name}"
FontSize="12"
VerticalAlignment="Center" />
<TextBlock Grid.Column="1"
Text="Installed"
FontSize="11"
Opacity="0.6"
VerticalAlignment="Center"
IsVisible="{Binding IsInstalled}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</ScrollViewer>
</UserControl>