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