Refactor PLib video library manager's UI to implement a side panel for settings, enhancing accessibility and layout. Update MainWindow and SettingsView to accommodate the new design, ensuring the settings panel integrates seamlessly without overlaying the main content. Revise styles for app bars and status bars for consistency across the application. Update README.md to reflect these UI changes.

This commit is contained in:
Leonid Pershin
2026-08-08 12:52:59 +03:00
parent 835937357e
commit 5920f7a940
5 changed files with 125 additions and 130 deletions
+8 -5
View File
@@ -9,9 +9,9 @@
- Метаданные (длительность, разрешение, кодек) через ffprobe. - Метаданные (длительность, разрешение, кодек) через ffprobe.
- Постеры кадром из видео через ffmpeg, с кэшем на диске. - Постеры кадром из видео через ffmpeg, с кэшем на диске.
- Виртуализированная сетка карточек, ленивая загрузка превью, поиск и сортировка. - Виртуализированная сетка карточек, ленивая загрузка превью, поиск и сортировка.
- Настройки — выдвижной панелью в том же окне: папки библиотеки (с удалением), параметры - Настройки — боковой панелью в том же окне (сетка сдвигается, а не перекрывается): папки
превью и сканирования, тема, очистка кэша. Всё пишется в `settings.json` и подхватывается библиотеки с удалением, параметры превью и сканирования, тема, очистка кэша. Всё пишется
без перезапуска. в `settings.json` и подхватывается без перезапуска.
- Светлая, тёмная и системная темы; выбор запоминается. - Светлая, тёмная и системная темы; выбор запоминается.
- Клик или Enter по карточке — открыть в системном плеере, правая кнопка — контекстное меню. - Клик или Enter по карточке — открыть в системном плеере, правая кнопка — контекстное меню.
@@ -63,9 +63,12 @@ dotnet test
декодированием в нужную ширину. Память зависит от размера окна, а не от размера библиотеки. декодированием в нужную ширину. Память зависит от размера окна, а не от размера библиотеки.
- **Scope на операцию.** `DbContext` живёт ровно одну операцию — ViewModel берёт - **Scope на операцию.** `DbContext` живёт ровно одну операцию — ViewModel берёт
`IServiceScopeFactory` и создаёт scope на каждый вызов. `IServiceScopeFactory` и создаёт scope на каждый вызов.
- **Одно окно.** Настройки — панель поверх сетки, а не второе окно: библиотека остаётся - **Одно окно.** Настройки — колонка макета, а не второе окно и не оверлей: открываясь, она
видна, в alt-tab ничего не добавляется, и приложение остаётся переносимым на сдвигает сетку, и та переливается в меньшее число столбцов, оставаясь целиком доступной.
В alt-tab ничего не добавляется, и приложение остаётся переносимым на
`ISingleViewApplicationLifetime`, где `ShowDialog` попросту не существует. `ISingleViewApplicationLifetime`, где `ShowDialog` попросту не существует.
Верхняя и нижняя полосы обеих половин заданы одной парой стилей, поэтому разделители
читаются как сплошные линии через всё окно.
- **Настройки — рабочая копия.** Панель правит снимок `AppSettings` и записывает его целиком - **Настройки — рабочая копия.** Панель правит снимок `AppSettings` и записывает его целиком
только по «Сохранить», так что отмена не оставляет следов. Пересканирование запускается только по «Сохранить», так что отмена не оставляет следов. Пересканирование запускается
только если изменилось то, что влияет на состав библиотеки, — смена темы или ширины кадра только если изменилось то, что влияет на состав библиотеки, — смена темы или ширины кадра
+40 -39
View File
@@ -104,56 +104,57 @@
<Setter Property="FontSize" Value="12.5" /> <Setter Property="FontSize" Value="12.5" />
</Style> </Style>
<!-- ======================== Settings overlay ======================== --> <!-- ========================= Window bars ========================= -->
<!-- <!--
The overlay stays in the tree and hides behind opacity rather than IsVisible: a collapsed The library and the settings panel sit side by side, so their top and bottom bars have to
element has nothing to animate from, and the slide-in is the whole point. Nothing inside line up: the separators read as one continuous line across the window. Fixing the heights
is focusable while it is closed, because the panel's content is null until it opens. here, in one place, is what keeps them from drifting apart as either side gains controls.
--> -->
<Style Selector="Panel.settingsOverlay"> <Style Selector="Border.appBar">
<Setter Property="Opacity" Value="0" /> <Setter Property="Height" Value="62" />
<Setter Property="IsHitTestVisible" Value="False" /> <Setter Property="Padding" Value="20,0" />
<Setter Property="Transitions"> <Setter Property="Background" Value="{DynamicResource SurfaceBrush}" />
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.16" Easing="CubicEaseOut" />
</Transitions>
</Setter>
</Style>
<Style Selector="Panel.settingsOverlay.open">
<Setter Property="Opacity" Value="1" />
<Setter Property="IsHitTestVisible" Value="True" />
</Style>
<!-- Click-anywhere-to-dismiss backdrop. A Button gets the click handling for free. -->
<Style Selector="Button.scrim">
<Setter Property="Background" Value="{DynamicResource OverlayBrush}" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}" />
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Border.drawer">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Width" Value="470" />
<Setter Property="Background" Value="{DynamicResource PageBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource SurfaceBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource SurfaceBorderBrush}" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
</Style>
<Style Selector="Border.statusBar">
<Setter Property="Height" Value="50" />
<Setter Property="Padding" Value="20,0" />
<Setter Property="Background" Value="{DynamicResource SurfaceBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource SurfaceBorderBrush}" />
<Setter Property="BorderThickness" Value="0,1,0,0" />
</Style>
<!-- ======================== Settings panel ======================== -->
<!--
The panel is a column of the layout rather than an overlay, so opening it squeezes the
grid instead of covering it. Width is animated because Avalonia has no transition for
GridLength; the content inside keeps its full width and is simply clipped while the
column grows, which stops the whole panel reflowing on every animation frame.
-->
<Style Selector="Border.settingsPanel">
<Setter Property="Width" Value="0" />
<Setter Property="ClipToBounds" Value="True" />
<!--
Its own backdrop and a stronger divider than the usual surface border: once the bars on
both sides line up, a hairline in the ordinary border colour reads as one continuous
bar and the panel stops looking like a separate region.
-->
<Setter Property="Background" Value="{DynamicResource PanelBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource PanelDividerBrush}" />
<Setter Property="BorderThickness" Value="1,0,0,0" /> <Setter Property="BorderThickness" Value="1,0,0,0" />
<Setter Property="RenderTransform" Value="translateX(30px)" />
<Setter Property="Transitions"> <Setter Property="Transitions">
<Transitions> <Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" /> <DoubleTransition Property="Width" Duration="0:0:0.22" Easing="CubicEaseOut" />
</Transitions> </Transitions>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Panel.settingsOverlay.open Border.drawer"> <Style Selector="Border.settingsPanel.open">
<Setter Property="RenderTransform" Value="none" /> <Setter Property="Width" Value="400" />
</Style> </Style>
<!-- ============================ Badge ============================ --> <!-- ============================ Badge ============================ -->
+53 -49
View File
@@ -1,49 +1,53 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui" <ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- <!--
Semantic brushes owned by this application. Semi.Avalonia styles the controls; the Semantic brushes owned by this application. Semi.Avalonia styles the controls; the
surfaces the library grid is built from are defined here so the card design does not surfaces the library grid is built from are defined here so the card design does not
depend on the internal resource keys of a third-party theme. depend on the internal resource keys of a third-party theme.
--> -->
<ResourceDictionary.ThemeDictionaries> <ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark"> <ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="PageBackgroundBrush" Color="#0E1014" /> <SolidColorBrush x:Key="PageBackgroundBrush" Color="#0E1014" />
<SolidColorBrush x:Key="SurfaceBrush" Color="#14171D" /> <SolidColorBrush x:Key="SurfaceBrush" Color="#14171D" />
<SolidColorBrush x:Key="SurfaceBorderBrush" Color="#20252E" /> <SolidColorBrush x:Key="SurfaceBorderBrush" Color="#20252E" />
<SolidColorBrush x:Key="CardBackgroundBrush" Color="#171B22" /> <SolidColorBrush x:Key="CardBackgroundBrush" Color="#171B22" />
<SolidColorBrush x:Key="CardBorderBrush" Color="#242A34" /> <SolidColorBrush x:Key="CardBorderBrush" Color="#242A34" />
<SolidColorBrush x:Key="CardHoverBorderBrush" Color="#3F4B5E" /> <SolidColorBrush x:Key="CardHoverBorderBrush" Color="#3F4B5E" />
<SolidColorBrush x:Key="ThumbnailPlaceholderBrush" Color="#1C222B" /> <SolidColorBrush x:Key="ThumbnailPlaceholderBrush" Color="#1C222B" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#E8EDF4" /> <SolidColorBrush x:Key="PanelBackgroundBrush" Color="#111419" />
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#97A2B2" /> <SolidColorBrush x:Key="PanelDividerBrush" Color="#333C4A" />
<SolidColorBrush x:Key="TextTertiaryBrush" Color="#69737F" /> <SolidColorBrush x:Key="TextPrimaryBrush" Color="#E8EDF4" />
<SolidColorBrush x:Key="AccentBrush" Color="#5B8CFF" /> <SolidColorBrush x:Key="TextSecondaryBrush" Color="#97A2B2" />
<SolidColorBrush x:Key="AccentSoftBrush" Color="#1E2A44" /> <SolidColorBrush x:Key="TextTertiaryBrush" Color="#69737F" />
<SolidColorBrush x:Key="BadgeBackgroundBrush" Color="#B3000000" /> <SolidColorBrush x:Key="AccentBrush" Color="#5B8CFF" />
<SolidColorBrush x:Key="BadgeForegroundBrush" Color="#F2F5FA" /> <SolidColorBrush x:Key="AccentSoftBrush" Color="#1E2A44" />
<SolidColorBrush x:Key="OverlayBrush" Color="#59000000" /> <SolidColorBrush x:Key="BadgeBackgroundBrush" Color="#B3000000" />
</ResourceDictionary> <SolidColorBrush x:Key="BadgeForegroundBrush" Color="#F2F5FA" />
<SolidColorBrush x:Key="OverlayBrush" Color="#59000000" />
<ResourceDictionary x:Key="Light"> </ResourceDictionary>
<SolidColorBrush x:Key="PageBackgroundBrush" Color="#F4F6F9" />
<SolidColorBrush x:Key="SurfaceBrush" Color="#FFFFFF" /> <ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="SurfaceBorderBrush" Color="#E6E9EF" /> <SolidColorBrush x:Key="PageBackgroundBrush" Color="#F4F6F9" />
<SolidColorBrush x:Key="CardBackgroundBrush" Color="#FFFFFF" /> <SolidColorBrush x:Key="SurfaceBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="CardBorderBrush" Color="#E4E7EC" /> <SolidColorBrush x:Key="SurfaceBorderBrush" Color="#E6E9EF" />
<SolidColorBrush x:Key="CardHoverBorderBrush" Color="#B9C3D3" /> <SolidColorBrush x:Key="CardBackgroundBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="ThumbnailPlaceholderBrush" Color="#E9EDF2" /> <SolidColorBrush x:Key="CardBorderBrush" Color="#E4E7EC" />
<SolidColorBrush x:Key="TextPrimaryBrush" Color="#111621" /> <SolidColorBrush x:Key="CardHoverBorderBrush" Color="#B9C3D3" />
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#586274" /> <SolidColorBrush x:Key="ThumbnailPlaceholderBrush" Color="#E9EDF2" />
<SolidColorBrush x:Key="TextTertiaryBrush" Color="#8A93A2" /> <SolidColorBrush x:Key="PanelBackgroundBrush" Color="#FAFBFD" />
<SolidColorBrush x:Key="AccentBrush" Color="#2F6BFF" /> <SolidColorBrush x:Key="PanelDividerBrush" Color="#C6CFDC" />
<SolidColorBrush x:Key="AccentSoftBrush" Color="#E5EDFF" /> <SolidColorBrush x:Key="TextPrimaryBrush" Color="#111621" />
<SolidColorBrush x:Key="BadgeBackgroundBrush" Color="#B3101418" /> <SolidColorBrush x:Key="TextSecondaryBrush" Color="#586274" />
<SolidColorBrush x:Key="BadgeForegroundBrush" Color="#FFFFFF" /> <SolidColorBrush x:Key="TextTertiaryBrush" Color="#8A93A2" />
<SolidColorBrush x:Key="OverlayBrush" Color="#4D000000" /> <SolidColorBrush x:Key="AccentBrush" Color="#2F6BFF" />
</ResourceDictionary> <SolidColorBrush x:Key="AccentSoftBrush" Color="#E5EDFF" />
<SolidColorBrush x:Key="BadgeBackgroundBrush" Color="#B3101418" />
</ResourceDictionary.ThemeDictionaries> <SolidColorBrush x:Key="BadgeForegroundBrush" Color="#FFFFFF" />
<SolidColorBrush x:Key="OverlayBrush" Color="#4D000000" />
</ResourceDictionary> </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
+22 -27
View File
@@ -9,7 +9,7 @@
Title="PLib — видеотека" Title="PLib — видеотека"
Width="1180" Width="1180"
Height="760" Height="760"
MinWidth="640" MinWidth="1000"
MinHeight="420" MinHeight="420"
Background="{DynamicResource PageBackgroundBrush}" Background="{DynamicResource PageBackgroundBrush}"
WindowStartupLocation="CenterScreen"> WindowStartupLocation="CenterScreen">
@@ -95,24 +95,22 @@
</Window.Resources> </Window.Resources>
<Grid RowDefinitions="Auto,*,Auto"> <Grid ColumnDefinitions="*,Auto">
<Grid Grid.Column="0" RowDefinitions="Auto,*,Auto">
<!-- ======================= Header ======================= --> <!-- ======================= Header ======================= -->
<Border Grid.Row="0" <Border Grid.Row="0" Classes="appBar">
Background="{DynamicResource SurfaceBrush}"
BorderBrush="{DynamicResource SurfaceBorderBrush}"
BorderThickness="0,0,0,1"
Padding="20,14">
<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="34" <Border Width="32"
Height="34" Height="32"
CornerRadius="9" CornerRadius="9"
Background="{DynamicResource AccentSoftBrush}"> Background="{DynamicResource AccentSoftBrush}">
<icons:MaterialIcon Kind="VideoVintage" <icons:MaterialIcon Kind="VideoVintage"
Width="19" Width="18"
Height="19" Height="18"
Foreground="{DynamicResource AccentBrush}" /> Foreground="{DynamicResource AccentBrush}" />
</Border> </Border>
<StackPanel VerticalAlignment="Center"> <StackPanel VerticalAlignment="Center">
@@ -122,6 +120,7 @@
</StackPanel> </StackPanel>
<TextBox Grid.Column="1" <TextBox Grid.Column="1"
MinWidth="150"
MaxWidth="420" MaxWidth="420"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -154,7 +153,10 @@
<icons:MaterialIcon Kind="CogOutline" Width="17" Height="17" /> <icons:MaterialIcon Kind="CogOutline" Width="17" Height="17" />
</Button> </Button>
<Button Classes="Primary" Command="{Binding AddFolderCommand}"> <!-- Redundant while the panel is open, and the header needs the room there. -->
<Button Classes="Primary"
Command="{Binding AddFolderCommand}"
IsVisible="{Binding !IsSettingsOpen}">
<StackPanel Orientation="Horizontal" Spacing="7"> <StackPanel Orientation="Horizontal" Spacing="7">
<icons:MaterialIcon Kind="FolderPlusOutline" Width="17" Height="17" /> <icons:MaterialIcon Kind="FolderPlusOutline" Width="17" Height="17" />
<TextBlock Text="Добавить папку" /> <TextBlock Text="Добавить папку" />
@@ -209,11 +211,7 @@
</Panel> </Panel>
<!-- ======================= Status bar ======================= --> <!-- ======================= Status bar ======================= -->
<Border Grid.Row="2" <Border Grid.Row="2" Classes="statusBar">
Background="{DynamicResource SurfaceBrush}"
BorderBrush="{DynamicResource SurfaceBorderBrush}"
BorderThickness="0,1,0,0"
Padding="20,9">
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="14"> <Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="14">
<ProgressBar Grid.Column="0" <ProgressBar Grid.Column="0"
@@ -239,17 +237,14 @@
</Grid> </Grid>
</Border> </Border>
</Grid>
<!-- ======================= Settings panel ======================= --> <!-- ======================= Settings panel ======================= -->
<Panel Grid.Row="0" <Border Grid.Column="1" Classes="settingsPanel" Classes.open="{Binding IsSettingsOpen}">
Grid.RowSpan="3" <ContentControl Width="400"
Classes="settingsOverlay" Content="{Binding SettingsPanel}"
Classes.open="{Binding IsSettingsOpen}"> ContentTemplate="{StaticResource SettingsTemplate}" />
<Button Classes="scrim" Command="{Binding CloseSettingsCommand}" /> </Border>
<Border Classes="drawer">
<ContentControl Content="{Binding SettingsPanel}"
ContentTemplate="{StaticResource SettingsTemplate}" />
</Border>
</Panel>
</Grid> </Grid>
</Window> </Window>
+2 -10
View File
@@ -31,11 +31,7 @@
<Grid RowDefinitions="Auto,*,Auto"> <Grid RowDefinitions="Auto,*,Auto">
<!-- ======================= Header ======================= --> <!-- ======================= Header ======================= -->
<Border Grid.Row="0" <Border Grid.Row="0" Classes="appBar">
Background="{DynamicResource SurfaceBrush}"
BorderBrush="{DynamicResource SurfaceBorderBrush}"
BorderThickness="0,0,0,1"
Padding="20,14">
<Grid ColumnDefinitions="Auto,*,Auto"> <Grid ColumnDefinitions="Auto,*,Auto">
<Border Grid.Column="0" <Border Grid.Column="0"
Width="32" Width="32"
@@ -227,11 +223,7 @@
</ScrollViewer> </ScrollViewer>
<!-- ======================= Footer ======================= --> <!-- ======================= Footer ======================= -->
<Border Grid.Row="2" <Border Grid.Row="2" Classes="statusBar">
Background="{DynamicResource SurfaceBrush}"
BorderBrush="{DynamicResource SurfaceBorderBrush}"
BorderThickness="0,1,0,0"
Padding="20,12">
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="14"> <Grid ColumnDefinitions="*,Auto" ColumnSpacing="14">
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
Classes="hint" Classes="hint"