Update README.md with new navigation structure and UI adjustments for video library manager. Enhance LibraryStyles.axaml to improve layout of filter and navigation bars. Revise MainWindow.axaml to accommodate updated UI components and ensure better user experience across different screen sizes.
This commit is contained in:
@@ -112,9 +112,13 @@
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
|
||||
<!-- A row of controls that belongs to the page below it rather than to the window. -->
|
||||
<Style Selector="Border.filterBar">
|
||||
<Setter Property="Padding" Value="20,10" />
|
||||
<!--
|
||||
A row of controls that belongs to the page below it rather than to the window. Lighter
|
||||
than the window bars on purpose: it sits inside the content, and giving it the same weight
|
||||
would make the window read as three stacked strips of chrome.
|
||||
-->
|
||||
<Style Selector="Border.filterBar, Border.navBar">
|
||||
<Setter Property="Padding" Value="20,9" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource SurfaceBorderBrush}" />
|
||||
<Setter Property="BorderThickness" Value="0,0,0,1" />
|
||||
</Style>
|
||||
|
||||
@@ -1,452 +1,467 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:PLib.Desktop.Controls"
|
||||
xmlns:icons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:views="clr-namespace:PLib.Desktop.Views"
|
||||
xmlns:vm="clr-namespace:PLib.Desktop.ViewModels"
|
||||
x:Class="PLib.Desktop.Views.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Title="PLib — видеотека"
|
||||
Width="1180"
|
||||
Height="760"
|
||||
MinWidth="900"
|
||||
MinHeight="420"
|
||||
Background="{DynamicResource PageBackgroundBrush}"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
|
||||
<Window.Resources>
|
||||
|
||||
<DataTemplate x:Key="SettingsTemplate" DataType="vm:SettingsViewModel">
|
||||
<views:SettingsView />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="PlayerTemplate" DataType="vm:VideoPlayerViewModel">
|
||||
<views:VideoPlayerView />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="MetadataScanTemplate" DataType="vm:MetadataScanViewModel">
|
||||
<views:MetadataScanView />
|
||||
</DataTemplate>
|
||||
|
||||
<!-- ======================= Video card ======================= -->
|
||||
<DataTemplate x:Key="VideoCardTemplate" DataType="vm:VideoCardViewModel">
|
||||
<Button Classes="card" Command="{Binding PlayCommand}" ToolTip.Tip="{Binding FullPath}">
|
||||
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Воспроизвести" Command="{Binding PlayCommand}" />
|
||||
<MenuItem Header="Открыть во внешнем плеере" Command="{Binding OpenExternallyCommand}" />
|
||||
<MenuItem Header="Показать в папке" Command="{Binding RevealCommand}" />
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
|
||||
<!-- Poster frame -->
|
||||
<Border Grid.Row="0"
|
||||
CornerRadius="9"
|
||||
ClipToBounds="True"
|
||||
Background="{DynamicResource ThumbnailPlaceholderBrush}">
|
||||
<Panel>
|
||||
<controls:AsyncImage Source="{Binding ThumbnailPath}"
|
||||
DecodeWidth="480"
|
||||
PlaceholderBrush="{DynamicResource ThumbnailPlaceholderBrush}" />
|
||||
|
||||
<!-- Fades in over the poster frame while the pointer rests on the card. Placed
|
||||
directly above it, so the badges and the play affordance stay on top. -->
|
||||
<controls:FilmstripImage Source="{Binding PreviewPath}"
|
||||
FrameCount="{Binding PreviewFrameCount}" />
|
||||
|
||||
<!-- Shown until ffmpeg has produced a frame for this file. -->
|
||||
<icons:MaterialIcon Kind="FilmstripBoxMultiple"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Opacity="0.35"
|
||||
Foreground="{DynamicResource TextTertiaryBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsPending}" />
|
||||
|
||||
<Border Classes="badge"
|
||||
Margin="8"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
IsVisible="{Binding QualityText, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<TextBlock Text="{Binding QualityText}" />
|
||||
</Border>
|
||||
|
||||
<Border Classes="badge"
|
||||
Margin="8"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom">
|
||||
<TextBlock Text="{Binding DurationText}" />
|
||||
</Border>
|
||||
|
||||
<!-- Watched marker, top-right, so it never collides with the quality badge. -->
|
||||
<Border Margin="8"
|
||||
Width="20"
|
||||
Height="20"
|
||||
CornerRadius="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="{DynamicResource AccentBrush}"
|
||||
IsVisible="{Binding IsWatched}"
|
||||
ToolTip.Tip="Просмотрено">
|
||||
<icons:MaterialIcon Kind="Check" Width="13" Height="13" Foreground="White" />
|
||||
</Border>
|
||||
|
||||
<!-- How far the viewer got, drawn across the bottom of the poster. -->
|
||||
<ProgressBar VerticalAlignment="Bottom"
|
||||
Height="3"
|
||||
Minimum="0"
|
||||
Maximum="1"
|
||||
Value="{Binding WatchedFraction}"
|
||||
IsVisible="{Binding HasProgress}"
|
||||
Background="{DynamicResource BadgeBackgroundBrush}"
|
||||
Foreground="{DynamicResource AccentBrush}" />
|
||||
|
||||
<Border Classes="playOverlay" Background="{DynamicResource OverlayBrush}">
|
||||
<Border Width="46"
|
||||
Height="46"
|
||||
CornerRadius="23"
|
||||
Background="{DynamicResource AccentBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<icons:MaterialIcon Kind="Play" Width="24" Height="24" Foreground="White" />
|
||||
</Border>
|
||||
</Border>
|
||||
</Panel>
|
||||
</Border>
|
||||
|
||||
<!-- Caption -->
|
||||
<StackPanel Grid.Row="1" Margin="4,10,4,2" Spacing="3">
|
||||
<TextBlock Classes="cardTitle" Text="{Binding Title}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock Classes="cardMeta" Text="{Binding SizeText}" />
|
||||
<TextBlock Classes="cardMeta"
|
||||
Foreground="{DynamicResource AccentBrush}"
|
||||
Text="{Binding ResumeText}"
|
||||
IsVisible="{Binding ResumeText, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
|
||||
</Window.Resources>
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<!-- ======================= Header ======================= -->
|
||||
<Border Grid.Row="0" Classes="appBar" IsVisible="{Binding !IsVideoFullScreen}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="20">
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||
<Border Width="32"
|
||||
Height="32"
|
||||
CornerRadius="9"
|
||||
Background="{DynamicResource AccentSoftBrush}">
|
||||
<icons:MaterialIcon Kind="VideoVintage"
|
||||
Width="18"
|
||||
Height="18"
|
||||
Foreground="{DynamicResource AccentBrush}" />
|
||||
</Border>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Classes="sectionTitle" Text="Видеотека" />
|
||||
<TextBlock Classes="cardMeta" Text="{Binding Videos.Count, StringFormat='{}{0} видео'}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Navigation. Text tabs rather than icons: "Актёры" and "Студии" have no icon a
|
||||
reader would guess, and a wrong guess costs a click to find out. -->
|
||||
<ItemsControl ItemsSource="{Binding Sections}" VerticalAlignment="Center" Margin="10,0,0,0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="2" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SectionTabViewModel">
|
||||
<Button Classes="sectionTab"
|
||||
Classes.active="{Binding IsSelected}"
|
||||
Command="{Binding SelectCommand}"
|
||||
Content="{Binding Title}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Grid.Column="1"
|
||||
MinWidth="150"
|
||||
MaxWidth="420"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderText="Поиск по названию…"
|
||||
Text="{Binding SearchText}" />
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
|
||||
<ComboBox MinWidth="180"
|
||||
ItemsSource="{Binding SortOptions}"
|
||||
SelectedItem="{Binding SelectedSort}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SortOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Button Command="{Binding ScanCommand}"
|
||||
IsEnabled="{Binding !IsScanning}"
|
||||
ToolTip.Tip="Пересканировать библиотеку">
|
||||
<icons:MaterialIcon Kind="Refresh" Width="17" Height="17" />
|
||||
</Button>
|
||||
|
||||
<ToggleButton IsChecked="{Binding ShowingDuplicatesOnly, Mode=OneWay}"
|
||||
Command="{Binding ToggleDuplicatesCommand}"
|
||||
ToolTip.Tip="Показать похожие видео">
|
||||
<icons:MaterialIcon Kind="ContentDuplicate" Width="17" Height="17" />
|
||||
</ToggleButton>
|
||||
|
||||
<Button Command="{Binding ToggleThemeCommand}" ToolTip.Tip="Сменить тему">
|
||||
<icons:MaterialIcon Kind="ThemeLightDark" Width="17" Height="17" />
|
||||
</Button>
|
||||
|
||||
<Button Command="{Binding OpenSettingsCommand}" ToolTip.Tip="Настройки">
|
||||
<icons:MaterialIcon Kind="CogOutline" Width="17" Height="17" />
|
||||
</Button>
|
||||
|
||||
<Button Classes="Primary" Command="{Binding AddFolderCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="7">
|
||||
<icons:MaterialIcon Kind="FolderPlusOutline" Width="17" Height="17" />
|
||||
<TextBlock Text="Добавить папку" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- =================== Library and settings =================== -->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto">
|
||||
|
||||
<Panel Grid.Column="0">
|
||||
|
||||
<!-- Библиотека -->
|
||||
<Panel IsVisible="{Binding !IsPlayerOpen}">
|
||||
|
||||
<Grid RowDefinitions="Auto,*" IsVisible="{Binding IsVideosSection}">
|
||||
|
||||
<!-- The label the grid is narrowed to. Present only while one is active, so the row
|
||||
costs nothing when the grid shows everything. -->
|
||||
<Border Grid.Row="0"
|
||||
Classes="filterBar"
|
||||
IsVisible="{Binding ActiveLabel, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<TextBlock Classes="cardMeta" VerticalAlignment="Center" Text="Отбор:" />
|
||||
<TextBlock Classes="panelTitle"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding ActiveLabel.Name}" />
|
||||
<Button Classes="transport"
|
||||
Padding="4"
|
||||
Command="{Binding ClearLabelFilterCommand}"
|
||||
ToolTip.Tip="Показать все видео">
|
||||
<icons:MaterialIcon Kind="Close" Width="13" Height="13" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Row="1" Padding="20,18" HorizontalScrollBarVisibility="Disabled">
|
||||
<ItemsRepeater ItemsSource="{Binding Videos}" ItemTemplate="{StaticResource VideoCardTemplate}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout ItemsStretch="Fill"
|
||||
MinItemWidth="230"
|
||||
MinItemHeight="212"
|
||||
MinColumnSpacing="16"
|
||||
MinRowSpacing="16" />
|
||||
</ItemsRepeater.Layout>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<!-- ================= Теги, актёры, студии, коллекции ================= -->
|
||||
<Grid RowDefinitions="Auto,*" IsVisible="{Binding IsEntitySection}">
|
||||
|
||||
<Border Grid.Row="0" Classes="filterBar">
|
||||
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="12">
|
||||
<TextBox Grid.Column="0"
|
||||
MaxWidth="360"
|
||||
HorizontalAlignment="Left"
|
||||
PlaceholderText="Поиск…"
|
||||
Text="{Binding EntitySearchText}" />
|
||||
<ComboBox Grid.Column="1"
|
||||
MinWidth="180"
|
||||
ItemsSource="{Binding EntitySortOptions}"
|
||||
SelectedItem="{Binding SelectedEntitySort}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:EntitySortOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Row="1" Padding="20,18" HorizontalScrollBarVisibility="Disabled">
|
||||
<ItemsRepeater ItemsSource="{Binding Entities}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout ItemsStretch="Fill"
|
||||
MinItemWidth="220"
|
||||
MinItemHeight="56"
|
||||
MinColumnSpacing="12"
|
||||
MinRowSpacing="12" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:LabelSummaryViewModel">
|
||||
<Button Classes="entity" Command="{Binding OpenCommand}">
|
||||
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="10">
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
ToolTip.Tip="{Binding Name}"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource TextPrimaryBrush}" />
|
||||
<Border Grid.Column="1" Classes="badge" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding CountText}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<!-- ======================= Метаданные ======================= -->
|
||||
<ContentControl Content="{Binding MetadataScan}"
|
||||
ContentTemplate="{StaticResource MetadataScanTemplate}"
|
||||
IsVisible="{Binding IsMetadataSection}" />
|
||||
|
||||
<!-- Empty state -->
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="14"
|
||||
MaxWidth="420"
|
||||
IsVisible="{Binding IsLibraryPromptVisible}">
|
||||
<icons:MaterialIcon Kind="VideoBoxOff"
|
||||
Width="52"
|
||||
Height="52"
|
||||
Opacity="0.35"
|
||||
Foreground="{DynamicResource TextTertiaryBrush}"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Classes="sectionTitle"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Здесь пока пусто" />
|
||||
<TextBlock Classes="subtle"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="Укажите папку с видеофайлами — PLib пройдётся по ней, соберёт превью и покажет всё карточками." />
|
||||
<Button Classes="Primary"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding AddFolderCommand}"
|
||||
Content="Выбрать папку" />
|
||||
</StackPanel>
|
||||
|
||||
</Panel>
|
||||
|
||||
<!-- Страница медиа. Content is cleared when the page closes, which detaches the
|
||||
player view and, with it, stops the decoder. -->
|
||||
<ContentControl Content="{Binding OpenedVideo}"
|
||||
ContentTemplate="{StaticResource PlayerTemplate}"
|
||||
IsVisible="{Binding IsPlayerOpen}" />
|
||||
|
||||
</Panel>
|
||||
|
||||
<!-- The panel is a column of the content row, so the bars above and below it stay
|
||||
whole: one header and one status bar belonging to the window, not two of each. -->
|
||||
<Border Grid.Column="1" Classes="settingsPanel" Classes.open="{Binding IsSettingsOpen}">
|
||||
<ContentControl Width="400"
|
||||
Content="{Binding SettingsPanel}"
|
||||
ContentTemplate="{StaticResource SettingsTemplate}" />
|
||||
</Border>
|
||||
|
||||
<Rectangle Grid.Column="1" Classes="panelDivider" IsVisible="{Binding IsSettingsOpen}" />
|
||||
|
||||
<!-- ======================= Tasks ======================= -->
|
||||
<Border Grid.Column="0"
|
||||
Classes="taskPanel"
|
||||
Classes.open="{Binding IsTaskPanelOpen}">
|
||||
<StackPanel Spacing="10">
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="panelTitle" Text="Фоновые задачи" />
|
||||
<Button Grid.Column="1"
|
||||
Classes="transport"
|
||||
Padding="4"
|
||||
Command="{Binding ToggleTaskPanelCommand}">
|
||||
<icons:MaterialIcon Kind="Close" Width="13" Height="13" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Tasks}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:BackgroundTaskViewModel">
|
||||
<StackPanel Spacing="4" Margin="0,0,0,10">
|
||||
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="8">
|
||||
<TextBlock Grid.Column="0" Classes="taskTitle" Text="{Binding Title}" />
|
||||
<TextBlock Grid.Column="1" Classes="cardMeta" Text="{Binding Detail}" />
|
||||
</Grid>
|
||||
<ProgressBar Height="3"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
Value="{Binding Progress}"
|
||||
IsIndeterminate="{Binding IsIndeterminate}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ======================= Status bar ======================= -->
|
||||
<Border Grid.Row="2" Classes="statusBar" IsVisible="{Binding !IsVideoFullScreen}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="14">
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Classes="transport"
|
||||
Padding="8,4"
|
||||
Command="{Binding ToggleTaskPanelCommand}"
|
||||
ToolTip.Tip="Фоновые задачи">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<icons:MaterialIcon Kind="FormatListChecks" Width="15" Height="15" />
|
||||
<ProgressBar Width="120"
|
||||
VerticalAlignment="Center"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
Value="{Binding ScanProgress}"
|
||||
IsIndeterminate="{Binding IsProgressIndeterminate}"
|
||||
IsVisible="{Binding IsScanning}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="subtle"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{Binding StatusText}" />
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Content="Отмена"
|
||||
Command="{Binding CancelScanCommand}"
|
||||
IsVisible="{Binding IsScanning}" />
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:PLib.Desktop.Controls"
|
||||
xmlns:icons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:views="clr-namespace:PLib.Desktop.Views"
|
||||
xmlns:vm="clr-namespace:PLib.Desktop.ViewModels"
|
||||
x:Class="PLib.Desktop.Views.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Title="PLib — видеотека"
|
||||
Width="1180"
|
||||
Height="760"
|
||||
MinWidth="900"
|
||||
MinHeight="420"
|
||||
Background="{DynamicResource PageBackgroundBrush}"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
|
||||
<Window.Resources>
|
||||
|
||||
<DataTemplate x:Key="SettingsTemplate" DataType="vm:SettingsViewModel">
|
||||
<views:SettingsView />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="PlayerTemplate" DataType="vm:VideoPlayerViewModel">
|
||||
<views:VideoPlayerView />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="MetadataScanTemplate" DataType="vm:MetadataScanViewModel">
|
||||
<views:MetadataScanView />
|
||||
</DataTemplate>
|
||||
|
||||
<!-- ======================= Video card ======================= -->
|
||||
<DataTemplate x:Key="VideoCardTemplate" DataType="vm:VideoCardViewModel">
|
||||
<Button Classes="card" Command="{Binding PlayCommand}" ToolTip.Tip="{Binding FullPath}">
|
||||
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Воспроизвести" Command="{Binding PlayCommand}" />
|
||||
<MenuItem Header="Открыть во внешнем плеере" Command="{Binding OpenExternallyCommand}" />
|
||||
<MenuItem Header="Показать в папке" Command="{Binding RevealCommand}" />
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
|
||||
<!-- Poster frame -->
|
||||
<Border Grid.Row="0"
|
||||
CornerRadius="9"
|
||||
ClipToBounds="True"
|
||||
Background="{DynamicResource ThumbnailPlaceholderBrush}">
|
||||
<Panel>
|
||||
<controls:AsyncImage Source="{Binding ThumbnailPath}"
|
||||
DecodeWidth="480"
|
||||
PlaceholderBrush="{DynamicResource ThumbnailPlaceholderBrush}" />
|
||||
|
||||
<!-- Fades in over the poster frame while the pointer rests on the card. Placed
|
||||
directly above it, so the badges and the play affordance stay on top. -->
|
||||
<controls:FilmstripImage Source="{Binding PreviewPath}"
|
||||
FrameCount="{Binding PreviewFrameCount}" />
|
||||
|
||||
<!-- Shown until ffmpeg has produced a frame for this file. -->
|
||||
<icons:MaterialIcon Kind="FilmstripBoxMultiple"
|
||||
Width="30"
|
||||
Height="30"
|
||||
Opacity="0.35"
|
||||
Foreground="{DynamicResource TextTertiaryBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsPending}" />
|
||||
|
||||
<Border Classes="badge"
|
||||
Margin="8"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
IsVisible="{Binding QualityText, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<TextBlock Text="{Binding QualityText}" />
|
||||
</Border>
|
||||
|
||||
<Border Classes="badge"
|
||||
Margin="8"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom">
|
||||
<TextBlock Text="{Binding DurationText}" />
|
||||
</Border>
|
||||
|
||||
<!-- Watched marker, top-right, so it never collides with the quality badge. -->
|
||||
<Border Margin="8"
|
||||
Width="20"
|
||||
Height="20"
|
||||
CornerRadius="10"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Background="{DynamicResource AccentBrush}"
|
||||
IsVisible="{Binding IsWatched}"
|
||||
ToolTip.Tip="Просмотрено">
|
||||
<icons:MaterialIcon Kind="Check" Width="13" Height="13" Foreground="White" />
|
||||
</Border>
|
||||
|
||||
<!-- How far the viewer got, drawn across the bottom of the poster. -->
|
||||
<ProgressBar VerticalAlignment="Bottom"
|
||||
Height="3"
|
||||
Minimum="0"
|
||||
Maximum="1"
|
||||
Value="{Binding WatchedFraction}"
|
||||
IsVisible="{Binding HasProgress}"
|
||||
Background="{DynamicResource BadgeBackgroundBrush}"
|
||||
Foreground="{DynamicResource AccentBrush}" />
|
||||
|
||||
<Border Classes="playOverlay" Background="{DynamicResource OverlayBrush}">
|
||||
<Border Width="46"
|
||||
Height="46"
|
||||
CornerRadius="23"
|
||||
Background="{DynamicResource AccentBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<icons:MaterialIcon Kind="Play" Width="24" Height="24" Foreground="White" />
|
||||
</Border>
|
||||
</Border>
|
||||
</Panel>
|
||||
</Border>
|
||||
|
||||
<!-- Caption -->
|
||||
<StackPanel Grid.Row="1" Margin="4,10,4,2" Spacing="3">
|
||||
<TextBlock Classes="cardTitle" Text="{Binding Title}" />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock Classes="cardMeta" Text="{Binding SizeText}" />
|
||||
<TextBlock Classes="cardMeta"
|
||||
Foreground="{DynamicResource AccentBrush}"
|
||||
Text="{Binding ResumeText}"
|
||||
IsVisible="{Binding ResumeText, Converter={x:Static ObjectConverters.IsNotNull}}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
|
||||
</Window.Resources>
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<!-- ======================= Header ======================= -->
|
||||
<Border Grid.Row="0" Classes="appBar" IsVisible="{Binding !IsVideoFullScreen}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="20">
|
||||
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||||
<Border Width="32"
|
||||
Height="32"
|
||||
CornerRadius="9"
|
||||
Background="{DynamicResource AccentSoftBrush}">
|
||||
<icons:MaterialIcon Kind="VideoVintage"
|
||||
Width="18"
|
||||
Height="18"
|
||||
Foreground="{DynamicResource AccentBrush}" />
|
||||
</Border>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Classes="sectionTitle" Text="Видеотека" />
|
||||
<TextBlock Classes="cardMeta" Text="{Binding Videos.Count, StringFormat='{}{0} видео'}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Grid.Column="1"
|
||||
MinWidth="150"
|
||||
MaxWidth="420"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
PlaceholderText="Поиск по названию, тегу, актёру…"
|
||||
Text="{Binding SearchText}" />
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
|
||||
<Button Command="{Binding ScanCommand}"
|
||||
IsEnabled="{Binding !IsScanning}"
|
||||
ToolTip.Tip="Пересканировать библиотеку">
|
||||
<icons:MaterialIcon Kind="Refresh" Width="17" Height="17" />
|
||||
</Button>
|
||||
|
||||
<ToggleButton IsChecked="{Binding ShowingDuplicatesOnly, Mode=OneWay}"
|
||||
Command="{Binding ToggleDuplicatesCommand}"
|
||||
ToolTip.Tip="Показать похожие видео">
|
||||
<icons:MaterialIcon Kind="ContentDuplicate" Width="17" Height="17" />
|
||||
</ToggleButton>
|
||||
|
||||
<Button Command="{Binding ToggleThemeCommand}" ToolTip.Tip="Сменить тему">
|
||||
<icons:MaterialIcon Kind="ThemeLightDark" Width="17" Height="17" />
|
||||
</Button>
|
||||
|
||||
<Button Command="{Binding OpenSettingsCommand}" ToolTip.Tip="Настройки">
|
||||
<icons:MaterialIcon Kind="CogOutline" Width="17" Height="17" />
|
||||
</Button>
|
||||
|
||||
<Button Classes="Primary" Command="{Binding AddFolderCommand}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="7">
|
||||
<icons:MaterialIcon Kind="FolderPlusOutline" Width="17" Height="17" />
|
||||
<TextBlock Text="Добавить папку" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- =================== Library and settings =================== -->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto">
|
||||
|
||||
<Panel Grid.Column="0">
|
||||
|
||||
<!-- Библиотека -->
|
||||
<Panel IsVisible="{Binding !IsPlayerOpen}">
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
|
||||
<!--
|
||||
Navigation and the controls that belong to whichever page is open. Below the window
|
||||
chrome rather than in it: the bar above holds what is true of the whole application,
|
||||
and cramming the tabs in beside the search left neither enough room.
|
||||
-->
|
||||
<Border Grid.Row="0" Classes="navBar">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="14">
|
||||
|
||||
<!-- Text tabs rather than icons: "Актёры" and "Студии" have no icon a reader
|
||||
would guess, and a wrong guess costs a click to find out. -->
|
||||
<ItemsControl Grid.Column="0" ItemsSource="{Binding Sections}" VerticalAlignment="Center">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="2" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SectionTabViewModel">
|
||||
<Button Classes="sectionTab"
|
||||
Classes.active="{Binding IsSelected}"
|
||||
Command="{Binding SelectCommand}"
|
||||
Content="{Binding Title}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- The label the grid is narrowed to; absent while it shows everything. -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ActiveLabel, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Border Classes="badge">
|
||||
<TextBlock Text="{Binding ActiveLabel.Name}" />
|
||||
</Border>
|
||||
<Button Classes="transport"
|
||||
Padding="3"
|
||||
Command="{Binding ClearLabelFilterCommand}"
|
||||
ToolTip.Tip="Показать все видео">
|
||||
<icons:MaterialIcon Kind="Close" Width="12" Height="12" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
|
||||
<TextBox Width="200"
|
||||
PlaceholderText="Поиск…"
|
||||
Text="{Binding EntitySearchText}"
|
||||
IsVisible="{Binding IsEntitySection}" />
|
||||
|
||||
<ComboBox MinWidth="170"
|
||||
ItemsSource="{Binding EntitySortOptions}"
|
||||
SelectedItem="{Binding SelectedEntitySort}"
|
||||
IsVisible="{Binding IsEntitySection}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:EntitySortOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<ComboBox MinWidth="190"
|
||||
ItemsSource="{Binding SortOptions}"
|
||||
SelectedItem="{Binding SelectedSort}"
|
||||
IsVisible="{Binding IsVideosSection}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:SortOption">
|
||||
<TextBlock Text="{Binding Label}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- One page at a time; the empty-state prompt sits over the grid, not over the bar. -->
|
||||
<Panel Grid.Row="1">
|
||||
|
||||
<ScrollViewer Padding="20,18"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
IsVisible="{Binding IsVideosSection}">
|
||||
<ItemsRepeater ItemsSource="{Binding Videos}" ItemTemplate="{StaticResource VideoCardTemplate}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout ItemsStretch="Fill"
|
||||
MinItemWidth="230"
|
||||
MinItemHeight="212"
|
||||
MinColumnSpacing="16"
|
||||
MinRowSpacing="16" />
|
||||
</ItemsRepeater.Layout>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ============= Теги, актёры, студии, коллекции ============= -->
|
||||
<ScrollViewer Padding="20,18"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
IsVisible="{Binding IsEntitySection}">
|
||||
<ItemsRepeater ItemsSource="{Binding Entities}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout ItemsStretch="Fill"
|
||||
MinItemWidth="220"
|
||||
MinItemHeight="56"
|
||||
MinColumnSpacing="12"
|
||||
MinRowSpacing="12" />
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:LabelSummaryViewModel">
|
||||
<Button Classes="entity" Command="{Binding OpenCommand}">
|
||||
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="10">
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
ToolTip.Tip="{Binding Name}"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource TextPrimaryBrush}" />
|
||||
<Border Grid.Column="1" Classes="badge" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding CountText}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ===================== Метаданные ===================== -->
|
||||
<ContentControl Content="{Binding MetadataScan}"
|
||||
ContentTemplate="{StaticResource MetadataScanTemplate}"
|
||||
IsVisible="{Binding IsMetadataSection}" />
|
||||
|
||||
<!-- Empty state -->
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="14"
|
||||
MaxWidth="420"
|
||||
IsVisible="{Binding IsLibraryPromptVisible}">
|
||||
<icons:MaterialIcon Kind="VideoBoxOff"
|
||||
Width="52"
|
||||
Height="52"
|
||||
Opacity="0.35"
|
||||
Foreground="{DynamicResource TextTertiaryBrush}"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Classes="sectionTitle"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Здесь пока пусто" />
|
||||
<TextBlock Classes="subtle"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="Укажите папку с видеофайлами — PLib пройдётся по ней, соберёт превью и покажет всё карточками." />
|
||||
<Button Classes="Primary"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding AddFolderCommand}"
|
||||
Content="Выбрать папку" />
|
||||
</StackPanel>
|
||||
|
||||
</Panel>
|
||||
</Grid>
|
||||
|
||||
</Panel>
|
||||
|
||||
<!-- Страница медиа. Content is cleared when the page closes, which detaches the
|
||||
player view and, with it, stops the decoder. -->
|
||||
<ContentControl Content="{Binding OpenedVideo}"
|
||||
ContentTemplate="{StaticResource PlayerTemplate}"
|
||||
IsVisible="{Binding IsPlayerOpen}" />
|
||||
|
||||
</Panel>
|
||||
|
||||
<!-- The panel is a column of the content row, so the bars above and below it stay
|
||||
whole: one header and one status bar belonging to the window, not two of each. -->
|
||||
<Border Grid.Column="1" Classes="settingsPanel" Classes.open="{Binding IsSettingsOpen}">
|
||||
<ContentControl Width="400"
|
||||
Content="{Binding SettingsPanel}"
|
||||
ContentTemplate="{StaticResource SettingsTemplate}" />
|
||||
</Border>
|
||||
|
||||
<Rectangle Grid.Column="1" Classes="panelDivider" IsVisible="{Binding IsSettingsOpen}" />
|
||||
|
||||
<!-- ======================= Tasks ======================= -->
|
||||
<Border Grid.Column="0"
|
||||
Classes="taskPanel"
|
||||
Classes.open="{Binding IsTaskPanelOpen}">
|
||||
<StackPanel Spacing="10">
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="panelTitle" Text="Фоновые задачи" />
|
||||
<Button Grid.Column="1"
|
||||
Classes="transport"
|
||||
Padding="4"
|
||||
Command="{Binding ToggleTaskPanelCommand}">
|
||||
<icons:MaterialIcon Kind="Close" Width="13" Height="13" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Tasks}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:BackgroundTaskViewModel">
|
||||
<StackPanel Spacing="4" Margin="0,0,0,10">
|
||||
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="8">
|
||||
<TextBlock Grid.Column="0" Classes="taskTitle" Text="{Binding Title}" />
|
||||
<TextBlock Grid.Column="1" Classes="cardMeta" Text="{Binding Detail}" />
|
||||
</Grid>
|
||||
<ProgressBar Height="3"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
Value="{Binding Progress}"
|
||||
IsIndeterminate="{Binding IsIndeterminate}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ======================= Status bar ======================= -->
|
||||
<Border Grid.Row="2" Classes="statusBar" IsVisible="{Binding !IsVideoFullScreen}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="14">
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Classes="transport"
|
||||
Padding="8,4"
|
||||
Command="{Binding ToggleTaskPanelCommand}"
|
||||
ToolTip.Tip="Фоновые задачи">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||||
<icons:MaterialIcon Kind="FormatListChecks" Width="15" Height="15" />
|
||||
<ProgressBar Width="120"
|
||||
VerticalAlignment="Center"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
Value="{Binding ScanProgress}"
|
||||
IsIndeterminate="{Binding IsProgressIndeterminate}"
|
||||
IsVisible="{Binding IsScanning}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="subtle"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{Binding StatusText}" />
|
||||
|
||||
<Button Grid.Column="2"
|
||||
Content="Отмена"
|
||||
Command="{Binding CancelScanCommand}"
|
||||
IsVisible="{Binding IsScanning}" />
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user