Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6f6a5761f | ||
|
|
06379ebaba | ||
|
|
0e84826a4e | ||
|
|
45eb2a1506 | ||
|
|
0e96c35d2d | ||
|
|
152ecc7ff0 | ||
|
|
8b38aa1e33 | ||
|
|
79e6995384 | ||
|
|
1d6e19a718 | ||
|
|
23cb4b11a9 | ||
|
|
11461a615b | ||
|
|
e8f0359fb2 | ||
|
|
eba81e8be3 | ||
|
|
2e0d437b08 | ||
|
|
cbb0b87392 | ||
|
|
3f63043150 | ||
|
|
77f2474562 | ||
|
|
8aa5c392df | ||
|
|
a453f4862d | ||
|
|
0e6702f3c3 | ||
|
|
412cdbdaf4 | ||
|
|
02dbbf983c | ||
|
|
8cf9830644 | ||
|
|
b04dfedf7e | ||
|
|
3d4e3209e6 | ||
|
|
e92130af85 | ||
|
|
de3d6f1695 | ||
|
|
0a3dadbd82 | ||
|
|
da377838e8 | ||
|
|
67cf6e47c8 | ||
|
|
abf4c3498f | ||
|
|
ff8c85b2d0 | ||
|
|
8cbdb32931 | ||
|
|
757f9f4156 | ||
|
|
c632f4b01a | ||
|
|
a843d0a0e3 | ||
|
|
905df92b05 | ||
|
|
7445299537 | ||
|
|
ba4958f837 | ||
|
|
8ca5245bf5 | ||
|
|
89aa97552a | ||
|
|
fa6da81988 | ||
|
|
aa692e2735 | ||
|
|
c7ee26f284 | ||
|
|
b103e6efd1 | ||
|
|
09e8cce69a | ||
|
|
16cd12e22b | ||
|
|
e0121bee64 | ||
|
|
34bdbc89bc | ||
|
|
b649e9761d | ||
|
|
f5638c6880 | ||
|
|
a9807f4f09 | ||
|
|
def287715d | ||
|
|
966f926112 | ||
|
|
4568293b51 | ||
|
|
10115ab0a8 | ||
|
|
ecefaedcb9 | ||
|
|
b9aae0866d | ||
|
|
afbfcc921e | ||
|
|
0606711023 | ||
|
|
8f997ca3d9 | ||
|
|
042d557eb1 | ||
|
|
153679228d | ||
|
|
5a4fe54ac2 | ||
|
|
14e8d88cd1 | ||
|
|
79e726db33 | ||
|
|
d4f1949833 | ||
|
|
6135f996bf | ||
|
|
5b97621af4 | ||
|
|
0f084d04f8 | ||
|
|
bcb114a171 | ||
|
|
7bbe2a3045 | ||
|
|
fab4e8fe22 | ||
|
|
bc4a06577e | ||
|
|
22a790fff0 | ||
|
|
5e3d0cb78f | ||
|
|
59ee2a6fc3 | ||
|
|
fd5e752b43 | ||
|
|
df31d03da7 | ||
|
|
a7ebba4a62 | ||
|
|
6ca1a84a54 | ||
|
|
bab5a827f6 | ||
|
|
3b1e0389aa | ||
|
|
ae05a55d31 | ||
|
|
4b6d417db3 |
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [jayfunc]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: founchoo
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
69
.github/workflows/pages.yml
vendored
@@ -1,69 +0,0 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||
name: Deploy Jekyll site to Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev", "stable"]
|
||||
paths:
|
||||
- "docs/**"
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: "3.3"
|
||||
bundler-cache: true
|
||||
cache-version: 0
|
||||
working-directory: "${{ github.workspace }}/docs"
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Build with Jekyll
|
||||
# Outputs to the './_site' directory by default
|
||||
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||
env:
|
||||
JEKYLL_ENV: production
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/_site/
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
6
.github/workflows/releases-to-discord.yml
vendored
@@ -1,6 +1,6 @@
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
github-releases-to-discord:
|
||||
@@ -13,8 +13,8 @@ jobs:
|
||||
with:
|
||||
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||
color: "2105893"
|
||||
username: "Release Changelog"
|
||||
avatar_url: "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png"
|
||||
username: "GitHub"
|
||||
avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png"
|
||||
content: "||@everyone||"
|
||||
footer_title: "Changelog"
|
||||
reduce_headings: true
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.11.0" />
|
||||
Version="1.0.30.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.SettingsControls/SettingsExpander/SettingsExpander.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
|
||||
<!-- Other merged dictionaries here -->
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
@@ -48,6 +49,7 @@
|
||||
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
@@ -82,7 +84,7 @@
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="16,0" />
|
||||
<Setter Property="Padding" Value="16,9,16,11" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
|
||||
@@ -92,11 +94,395 @@
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="CardGridStyle" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GhostSliderStyle" TargetType="Slider">
|
||||
<Setter Property="Background" Value="{ThemeResource ControlStrokeColorOnAccentDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="ManipulationMode" Value="None" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
|
||||
<Setter Property="IsFocusEngagementEnabled" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Slider">
|
||||
<Grid Margin="{TemplateBinding Padding}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0,1,1,0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="HeaderContentPresenter"
|
||||
Grid.Row="0"
|
||||
Margin="{ThemeResource SliderTopHeaderMargin}"
|
||||
x:DeferLoadStrategy="Lazy"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
|
||||
Foreground="{ThemeResource SliderHeaderForeground}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="SliderContainer"
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource SliderContainerBackground}"
|
||||
Control.IsTemplateFocusTarget="True">
|
||||
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle
|
||||
x:Name="HorizontalTrackRect"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="HorizontalDecreaseRect"
|
||||
Grid.Row="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="TopTickBar"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,0,4"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="HorizontalInlineTickBar"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="BottomTickBar"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,4,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="HorizontalThumb"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="2"
|
||||
Height="2"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-14,-6,-14,-6"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="VerticalTemplate"
|
||||
MinWidth="{ThemeResource SliderVerticalWidth}"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="VerticalTrackRect"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="VerticalDecreaseRect"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="LeftTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="VerticalInlineTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="RightTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="VerticalThumb"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Width="24"
|
||||
Height="8"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-6,-14,-6,-14"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="TransparentSliderStyle" TargetType="Slider">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="ManipulationMode" Value="None" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
|
||||
<Setter Property="IsFocusEngagementEnabled" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Slider">
|
||||
<Grid Margin="{TemplateBinding Padding}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0,1,1,0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="HeaderContentPresenter"
|
||||
Grid.Row="0"
|
||||
Margin="{ThemeResource SliderTopHeaderMargin}"
|
||||
x:DeferLoadStrategy="Lazy"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
|
||||
Foreground="{ThemeResource SliderHeaderForeground}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="SliderContainer"
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource SliderContainerBackground}"
|
||||
Control.IsTemplateFocusTarget="True">
|
||||
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle
|
||||
x:Name="HorizontalTrackRect"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="8"
|
||||
Fill="Transparent" />
|
||||
<Rectangle
|
||||
x:Name="HorizontalDecreaseRect"
|
||||
Grid.Row="1"
|
||||
Fill="Transparent" />
|
||||
<TickBar
|
||||
x:Name="TopTickBar"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,0,4"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="HorizontalInlineTickBar"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="8"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="BottomTickBar"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,4,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="HorizontalThumb"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="8"
|
||||
Height="8"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-14,-6,-14,-6"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="VerticalTemplate"
|
||||
MinWidth="{ThemeResource SliderVerticalWidth}"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="VerticalTrackRect"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="VerticalDecreaseRect"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="LeftTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="VerticalInlineTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="RightTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="VerticalThumb"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Width="8"
|
||||
Height="8"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-6,-14,-6,-14"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ListViewStretchedItemContainerStyle" TargetType="ListViewItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
|
||||
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
<!-- Fonts -->
|
||||
<FontFamily x:Key="IconFontFamily">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
|
||||
<FontFamily x:Key="IconFontFamily">ms-appx:///Assets/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -10,13 +10,17 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using Serilog;
|
||||
using ShadowViewer.Controls;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
@@ -33,6 +37,8 @@ namespace BetterLyrics.WinUI3
|
||||
public NotificationPanel? LyricsWindowNotificationPanel { get; set; }
|
||||
public NotificationPanel? SettingsWindowNotificationPanel { get; set; }
|
||||
|
||||
private static Mutex? _instanceMutex;
|
||||
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@@ -41,6 +47,8 @@ namespace BetterLyrics.WinUI3
|
||||
DispatcherQueueTimer = DispatcherQueue.CreateTimer();
|
||||
ResourceLoader = new ResourceLoader();
|
||||
|
||||
EnsureSingleInstance();
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
PathHelper.EnsureDirectories();
|
||||
ConfigureServices();
|
||||
@@ -53,12 +61,27 @@ namespace BetterLyrics.WinUI3
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
}
|
||||
|
||||
private void EnsureSingleInstance()
|
||||
{
|
||||
bool createdNew;
|
||||
_instanceMutex = new Mutex(true, MetadataHelper.AppName, out createdNew);
|
||||
|
||||
if (!createdNew)
|
||||
{
|
||||
User32.MessageBox(HWND.NULL, ResourceLoader!.GetString("TryRunMultipleInstance"), null, User32.MB_FLAGS.MB_APPLMODAL);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
|
||||
|
||||
WindowHelper.OpenWindow<LyricsWindow>();
|
||||
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow == null) return;
|
||||
|
||||
lyricsWindow.ViewModel.InitLockHotKey();
|
||||
lyricsWindow.AutoSelectLyricsMode();
|
||||
}
|
||||
|
||||
@@ -90,6 +113,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<SystemTrayViewModel>()
|
||||
.AddSingleton<SettingsPageViewModel>()
|
||||
.AddSingleton<LyricsPageViewModel>()
|
||||
.AddSingleton<MusicGalleryViewModel>()
|
||||
.AddSingleton<LyricsRendererViewModel>()
|
||||
.BuildServiceProvider()
|
||||
);
|
||||
|
||||
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/EmptyBox.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/EmptyState.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Telegram.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
@@ -1,118 +1,145 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="ViewModels\Lyrics\**" />
|
||||
<Content Remove="ViewModels\Lyrics\**" />
|
||||
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
|
||||
<None Remove="ViewModels\Lyrics\**" />
|
||||
<Page Remove="ViewModels\Lyrics\**" />
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Core14.profile.xml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Views\SettingsWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\SettingsWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ShouldCreateLogs>True</ShouldCreateLogs>
|
||||
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
|
||||
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
|
||||
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
|
||||
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
|
||||
<UpdatePackageVersion>True</UpdatePackageVersion>
|
||||
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
|
||||
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
|
||||
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
|
||||
<Version>2025.6.0</Version>
|
||||
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
|
||||
<FileVersion>2025.6.18.0110</FileVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>BetterLyrics.WinUI3</RootNamespace>
|
||||
<Platforms>x86;x64;ARM64</Platforms>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="ViewModels\Lyrics\**" />
|
||||
<Content Remove="ViewModels\Lyrics\**" />
|
||||
<EmbeddedResource Remove="ViewModels\Lyrics\**" />
|
||||
<None Remove="ViewModels\Lyrics\**" />
|
||||
<Page Remove="ViewModels\Lyrics\**" />
|
||||
<PRIResource Remove="ViewModels\Lyrics\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Core14.profile.xml" />
|
||||
<None Remove="Assets\Segoe Fluent Icons.ttf" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Views\MusicGalleryPage.xaml" />
|
||||
<None Remove="Views\MusicGalleryWindow.xaml" />
|
||||
<None Remove="Views\SettingsWindow.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Logo.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="3v.EvtSource" Version="2.0.0" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250703-build.2173" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.MetadataControl" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.7" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
<PackageReference Include="NTextCat" Version="0.3.65" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.10" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.7" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.CoreAudio" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\DesktopLyricsRenderer.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!--Disable Trimming for Specific Packages-->
|
||||
<ItemGroup>
|
||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\Segoe Fluent Icons.ttf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\MusicGalleryWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\MusicGalleryPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Views\SettingsWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\SystemTray.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DefineConstants>$(DefineConstants)</DefineConstants>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<ApplicationIcon>Logo.ico</ApplicationIcon>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ShouldCreateLogs>True</ShouldCreateLogs>
|
||||
<AdvancedSettingsExpanded>True</AdvancedSettingsExpanded>
|
||||
<UpdateAssemblyVersion>False</UpdateAssemblyVersion>
|
||||
<UpdateAssemblyFileVersion>False</UpdateAssemblyFileVersion>
|
||||
<UpdateAssemblyInfoVersion>False</UpdateAssemblyInfoVersion>
|
||||
<UpdatePackageVersion>True</UpdatePackageVersion>
|
||||
<AssemblyInfoVersionType>SettingsVersion</AssemblyInfoVersionType>
|
||||
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
|
||||
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
|
||||
<Version>2025.6.0</Version>
|
||||
<AssemblyVersion>2025.6.18.0110</AssemblyVersion>
|
||||
<FileVersion>2025.6.18.0110</FileVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
AreOpenCloseAnimationsEnabled="True"
|
||||
LightDismissOverlayMode="On"
|
||||
ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<MenuFlyoutItem x:Uid="SystemTrayMusicGallery" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTraySettings" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
|
||||
<MenuFlyoutItem x:Uid="SystemTrayExit" Command="{x:Bind ViewModel.ExitAppCommand}" />
|
||||
<MenuFlyoutItem
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
|
||||
public partial class AlbumArtSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
internal partial class CornerRadiusToDoubleConverter : IValueConverter
|
||||
public partial class CornerRadiusToDoubleConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
internal partial class EnumToIntConverter : IValueConverter
|
||||
public partial class EnumToIntConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class SecondsToFormattedTimeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is double seconds)
|
||||
{
|
||||
return TimeSpan.FromSeconds(seconds).ToString(@"mm\:ss");
|
||||
}
|
||||
else if (value is int secondsInt)
|
||||
{
|
||||
return TimeSpan.FromSeconds(secondsInt).ToString(@"mm\:ss");
|
||||
}
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum CommonSongProperty
|
||||
{
|
||||
Title,
|
||||
Album,
|
||||
Artist
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum DockPlacement
|
||||
{
|
||||
Top,
|
||||
Bottom
|
||||
}
|
||||
|
||||
public static class DockPlacementExtensions
|
||||
{
|
||||
public static WindowPixelSampleMode ToWindowPixelSampleMode(this DockPlacement placement)
|
||||
{
|
||||
return placement switch
|
||||
{
|
||||
DockPlacement.Top => WindowPixelSampleMode.BelowWindow,
|
||||
DockPlacement.Bottom => WindowPixelSampleMode.AboveWindow,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(placement), placement, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,5 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
AlbumArtOnly,
|
||||
LyricsOnly,
|
||||
SplitView,
|
||||
PlaceholderOnly,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum PlaybackOrder
|
||||
{
|
||||
RepeatAll,
|
||||
RepeatOne,
|
||||
Shuffle,
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
public enum WindowPixelSampleMode
|
||||
{
|
||||
BelowWindow,
|
||||
AboveWindow,
|
||||
WindowArea,
|
||||
WindowEdge,
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class AlbumArtChangedEventArgs : EventArgs
|
||||
public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, Color? albumArtAccentColor) : EventArgs
|
||||
{
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null;
|
||||
public Color? AlbumArtAccentColor { get; set; } = null;
|
||||
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
|
||||
public Color? AlbumArtAccentColor { get; set; } = albumArtAccentColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
|
||||
public class TimelineChangedEventArgs(TimeSpan position, TimeSpan end) : EventArgs()
|
||||
{
|
||||
public TimeSpan Position { get; set; } = position;
|
||||
public TimeSpan End { get; set; } = end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.UI.Windowing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class AppWindowHelper
|
||||
{
|
||||
public static void SetIcons(this AppWindow appWindow)
|
||||
{
|
||||
appWindow.SetIcon(@"Assets/Logo.ico");
|
||||
appWindow.SetTaskbarIcon(@"Assets/Logo.ico");
|
||||
appWindow.SetTitleBarIcon(@"Assets/Logo.ico");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class CollectionHelper
|
||||
{
|
||||
public static ObservableCollection<GroupInfoList> GetGroupedBy<T>(
|
||||
this IEnumerable<T> items,
|
||||
Func<T, object> groupKeySelector,
|
||||
Func<object, object>? orderSelector = null)
|
||||
{
|
||||
var query = from item in items
|
||||
group item by groupKeySelector(item) into g
|
||||
orderby g.Key
|
||||
select new GroupInfoList(g.Cast<object>(), orderSelector) { Key = g.Key };
|
||||
|
||||
return new ObservableCollection<GroupInfoList>(query);
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
|
||||
{
|
||||
if (collection == null) return;
|
||||
if (items == null) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
collection.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertRange<T>(this IList<T> list, int index, IEnumerable<T> items)
|
||||
{
|
||||
if (list == null) return;
|
||||
if (items == null) return;
|
||||
if (index < 0 || index > list.Count) return;
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Insert(index++, item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,13 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int sampleY = myRect.Bottom + 1;
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowPixelSampleMode.AboveWindow:
|
||||
{
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int sampleHeight = 1;
|
||||
int sampleY = myRect.Top - 1;
|
||||
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowArea:
|
||||
{
|
||||
int width = myRect.Right - myRect.Left;
|
||||
|
||||
@@ -5,6 +5,8 @@ using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Vanara.PInvoke;
|
||||
using WinRT.Interop;
|
||||
@@ -71,6 +73,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
int targetX = _settingsService.DesktopWindowLeft;
|
||||
int targetY = _settingsService.DesktopWindowTop;
|
||||
|
||||
if (targetWidth <= 0 || targetHeight <= 0 || targetX < 0 || targetY < 0)
|
||||
{
|
||||
targetWidth = 1200;
|
||||
targetHeight = 600;
|
||||
targetX = 200;
|
||||
targetY = 200;
|
||||
}
|
||||
|
||||
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
|
||||
window.AppWindow.MoveAndResize(
|
||||
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
|
||||
@@ -90,6 +100,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
int exStyle = User32.GetWindowLong(hwnd, User32.WindowLongFlags.GWL_EXSTYLE);
|
||||
|
||||
if (enable)
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>ʽ
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
@@ -20,15 +22,21 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static void Disable(Window window)
|
||||
{
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (!_registered.Contains(hwnd)) return;
|
||||
|
||||
window.SetIsShownInSwitchers(true);
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
window.SetIsAlwaysOnTop(false);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
UnregisterAppBar(hwnd);
|
||||
|
||||
window.SetWindowStyle(_originalWindowStyle[hwnd]);
|
||||
_originalWindowStyle.Remove(hwnd);
|
||||
|
||||
window.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
if (_originalPositions.TryGetValue(hwnd, out var rect))
|
||||
{
|
||||
User32.SetWindowPos(
|
||||
@@ -42,14 +50,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
);
|
||||
_originalPositions.Remove(hwnd);
|
||||
}
|
||||
|
||||
UnregisterAppBar(hwnd);
|
||||
}
|
||||
|
||||
public static void Enable(Window window, int appBarHeight)
|
||||
public static void Enable(Window window, int appBarHeight, DockPlacement dockPlacement)
|
||||
{
|
||||
window.SetIsShownInSwitchers(false);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
//window.ExtendsContentIntoTitleBar = false;
|
||||
window.SetIsAlwaysOnTop(true);
|
||||
|
||||
IntPtr hwnd = WindowNative.GetWindowHandle(window);
|
||||
@@ -58,7 +64,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
_originalWindowStyle[hwnd] = window.GetWindowStyle();
|
||||
}
|
||||
window.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
|
||||
|
||||
if (!_originalPositions.ContainsKey(hwnd))
|
||||
{
|
||||
@@ -68,40 +73,52 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
RegisterAppBar(hwnd, appBarHeight);
|
||||
RegisterAppBar(hwnd, appBarHeight, dockPlacement);
|
||||
|
||||
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
|
||||
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
|
||||
int y = dockPlacement == DockPlacement.Top ? 0 : screenHeight - appBarHeight;
|
||||
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
y,
|
||||
screenWidth,
|
||||
appBarHeight,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
User32.SetWindowPosFlags.SWP_HIDEWINDOW
|
||||
);
|
||||
window.ExtendsContentIntoTitleBar = false;
|
||||
window.ToggleWindowStyle(true, WindowStyle.Popup);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private static void RegisterAppBar(IntPtr hwnd, int height)
|
||||
private static void RegisterAppBar(IntPtr hwnd, int height, DockPlacement dockPlacement)
|
||||
{
|
||||
if (_registered.Contains(hwnd)) return;
|
||||
|
||||
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
|
||||
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
|
||||
int top = dockPlacement == DockPlacement.Top ? 0 : screenHeight - height;
|
||||
int bottom = dockPlacement == DockPlacement.Top ? height : screenHeight;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
uEdge = uEdge,
|
||||
rc = new RECT
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Top = top,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = height,
|
||||
Bottom = bottom,
|
||||
},
|
||||
};
|
||||
|
||||
// Ref: https://github.com/TwilightLemon/AppBarTest/blob/master/AppBarCreator.cs
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
_registered.Add(hwnd);
|
||||
@@ -119,40 +136,56 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_REMOVE, ref abd);
|
||||
|
||||
_registered.Remove(hwnd);
|
||||
}
|
||||
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight)
|
||||
private static void RefreshWorkArea()
|
||||
{
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
User32.SendMessage(HWND.HWND_BROADCAST, User32.WindowMessage.WM_SETTINGCHANGE, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight, DockPlacement dockPlacement)
|
||||
{
|
||||
App.DispatcherQueueTimer?.Debounce(() =>
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = Shell32.ABE.ABE_TOP,
|
||||
rc = new RECT
|
||||
if (!_registered.Contains(hwnd))
|
||||
return;
|
||||
|
||||
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
|
||||
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
|
||||
int top = dockPlacement == DockPlacement.Top ? 0 : screenHeight - newHeight;
|
||||
int bottom = dockPlacement == DockPlacement.Top ? newHeight : screenHeight;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
Left = 0,
|
||||
Top = 0,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = newHeight,
|
||||
},
|
||||
};
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = uEdge,
|
||||
rc = new RECT
|
||||
{
|
||||
Left = 0,
|
||||
Top = top,
|
||||
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
Bottom = bottom,
|
||||
},
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
// 同步窗口实际高度
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
0,
|
||||
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
newHeight,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
// 同步窗口实际高度和位置
|
||||
int y = dockPlacement == DockPlacement.Top ? 0 : screenHeight - newHeight;
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
y,
|
||||
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
|
||||
newHeight,
|
||||
newHeight == 0 ? User32.SetWindowPosFlags.SWP_HIDEWINDOW : User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
}, TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
20
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/FontHelper.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class FontHelper
|
||||
{
|
||||
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies();
|
||||
|
||||
public static string GetUserPreferredFontFamily() => SystemFontFamilies.ElementAtOrDefault(_settingsService.SelectedFontFamilyIndex) ?? "Segoe UI";
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
@@ -16,6 +17,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
private readonly List<User32.HWINEVENTHOOK> _hooks = new();
|
||||
private HWND _currentForeground = HWND.NULL;
|
||||
private readonly IntPtr _selfHwnd;
|
||||
private readonly ThrottleHelper _winEventProcThrottle = new(TimeSpan.FromSeconds(1));
|
||||
|
||||
public delegate void WindowChangedHandler(HWND hwnd);
|
||||
private readonly WindowChangedHandler _onWindowChanged;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class GlobalHotKeyHelper
|
||||
{
|
||||
private static Dictionary<int, Action> _hotKeyActions = [];
|
||||
private static int _nextId = 0;
|
||||
|
||||
public static void RegisterHotKey(Window window, User32.HotKeyModifiers modifiers, uint key, Action action)
|
||||
{
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
int id = _nextId++;
|
||||
User32.RegisterHotKey(hwnd, id, modifiers, key);
|
||||
_hotKeyActions[id] = action;
|
||||
}
|
||||
|
||||
public static void UnregisterAllHotKeys(Window window)
|
||||
{
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
foreach (var id in _hotKeyActions.Keys.ToList())
|
||||
{
|
||||
User32.UnregisterHotKey(hwnd, id);
|
||||
_hotKeyActions.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryInvokeAction(int id)
|
||||
{
|
||||
if (_hotKeyActions.TryGetValue(id, out var action))
|
||||
{
|
||||
action?.Invoke();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,14 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -7,10 +16,6 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
@@ -30,62 +35,44 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(string text, int width, int height)
|
||||
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
|
||||
{
|
||||
var stream = new InMemoryRandomAccessStream();
|
||||
var writer = new DataWriter(stream);
|
||||
writer.WriteBytes(bytes);
|
||||
writer.StoreAsync().GetAwaiter().GetResult();
|
||||
writer.FlushAsync().GetAwaiter().GetResult();
|
||||
writer.DetachStream();
|
||||
return RandomAccessStreamReference.CreateFromStream(stream);
|
||||
}
|
||||
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(int width, int height)
|
||||
{
|
||||
var device = CanvasDevice.GetSharedDevice();
|
||||
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
|
||||
// 居中绘制文字
|
||||
// 随机生成渐变色
|
||||
Windows.UI.Color RandomColor()
|
||||
{
|
||||
var rand = new Random(Guid.NewGuid().GetHashCode());
|
||||
double h = rand.NextDouble() * 360;
|
||||
double s = 0.35 + rand.NextDouble() * 0.3; // 0.35~0.65,适中饱和度
|
||||
double l = 0.5 + rand.NextDouble() * 0.3; // 0.5~0.8,明亮
|
||||
return HslToColor(h, s, l);
|
||||
}
|
||||
|
||||
Windows.UI.Color color1 = RandomColor();
|
||||
Windows.UI.Color color2 = RandomColor();
|
||||
|
||||
using (var ds = renderTarget.CreateDrawingSession())
|
||||
{
|
||||
// 背景色
|
||||
ds.Clear(Colors.LightGray);
|
||||
|
||||
// 文字格式
|
||||
var format = new CanvasTextFormat
|
||||
// 绘制线性渐变背景
|
||||
var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
|
||||
{
|
||||
FontSize = Math.Min(width, height) / 6f,
|
||||
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
||||
HorizontalAlignment = CanvasHorizontalAlignment.Center,
|
||||
VerticalAlignment = CanvasVerticalAlignment.Center,
|
||||
WordWrapping = CanvasWordWrapping.Wrap,
|
||||
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
|
||||
Options = CanvasDrawTextOptions.Default,
|
||||
StartPoint = new System.Numerics.Vector2(0, 0),
|
||||
EndPoint = new System.Numerics.Vector2(width, height)
|
||||
};
|
||||
|
||||
// 设定边距
|
||||
float margin = Math.Min(width, height) / 12f;
|
||||
float availableWidth = width - 2 * margin;
|
||||
float availableHeight = height - 2 * margin;
|
||||
|
||||
// 计算合适的字体大小以适应内容区域
|
||||
float fontSize = format.FontSize;
|
||||
float minFontSize = 8f;
|
||||
float maxFontSize = format.FontSize;
|
||||
CanvasTextLayout layout;
|
||||
do
|
||||
{
|
||||
format.FontSize = fontSize;
|
||||
layout = new CanvasTextLayout(
|
||||
ds,
|
||||
text,
|
||||
format,
|
||||
availableWidth,
|
||||
availableHeight
|
||||
);
|
||||
if (
|
||||
layout.LayoutBounds.Width <= availableWidth
|
||||
&& layout.LayoutBounds.Height <= availableHeight
|
||||
)
|
||||
break;
|
||||
fontSize -= 1f;
|
||||
} while (fontSize >= minFontSize);
|
||||
|
||||
// 居中绘制文字(在内容区域内居中)
|
||||
var bounds = layout.LayoutBounds;
|
||||
var x = margin + (availableWidth - (float)bounds.Width) / 2f - (float)bounds.X;
|
||||
var y = margin + (availableHeight - (float)bounds.Height) / 2f - (float)bounds.Y;
|
||||
ds.DrawTextLayout(layout, new Vector2(x, y), Colors.DarkGray);
|
||||
ds.FillRectangle(0, 0, width, height, gradientBrush);
|
||||
}
|
||||
|
||||
// 保存为 PNG 并转为 byte[]
|
||||
@@ -100,9 +87,34 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// HSL转Color
|
||||
static Windows.UI.Color HslToColor(double h, double s, double l)
|
||||
{
|
||||
h = h / 360.0;
|
||||
double r = l, g = l, b = l;
|
||||
if (s != 0)
|
||||
{
|
||||
double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
double p = 2 * l - q;
|
||||
r = HueToRgb(p, q, h + 1.0 / 3.0);
|
||||
g = HueToRgb(p, q, h);
|
||||
b = HueToRgb(p, q, h - 1.0 / 3.0);
|
||||
}
|
||||
return Windows.UI.Color.FromArgb(255, (byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
|
||||
}
|
||||
static double HueToRgb(double p, double q, double t)
|
||||
{
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1.0 / 6.0) return p + (q - p) * 6 * t;
|
||||
if (t < 1.0 / 2.0) return q;
|
||||
if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Color> GetAccentColorsFromByte(byte[] bytes)
|
||||
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes)
|
||||
{
|
||||
// 使用 ImageSharp 读取图片
|
||||
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
|
||||
@@ -137,7 +149,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
//{
|
||||
// var stream = new InMemoryRandomAccessStream();
|
||||
@@ -167,9 +178,11 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.AsStreamForRead().CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
using var reader = new DataReader(stream);
|
||||
await reader.LoadAsync((uint)stream.Size);
|
||||
byte[] buffer = new byte[stream.Size];
|
||||
reader.ReadBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static float GetAverageLuminance(CanvasBitmap bitmap)
|
||||
@@ -188,5 +201,35 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return (float)(sum / (pixels.Length / 4));
|
||||
}
|
||||
|
||||
public static byte[] MakeSquareWithThemeColor(byte[] imageBytes)
|
||||
{
|
||||
using var image = Image.Load<Rgba32>(imageBytes);
|
||||
|
||||
if (image.Width == image.Height)
|
||||
{
|
||||
// 已经是正方形,直接返回
|
||||
return imageBytes;
|
||||
}
|
||||
|
||||
int size = Math.Max(image.Width, image.Height);
|
||||
|
||||
var themeColor = Rgba32.ParseHex(GetAccentColorsFromByte(imageBytes).FirstOrDefault().ToHex());
|
||||
|
||||
// 新建正方形画布
|
||||
using var square = new Image<Rgba32>(size, size, themeColor);
|
||||
|
||||
// 计算居中位置
|
||||
int offsetX = (size - image.Width) / 2;
|
||||
int offsetY = (size - image.Height) / 2;
|
||||
|
||||
// 绘制原图到正方形画布
|
||||
square.Mutate(ctx => ctx.DrawImage(image, new Point(offsetX, offsetY), 1f));
|
||||
|
||||
// 保存为 PNG 字节流
|
||||
using var ms = new MemoryStream();
|
||||
square.Save(ms, new PngEncoder());
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using TinyPinyin;
|
||||
using Windows.Globalization;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
@@ -90,28 +92,53 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
public static bool IsCJK(string text)
|
||||
{
|
||||
return DetectLanguageCode(text) switch
|
||||
return DetectLanguageCode(text)?.Substring(0, 2) switch
|
||||
{
|
||||
"zh" or "ja" or "ko" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static string DetectCountryCode(string? text)
|
||||
public static string ConvertToCountryCode(string? languageCode)
|
||||
{
|
||||
if (text == null) return "en";
|
||||
var code = DetectLanguageCode(text);
|
||||
if (code == null) return "en";
|
||||
// 处理中文简体和繁体
|
||||
if (code == "zh-Hans") return "cn";
|
||||
if (code == "zh-Hant") return "cn";
|
||||
// 其他语言直接返回两字母代码
|
||||
return code;
|
||||
if (languageCode == null) return "us";
|
||||
|
||||
return languageCode switch
|
||||
{
|
||||
"zh" => "cn",
|
||||
"zh-Hans" => "cn",
|
||||
"zh-Hant" => "tw",
|
||||
"ja" => "jp",
|
||||
"ko" => "kr",
|
||||
_ => "us"
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetUserTargetLanguageCode()
|
||||
{
|
||||
return SupportedTargetLanguages[_settingsService.SelectedTargetLanguageIndex].Code;
|
||||
}
|
||||
|
||||
public static int GetDefaultTargetLanguageIndex()
|
||||
{
|
||||
int found = SupportedTargetLanguages.FindIndex(x => ApplicationLanguages.Languages.FirstOrDefault()?.Contains(x.Code) == true);
|
||||
if (found == -1) found = 7; // 默认使用英语
|
||||
return found;
|
||||
}
|
||||
|
||||
public static string GetOrderChar(string text)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text)) return "#";
|
||||
char c = text.ElementAtOrDefault(0);
|
||||
if (char.IsLetter(c) && c < 128)
|
||||
return char.ToUpper(c).ToString();
|
||||
|
||||
if (PinyinHelper.IsChinese(c))
|
||||
{
|
||||
return PinyinHelper.GetPinyinInitials($"{c}");
|
||||
}
|
||||
|
||||
return "#";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Nito.AsyncEx;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -9,23 +10,34 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LatestOnlyTaskRunner
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
private readonly AsyncLock _mutex = new();
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
public async Task RunAsync(Func<CancellationToken, Task> func)
|
||||
public async Task RunAsync(Func<CancellationToken, Task> action)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
CancellationTokenSource oldCts;
|
||||
|
||||
// 使用 AsyncLock 保证线程安全
|
||||
using (await _mutex.LockAsync())
|
||||
{
|
||||
// 取消旧的
|
||||
oldCts = _cts;
|
||||
_cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
oldCts?.Cancel();
|
||||
oldCts?.Dispose();
|
||||
|
||||
CancellationToken token = _cts.Token;
|
||||
|
||||
try
|
||||
{
|
||||
await func(token);
|
||||
await action(token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 可以选择忽略取消异常
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LauncherHelper
|
||||
{
|
||||
public static async Task SelectAndShowFile(string filePath)
|
||||
{
|
||||
var file = await StorageFile.GetFileFromPathAsync(filePath);
|
||||
var folder = await file.GetParentAsync();
|
||||
|
||||
var folderOptions = new FolderLauncherOptions();
|
||||
folderOptions.ItemsToSelect.Add(file);
|
||||
|
||||
await Launcher.LaunchFolderAsync(folder, folderOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ListViewHelper
|
||||
{
|
||||
public static int FindChildIndex(this ListView listView, object frameworkElement)
|
||||
{
|
||||
var children = listView.ItemsPanelRoot.Children.Select(x => ((ListViewItem)x).ContentTemplateRoot).ToList();
|
||||
return children.IndexOf((UIElement)frameworkElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
break;
|
||||
}
|
||||
}
|
||||
PostProcessLyricsLines(durationMs.Value);
|
||||
_lyricsDataArr.Add(new LyricsData()); // 为机翻预留
|
||||
return _lyricsDataArr;
|
||||
}
|
||||
|
||||
@@ -109,12 +109,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
// 按时间分组
|
||||
var grouped = lrcLines.GroupBy(l => l.time).OrderBy(g => g.Key).ToList();
|
||||
int languageCount = grouped.Max(g => g.Count());
|
||||
int languageCount = 0;
|
||||
if (grouped != null && grouped.Count > 0)
|
||||
{
|
||||
// 计算最大语言数量
|
||||
languageCount = grouped.Max(g => g.Count());
|
||||
}
|
||||
|
||||
// 初始化每种语言的歌词列表
|
||||
_lyricsDataArr.Clear();
|
||||
for (int i = 0; i < languageCount; i++)
|
||||
_lyricsDataArr.Add(new LyricsData());
|
||||
for (int i = 0; i < languageCount; i++) _lyricsDataArr.Add(new LyricsData());
|
||||
|
||||
// 遍历每个时间分组
|
||||
foreach (var group in grouped)
|
||||
@@ -128,9 +132,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
var line = new LyricsLine
|
||||
{
|
||||
StartMs = start,
|
||||
EndMs = 0, // 稍后统一修正
|
||||
OriginalText = text,
|
||||
CharTimings = [],
|
||||
LyricsChars = [],
|
||||
};
|
||||
if (syllables != null && syllables.Count > 0)
|
||||
{
|
||||
@@ -139,11 +142,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
var (charStart, charText) = syllables[j];
|
||||
int startIndex = currentIndex;
|
||||
line.CharTimings.Add(
|
||||
new CharTiming
|
||||
line.LyricsChars.Add(
|
||||
new LyricsChar
|
||||
{
|
||||
StartMs = charStart,
|
||||
EndMs = 0, // Fixed later
|
||||
Text = charText ?? "",
|
||||
StartIndex = startIndex,
|
||||
}
|
||||
@@ -162,7 +164,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
List<LyricsLine> originalLines = [];
|
||||
List<LyricsLine> translationLines = [];
|
||||
var xdoc = XDocument.Parse(raw);
|
||||
var xdoc = XDocument.Parse(raw, LoadOptions.PreserveWhitespace);
|
||||
var body = xdoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "body");
|
||||
if (body == null) return;
|
||||
var ps = body.Descendants().Where(e => e.Name.LocalName == "p");
|
||||
@@ -170,7 +172,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
// 句级时间
|
||||
string? pBegin = p.Attribute("begin")?.Value;
|
||||
string? pEnd = p.Attribute("end")?.Value;
|
||||
int pStartMs = ParseTtmlTime(pBegin);
|
||||
int pEndMs = ParseTtmlTime(pEnd);
|
||||
|
||||
// 只获取一级span,且排除ttm:role="x-bg"的span
|
||||
var spans = p.Elements()
|
||||
@@ -186,27 +190,31 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
.Where(s => s.Attribute(XName.Get("role", "http://www.w3.org/ns/ttml#metadata"))?.Value == "x-translation")
|
||||
.ToList();
|
||||
|
||||
// 原文(非 CJK 语言添加空格)
|
||||
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
if (!LanguageHelper.IsCJK(originalText))
|
||||
// 处理原文span后的空白
|
||||
for (int i = 0; i < originalTextSpans.Count; i++)
|
||||
{
|
||||
foreach (var span in originalTextSpans)
|
||||
var span = originalTextSpans[i];
|
||||
var nextNode = span.NodesAfterSelf().FirstOrDefault();
|
||||
if (nextNode is XText textNode)
|
||||
{
|
||||
span.Value += " ";
|
||||
span.Value += textNode.Value;
|
||||
}
|
||||
originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
}
|
||||
// 拼接空白字符后的原文
|
||||
string originalText = string.Concat(originalTextSpans.Select(s => s.Value));
|
||||
|
||||
var originalCharTimings = new List<CharTiming>();
|
||||
var originalCharTimings = new List<LyricsChar>();
|
||||
int originalStartIndex = 0;
|
||||
foreach (var span in originalTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
originalCharTimings.Add(new CharTiming
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
originalCharTimings.Add(new LyricsChar
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
EndMs = sEndMs,
|
||||
StartIndex = originalStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
@@ -218,23 +226,25 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
originalLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
EndMs = pEndMs,
|
||||
OriginalText = originalText,
|
||||
CharTimings = originalCharTimings,
|
||||
LyricsChars = originalCharTimings,
|
||||
});
|
||||
|
||||
// 翻译
|
||||
string translationText = string.Concat(translationTextSpans.Select(s => s.Value));
|
||||
var translationCharTimings = new List<CharTiming>();
|
||||
var translationCharTimings = new List<LyricsChar>();
|
||||
int translationStartIndex = 0;
|
||||
foreach (var span in translationTextSpans)
|
||||
{
|
||||
string? sBegin = span.Attribute("begin")?.Value;
|
||||
string? sEnd = span.Attribute("end")?.Value;
|
||||
int sStartMs = ParseTtmlTime(sBegin);
|
||||
translationCharTimings.Add(new CharTiming
|
||||
int sEndMs = ParseTtmlTime(sEnd);
|
||||
translationCharTimings.Add(new LyricsChar
|
||||
{
|
||||
StartMs = sStartMs,
|
||||
EndMs = 0,
|
||||
EndMs = sEndMs,
|
||||
StartIndex = translationStartIndex,
|
||||
Text = span.Value
|
||||
});
|
||||
@@ -245,9 +255,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
translationLines.Add(new LyricsLine
|
||||
{
|
||||
StartMs = pStartMs,
|
||||
EndMs = 0,
|
||||
EndMs = pEndMs,
|
||||
OriginalText = translationText,
|
||||
CharTimings = translationCharTimings,
|
||||
LyricsChars = translationCharTimings,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -336,9 +346,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
var lineWrite = new LyricsLine
|
||||
{
|
||||
StartMs = lineRead.StartTime ?? 0,
|
||||
EndMs = 0,
|
||||
EndMs = lineRead.EndTime ?? 0,
|
||||
OriginalText = lineRead.Text,
|
||||
CharTimings = [],
|
||||
LyricsChars = [],
|
||||
};
|
||||
|
||||
var syllables = (lineRead as SyllableLineInfo)?.Syllables;
|
||||
@@ -352,22 +362,14 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
)
|
||||
{
|
||||
var syllable = syllables[syllableIndex];
|
||||
var charTiming = new CharTiming
|
||||
var charTiming = new LyricsChar
|
||||
{
|
||||
StartMs = syllable.StartTime,
|
||||
EndMs = 0,
|
||||
EndMs = syllable.EndTime,
|
||||
Text = syllable.Text,
|
||||
StartIndex = startIndex,
|
||||
};
|
||||
if (syllableIndex + 1 < syllables.Count)
|
||||
{
|
||||
charTiming.EndMs = syllables[syllableIndex + 1].StartTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
charTiming.EndMs = lineWrite.EndMs;
|
||||
}
|
||||
lineWrite.CharTimings.Add(charTiming);
|
||||
lineWrite.LyricsChars.Add(charTiming);
|
||||
startIndex += syllable.Text.Length;
|
||||
}
|
||||
}
|
||||
@@ -378,57 +380,5 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
_lyricsDataArr.Add(new LyricsData(lyricsLines));
|
||||
}
|
||||
|
||||
private void PostProcessLyricsLines(int durationMs)
|
||||
{
|
||||
for (int langIdx = 0; langIdx < _lyricsDataArr.Count; langIdx++)
|
||||
{
|
||||
var lines = _lyricsDataArr[langIdx].LyricsLines;
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
if (i + 1 < lines.Count)
|
||||
{
|
||||
lines[i].EndMs = lines[i + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
lines[i].EndMs = durationMs;
|
||||
}
|
||||
|
||||
// 修正 CharTimings 的 EndMs
|
||||
var timings = lines[i].CharTimings;
|
||||
if (timings.Count > 0)
|
||||
{
|
||||
for (int j = 0; j < timings.Count; j++)
|
||||
{
|
||||
if (j + 1 < timings.Count)
|
||||
{
|
||||
timings[j].EndMs = timings[j + 1].StartMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
timings[j].EndMs = lines[i].EndMs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lines.Count > 0)
|
||||
{
|
||||
if (lines[0].StartMs > 0)
|
||||
{
|
||||
lines.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartMs = 0,
|
||||
EndMs = lines[0].StartMs,
|
||||
OriginalText = "● ● ●",
|
||||
CharTimings = [],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
public const string QQGroupUrl = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
|
||||
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
|
||||
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";
|
||||
|
||||
public static async Task<DateTime> GetBuildDate()
|
||||
{
|
||||
|
||||
26
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/NetHelper.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class NetHelper
|
||||
{
|
||||
public static async Task<bool> CheckConnectivity(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var client = new System.Net.Http.HttpClient();
|
||||
// Try to reach a reliable endpoint
|
||||
var res = await client.GetAsync(url);
|
||||
return res.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false; // If any exception occurs, assume no connectivity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using static BetterLyrics.WinUI3.Helper.NoiseOverlayHelper.BitmapFileCreator;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
internal static class NoiseOverlayHelper
|
||||
{
|
||||
|
||||
const string NoiseOverlayFileName = "noise_overlay.bmp";
|
||||
|
||||
static readonly string NoiseOverlayFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "Assets", NoiseOverlayFileName);
|
||||
|
||||
/// <summary>
|
||||
/// 生成 BGRA 格式的灰阶噪声像素数据
|
||||
/// </summary>
|
||||
public static byte[] GenerateNoiseBitmapBGRA(int width, int height)
|
||||
{
|
||||
var random = new Random();
|
||||
var pixelData = new byte[width * height * 4];
|
||||
for (int i = 0; i < width * height; i++)
|
||||
{
|
||||
byte gray = (byte)random.Next(0, 256);
|
||||
pixelData[i * 4 + 0] = gray; // B
|
||||
pixelData[i * 4 + 1] = gray; // G
|
||||
pixelData[i * 4 + 2] = gray; // R
|
||||
pixelData[i * 4 + 3] = 255; // A
|
||||
}
|
||||
return pixelData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成单色灰阶随机噪声
|
||||
/// </summary>
|
||||
/// <param name="outputPath">输出文件路径</param>
|
||||
/// <param name="width">图片宽度</param>
|
||||
/// <param name="height">图片高度</param>
|
||||
public static BitmapFile GenerateNoiseBitmap(int width, int height)
|
||||
{
|
||||
const uint NumOfGrayscale = 16;
|
||||
uint bitCount = NextPowerOfTwo((uint)Math.Round(Math.Sqrt(NumOfGrayscale)));
|
||||
|
||||
var palette = BitmapFileCreator.CreateGrayscalePalette(16);
|
||||
var pixelData = GenerateRandomNoise(width, height, bitCount);
|
||||
|
||||
var fileHeader = BitmapFileCreator.CreateFileHeader(palette, pixelData);
|
||||
var infoHeader = BitmapFileCreator.CreateInfoHeader(width, height, bitCount);
|
||||
|
||||
return new BitmapFile(fileHeader, infoHeader, palette, pixelData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取噪声图片的位头信息
|
||||
/// </summary>
|
||||
public static BitmapFileCreator.WINBMPINFOHEADER ReadBitmapInfoHeaders(string? FilePath)
|
||||
{
|
||||
var _filePath = FilePath ?? NoiseOverlayFilePath;
|
||||
using var fs = new FileStream(_filePath, FileMode.Open, FileAccess.Read);
|
||||
using var br = new BinaryReader(fs);
|
||||
|
||||
// 跳过文件头
|
||||
fs.Seek(Marshal.SizeOf<BitmapFileCreator.BITMAPFILEHEADER>(), SeekOrigin.Begin);
|
||||
|
||||
// 读取信息头
|
||||
byte[] infoHeaderBytes = br.ReadBytes(Marshal.SizeOf<BitmapFileCreator.WINBMPINFOHEADER>());
|
||||
return MemoryMarshal.Read<BitmapFileCreator.WINBMPINFOHEADER>(infoHeaderBytes);
|
||||
}
|
||||
|
||||
public static BitmapFileCreator.WINBMPINFOHEADER ReadBitmapInfoHeaders(byte[] FileBytes)
|
||||
{
|
||||
// 跳过文件头
|
||||
var offset = Marshal.SizeOf<BitmapFileCreator.BITMAPFILEHEADER>();
|
||||
|
||||
// 读取信息头
|
||||
var infoHeaderLength = Marshal.SizeOf<BitmapFileCreator.WINBMPINFOHEADER>();
|
||||
Span<byte> infoHeaderBytes = new(FileBytes, offset, infoHeaderLength);
|
||||
return MemoryMarshal.Read<BitmapFileCreator.WINBMPINFOHEADER>(infoHeaderBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// safe 的写入 struct 到流
|
||||
/// </summary>
|
||||
/// <typeparam name="T">值 strcut</typeparam>
|
||||
/// <param name="stream">要写入的字节流</param>
|
||||
/// <param name="structure">要被写入的结构</param>
|
||||
public static void WriteStruct<T>(Stream stream, in T structure) where T : struct
|
||||
{
|
||||
int size = Unsafe.SizeOf<T>();
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
|
||||
try
|
||||
{
|
||||
MemoryMarshal.Write(buffer, in structure);
|
||||
stream.Write(buffer, 0, size);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 随机填充位图内容
|
||||
/// </summary>
|
||||
/// <param name="width">填充宽度</param>
|
||||
/// <param name="height">填充高度</param>
|
||||
/// <param name="bitCount">单个调色盘索引所占比特位数</param>
|
||||
/// <returns>字节数据</returns>
|
||||
private static byte[] GenerateRandomNoise(int width, int height, uint bitCount)
|
||||
{
|
||||
// 创建位图行字节数,4K 对齐
|
||||
int rowSize = ((width * (int)bitCount + 31) >> 5) << 2;
|
||||
|
||||
// 创建随机位图数据
|
||||
Random rnd = new();
|
||||
return Enumerable.Range(0, rowSize * height)
|
||||
.Select(i => (byte)rnd.Next(0x00, 0xFF))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static uint NextPowerOfTwo(uint value)
|
||||
{
|
||||
value--;
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
return value + 1;
|
||||
}
|
||||
public static class BitmapFileCreator
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 创建BMP文件头
|
||||
/// </summary>
|
||||
/// <param name="palette">调色盘数据</param>
|
||||
/// <param name="pixelData">像素数据</param>
|
||||
/// <returns>文件头结构</returns>
|
||||
public static BITMAPFILEHEADER CreateFileHeader(byte[] palette, byte[] pixelData)
|
||||
{
|
||||
return new BITMAPFILEHEADER
|
||||
{
|
||||
bfType = 0x4D42,
|
||||
bfSize = (uint)(Marshal.SizeOf<BITMAPFILEHEADER>() +
|
||||
Marshal.SizeOf<WINBMPINFOHEADER>() +
|
||||
palette.Length +
|
||||
pixelData.Length),
|
||||
bfReserved1 = 0,
|
||||
bfReserved2 = 0,
|
||||
bfOffBits = (uint)(Marshal.SizeOf<BITMAPFILEHEADER>() +
|
||||
Marshal.SizeOf<WINBMPINFOHEADER>() +
|
||||
palette.Length)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定值填充到为最接近的2的幂数
|
||||
/// </summary>
|
||||
/// <summary>
|
||||
/// 生成灰阶调色盘
|
||||
/// </summary>
|
||||
/// <param name="colors">灰阶数量</param>
|
||||
/// <returns>调色盘byte数组</returns>
|
||||
public static byte[] CreateGrayscalePalette(int colors)
|
||||
{
|
||||
return Enumerable.Range(0, colors)
|
||||
.SelectMany(i => Enumerable.Repeat((byte)(i * 0x10), 4))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建BMP信息头
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
/// <param name="height">高度</param>
|
||||
/// <param name="bitCount">单个像素(调色盘索引)位数</param>
|
||||
/// <returns>BMP信息头结构</returns>
|
||||
public static WINBMPINFOHEADER CreateInfoHeader(int width, int height, uint bitCount)
|
||||
{
|
||||
return new WINBMPINFOHEADER
|
||||
{
|
||||
biSize = (uint)Marshal.SizeOf<WINBMPINFOHEADER>(),
|
||||
biWidth = (uint)width,
|
||||
biHeight = (uint)height,
|
||||
biPlanes = 1,
|
||||
biBitCount = (ushort)bitCount,
|
||||
biCompression = 0,
|
||||
biSizeImage = 0,
|
||||
biXPelsPerMeter = 0,
|
||||
biYPelsPerMeter = 0,
|
||||
biClrUsed = 0,
|
||||
biClrImportant = 0
|
||||
};
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
/// <summary>
|
||||
/// BMP 位图文件头
|
||||
/// </summary>
|
||||
public struct BITMAPFILEHEADER
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件类型标识,用于指定文件格式
|
||||
/// </summary>
|
||||
public ushort bfType;
|
||||
/// <summary>
|
||||
/// 文件大小,以字节为单位
|
||||
/// </summary>
|
||||
public uint bfSize;
|
||||
/// <summary>
|
||||
/// 保留字段,未使用
|
||||
/// </summary>
|
||||
public ushort bfReserved1;
|
||||
/// <summary>
|
||||
/// 保留字段,未使用
|
||||
/// </summary>
|
||||
public ushort bfReserved2;
|
||||
/// <summary>
|
||||
/// 像素数据的起始位置,以字节为单位,从文件头开始计算
|
||||
/// </summary>
|
||||
public uint bfOffBits;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
/// <summary>
|
||||
/// BMP 位图信息头
|
||||
/// </summary>
|
||||
public struct WINBMPINFOHEADER
|
||||
{
|
||||
/// <summary>
|
||||
/// 指定此结构体的字节大小。
|
||||
/// </summary>
|
||||
public uint biSize;
|
||||
|
||||
/// <summary>
|
||||
/// 以像素为单位的位图宽度。
|
||||
/// </summary>
|
||||
public uint biWidth;
|
||||
|
||||
/// <summary>
|
||||
/// 以像素为单位的位图高度。
|
||||
/// </summary>
|
||||
public uint biHeight;
|
||||
|
||||
/// <summary>
|
||||
/// 目标设备的平面数,通常为1。
|
||||
/// </summary>
|
||||
public ushort biPlanes;
|
||||
|
||||
/// <summary>
|
||||
/// 每个像素的位数,表示颜色深度,如1、4、8、16、24、32等。
|
||||
/// </summary>
|
||||
public ushort biBitCount;
|
||||
|
||||
/// <summary>
|
||||
/// 压缩类型,0表示不压缩。
|
||||
/// </summary>
|
||||
public uint biCompression;
|
||||
|
||||
/// <summary>
|
||||
/// 位图图像数据的大小(以字节为单位),若图像未压缩,该值可设为0。
|
||||
/// </summary>
|
||||
public uint biSizeImage;
|
||||
|
||||
/// <summary>
|
||||
/// 每米X轴方向的像素数(水平分辨率),通常设为0。
|
||||
/// </summary>
|
||||
public uint biXPelsPerMeter;
|
||||
|
||||
/// <summary>
|
||||
/// 每米Y轴方向的像素数(垂直分辨率),通常设为0。
|
||||
/// </summary>
|
||||
public uint biYPelsPerMeter;
|
||||
|
||||
/// <summary>
|
||||
/// 实际使用的颜色索引数,若为0,则使用位图中实际出现的颜色数。
|
||||
/// </summary>
|
||||
public uint biClrUsed;
|
||||
|
||||
/// <summary>
|
||||
/// 重要颜色索引数,0表示所有颜色都重要。
|
||||
/// </summary>
|
||||
public uint biClrImportant;
|
||||
}
|
||||
}
|
||||
|
||||
public class BitmapFile(BitmapFileCreator.BITMAPFILEHEADER fileHeader, BitmapFileCreator.WINBMPINFOHEADER infoHeader, byte[] palette, byte[] pixelData)
|
||||
{
|
||||
public BITMAPFILEHEADER FileHeader = fileHeader;
|
||||
public WINBMPINFOHEADER InfoHeader = infoHeader;
|
||||
public byte[] Palette = palette;
|
||||
public byte[] PixelData = pixelData;
|
||||
|
||||
/// <summary>
|
||||
/// 转换为byte[]
|
||||
/// </summary>
|
||||
/// <param name="bf"></param>
|
||||
public static explicit operator byte[](BitmapFile bf)
|
||||
{
|
||||
var result = new byte[bf.FileHeader.bfSize];
|
||||
var ms = new MemoryStream(result);
|
||||
bf.WriteToStream(ms);
|
||||
return result;
|
||||
}
|
||||
|
||||
public byte[] ToByteArray() => (byte[])this;
|
||||
|
||||
public Task<byte[]> ToByteArrayAsync() { return Task.FromResult(ToByteArray());}
|
||||
|
||||
/// <summary>
|
||||
/// 写入此结构到流中
|
||||
/// </summary>
|
||||
public void WriteToStream(Stream stream)
|
||||
{
|
||||
NoiseOverlayHelper.WriteStruct(stream, FileHeader);
|
||||
NoiseOverlayHelper.WriteStruct(stream, InfoHeader);
|
||||
stream.Write(Palette);
|
||||
stream.Write(PixelData);
|
||||
stream.Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static string TranslationCacheDirectory => Path.Combine(CacheFolder, "translations");
|
||||
|
||||
public static string QQTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "qq");
|
||||
public static string NeteaseTranslationCacheDirectory => Path.Combine(TranslationCacheDirectory, "netease");
|
||||
|
||||
public static string AlbumArtCacheDirectory => Path.Combine(CacheFolder, "album-art");
|
||||
|
||||
@@ -49,6 +50,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
Directory.CreateDirectory(AmllTtmlDbLyricsCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(QQTranslationCacheDirectory);
|
||||
Directory.CreateDirectory(NeteaseTranslationCacheDirectory);
|
||||
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using Vanara.Extensions;
|
||||
using Vanara.PInvoke;
|
||||
using static Vanara.PInvoke.CoreAudio;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class SystemVolumeHelper
|
||||
{
|
||||
private readonly static IMMDeviceEnumerator _deviceEnumerator = new();
|
||||
private static IAudioEndpointVolume? _endpointVolume = null;
|
||||
private static VolumeCallbackImpl? _callbackImpl;
|
||||
private static int _masterVolume = 0;
|
||||
private static DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
public static event Action<int>? VolumeChanged;
|
||||
|
||||
static SystemVolumeHelper()
|
||||
{
|
||||
var device = _deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia);
|
||||
if (device != null)
|
||||
{
|
||||
device.Activate(typeof(IAudioEndpointVolume).GUID, 0, null, out var obj);
|
||||
if (obj is IAudioEndpointVolume endpointVolume)
|
||||
{
|
||||
_endpointVolume = endpointVolume;
|
||||
_callbackImpl = new VolumeCallbackImpl();
|
||||
_endpointVolume.RegisterControlChangeNotify(_callbackImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前系统主音量(0~100)。
|
||||
/// </summary>
|
||||
public static int GetMasterVolume()
|
||||
{
|
||||
if (_endpointVolume != null)
|
||||
{
|
||||
float level = _endpointVolume.GetMasterVolumeLevelScalar();
|
||||
_masterVolume = (int)(level * 100);
|
||||
}
|
||||
|
||||
return _masterVolume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前系统主音量(0~100)。
|
||||
/// </summary>
|
||||
public static void SetMasterVolume(int volume)
|
||||
{
|
||||
if (_masterVolume == volume) return;
|
||||
|
||||
_masterVolume = volume;
|
||||
_endpointVolume?.SetMasterVolumeLevelScalar(_masterVolume / 100f, Guid.Empty);
|
||||
}
|
||||
|
||||
// 内部回调实现
|
||||
private class VolumeCallbackImpl : IAudioEndpointVolumeCallback
|
||||
{
|
||||
HRESULT IAudioEndpointVolumeCallback.OnNotify(nint pNotify)
|
||||
{
|
||||
var data = pNotify.ToStructure<AUDIO_VOLUME_NOTIFICATION_DATA>();
|
||||
_masterVolume = (int)(data.fMasterVolume * 100);
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
VolumeChanged?.Invoke(_masterVolume);
|
||||
});
|
||||
return HRESULT.S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ThrottleHelper
|
||||
{
|
||||
private DateTime _lastTriggerTime = DateTime.MinValue;
|
||||
private readonly TimeSpan _interval;
|
||||
|
||||
public ThrottleHelper(TimeSpan interval)
|
||||
{
|
||||
_interval = interval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以触发(距离上次触发已超过设定间隔),如果可以则更新时间戳并返回 true,否则返回 false。
|
||||
/// </summary>
|
||||
public bool CanTrigger()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
if ((now - _lastTriggerTime) >= _interval)
|
||||
{
|
||||
_lastTriggerTime = now;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置触发时间
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_lastTriggerTime = DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExitAllWindows()
|
||||
{
|
||||
while (_activeWindows.Count > 0)
|
||||
{
|
||||
var window = _activeWindows[0];
|
||||
((Window)window).Close();
|
||||
_activeWindows.Remove(window);
|
||||
}
|
||||
App.Current.Exit();
|
||||
}
|
||||
|
||||
public static T? GetWindowByWindowType<T>()
|
||||
{
|
||||
foreach (var window in _activeWindows)
|
||||
@@ -48,33 +37,33 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
return default;
|
||||
}
|
||||
public static void OpenOrShowWindow<T>()
|
||||
public static void OpenWindow<T>()
|
||||
{
|
||||
var window = _activeWindows.Find(w => w is T);
|
||||
if (window != null)
|
||||
if (window == null)
|
||||
{
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Restore();
|
||||
}
|
||||
else
|
||||
{
|
||||
object newWindow;
|
||||
if (typeof(T) == typeof(LyricsWindow))
|
||||
{
|
||||
newWindow = new LyricsWindow();
|
||||
((LyricsWindow)newWindow).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
window = new LyricsWindow();
|
||||
((LyricsWindow)window).SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
|
||||
}
|
||||
else if (typeof(T) == typeof(SettingsWindow))
|
||||
{
|
||||
newWindow = new SettingsWindow();
|
||||
window = new SettingsWindow();
|
||||
}
|
||||
else if (typeof(T) == typeof(MusicGalleryWindow))
|
||||
{
|
||||
window = new MusicGalleryWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Unsupported window type", nameof(T));
|
||||
}
|
||||
((Window)newWindow).Activate();
|
||||
TrackWindow(newWindow);
|
||||
TrackWindow(window);
|
||||
}
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Restore();
|
||||
castedWindow.Activate();
|
||||
}
|
||||
|
||||
public static void RestartApp(string args = "")
|
||||
@@ -100,7 +89,19 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
private static void TrackWindow(object window)
|
||||
{
|
||||
if (!_activeWindows.Contains(window))
|
||||
{
|
||||
_activeWindows.Add(window);
|
||||
var castedWindow = (Window)window;
|
||||
castedWindow.Closed += WindowHelper_Closed;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WindowHelper_Closed(object sender, WindowEventArgs args)
|
||||
{
|
||||
if (_activeWindows.Contains(sender))
|
||||
{
|
||||
_activeWindows.Remove(sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class GroupInfoList : List<object>
|
||||
{
|
||||
public required object Key { get; set; }
|
||||
|
||||
public GroupInfoList(IEnumerable<object> items, Func<object, object>? orderSelector = null)
|
||||
: base(orderSelector != null
|
||||
? items.OrderBy(orderSelector)
|
||||
: items)
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Key}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class LocalLyricsFolder : ObservableObject
|
||||
public partial class LocalMediaFolder : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
@@ -12,9 +12,9 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string Path { get; set; }
|
||||
|
||||
public LocalLyricsFolder() { }
|
||||
public LocalMediaFolder() { }
|
||||
|
||||
public LocalLyricsFolder(string path, bool isEnabled)
|
||||
public LocalMediaFolder(string path, bool isEnabled)
|
||||
{
|
||||
Path = path;
|
||||
IsEnabled = isEnabled;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class CharTiming
|
||||
public class LyricsChar
|
||||
{
|
||||
public int EndMs { get; set; }
|
||||
public int? EndMs { get; set; }
|
||||
public int StartIndex { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
public string Text { get; set; } = string.Empty;
|
||||
@@ -1,10 +1,12 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using StringHelper = BetterLyrics.WinUI3.Helper.StringHelper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
@@ -24,20 +26,39 @@ namespace BetterLyrics.WinUI3.Models
|
||||
LyricsLines = lyricsLines;
|
||||
}
|
||||
|
||||
public void SetDisplayedTextAlongWith(LyricsData translationData)
|
||||
public void SetDisplayedTextAlongWith(LyricsData translationData, int toleranceMs = 0)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var line in LyricsLines)
|
||||
{
|
||||
if (i >= translationData.LyricsLines.Count)
|
||||
// 在翻译歌词中查找与当前行开始时间最接近且在容忍范围内的行
|
||||
var transLine = translationData.LyricsLines
|
||||
.FirstOrDefault(t => Math.Abs(t.StartMs - line.StartMs) <= toleranceMs);
|
||||
|
||||
if (transLine != null)
|
||||
{
|
||||
line.DisplayedText = line.OriginalText; // No translation available, keep original text
|
||||
if (translationData.LanguageCode?.StartsWith("zh") == true)
|
||||
{
|
||||
string tmp = "";
|
||||
if (LanguageHelper.GetUserTargetLanguageCode() == "zh-Hant")
|
||||
{
|
||||
tmp = ChineseConverter.ConvertToTraditionalChinese(transLine.OriginalText);
|
||||
}
|
||||
else if (LanguageHelper.GetUserTargetLanguageCode() == "zh-Hans")
|
||||
{
|
||||
tmp = ChineseConverter.ConvertToSimplifiedChinese(transLine.OriginalText);
|
||||
}
|
||||
line.DisplayedText = $"{line.OriginalText}\n{tmp}";
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}\n{transLine.OriginalText}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationData.LyricsLines[i].OriginalText})";
|
||||
// 没有匹配的翻译,翻译部分留空
|
||||
line.DisplayedText = $"{line.OriginalText}\n";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +74,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
}
|
||||
else
|
||||
{
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationArr[i]})";
|
||||
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}{translationArr[i]}";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
@@ -67,6 +88,30 @@ namespace BetterLyrics.WinUI3.Models
|
||||
}
|
||||
}
|
||||
|
||||
public LyricsData CreateLyricsDataFrom(string translation)
|
||||
{
|
||||
var result = new LyricsData(LyricsLines.Select(line => new LyricsLine
|
||||
{
|
||||
StartMs = line.StartMs,
|
||||
EndMs = line.EndMs,
|
||||
}).ToList());
|
||||
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
|
||||
int i = 0;
|
||||
foreach (var line in result.LyricsLines)
|
||||
{
|
||||
if (i >= translationArr.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
line.OriginalText = translationArr[i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static LyricsData GetNotfoundPlaceholder(int durationMs)
|
||||
{
|
||||
return new LyricsData([new LyricsLine
|
||||
@@ -74,7 +119,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
StartMs = 0,
|
||||
EndMs = durationMs,
|
||||
OriginalText = App.ResourceLoader!.GetString("LyricsNotFound"),
|
||||
CharTimings = [],
|
||||
LyricsChars = [],
|
||||
}]);
|
||||
}
|
||||
|
||||
@@ -87,7 +132,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
EndMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds,
|
||||
OriginalText = "● ● ●",
|
||||
DisplayedText = "● ● ●",
|
||||
CharTimings = [],
|
||||
LyricsChars = [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public Vector2 CenterPosition { get; set; }
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public List<CharTiming> CharTimings { get; set; } = [];
|
||||
public List<LyricsChar> LyricsChars { get; set; } = [];
|
||||
|
||||
public int DurationMs => EndMs - StartMs;
|
||||
public int EndMs { get; set; }
|
||||
public int? DurationMs => EndMs - StartMs;
|
||||
public int? EndMs { get; set; }
|
||||
public int StartMs { get; set; }
|
||||
|
||||
public string DisplayedText { get; set; } = "";
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using ATL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class PlayQueueItem
|
||||
{
|
||||
public Track Track { get; set; }
|
||||
|
||||
public PlayQueueItem(Track track)
|
||||
{
|
||||
Track = track;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string Artist { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int? Duration { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double? DurationMs { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class SongsTabInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Icon { get; set; }
|
||||
|
||||
public bool IsClosable { get; set; }
|
||||
|
||||
public CommonSongProperty FilterProperty { get; set; }
|
||||
|
||||
public string FilterValue { get; set; }
|
||||
|
||||
public SongsTabInfo()
|
||||
{
|
||||
Name = string.Empty;
|
||||
Icon = string.Empty;
|
||||
IsClosable = true;
|
||||
FilterProperty = CommonSongProperty.Title;
|
||||
FilterValue = string.Empty;
|
||||
}
|
||||
|
||||
public SongsTabInfo(string name, string icon, bool isClosable, CommonSongProperty filterProperty, string filterValue)
|
||||
{
|
||||
Name = name;
|
||||
Icon = icon;
|
||||
IsClosable = isClosable;
|
||||
FilterProperty = filterProperty;
|
||||
FilterValue = filterValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class TrimmedTrack
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Artist { get; set; }
|
||||
public string Album { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public string Genre { get; set; }
|
||||
public string FilePath { get; set; }
|
||||
public int Duration { get; set; }
|
||||
public byte[]? AlbumArt { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace BetterLyrics.WinUI3.Serialization
|
||||
[JsonSerializable(typeof(List<AlbumArtSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LyricsSearchProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<MediaSourceProviderInfo>))]
|
||||
[JsonSerializable(typeof(List<LocalLyricsFolder>))]
|
||||
[JsonSerializable(typeof(List<LocalMediaFolder>))]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
[JsonSerializable(typeof(TranslateResponse))]
|
||||
[JsonSerializable(typeof(JsonElement))]
|
||||
|
||||
@@ -7,8 +7,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -48,7 +50,11 @@ namespace BetterLyrics.WinUI3.Services
|
||||
result = bytesFromSMTC;
|
||||
break;
|
||||
case AlbumArtSearchProvider.iTunes:
|
||||
result = await SearchiTunesAsync(artist, album);
|
||||
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
|
||||
{
|
||||
result = await SearchiTunesAsync(artist, album, title, countryCode);
|
||||
if (result != null) break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -61,7 +67,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private byte[]? SearchFile(string artist, string album)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
foreach (var folder in _settingsService.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
@@ -82,7 +88,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<byte[]?> SearchiTunesAsync(string artist, string album)
|
||||
private async Task<byte[]?> SearchiTunesAsync(string artist, string album, string title, string countryCode)
|
||||
{
|
||||
// Source: https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce
|
||||
try
|
||||
@@ -96,10 +102,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
}
|
||||
|
||||
// Build the iTunes API URL
|
||||
string url = $"https://itunes.apple.com/search?term=" + artist + "+" + album + "&country=" + LanguageHelper.DetectCountryCode(album + artist) + "&entity=album";
|
||||
url.Replace(" ", "-");
|
||||
// Make a request to the API
|
||||
string url = $"https://itunes.apple.com/search?term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
|
||||
|
||||
// Make a request to the API
|
||||
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
@@ -14,6 +14,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders);
|
||||
public void UpdateWatchers(List<LocalMediaFolder> folders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ILyricsSearchService
|
||||
{
|
||||
Task<string?> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
Task<(string?, LyricsSearchProvider?)> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
@@ -9,9 +10,18 @@ namespace BetterLyrics.WinUI3.Services
|
||||
public interface IPlaybackService
|
||||
{
|
||||
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
|
||||
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
|
||||
Task PlayAsync();
|
||||
Task PauseAsync();
|
||||
Task PreviousAsync();
|
||||
Task NextAsync();
|
||||
Task ChangePosition(double seconds);
|
||||
|
||||
bool IsPlaying { get; }
|
||||
SongInfo? SongInfo { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
@@ -21,6 +19,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
int CoverOverlayBlurAmount { get; set; }
|
||||
int CoverOverlayOpacity { get; set; }
|
||||
bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
int CoverAcrylicEffectAmount { get; set; }
|
||||
bool IsFanLyricsEnabled { get; set; }
|
||||
bool IsFirstRun { get; set; }
|
||||
bool IsLyricsGlowEffectEnabled { get; set; }
|
||||
@@ -39,9 +38,11 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
string LibreTranslateServer { get; set; }
|
||||
int SelectedTargetLanguageIndex { get; set; }
|
||||
bool ResetPositionOffsetOnSongChanged { get; set; }
|
||||
int PositionOffset { get; set; }
|
||||
// Lyrics lib
|
||||
|
||||
List<LocalLyricsFolder> LocalLyricsFolders { get; set; }
|
||||
List<LocalMediaFolder> LocalMediaFolders { get; set; }
|
||||
|
||||
// Lyrics style and effetc
|
||||
|
||||
@@ -54,6 +55,8 @@ namespace BetterLyrics.WinUI3.Services
|
||||
Color LyricsCustomFgFontColor { get; set; }
|
||||
Color LyricsCustomStrokeFontColor { get; set; }
|
||||
|
||||
int LyricsBgFontOpacity { get; set; }
|
||||
|
||||
LyricsFontColorType LyricsBgFontColorType { get; set; }
|
||||
LyricsFontColorType LyricsFgFontColorType { get; set; }
|
||||
LyricsFontColorType LyricsStrokeFontColorType { get; set; }
|
||||
@@ -69,6 +72,8 @@ namespace BetterLyrics.WinUI3.Services
|
||||
LineRenderingType LyricsGlowEffectScope { get; set; }
|
||||
LineRenderingType LyricsHighlightScope { get; set; }
|
||||
|
||||
bool IsLyricsFloatAnimationEnabled { get; set; }
|
||||
|
||||
float LyricsLineSpacingFactor { get; set; }
|
||||
|
||||
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
@@ -83,9 +88,21 @@ namespace BetterLyrics.WinUI3.Services
|
||||
bool IgnoreFullscreenWindow { get; set; }
|
||||
|
||||
bool IsTranslationEnabled { get; set; }
|
||||
bool ShowTranslationOnly { get; set; }
|
||||
|
||||
LyricsDisplayType PreferredDisplayType { get; set; }
|
||||
LyricsDisplayType DisplayType { get; set; }
|
||||
|
||||
int TimelineSyncThreshold { get; set; }
|
||||
int LockHotKeyIndex { get; set; }
|
||||
bool IsImmersiveMode { get; set; }
|
||||
string LXMusicServer { get; set; }
|
||||
DockPlacement DockPlacement { get; set; }
|
||||
bool HideWindowWhenNotPlaying { get; set; }
|
||||
int DockWindowHeight { get; set; }
|
||||
int SelectedFontFamilyIndex { get; set; }
|
||||
string LyricsFontFamily { get; set; }
|
||||
bool IsDragEverywhereEnabled { get; set; }
|
||||
PlaybackOrder PlaybackOrder { get; set; }
|
||||
bool IsLibreTranslateEnabled { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,18 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LibWatcherService : IDisposable, ILibWatcherService
|
||||
public class LibWatcherService : BaseViewModel, IDisposable, ILibWatcherService
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private readonly Dictionary<string, FileSystemWatcher> _watchers = [];
|
||||
|
||||
public LibWatcherService(ISettingsService settingsService)
|
||||
public LibWatcherService(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
UpdateWatchers(_settingsService.LocalLyricsFolders);
|
||||
UpdateWatchers(_settingsService.LocalMediaFolders);
|
||||
}
|
||||
|
||||
public event EventHandler<LibChangedEventArgs>? MusicLibraryFilesChanged;
|
||||
@@ -32,7 +31,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_watchers.Clear();
|
||||
}
|
||||
|
||||
public void UpdateWatchers(List<LocalLyricsFolder> folders)
|
||||
public void UpdateWatchers(List<LocalMediaFolder> folders)
|
||||
{
|
||||
// 移除不再监听的
|
||||
foreach (var key in _watchers.Keys.ToList())
|
||||
@@ -69,16 +68,13 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private void OnChanged(string folder, FileSystemEventArgs e)
|
||||
{
|
||||
App.DispatcherQueue!.TryEnqueue(
|
||||
Microsoft.UI.Dispatching.DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
}
|
||||
);
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
MusicLibraryFilesChanged?.Invoke(
|
||||
this,
|
||||
new LibChangedEventArgs(folder, e.FullPath, e.ChangeType)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Providers.Web.Kugou;
|
||||
using Lyricify.Lyrics.Searchers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NTextCat.Commons;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -85,86 +86,90 @@ namespace BetterLyrics.WinUI3.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
public async Task<(string?, LyricsSearchProvider?)> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
|
||||
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
try
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string? cachedLyrics;
|
||||
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
|
||||
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
return cachedLyrics;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
string? cachedLyrics;
|
||||
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
|
||||
|
||||
if (provider.Provider.IsLocal())
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
searchedLyrics = SearchEmbedded(title, artist);
|
||||
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return (cachedLyrics, provider.Provider);
|
||||
}
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
|
||||
if (provider.Provider.IsLocal())
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
searchedLyrics = SearchEmbedded(title, artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (provider.Provider)
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
}
|
||||
|
||||
return (searchedLyrics, provider.Provider);
|
||||
}
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
{
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
}
|
||||
|
||||
return searchedLyrics;
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return null;
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private async Task<string?> SearchFile(string title, string artist, LyricsFormat format)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
foreach (var folder in _settingsService.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
@@ -186,7 +191,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private string? SearchEmbedded(string title, string artist)
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalLyricsFolders)
|
||||
foreach (var folder in _settingsService.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
@@ -327,11 +332,33 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
|
||||
var original = response?.Lyrics;
|
||||
var translated = response?.Trans;
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
FileHelper.WriteLyricsCache(
|
||||
title,
|
||||
artist,
|
||||
translated,
|
||||
LyricsFormat.Lrc,
|
||||
PathHelper.QQTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
return original;
|
||||
}
|
||||
else if (result is NeteaseSearchResult neteaseResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
|
||||
var translated = response?.Tlyric?.Lyric;
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
FileHelper.WriteLyricsCache(
|
||||
title,
|
||||
artist,
|
||||
translated,
|
||||
LyricsFormat.Lrc,
|
||||
PathHelper.NeteaseTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
return response?.Lrc.Lyric;
|
||||
}
|
||||
else if (result is KugouSearchResult kugouResult)
|
||||
|
||||
@@ -7,19 +7,25 @@ using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using EvtSource;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI.Shell;
|
||||
using WindowsMediaController;
|
||||
using static WindowsMediaController.MediaManager;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
@@ -29,17 +35,26 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
private readonly IAlbumArtSearchService _albumArtSearchService;
|
||||
private readonly ILogger<PlaybackService> _logger;
|
||||
|
||||
private readonly string _lxMusicId = "cn.toside.music.desktop";
|
||||
private double _lxMusicPositionSeconds = 0;
|
||||
private double _lxMusicDurationSeconds = 0;
|
||||
|
||||
private bool _cachedIsPlaying = false;
|
||||
|
||||
private EventSourceReader? _sse = null;
|
||||
|
||||
private readonly MediaManager _mediaManager = new();
|
||||
private readonly LatestOnlyTaskRunner _AlbumArtRefreshRunner = new();
|
||||
private readonly LatestOnlyTaskRunner _OnAnyMediaPropertyChangedRunner = new();
|
||||
|
||||
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
|
||||
private readonly LatestOnlyTaskRunner _onAnyMediaPropertyChangedRunner = new();
|
||||
|
||||
private SongInfo? _cachedSongInfo;
|
||||
private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
|
||||
private byte[]? _SMTCAlbumArtBytes = null;
|
||||
private AlbumArtChangedEventArgs _albumArtChangedEventArgs = new AlbumArtChangedEventArgs();
|
||||
|
||||
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
public event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
public event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
|
||||
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
public event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
@@ -53,6 +68,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
InitMediaManager();
|
||||
}
|
||||
|
||||
public bool IsPlaying => _cachedIsPlaying;
|
||||
public SongInfo? SongInfo => _cachedSongInfo;
|
||||
|
||||
private bool IsMediaSourceEnabled(string id)
|
||||
{
|
||||
return _mediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true;
|
||||
@@ -60,8 +78,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private void InitMediaManager()
|
||||
{
|
||||
_mediaManager.Start();
|
||||
|
||||
_mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened;
|
||||
_mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed;
|
||||
_mediaManager.OnFocusedSessionChanged += MediaManager_OnFocusedSessionChanged;
|
||||
@@ -69,110 +85,123 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_mediaManager.OnAnyPlaybackStateChanged += MediaManager_OnAnyPlaybackStateChanged;
|
||||
_mediaManager.OnAnyTimelinePropertyChanged += MediaManager_OnAnyTimelinePropertyChanged;
|
||||
|
||||
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
|
||||
_mediaManager.Start();
|
||||
Task.Run(() =>
|
||||
{
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession mediaSession)
|
||||
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
|
||||
{
|
||||
if (mediaSession == null || !IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId))
|
||||
{
|
||||
SendNullMessages();
|
||||
}
|
||||
else
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var props = await mediaSession.ControlSession.TryGetMediaPropertiesAsync();
|
||||
MediaManager_OnAnyMediaPropertyChanged(mediaSession, props);
|
||||
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "TryGetMediaPropertiesAsync failed");
|
||||
SendNullMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
|
||||
SendFocusedMessagesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
|
||||
{
|
||||
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(timelineProperties.Position));
|
||||
}
|
||||
);
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id) || mediaSession != focusedSession) return;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(timelineProperties.Position, timelineProperties.EndTime));
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
|
||||
{
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
{
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(playbackInfo.PlaybackStatus switch
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
}));
|
||||
}
|
||||
);
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id) || mediaSession != focusedSession) return;
|
||||
|
||||
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
|
||||
{
|
||||
_ = _OnAnyMediaPropertyChangedRunner.RunAsync(async token =>
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
string id = mediaSession.Id;
|
||||
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (!IsMediaSourceEnabled(id) || mediaSession != focusedSession) return;
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = mediaProperties.Artist,
|
||||
Album = mediaProperties.AlbumTitle,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
|
||||
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
|
||||
|
||||
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
|
||||
{
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
string id = mediaSession.ControlSession.SourceAppUserModelId;
|
||||
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return;
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
if (id == _lxMusicId)
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = mediaProperties.Artist,
|
||||
Album = mediaProperties.AlbumTitle,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
StartSSE();
|
||||
}
|
||||
else
|
||||
{
|
||||
StopSSE();
|
||||
}
|
||||
|
||||
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
_SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
else
|
||||
{
|
||||
_SMTCAlbumArtBytes = null;
|
||||
}
|
||||
|
||||
_ = _AlbumArtRefreshRunner.RunAsync(async tokne =>
|
||||
await _albumArtRefreshRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await UpdateAlbumArtRelated(tokne);
|
||||
});
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
});
|
||||
}
|
||||
});
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
if (_mediaManager.CurrentMediaSessions.Count == 0)
|
||||
{
|
||||
SendNullMessages();
|
||||
@@ -181,12 +210,19 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private void MediaManager_OnAnySessionOpened(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
SendFocusedMessagesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void RecordMediaSourceProviderInfo(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
var id = mediaSession?.ControlSession?.SourceAppUserModelId;
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
var id = mediaSession?.Id;
|
||||
if (string.IsNullOrEmpty(id)) return;
|
||||
|
||||
var found = _mediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id);
|
||||
@@ -194,8 +230,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
_mediaSourceProvidersInfo.Add(new MediaSourceProviderInfo(id, true));
|
||||
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
MediaSourceProvidersInfoChanged?.Invoke(this, new MediaSourceProvidersInfoEventArgs(_mediaSourceProvidersInfo));
|
||||
});
|
||||
@@ -204,16 +239,27 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private void SendNullMessages()
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
_cachedSongInfo = null;
|
||||
_cachedIsPlaying = false;
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(false));
|
||||
PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.Zero));
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(TimeSpan.Zero, TimeSpan.Zero));
|
||||
});
|
||||
}
|
||||
|
||||
private async Task SendFocusedMessagesAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession == null || focusedSession.ControlSession == null) return;
|
||||
|
||||
var mediaProps = await focusedSession.ControlSession.TryGetMediaPropertiesAsync();
|
||||
MediaManager_OnAnyMediaPropertyChanged(focusedSession, mediaProps);
|
||||
MediaManager_OnAnyPlaybackStateChanged(focusedSession, focusedSession.ControlSession.GetPlaybackInfo());
|
||||
MediaManager_OnAnyTimelinePropertyChanged(focusedSession, focusedSession.ControlSession.GetTimelineProperties());
|
||||
}
|
||||
|
||||
private async Task UpdateAlbumArtRelated(CancellationToken token)
|
||||
{
|
||||
if (_cachedSongInfo == null)
|
||||
@@ -232,10 +278,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
if (bytes == null)
|
||||
{
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync($"{_cachedSongInfo!.Artist} - {_cachedSongInfo.Title}", 400, 400);
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(400, 400);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
token.ThrowIfCancellationRequested();
|
||||
@@ -243,18 +291,109 @@ namespace BetterLyrics.WinUI3.Services
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_albumArtChangedEventArgs.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
var _albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
_albumArtChangedEventArgs.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
|
||||
var _albumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
|
||||
() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
AlbumArtChangedChanged?.Invoke(this, _albumArtChangedEventArgs);
|
||||
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(_albumArtSwBitmap, _albumArtAccentColor));
|
||||
});
|
||||
}
|
||||
|
||||
private void StartSSE()
|
||||
{
|
||||
try
|
||||
{
|
||||
_sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}/subscribe-player-status?filter=progress,duration")).Start();
|
||||
_sse.MessageReceived += Sse_MessageReceived;
|
||||
_sse.Disconnected += Sse_Disconnected;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.LogError("Failed to start SSE connection for LX Music.");
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("FailToStartLXMusicServer"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
});
|
||||
StopSSE();
|
||||
}
|
||||
}
|
||||
|
||||
private void StopSSE()
|
||||
{
|
||||
if (_sse != null)
|
||||
{
|
||||
_sse.MessageReceived -= Sse_MessageReceived;
|
||||
_sse.Disconnected -= Sse_Disconnected;
|
||||
_sse.Dispose();
|
||||
_sse = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Sse_Disconnected(object sender, DisconnectEventArgs e)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(e.ReconnectDelay);
|
||||
if (_sse != null && !_sse.IsDisposed) _sse.Start();
|
||||
});
|
||||
}
|
||||
|
||||
private void Sse_MessageReceived(object sender, EventSourceMessageEventArgs e)
|
||||
{
|
||||
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
if (data.TryGetDouble(out double seconds))
|
||||
{
|
||||
if (_cachedSongInfo?.SourceAppUserModelId == _lxMusicId)
|
||||
{
|
||||
if (e.Event == "progress")
|
||||
{
|
||||
_lxMusicPositionSeconds = seconds;
|
||||
}
|
||||
else if (e.Event == "duration")
|
||||
{
|
||||
_lxMusicDurationSeconds = seconds;
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(TimeSpan.FromSeconds(_lxMusicPositionSeconds), TimeSpan.FromSeconds(_lxMusicDurationSeconds)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PlayAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
await focusedSession?.ControlSession.TryPlayAsync();
|
||||
}
|
||||
|
||||
public async Task PauseAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
await focusedSession?.ControlSession.TryPauseAsync();
|
||||
}
|
||||
|
||||
public async Task PreviousAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
await focusedSession?.ControlSession.TrySkipPreviousAsync();
|
||||
}
|
||||
|
||||
public async Task NextAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
await focusedSession?.ControlSession.TrySkipNextAsync();
|
||||
}
|
||||
|
||||
public async Task ChangePosition(double seconds)
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
await focusedSession?.ControlSession.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
@@ -263,12 +402,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
_mediaSourceProvidersInfo = [.. message.NewValue];
|
||||
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
|
||||
MediaManager_OnFocusedSessionChanged(_mediaManager.GetFocusedSession());
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> message)
|
||||
public async void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
@@ -276,7 +415,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
// Album art search providers info changed, re-fetch album art
|
||||
_logger.LogInformation("Album art search providers info changed, refreshing album art.");
|
||||
_ = _AlbumArtRefreshRunner.RunAsync(async tokne =>
|
||||
await _albumArtRefreshRunner.RunAsync(async tokne =>
|
||||
{
|
||||
await UpdateAlbumArtRelated(tokne);
|
||||
});
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace BetterLyrics.WinUI3.Services
|
||||
private const string CoverOverlayOpacityKey = "CoverOverlayOpacity";
|
||||
private const string IsCoverOverlayEnabledKey = "IsCoverOverlayEnabled";
|
||||
|
||||
private const string CoverAcrylicEffectAmountKey = "CoverAcrylicEffectAmount";
|
||||
|
||||
private const string DesktopWindowLeftKey = "DesktopWindowLeft";
|
||||
private const string DesktopWindowTopKey = "DesktopWindowTop";
|
||||
private const string DesktopWindowWidthKey = "DesktopWindowWidth";
|
||||
@@ -41,6 +43,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
private const string StandardWindowHeightKey = "StandardWindowHeight";
|
||||
|
||||
private const string AutoLockOnDesktopModeKey = "AutoLockOnDesktopMode";
|
||||
private const string IsImmersiveModeKey = "IsImmersiveMode";
|
||||
|
||||
private const string IsDynamicCoverOverlayEnabledKey = "IsDynamicCoverOverlayEnabled";
|
||||
private const string IsFanLyricsEnabledKey = "IsFanLyricsEnabled";
|
||||
@@ -70,10 +73,16 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
private const string MediaSourceProvidersInfoKey = "MediaSourceProvidersInfo";
|
||||
|
||||
// Translation
|
||||
private const string IsTranslationEnabledKey = "IsTranslationEnabled";
|
||||
private const string ShowTranslationOnlyKey = "ShowTranslationOnly";
|
||||
private const string IsLibreTranslateEnabledKey = "IsLibreTranslateEnabled";
|
||||
private const string LibreTranslateServerKey = "LibreTranslateServer";
|
||||
private const string SelectedTargetLanguageIndexKey = "SelectedTargetLanguageIndex";
|
||||
|
||||
// LX Music
|
||||
private const string LXMusicServerKey = "LXMusicServer";
|
||||
|
||||
private const string LyricsBackgroundThemeKey = "LyricsBackgroundTheme";
|
||||
private const string IgnoreFullscreenWindowKey = "IgnoreFullscreenWindow";
|
||||
private const string PreferredDisplayTypeKey = "PreferredDisplayTypeKey";
|
||||
@@ -83,6 +92,23 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
public const string TimelineSyncThresholdKey = "TimelineSyncThreshold";
|
||||
|
||||
private const string IsLyricsFloatAnimationEnabledKey = "IsLyricsFloatAnimationEnabled";
|
||||
|
||||
private const string ResetPositionOffsetOnSongChangedKey = "ResetPositionOffsetOnSongChanged";
|
||||
private const string PlaybackOrderKey = "PlaybackOrder";
|
||||
|
||||
private const string PositionOffsetKey = "PositionOffset";
|
||||
|
||||
private const string LockHotKeyIndexKey = "LockHotKeyIndex";
|
||||
private const string DockPlacementKey = "DockPlacement";
|
||||
private const string LyricsBgFontOpacityKey = "LyricsBgFontOpacity";
|
||||
private const string HideWindowWhenNotPlayingKey = "HideWindowWhenNotPlaying";
|
||||
private const string DockWindowHeightKey = "DockWindowHeight";
|
||||
|
||||
private const string SelectedFontFamilyIndexKey = "SelectedFontFamilyIndex";
|
||||
private const string LyricsFontFamilyKey = "LyricsFontFamily";
|
||||
private const string IsDragEverywhereEnabledKey = "IsDragEverywhereEnabled";
|
||||
|
||||
private readonly ApplicationDataContainer _localSettings;
|
||||
|
||||
public SettingsService()
|
||||
@@ -152,6 +178,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(StandardWindowWidthKey, 1600);
|
||||
|
||||
SetDefault(AutoLockOnDesktopModeKey, false);
|
||||
SetDefault(IsImmersiveModeKey, false);
|
||||
// App behavior
|
||||
SetDefault(AutoStartWindowTypeKey, (int)AutoStartWindowType.StandardMode);
|
||||
// Album art
|
||||
@@ -160,6 +187,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(CoverOverlayOpacityKey, 100); // 100 % = 1.0
|
||||
SetDefault(CoverOverlayBlurAmountKey, 100);
|
||||
SetDefault(CoverImageRadiusKey, 12); // 12 %
|
||||
SetDefault(CoverAcrylicEffectAmountKey, 0);
|
||||
// Lyrics
|
||||
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Center);
|
||||
SetDefault(SongInfoAlignmentTypeKey, (int)TextAlignmentType.Left);
|
||||
@@ -185,8 +213,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(IsFanLyricsEnabledKey, false);
|
||||
|
||||
SetDefault(LibreTranslateServerKey, "");
|
||||
SetDefault(IsTranslationEnabledKey, false);
|
||||
SetDefault(SelectedTargetLanguageIndexKey, 6);
|
||||
SetDefault(IsLibreTranslateEnabledKey, false);
|
||||
SetDefault(IsTranslationEnabledKey, true);
|
||||
SetDefault(ShowTranslationOnlyKey, false);
|
||||
SetDefault(SelectedTargetLanguageIndexKey, LanguageHelper.GetDefaultTargetLanguageIndex());
|
||||
|
||||
SetDefault(LXMusicServerKey, "");
|
||||
|
||||
SetDefault(LyricsFontStrokeWidthKey, 3);
|
||||
SetDefault(IgnoreFullscreenWindowKey, false);
|
||||
@@ -195,6 +227,73 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutQuad);
|
||||
SetDefault(LyricsScrollDurationKey, 500); // 500ms
|
||||
SetDefault(TimelineSyncThresholdKey, 0); // 0ms
|
||||
|
||||
SetDefault(IsLyricsFloatAnimationEnabledKey, true);
|
||||
|
||||
SetDefault(ResetPositionOffsetOnSongChangedKey, false);
|
||||
SetDefault(PositionOffsetKey, 0);
|
||||
SetDefault(LockHotKeyIndexKey, 'U' - 'A');
|
||||
SetDefault(DockPlacementKey, (int)DockPlacement.Top);
|
||||
SetDefault(LyricsBgFontOpacityKey, 30); // 30%
|
||||
SetDefault(HideWindowWhenNotPlayingKey, false);
|
||||
SetDefault(DockWindowHeightKey, 64); // 64px
|
||||
SetDefault(SelectedFontFamilyIndexKey, 0);
|
||||
SetDefault(LyricsFontFamilyKey, FontHelper.SystemFontFamilies.ElementAtOrDefault(0));
|
||||
SetDefault(IsDragEverywhereEnabledKey, false);
|
||||
}
|
||||
|
||||
public bool IsDragEverywhereEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsDragEverywhereEnabledKey);
|
||||
set => SetValue(IsDragEverywhereEnabledKey, value);
|
||||
}
|
||||
|
||||
public string LyricsFontFamily
|
||||
{
|
||||
get => GetValue<string>(LyricsFontFamilyKey)!;
|
||||
set => SetValue(LyricsFontFamilyKey, value);
|
||||
}
|
||||
|
||||
public int SelectedFontFamilyIndex
|
||||
{
|
||||
get => GetValue<int>(SelectedFontFamilyIndexKey);
|
||||
set => SetValue(SelectedFontFamilyIndexKey, value);
|
||||
}
|
||||
|
||||
public bool HideWindowWhenNotPlaying
|
||||
{
|
||||
get => GetValue<bool>(HideWindowWhenNotPlayingKey);
|
||||
set => SetValue(HideWindowWhenNotPlayingKey, value);
|
||||
}
|
||||
|
||||
public int DockWindowHeight
|
||||
{
|
||||
get => GetValue<int>(DockWindowHeightKey);
|
||||
set => SetValue(DockWindowHeightKey, value);
|
||||
}
|
||||
|
||||
public int LyricsBgFontOpacity
|
||||
{
|
||||
get => GetValue<int>(LyricsBgFontOpacityKey);
|
||||
set => SetValue(LyricsBgFontOpacityKey, value);
|
||||
}
|
||||
|
||||
public bool ShowTranslationOnly
|
||||
{
|
||||
get => GetValue<bool>(ShowTranslationOnlyKey);
|
||||
set => SetValue(ShowTranslationOnlyKey, value);
|
||||
}
|
||||
|
||||
public DockPlacement DockPlacement
|
||||
{
|
||||
get => (DockPlacement)GetValue<int>(DockPlacementKey);
|
||||
set => SetValue(DockPlacementKey, (int)value);
|
||||
}
|
||||
|
||||
public int LockHotKeyIndex
|
||||
{
|
||||
get => GetValue<int>(LockHotKeyIndexKey);
|
||||
set => SetValue(LockHotKeyIndexKey, value);
|
||||
}
|
||||
|
||||
public EasingType LyricsScrollEasingType
|
||||
@@ -209,7 +308,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(LyricsScrollDurationKey, value);
|
||||
}
|
||||
|
||||
public LyricsDisplayType PreferredDisplayType
|
||||
public LyricsDisplayType DisplayType
|
||||
{
|
||||
get => (LyricsDisplayType)GetValue<int>(PreferredDisplayTypeKey);
|
||||
set => SetValue(PreferredDisplayTypeKey, (int)value);
|
||||
@@ -305,6 +404,12 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(IsDynamicCoverOverlayEnabledKey, value);
|
||||
}
|
||||
|
||||
public int CoverAcrylicEffectAmount
|
||||
{
|
||||
get => GetValue<int>(CoverAcrylicEffectAmountKey);
|
||||
set => SetValue(CoverAcrylicEffectAmountKey, value);
|
||||
}
|
||||
|
||||
public bool IsFanLyricsEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsFanLyricsEnabledKey);
|
||||
@@ -329,19 +434,19 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(LanguageKey, (int)value);
|
||||
}
|
||||
|
||||
public List<LocalLyricsFolder> LocalLyricsFolders
|
||||
public List<LocalMediaFolder> LocalMediaFolders
|
||||
{
|
||||
get =>
|
||||
System.Text.Json.JsonSerializer.Deserialize(
|
||||
GetValue<string>(LocalLyricsFoldersKey) ?? "[]",
|
||||
SourceGenerationContext.Default.ListLocalLyricsFolder
|
||||
SourceGenerationContext.Default.ListLocalMediaFolder
|
||||
)!;
|
||||
set =>
|
||||
SetValue(
|
||||
LocalLyricsFoldersKey,
|
||||
System.Text.Json.JsonSerializer.Serialize(
|
||||
value,
|
||||
SourceGenerationContext.Default.ListLocalLyricsFolder
|
||||
SourceGenerationContext.Default.ListLocalMediaFolder
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -505,12 +610,24 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(IsTranslationEnabledKey, value);
|
||||
}
|
||||
|
||||
public bool IsLibreTranslateEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsLibreTranslateEnabledKey);
|
||||
set => SetValue(IsLibreTranslateEnabledKey, value);
|
||||
}
|
||||
|
||||
public int SelectedTargetLanguageIndex
|
||||
{
|
||||
get => GetValue<int>(SelectedTargetLanguageIndexKey);
|
||||
set => SetValue(SelectedTargetLanguageIndexKey, value);
|
||||
}
|
||||
|
||||
public string LXMusicServer
|
||||
{
|
||||
get => GetValue<string>(LXMusicServerKey)!;
|
||||
set => SetValue(LXMusicServerKey, value);
|
||||
}
|
||||
|
||||
public bool IgnoreFullscreenWindow
|
||||
{
|
||||
get => GetValue<bool>(IgnoreFullscreenWindowKey);
|
||||
@@ -523,6 +640,36 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(TimelineSyncThresholdKey, value);
|
||||
}
|
||||
|
||||
public bool IsLyricsFloatAnimationEnabled
|
||||
{
|
||||
get => GetValue<bool>(IsLyricsFloatAnimationEnabledKey);
|
||||
set => SetValue(IsLyricsFloatAnimationEnabledKey, value);
|
||||
}
|
||||
|
||||
public bool ResetPositionOffsetOnSongChanged
|
||||
{
|
||||
get => GetValue<bool>(ResetPositionOffsetOnSongChangedKey);
|
||||
set => SetValue(ResetPositionOffsetOnSongChangedKey, value);
|
||||
}
|
||||
|
||||
public PlaybackOrder PlaybackOrder
|
||||
{
|
||||
get => (PlaybackOrder)GetValue<int>(PlaybackOrderKey);
|
||||
set => SetValue(PlaybackOrderKey, (int)value);
|
||||
}
|
||||
|
||||
public int PositionOffset
|
||||
{
|
||||
get => GetValue<int>(PositionOffsetKey);
|
||||
set => SetValue(PositionOffsetKey, value);
|
||||
}
|
||||
|
||||
public bool IsImmersiveMode
|
||||
{
|
||||
get => GetValue<bool>(IsImmersiveModeKey);
|
||||
set => SetValue(IsImmersiveModeKey, value);
|
||||
}
|
||||
|
||||
private T? GetValue<T>(string key)
|
||||
{
|
||||
if (_localSettings.Values.TryGetValue(key, out object? value))
|
||||
|
||||
@@ -3,6 +3,7 @@ using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Serialization;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -18,7 +19,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public TranslateService(ISettingsService settingsService) :base(settingsService)
|
||||
public TranslateService(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
@@ -27,7 +28,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Text and target language must be provided.");
|
||||
throw new Exception(text + " is empty or null.");
|
||||
}
|
||||
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(text);
|
||||
@@ -46,15 +47,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
if (string.IsNullOrEmpty(_settingsService.LibreTranslateServer))
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("TranslateServerNotSet"),
|
||||
Microsoft.UI.Xaml.Controls.InfoBarSeverity.Warning
|
||||
);
|
||||
});
|
||||
|
||||
throw new InvalidOperationException("LibreTranslate server URL is not configured.");
|
||||
throw new Exception("LibreTranslate server URL is not set in settings.");
|
||||
}
|
||||
|
||||
var url = $"{_settingsService.LibreTranslateServer}/translate";
|
||||
@@ -76,18 +69,18 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
public int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr)
|
||||
{
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode();
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode().Substring(0, 2);
|
||||
if (lyricsDataArr.Count > 1)
|
||||
{
|
||||
for (int i = 1; i < lyricsDataArr.Count; i++)
|
||||
for (int i = 1; i < lyricsDataArr.Count; i++)
|
||||
{
|
||||
if (lyricsDataArr[i].LanguageCode == targetLangCode)
|
||||
if (lyricsDataArr[i].LanguageCode?.Substring(0, 2) == targetLangCode)
|
||||
{
|
||||
return i; // Translation lyrics data found
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1; // No translation lyrics data found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,9 +288,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>Glow effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>Glow effect scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Configure lyrics source</value>
|
||||
</data>
|
||||
@@ -304,7 +301,8 @@
|
||||
<value>Welcome to BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>Hover the mouse over the top or bottom area of the app to display more function options</value>
|
||||
<value>Click on the top-left button to enable immersive mode.
|
||||
If you encounter any problems, please go to the Settings page, About tab, and view the FAQ or contact the author for feedback</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>No music playing now</value>
|
||||
@@ -420,9 +418,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Dock mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>Dock mode</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>Desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>Font weight</value>
|
||||
</data>
|
||||
@@ -528,8 +532,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>Lock</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>To unlock after locking, go to the system tray to unlock</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>To unlock after locking, go to the system tray to unlock or press</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>Fan lyrics</value>
|
||||
@@ -589,7 +593,7 @@
|
||||
<value>Lyrics timeline offset (ms)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>Configure translation services</value>
|
||||
<value>LibreTranslate translation service</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>Server address</value>
|
||||
@@ -606,10 +610,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>Visit https://github.com/LibreTranslate/LibreTranslate for installation instructions and more information (this software is not affiliated with this translation service in any way)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>Server test successful</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>Server test failed</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -649,7 +653,7 @@
|
||||
<value>Translate server is not set, please configure it in settings first</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>Will automatically reset to 0 when switching songs</value>
|
||||
<value>Reset to 0 when switching songs</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>The translation in the lyrics will be read first. If there is no match, the machine translation will be requested from the LibreTranslate server</value>
|
||||
@@ -735,4 +739,175 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>Join now</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>Floating animation</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>Scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>Unlock and lock shortcut keys</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX Music Server</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>Dock mode placement</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>Top</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>Bottom</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>Enable translation</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>Show translation only</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>Font opacity (Non-current playback area)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>Frequently asked questions</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>Unable to connect to LX Music server, please go to Settings - Advanced options to check if the link is entered correctly</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>Auto-hide window</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>Automatically hide the window when no songs are playing in dock mode or desktop mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>Window height</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>Lyrics font family</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>Global drag</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>Extend the title bar to the entire page so that the window can be dragged in any non-interactive area</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>Fluid acrylic effect roughness</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>Music gallery - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>Requesting translation from LibreTranslate failed, please check the settings or the native LibreTranslate configuration</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>File info</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>Title</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>Artist</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>Album</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>Year</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>Duration</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>Bitrate</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>Sample rate</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>Bit depth</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>Format</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>Encoder</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>Path</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Search songs</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>Sort type</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>Title</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>Album</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>Artist</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>No songs were found in the media library</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>Play queue</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>Clear play queue</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>Scroll to playing item</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>List loop</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>Single loop</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>Random</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>Remove from play queue</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>Play queue is empty</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>Select all</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>Add to play queue</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>Next items</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>End of the list</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>Add to playlist</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>Create a playlist</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>Open music gallery</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics is already running</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>All songs</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>この値を調整すると、アルバム画像のバックグラウンドブラー強度も増加します。</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>現在の値:</value>
|
||||
<value>現在の値: </value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>ぼかしが有効になっている場合のGPU使用量が大幅に高くなります(> 0)</value>
|
||||
@@ -288,9 +288,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>グロー効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>グローエフェクトスコープ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>歌詞ソースを構成します</value>
|
||||
</data>
|
||||
@@ -304,7 +301,8 @@
|
||||
<value>BetterLyrics へようこそ</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>マウスをアプリの上または下部の領域にホバリングして、より多くの機能オプションを表示します</value>
|
||||
<value>左上ボタンをクリックして、没入型モードを有効にします。
|
||||
問題が発生した場合は、[設定]ページ、[タブについて]にアクセスして、FAQを表示するか、フィードバックについて著者に連絡してください</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>今は音楽が再生されていません</value>
|
||||
@@ -420,9 +418,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ドックモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>ドックモード</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>デスクトップモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>デスクトップモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>フォント重量</value>
|
||||
</data>
|
||||
@@ -528,8 +532,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>ロック</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除します</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>ロック後にロックを解除するには、システムトレイに移動してロックを解除または押します</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>ファンの歌詞</value>
|
||||
@@ -589,7 +593,7 @@
|
||||
<value>歌詞タイムラインオフセット(ミリ秒)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>翻訳サービスを構成します</value>
|
||||
<value>リブレットランレート翻訳サービス</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>サーバーアドレス</value>
|
||||
@@ -606,10 +610,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>https://github.com/LibreTranslate/LibreTranslate にアクセスしてください。</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>サーバーテストが成功しました</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>サーバーテストに失敗しました</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -649,7 +653,7 @@
|
||||
<value>翻訳サーバーは設定されていません。最初に設定で構成してください</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>曲を切り替えると、0 に自動的にリセットされます</value>
|
||||
<value>曲を切り替えるときに0にリセットします</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>歌詞の翻訳は最初に読まれます。一致していない場合、機械の翻訳はLibretranslate Serverから要求されます</value>
|
||||
@@ -735,4 +739,175 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>今すぐ参加してください</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>フローティングアニメーション</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>範囲</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>ショートカットキーのロックを解除およびロックします</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX Music Server</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>ドックモード配置</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>トップ</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>底</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>翻訳を有効にします</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>翻訳のみを表示します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>フォントの不透明度(非電流再生エリア)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>よくある質問</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>LX Music Serverに接続できない場合は、設定に移動してください。リンクが正しく入力されているかどうかを確認するための高度なオプションに移動してください</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>自動ハイドウィンドウ</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>ドックモードやデスクトップモードで再生されている曲がないときにウィンドウを自動的に非表示にする</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>窓の高さ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>歌詞フォントファミリー</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>グローバルドラッグ</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>タイトルバーをページ全体に拡張して、ウィンドウを非対話領域でドラッグできるようにします</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>仿亚克力效果粗糙度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>音楽ギャラリー - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>リブレットランスレートからの翻訳のリクエストが失敗しました。設定またはネイティブリブレットランレート構成を確認してください</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>ファイル情報</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>タイトル</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>アーティスト</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>アルバム</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>年</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>間隔</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>ビットレート</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>サンプルレート</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>ビットの深さ</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>形式</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>エンコーダー</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>パス</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>曲を検索します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>並べ替えタイプ</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>タイトル</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>アルバム</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>アーティスト</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>メディアライブラリには歌が見つかりませんでした</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>キューを再生します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>クリアプレイキュー</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>アイテムを再生するための位置</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>ループをリストします</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>シングルループ</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>ランダム</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>プレイリストから取り外します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>キューを再生するのは空です</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>すべてを選択します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>キューを再生するために追加します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>次のアイテム</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>リストの終わり</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>プレイリストに追加します</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>プレイリストを作成します</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>オープンミュージックギャラリー</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics はすでに実行されています!</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>すべての曲</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>이 값을 조정하면 앨범 이미지의 배경 흐림 강도가 증가합니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>현재 가치 :</value>
|
||||
<value>현재 가치 : </value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>Blur가 활성화 될 때 상당히 높은 GPU 사용량 (> 0)</value>
|
||||
@@ -288,9 +288,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>글로우 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>글로우 효과 범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>가사 소스를 구성하십시오</value>
|
||||
</data>
|
||||
@@ -304,7 +301,8 @@
|
||||
<value>Betterlyrics에 오신 것을 환영합니다</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>더 많은 기능 옵션을 표시하려면 앱의 상단 또는 하단 영역 위로 마우스를 가져옵니다.</value>
|
||||
<value>몰입 형 모드를 활성화하려면 왼쪽 상단 버튼을 클릭하십시오.
|
||||
문제가 발생하면 설정 페이지, 탭 정보로 이동하여 FAQ를보고 저자에게 연락하여 피드백을 받으십시오.</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>지금 음악이 재생되지 않습니다</value>
|
||||
@@ -420,9 +418,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>도크 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>도크 모드</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>데스크탑 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>데스크탑 모드</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>글꼴 무게</value>
|
||||
</data>
|
||||
@@ -528,8 +532,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>잠금</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하십시오.</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>잠금 잠금을 해제하려면 시스템 트레이로 이동하여 잠금을 해제하거나 누릅니다.</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>팬 가사</value>
|
||||
@@ -589,7 +593,7 @@
|
||||
<value>가사 타임 라인 오프셋 (밀리초)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>번역 서비스를 구성하십시오</value>
|
||||
<value>libretranslate 번역 서비스</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>서버 주소</value>
|
||||
@@ -606,10 +610,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>설치 지침 및 자세한 정보는 https://github.com/LibreTranslate/LibreTranslate 를 방문하십시오 (이 소프트웨어는이 번역 서비스와 제휴하지 않습니다).</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>서버 테스트 성공</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>서버 테스트가 실패했습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -649,7 +653,7 @@
|
||||
<value>번역 서버가 설정되지 않았습니다. 먼저 설정으로 구성하십시오.</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>노래를 전환 할 때 자동으로 0 으로 재설정됩니다</value>
|
||||
<value>노래를 전환 할 때 0 으로 재설정하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>가사의 번역은 먼저 읽습니다. 일치하지 않으면 LibreTranslate 서버에서 기계 번역이 요청됩니다.</value>
|
||||
@@ -735,4 +739,175 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>지금 가입하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>떠 다니는 애니메이션</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>바로 가기 키를 잠금 해제하고 잠그십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX 음악 서버</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>도크 모드 배치</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>맨 위</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>맨 아래</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>번역 활성화</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>번역 만 표시하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>글꼴 불투명 (비 전류 재생 영역)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>자주 묻는 질문</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>LX Music Server에 연결할 수 없습니다. 설정으로 이동하십시오 - 고급 옵션이 링크가 올바르게 입력되었는지 확인하십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>자동 가죽 창</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>도크 모드 또는 데스크탑 모드에서 재생되지 않을 때는 창을 자동으로 숨기고 있습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>창 높이</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>가사 글꼴 가족</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>글로벌 드래그</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>비 중과 영역에서 창을 드래그 할 수 있도록 제목 표시 줄을 전체 페이지로 확장하십시오.</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>仿亚克力效果粗糙度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>음악 갤러리 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>LibreTranslate에서 번역 요청 실패, 설정 또는 기본 LibreTranslate 구성을 확인하십시오.</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>파일 정보</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>제목</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>아티스트</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>앨범</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>년도</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>지속</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>비트 레이트</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>샘플 속도</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>비트 깊이</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>체재</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>인코더</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>길</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>노래 검색</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>정렬 유형</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>제목</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>앨범</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>아티스트</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>미디어 라이브러리에는 노래가 없습니다</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>대기열을 재생하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>플레이 대기열을 클리어합니다</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>아이템을 재생하기위한 포지셔닝</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>목록 루프</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>단일 루프</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>무작위의</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>재생 목록에서 제거하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>플레이 대기열이 비어 있습니다</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>모두를 선택하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>재생 큐에 추가하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>다음 항목</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>목록의 끝</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>재생 목록에 추가하십시오</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>재생 목록을 만듭니다</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>오픈 음악 갤러리</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics 가 이미 실행 중입니다</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>모든 노래</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>调整该数值将同步提高专辑图片背景模糊强度</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>当前值:</value>
|
||||
<value>当前值: </value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>启用模糊(> 0)时将显著提升 GPU 占用率</value>
|
||||
@@ -288,9 +288,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>辉光效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>辉光效果作用范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌词源</value>
|
||||
</data>
|
||||
@@ -304,7 +301,8 @@
|
||||
<value>欢迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>将鼠标悬停在应用程序的顶部或底部区域以显示更多功能选项</value>
|
||||
<value>单击左上按钮以启用沉浸式模式。
|
||||
如果遇到任何问题,请转到“设置”页面,关于标签,查看常见问题解答或联系作者以获取反馈</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>当前没有正在播放的音乐</value>
|
||||
@@ -420,9 +418,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>字体粗细</value>
|
||||
</data>
|
||||
@@ -528,8 +532,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>锁定</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>锁定后解锁,请转到系统托盘解锁</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>锁定后解锁,请转到系统托盘解锁或按下</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>扇形歌词</value>
|
||||
@@ -589,7 +593,7 @@
|
||||
<value>歌词时间轴偏移(毫秒)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>配置翻译服务</value>
|
||||
<value>LibreTranslate 翻译服务</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>服务器地址</value>
|
||||
@@ -606,10 +610,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>访问 https://github.com/LibreTranslate/LibreTranslate 获取安装教程及更多信息(本软件与该翻译服务无任何联系)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>服务器测试成功</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>服务器测试失败</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -646,10 +650,10 @@
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>未设置翻译服务器,请先在设置中进行配置</value>
|
||||
<value>未设置Translate服务器,请先在设置中进行配置</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>切换歌曲时将自动重置为 0</value>
|
||||
<value>切换歌曲时重置为 0</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>将优先读取歌词内翻译,若无匹配则向 LibreTranslate 服务器请求机器翻译</value>
|
||||
@@ -735,4 +739,175 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>立即加入</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>浮动动画</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>解锁和锁定快捷键</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX 音乐服务器</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>停靠模式位置</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>顶部</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>底部</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>启用翻译</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>仅显示翻译</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>字体不透明度(非当前播放区域)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>常见问题与解答</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>无法连接到 LX 音乐服务器,请转到设置 - 高级选项以检查是否正确输入链接</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>自动隐藏窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>停靠模式或桌面模式下,无歌曲正在播放时,自动隐藏窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>窗口高度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>歌词字体</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>全局拖拽</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>将标题栏扩展至整个页面使得在任意非交互区域均可拖拽窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>仿亚克力效果粗糙度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>音乐库 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>向 LibreTranslate 请求翻译失败,请检查设置或本机 LibreTranslate 配置</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>文件信息</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>标题</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>艺术家</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>专辑</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>年</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>期间</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>比特率</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>样本率</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>位深度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>格式</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>编码器</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>路径</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>搜索歌曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>排序类型</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>标题</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>专辑</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>艺术家</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>未在媒体库内找到任何歌曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>播放队列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>清除播放队列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>定位到播放项</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>列表循环</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>单曲循环</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>随机</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>从播放列表移除</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>播放队列是空的</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>选择全部</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>添加到播放队列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>下一个项目</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>列表的结尾</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>添加到歌单</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>创建歌单</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>打开音乐库</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics 已经在运行</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>所有歌曲</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>調整此數值將同步提升專輯圖片背景模糊強度</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>目前值:</value>
|
||||
<value>目前值: </value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBlurHighGPUUsage.Text" xml:space="preserve">
|
||||
<value>啟用模糊(> 0)時將顯著提升 GPU 佔用率</value>
|
||||
@@ -288,9 +288,6 @@
|
||||
<data name="SettingsPageLyricsGlowEffect.Header" xml:space="preserve">
|
||||
<value>輝光效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>輝光效果作用範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌詞源</value>
|
||||
</data>
|
||||
@@ -304,7 +301,8 @@
|
||||
<value>歡迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainPageWelcomeTeachingTip.Subtitle" xml:space="preserve">
|
||||
<value>將鼠標懸停在應用程序的頂部或底部區域以顯示更多功能選項</value>
|
||||
<value>單擊左上按鈕以啟用沉浸式模式。
|
||||
如果遇到任何問題,請轉到“設置”頁面,關於標籤,查看常見問題解答或聯繫作者以獲取反饋</value>
|
||||
</data>
|
||||
<data name="MainPageNoMusicPlaying.Text" xml:space="preserve">
|
||||
<value>目前沒有正在播放的音樂</value>
|
||||
@@ -337,7 +335,7 @@
|
||||
<value>適應歌詞背景(灰色)</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumStyle.Content" xml:space="preserve">
|
||||
<value>专辑区域样式</value>
|
||||
<value>專輯區域樣式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumRadius.Header" xml:space="preserve">
|
||||
<value>圓角半徑</value>
|
||||
@@ -420,9 +418,15 @@
|
||||
<data name="HostWindowDockFlyoutItem.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDock.Text" xml:space="preserve">
|
||||
<value>停靠模式</value>
|
||||
</data>
|
||||
<data name="HostWindowDesktopFlyoutItem.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAppDesktop.Text" xml:space="preserve">
|
||||
<value>桌面模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontWeight.Header" xml:space="preserve">
|
||||
<value>字體粗細</value>
|
||||
</data>
|
||||
@@ -528,8 +532,8 @@
|
||||
<data name="HostWindowClickThroughButton.Content" xml:space="preserve">
|
||||
<value>鎖定</value>
|
||||
</data>
|
||||
<data name="HostWindowLockToolTip.Content" xml:space="preserve">
|
||||
<value>鎖定後解鎖,請轉到系統托盤解鎖</value>
|
||||
<data name="HostWindowLockToolTip.Text" xml:space="preserve">
|
||||
<value>鎖定後解鎖,請轉到系統托盤解鎖或按下</value>
|
||||
</data>
|
||||
<data name="SettingsPageFan.Header" xml:space="preserve">
|
||||
<value>扇形歌詞</value>
|
||||
@@ -589,7 +593,7 @@
|
||||
<value>歌詞時間軸偏移(毫秒)</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>配置翻譯服務</value>
|
||||
<value>LibreTranslate 翻譯服務</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateServer.Header" xml:space="preserve">
|
||||
<value>服務器地址</value>
|
||||
@@ -606,10 +610,10 @@
|
||||
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
|
||||
<value>造訪 https://github.com/LibreTranslate/LibreTranslate 以取得安裝教學及更多資訊(本軟體與此翻譯服務無任何關聯)</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestSuccessInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestSuccessInfo" xml:space="preserve">
|
||||
<value>服務器測試成功</value>
|
||||
</data>
|
||||
<data name="SettingsPageLibreTranslateTestFailedInfo" xml:space="preserve">
|
||||
<data name="SettingsPageServerTestFailedInfo" xml:space="preserve">
|
||||
<value>服務器測試失敗</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontStrokeWidth.Header" xml:space="preserve">
|
||||
@@ -649,7 +653,7 @@
|
||||
<value>未設定翻譯伺服器,請先在設定中進行配置</value>
|
||||
</data>
|
||||
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
|
||||
<value>將在切換歌曲時自動重設為 0</value>
|
||||
<value>切換歌曲時重置為 0</value>
|
||||
</data>
|
||||
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
|
||||
<value>將優先讀取歌詞內翻譯,若無匹配則向 LibreTranslate 伺服器請求機器翻譯</value>
|
||||
@@ -735,4 +739,175 @@
|
||||
<data name="SettingsPageJoinNowButton.Content" xml:space="preserve">
|
||||
<value>立即加入</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFloatAnimation.Header" xml:space="preserve">
|
||||
<value>浮動動畫</value>
|
||||
</data>
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageLockHotKey.Header" xml:space="preserve">
|
||||
<value>解鎖和鎖定快捷鍵</value>
|
||||
</data>
|
||||
<data name="SettingsPageLXMusicServer.Header" xml:space="preserve">
|
||||
<value>LX 音樂服務器</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacement.Header" xml:space="preserve">
|
||||
<value>停靠模式位置</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementTop.Content" xml:space="preserve">
|
||||
<value>頂部</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockPlacementBottom.Content" xml:space="preserve">
|
||||
<value>底部</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationEnabled.Header" xml:space="preserve">
|
||||
<value>啟用翻譯</value>
|
||||
</data>
|
||||
<data name="LyricsPageTranslationOnly.Header" xml:space="preserve">
|
||||
<value>僅顯示翻譯</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontOpacity.Header" xml:space="preserve">
|
||||
<value>字體不透明度(非目前播放區域)</value>
|
||||
</data>
|
||||
<data name="SettingsPageFAQ.Header" xml:space="preserve">
|
||||
<value>常見問題與解答</value>
|
||||
</data>
|
||||
<data name="FailToStartLXMusicServer" xml:space="preserve">
|
||||
<value>無法連接到 LX 音樂服務器,請轉到設置 - 高級選項以檢查是否正確輸入鏈接</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>自動隱藏窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>停靠模式或桌面模式下,無歌曲正在播放時,自動隱藏窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockWindowHeight.Header" xml:space="preserve">
|
||||
<value>窗口高度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsFontFamily.Header" xml:space="preserve">
|
||||
<value>歌詞字體</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Header" xml:space="preserve">
|
||||
<value>全域拖曳</value>
|
||||
</data>
|
||||
<data name="SettingsPageGlobalDrag.Description" xml:space="preserve">
|
||||
<value>將標題列擴展至整個頁面使得在任意非互動區域均可拖曳窗口</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>仿亚克力效果粗糙度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageTitle" xml:space="preserve">
|
||||
<value>音樂庫 - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>向 LibreTranslate 請求翻譯失敗,請檢查設置或本機 LibreTranslate 配置</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfo.Text" xml:space="preserve">
|
||||
<value>文件信息</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoTitle.Text" xml:space="preserve">
|
||||
<value>標題</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileArtist.Text" xml:space="preserve">
|
||||
<value>藝術家</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileAlbum.Text" xml:space="preserve">
|
||||
<value>專輯</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoYear.Text" xml:space="preserve">
|
||||
<value>年</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoDuration.Text" xml:space="preserve">
|
||||
<value>期間</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitrate.Text" xml:space="preserve">
|
||||
<value>比特率</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoSampleRate.Text" xml:space="preserve">
|
||||
<value>樣本率</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoBitDepth.Text" xml:space="preserve">
|
||||
<value>位深度</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoFormat.Text" xml:space="preserve">
|
||||
<value>格式</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoEncoder.Text" xml:space="preserve">
|
||||
<value>編碼器</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileInfoPath.Text" xml:space="preserve">
|
||||
<value>路徑</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSongSearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>搜索歌曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>排序類型</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByTitle.Content" xml:space="preserve">
|
||||
<value>標題</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByAlbum.Content" xml:space="preserve">
|
||||
<value>專輯</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortByArtist.Content" xml:space="preserve">
|
||||
<value>藝術家</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>未在媒體庫內找到任何歌曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||
<value>清除播放</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Content" xml:space="preserve">
|
||||
<value>清除播放隊列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageScrollToPlayingItem.Content" xml:space="preserve">
|
||||
<value>定位到播放項</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueLoop.Content" xml:space="preserve">
|
||||
<value>列表循環</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSingleLoop.Content" xml:space="preserve">
|
||||
<value>單曲循環</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageQueueRandom.Content" xml:space="preserve">
|
||||
<value>隨機</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageRemoveFromPlayingQueue.Text" xml:space="preserve">
|
||||
<value>從播放列表移除</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPagePlayingQueueEmpty.Text" xml:space="preserve">
|
||||
<value>播放隊列是空的</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSelectAll.Content" xml:space="preserve">
|
||||
<value>選擇全部</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToPlayingQueue.Content" xml:space="preserve">
|
||||
<value>新增到播放隊列</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToNext.Text" xml:space="preserve">
|
||||
<value>下一個項目</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToEnd.Text" xml:space="preserve">
|
||||
<value>列表的結尾</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>添加到歌單</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||
<value>建立歌單</value>
|
||||
</data>
|
||||
<data name="SystemTrayMusicGallery.Text" xml:space="preserve">
|
||||
<value>開啟音樂庫</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics 已經在運作</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>所有歌曲</value>
|
||||
</data>
|
||||
<data name="SettingsPageTelegram.Header" xml:space="preserve">
|
||||
<value>Telegram</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,25 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace BetterLyrics.WinUI3.TemplateSelector;
|
||||
|
||||
public class SongOrderTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate ByTitleTemplate { get; set; }
|
||||
public DataTemplate ByAlbumTemplate { get; set; }
|
||||
public DataTemplate ByArtistTemplate { get; set; }
|
||||
|
||||
public CommonSongProperty SongOrderType { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item)
|
||||
{
|
||||
return SongOrderType switch
|
||||
{
|
||||
CommonSongProperty.Title => ByTitleTemplate,
|
||||
CommonSongProperty.Album => ByAlbumTemplate,
|
||||
CommonSongProperty.Artist => ByArtistTemplate,
|
||||
_ => ByTitleTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -7,22 +7,20 @@ using Microsoft.UI.Dispatching;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class BaseViewModel : ObservableRecipient, IDisposable
|
||||
public partial class BaseViewModel : ObservableRecipient
|
||||
{
|
||||
private protected readonly DispatcherQueue _dispatcherQueue =
|
||||
DispatcherQueue.GetForCurrentThread();
|
||||
private protected readonly DispatcherQueue _dispatcherQueue;
|
||||
|
||||
private protected readonly DispatcherQueueTimer _dispatcherQueueTimer;
|
||||
|
||||
private protected readonly ISettingsService _settingsService;
|
||||
|
||||
public BaseViewModel(ISettingsService settingsService)
|
||||
{
|
||||
IsActive = true;
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
_dispatcherQueueTimer = _dispatcherQueue.CreateTimer();
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,38 +9,102 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsPageViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<bool>>
|
||||
public partial class LyricsPageViewModel : BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>
|
||||
{
|
||||
private readonly IPlaybackService _playbackService;
|
||||
|
||||
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToNonStandardMode;
|
||||
private readonly ThrottleHelper _timelineThrottle = new(TimeSpan.FromSeconds(1));
|
||||
|
||||
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
|
||||
{
|
||||
IsFirstRun = _settingsService.IsFirstRun;
|
||||
IsTranslationEnabled = _settingsService.IsTranslationEnabled;
|
||||
PreferredDisplayType = _settingsService.PreferredDisplayType;
|
||||
DisplayType = _settingsService.DisplayType;
|
||||
ResetPositionOffsetOnSongChanged = _settingsService.ResetPositionOffsetOnSongChanged;
|
||||
PositionOffset = _settingsService.PositionOffset;
|
||||
IsImmersiveMode = _settingsService.IsImmersiveMode;
|
||||
ShowTranslationOnly = _settingsService.ShowTranslationOnly;
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
LyricsFontFamily = _settingsService.LyricsFontFamily;
|
||||
|
||||
OnIsImmersiveModeChanged(IsImmersiveMode);
|
||||
|
||||
//Volume = SystemVolumeHelper.GetMasterVolume();
|
||||
//SystemVolumeHelper.VolumeChanged += SystemVolumeHelper_VolumeChanged;
|
||||
|
||||
_playbackService = playbackService;
|
||||
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
_playbackService.TimelineChanged += PlaybackService_TimelineChanged;
|
||||
|
||||
IsSongPlaying = _playbackService.IsPlaying;
|
||||
}
|
||||
|
||||
private void PlaybackService_TimelineChanged(object? sender, Events.TimelineChangedEventArgs e)
|
||||
{
|
||||
SongDurationSeconds = (int)e.End.TotalSeconds;
|
||||
}
|
||||
|
||||
//private void SystemVolumeHelper_VolumeChanged(int volume)
|
||||
//{
|
||||
// Volume = volume;
|
||||
//}
|
||||
|
||||
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
|
||||
{
|
||||
IsSongPlaying = e.IsPlaying;
|
||||
}
|
||||
|
||||
private void PlaybackService_SongInfoChanged(object? sender, Events.SongInfoChangedEventArgs e)
|
||||
{
|
||||
SongInfo = e.SongInfo;
|
||||
PositionOffset = 0; // Reset position offset when song changes
|
||||
TrySwitchToPreferredDisplayType(e.SongInfo);
|
||||
SongDurationSeconds = SongInfo?.Duration ?? 0;
|
||||
if (ResetPositionOffsetOnSongChanged)
|
||||
{
|
||||
PositionOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial double TimelinePositionSeconds { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SongDurationSeconds { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int Volume { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string LyricsFontFamily { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int LyricsFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsImmersiveMode { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial float BottomCommandGridOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial float BottomCommandFlyoutTriggerOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsDisplayType DisplayType { get; set; } = LyricsDisplayType.PlaceholderOnly;
|
||||
public partial LyricsDisplayType DisplayType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsFirstRun { get; set; }
|
||||
@@ -48,29 +112,27 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial bool IsWelcomeTeachingTipOpen { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsDisplayType PreferredDisplayType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial SongInfo? SongInfo { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int PositionOffset { get; set; } = 0;
|
||||
public partial int PositionOffset { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsTranslationEnabled { get; set; }
|
||||
|
||||
partial void OnIsTranslationEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsTranslationEnabled = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool ShowTranslationOnly { get; set; }
|
||||
|
||||
partial void OnPreferredDisplayTypeChanged(LyricsDisplayType value)
|
||||
{
|
||||
_settingsService.PreferredDisplayType = value;
|
||||
}
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsSongPlaying { get; set; }
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
@@ -78,13 +140,29 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
|
||||
{
|
||||
SetNonStandardModePreferredDisplayType(message.NewValue);
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
if (message.NewValue)
|
||||
{
|
||||
DisplayType = LyricsDisplayType.LyricsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayType = _settingsService.DisplayType;
|
||||
}
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
SetNonStandardModePreferredDisplayType(message.NewValue);
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
if (message.NewValue)
|
||||
{
|
||||
DisplayType = LyricsDisplayType.LyricsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayType = _settingsService.DisplayType;
|
||||
}
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsImmersiveMode))
|
||||
{
|
||||
IsImmersiveMode = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,41 +170,31 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[RelayCommand]
|
||||
private static void OpenSettingsWindow()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
WindowHelper.OpenWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
private void SetNonStandardModePreferredDisplayType(bool isEnabled)
|
||||
[RelayCommand]
|
||||
private async Task PlaySongAsync()
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
_preferredDisplayTypeBeforeSwitchToNonStandardMode = PreferredDisplayType;
|
||||
PreferredDisplayType = LyricsDisplayType.LyricsOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreferredDisplayType = _preferredDisplayTypeBeforeSwitchToNonStandardMode ?? LyricsDisplayType.SplitView;
|
||||
}
|
||||
await _playbackService.PlayAsync();
|
||||
}
|
||||
|
||||
private void TrySwitchToPreferredDisplayType(SongInfo? songInfo)
|
||||
[RelayCommand]
|
||||
private async Task PauseSongAsync()
|
||||
{
|
||||
LyricsDisplayType displayType;
|
||||
await _playbackService.PauseAsync();
|
||||
}
|
||||
|
||||
if (songInfo == null)
|
||||
{
|
||||
displayType = LyricsDisplayType.PlaceholderOnly;
|
||||
}
|
||||
else if (PreferredDisplayType is LyricsDisplayType preferredDisplayType)
|
||||
{
|
||||
displayType = preferredDisplayType;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayType = LyricsDisplayType.SplitView;
|
||||
}
|
||||
|
||||
DisplayType = displayType;
|
||||
[RelayCommand]
|
||||
private async Task PreviousSongAsync()
|
||||
{
|
||||
await _playbackService.PreviousAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task NextSongAsync()
|
||||
{
|
||||
await _playbackService.NextAsync();
|
||||
}
|
||||
|
||||
partial void OnIsFirstRunChanged(bool value)
|
||||
@@ -134,5 +202,78 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IsWelcomeTeachingTipOpen = value;
|
||||
_settingsService.IsFirstRun = false;
|
||||
}
|
||||
|
||||
partial void OnIsTranslationEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsTranslationEnabled = value;
|
||||
}
|
||||
|
||||
partial void OnPositionOffsetChanged(int value)
|
||||
{
|
||||
_settingsService.PositionOffset = value;
|
||||
}
|
||||
|
||||
partial void OnIsImmersiveModeChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
BottomCommandGridOpacity = 0f;
|
||||
BottomCommandFlyoutTriggerOpacity = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
BottomCommandGridOpacity = 1f;
|
||||
BottomCommandFlyoutTriggerOpacity = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnShowTranslationOnlyChanged(bool value)
|
||||
{
|
||||
_settingsService.ShowTranslationOnly = value;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
|
||||
{
|
||||
LyricsFontSize = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontFamily))
|
||||
{
|
||||
LyricsFontFamily = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//partial void OnVolumeChanged(int value)
|
||||
//{
|
||||
// SystemVolumeHelper.SetMasterVolume(value);
|
||||
//}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.TotalTime))
|
||||
{
|
||||
if (_timelineThrottle.CanTrigger())
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelinePositionSeconds = message.NewValue.TotalSeconds;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -20,12 +21,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
_albumArtBgOpacity = _settingsService.CoverOverlayOpacity;
|
||||
_albumArtBgBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
_coverAcrylicEffectAmount = _settingsService.CoverAcrylicEffectAmount;
|
||||
|
||||
_lyricsBgFontColorType = _settingsService.LyricsBgFontColorType;
|
||||
_lyricsFgFontColorType = _settingsService.LyricsFgFontColorType;
|
||||
|
||||
_lyricsTextFormat.FontWeight = _settingsService.LyricsFontWeight.ToFontWeight();
|
||||
|
||||
_lyricsTextFormat.FontFamily = _artistTextFormat.FontFamily = _titleTextFormat.FontFamily = _settingsService.LyricsFontFamily;
|
||||
|
||||
_lyricsAlignmentType = _settingsService.LyricsAlignmentType;
|
||||
_lyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
|
||||
_lyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
|
||||
@@ -43,6 +47,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
|
||||
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
|
||||
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
|
||||
_showTranslationOnly = _settingsService.ShowTranslationOnly;
|
||||
_isLibreTranslateEnabled = _settingsService.IsLibreTranslateEnabled;
|
||||
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
|
||||
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
|
||||
|
||||
@@ -50,6 +56,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
_canvasYScrollTransition.SetDuration(_settingsService.LyricsScrollDuration / 1000f);
|
||||
_canvasYScrollTransition.SetEasingType(_settingsService.LyricsScrollEasingType);
|
||||
_defaultOpacity = _settingsService.LyricsBgFontOpacity / 100f;
|
||||
|
||||
_isLyricsFloatAnimationEnabled = _settingsService.IsLyricsFloatAnimationEnabled;
|
||||
_displayType = _displayTypeReceived = _settingsService.DisplayType;
|
||||
|
||||
_libWatcherService.MusicLibraryFilesChanged +=
|
||||
LibWatcherService_MusicLibraryFilesChanged;
|
||||
@@ -57,7 +67,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
_playbackService.AlbumArtChangedChanged += PlaybackService_AlbumArtChangedChanged;
|
||||
_playbackService.PositionChanged += PlaybackService_PositionChanged;
|
||||
_playbackService.TimelineChanged += PlaybackService_TimelineChanged;
|
||||
|
||||
_isPlaying = _playbackService.IsPlaying;
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Brushes;
|
||||
using Microsoft.Graphics.Canvas.Effects;
|
||||
@@ -8,17 +7,15 @@ using Microsoft.Graphics.Canvas.Geometry;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Graphics.Effects;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -51,7 +48,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else if (_isDesktopMode)
|
||||
{
|
||||
DrawImmersiveBackground(control, combinedDs, 12f);
|
||||
DrawImmersiveBackground(control, combinedDs, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -74,7 +71,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (currentPlayingLine != null)
|
||||
{
|
||||
GetLinePlayingProgress(
|
||||
currentPlayingLine,
|
||||
_playingLineIndex,
|
||||
out int charStartIndex,
|
||||
out int charLength,
|
||||
out float charProgress
|
||||
@@ -84,7 +81,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
$"[DEBUG]\n" +
|
||||
$"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
|
||||
$"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
|
||||
$"Cur time {_totalTime + _positionOffset}\n" +
|
||||
$"Cur time {TotalTime + _positionOffset}\n" +
|
||||
$"Lang size {_lyricsDataArr.Count}\n" +
|
||||
$"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
|
||||
new Vector2(10, 10),
|
||||
@@ -139,8 +136,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
BlackPoint = new Vector2(blackX, blackY),
|
||||
},
|
||||
Opacity = opacity,
|
||||
}, new Vector2(x, y)
|
||||
);
|
||||
}, new Vector2(x, y));
|
||||
}
|
||||
|
||||
private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
|
||||
@@ -180,10 +176,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
|
||||
//ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
|
||||
|
||||
using var overlappedCovers = new CanvasCommandList(control.Device);
|
||||
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
|
||||
overlappedCoversDs.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
|
||||
|
||||
if (_lastAlbumArtCanvasBitmap != null)
|
||||
{
|
||||
@@ -194,20 +191,40 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
DrawBackgroundImgae(control, overlappedCoversDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
|
||||
}
|
||||
|
||||
using var coverOverlayEffect = new OpacityEffect
|
||||
overlappedCoversDs.Transform = Matrix3x2.Identity;
|
||||
|
||||
IGraphicsEffectSource blurredCover = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = _albumArtBgBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
Optimization = EffectOptimization.Speed,
|
||||
};
|
||||
|
||||
// 应用亚克力噪点效果
|
||||
// TODO: 没有写_coverAcrylicNoiseCanvasBitmap加载的代码
|
||||
if (_coverAcrylicEffectAmount > 0 && _coverAcrylicNoiseCanvasBitmap != null)
|
||||
{
|
||||
blurredCover = new BlendEffect
|
||||
{
|
||||
Mode = BlendEffectMode.SoftLight,
|
||||
Background = blurredCover,
|
||||
Foreground = new OpacityEffect
|
||||
{
|
||||
Source = _coverAcrylicNoiseCanvasBitmap,
|
||||
Opacity = _coverAcrylicEffectAmount / 100f,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
var coverOverlayEffect = new OpacityEffect
|
||||
{
|
||||
Opacity = _albumArtBgOpacity / 100f,
|
||||
Source = new GaussianBlurEffect
|
||||
{
|
||||
BlurAmount = _albumArtBgBlurAmount,
|
||||
Source = overlappedCovers,
|
||||
BorderMode = EffectBorderMode.Soft,
|
||||
Optimization = EffectOptimization.Quality,
|
||||
},
|
||||
Source = blurredCover,
|
||||
};
|
||||
ds.DrawImage(coverOverlayEffect);
|
||||
|
||||
ds.Transform = Matrix3x2.Identity;
|
||||
//ds.Transform = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
private void DrawAlbumArt(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
@@ -286,28 +303,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (line == null) continue;
|
||||
|
||||
var textLayout = line.CanvasTextLayout;
|
||||
|
||||
if (textLayout == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (textLayout == null) continue;
|
||||
|
||||
var position = new Vector2(line.Position.X, line.Position.Y);
|
||||
|
||||
float layoutWidth = (float)textLayout.LayoutBounds.Width;
|
||||
float layoutHeight = (float)textLayout.LayoutBounds.Height;
|
||||
|
||||
if (layoutWidth <= 0 || layoutHeight <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (layoutWidth <= 0 || layoutHeight <= 0) continue;
|
||||
|
||||
float centerX = position.X;
|
||||
float centerY = position.Y + layoutHeight / 2;
|
||||
@@ -329,11 +335,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
break;
|
||||
}
|
||||
|
||||
float xOffset = _lyricsXTransition.Value;
|
||||
float yOffset = _canvasYScrollTransition.Value + _canvasHeight / 2;
|
||||
|
||||
// 组合变换:缩放 -> 旋转 -> 平移
|
||||
ds.Transform =
|
||||
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
|
||||
* Matrix3x2.CreateRotation(line.AngleTransition.Value, currentPlayingLine.Position)
|
||||
* Matrix3x2.CreateTranslation(_lyricsXTransition.Value, _canvasYScrollTransition.Value + _canvasHeight / 2);
|
||||
* Matrix3x2.CreateTranslation(xOffset, yOffset);
|
||||
|
||||
// Create the background lyrics line with stroke and fill
|
||||
using var bgLyrics = new CanvasCommandList(control.Device);
|
||||
@@ -344,22 +353,23 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
using var fgLyricsDs = fgLyrics.CreateDrawingSession();
|
||||
|
||||
// 创建文字几何体
|
||||
using (var textGeometry = CanvasGeometry.CreateText(textLayout))
|
||||
using var textGeometry = CanvasGeometry.CreateText(textLayout);
|
||||
if (_isDesktopMode)
|
||||
{
|
||||
if (_isDesktopMode)
|
||||
{
|
||||
bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
|
||||
fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
|
||||
}
|
||||
|
||||
bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
|
||||
fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充
|
||||
bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
|
||||
fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
|
||||
}
|
||||
|
||||
bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
|
||||
fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充
|
||||
|
||||
using var combined = new CanvasCommandList(control.Device);
|
||||
using var combinedDs = combined.CreateDrawingSession();
|
||||
|
||||
// Mock gradient blurred lyrics layer
|
||||
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
|
||||
// Current line will not be blurred
|
||||
ds.DrawImage(
|
||||
combinedDs.DrawImage(
|
||||
new GaussianBlurEffect
|
||||
{
|
||||
Source = new OpacityEffect { Source = bgLyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
|
||||
@@ -381,7 +391,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (i == _playingLineIndex)
|
||||
{
|
||||
GetLinePlayingProgress(
|
||||
line,
|
||||
i,
|
||||
out int charStartIndex,
|
||||
out int charLength,
|
||||
out float charProgress
|
||||
@@ -402,7 +412,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
region.LayoutBounds.Width,
|
||||
region.LayoutBounds.Height
|
||||
);
|
||||
maskDs.FillRectangle(rect, Colors.Black);
|
||||
maskDs.FillRectangle(rect, Color.FromArgb(255, 128, 128, 128));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +457,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
fadingWidth
|
||||
);
|
||||
|
||||
maskDs.FillRectangle(highlightRect, Colors.White);
|
||||
maskDs.FillRectangle(highlightRect, Color.FromArgb(255, 128, 128, 128));
|
||||
maskDs.FillRectangle(fadeOutRect, fadeOutBrush);
|
||||
|
||||
highlightMaskDs.FillRectangle(fadeInRect, fadeInBrush);
|
||||
@@ -456,7 +466,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
else
|
||||
{
|
||||
float height = 0f;
|
||||
var regions = textLayout.GetCharacterRegions(0, string.Join("", line.CharTimings.Select(x => x.Text)).Length);
|
||||
//var regions = textLayout.GetCharacterRegions(0, string.Join("", line.LyricsChars.Select(x => x.Text)).Length);
|
||||
var regions = textLayout.GetCharacterRegions(0, line.OriginalText.Length);
|
||||
if (regions.Length > 0)
|
||||
{
|
||||
height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
|
||||
@@ -473,12 +484,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
}
|
||||
|
||||
ds.DrawImage(
|
||||
new OpacityEffect
|
||||
using var opacityEffect = new OpacityEffect
|
||||
{
|
||||
Source = new BlendEffect
|
||||
{
|
||||
Source = new BlendEffect
|
||||
{
|
||||
Background = _isLyricsGlowEffectEnabled
|
||||
Background = _isLyricsGlowEffectEnabled
|
||||
? new GaussianBlurEffect
|
||||
{
|
||||
Source = new AlphaMaskEffect
|
||||
@@ -496,21 +506,49 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
Optimization = EffectOptimization.Quality,
|
||||
}
|
||||
: new CanvasCommandList(control.Device),
|
||||
Foreground = new AlphaMaskEffect
|
||||
Foreground = new AlphaMaskEffect
|
||||
{
|
||||
Source = fgLyrics,
|
||||
AlphaMask = _lyricsHighlightScope switch
|
||||
{
|
||||
Source = fgLyrics,
|
||||
AlphaMask = _lyricsHighlightScope switch
|
||||
{
|
||||
LineRenderingType.CurrentChar => highlightMask,
|
||||
LineRenderingType.LineStartToCurrentChar => mask,
|
||||
LineRenderingType.CurrentLine => fgLyrics,
|
||||
_ => mask,
|
||||
},
|
||||
LineRenderingType.CurrentChar => highlightMask,
|
||||
LineRenderingType.LineStartToCurrentChar => mask,
|
||||
LineRenderingType.CurrentLine => fgLyrics,
|
||||
_ => mask,
|
||||
},
|
||||
},
|
||||
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
|
||||
},
|
||||
Opacity = line.HighlightOpacityTransition.Value * _lyricsOpacityTransition.Value,
|
||||
};
|
||||
|
||||
combinedDs.DrawImage(opacityEffect);
|
||||
|
||||
if (i == _playingLineIndex)
|
||||
{
|
||||
if (_isLyricsFloatAnimationEnabled)
|
||||
{
|
||||
ds.DrawImage(new DisplacementMapEffect
|
||||
{
|
||||
Source = combined,
|
||||
Displacement = mask,
|
||||
XChannelSelect = EffectChannelSelect.Red,
|
||||
YChannelSelect = EffectChannelSelect.Alpha,
|
||||
Amount = 1f
|
||||
});
|
||||
}
|
||||
);
|
||||
else
|
||||
{
|
||||
ds.DrawImage(combined);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ds.DrawImage(combined);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ds.DrawImage(combined);
|
||||
}
|
||||
|
||||
// Reset scale
|
||||
@@ -548,7 +586,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
.Select(stops => new CanvasGradientStop
|
||||
{
|
||||
Position = stops.position,
|
||||
Color = Color.FromArgb((byte)(stops.opacity * 255), 0, 0, 0),
|
||||
Color = Color.FromArgb((byte)(stops.opacity * 255), 128, 128, 128),
|
||||
})
|
||||
.ToArray()
|
||||
)
|
||||
|
||||
@@ -6,12 +6,14 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
: IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<float>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<Color>>,
|
||||
@@ -23,13 +25,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<EasingType>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>>>
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalMediaFolder>>>
|
||||
{
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<LocalLyricsFolder>> message)
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LocalLyricsFolders))
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LocalMediaFolders))
|
||||
{
|
||||
// Music lib changed, re-fetch lyrics
|
||||
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
|
||||
@@ -78,6 +80,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isFanLyricsEnabled = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsFloatAnimationEnabled))
|
||||
{
|
||||
_isLyricsFloatAnimationEnabled = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLibreTranslateEnabled))
|
||||
{
|
||||
_isLibreTranslateEnabled = message.NewValue;
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
@@ -85,20 +96,23 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_isDockMode = message.NewValue;
|
||||
UpdateColorConfig();
|
||||
UpdateImmersiveBackgroundOpacity();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
|
||||
{
|
||||
_isDesktopMode = message.NewValue;
|
||||
UpdateColorConfig();
|
||||
UpdateImmersiveBackgroundOpacity();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsLyricsWindowLocked))
|
||||
{
|
||||
_isLyricsWindowLocked = message.NewValue;
|
||||
UpdateImmersiveBackgroundOpacity();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsMouseWithinWindow))
|
||||
{
|
||||
_isMouseWithinWindow = message.NewValue;
|
||||
_immersiveBgOpacityTransition.StartTransition(_isDesktopMode ? (_isMouseWithinWindow ? 1f : 0f) : 1f);
|
||||
UpdateImmersiveBackgroundOpacity();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsPageViewModel)
|
||||
@@ -109,6 +123,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _isTranslationEnabled);
|
||||
UpdateTranslations();
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsPageViewModel.ShowTranslationOnly))
|
||||
{
|
||||
_showTranslationOnly = message.NewValue;
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +190,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_albumArtBgBlurAmount = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverAcrylicEffectAmount))
|
||||
{
|
||||
_coverAcrylicEffectAmount = message.NewValue;
|
||||
_isCoverAcrylicEffectAmountChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsVerticalEdgeOpacity))
|
||||
{
|
||||
_lyricsVerticalEdgeOpacity = message.NewValue;
|
||||
@@ -204,6 +228,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_timelineSyncThreshold = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBgFontOpacity))
|
||||
{
|
||||
_defaultOpacity = message.NewValue / 100f;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsPageViewModel)
|
||||
{
|
||||
@@ -306,5 +335,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontFamily))
|
||||
{
|
||||
_lyricsTextFormat.FontFamily = _artistTextFormat.FontFamily = _titleTextFormat.FontFamily = message.NewValue;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,12 @@ using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
@@ -19,7 +23,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private bool _isCanvasHeightChanged = false;
|
||||
|
||||
private bool _isDisplayTypeChanged = false;
|
||||
|
||||
|
||||
private bool _isPlayingLineChanged = false;
|
||||
private bool _isVisibleLinesBoundaryChanged = false;
|
||||
|
||||
@@ -29,7 +33,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
if (_isPlaying)
|
||||
{
|
||||
_totalTime += _elapsedTime;
|
||||
TotalTime += _elapsedTime;
|
||||
}
|
||||
|
||||
var playingLineIndex = GetCurrentPlayingLineIndex();
|
||||
@@ -69,6 +73,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_albumArtSize = MathF.Max(0, _albumArtSize);
|
||||
|
||||
_titleY = _albumArtY + _albumArtSize * 1.05f;
|
||||
|
||||
_isCoverAcrylicEffectAmountChanged = true;
|
||||
}
|
||||
|
||||
if (_isCoverAcrylicEffectAmountChanged)
|
||||
{
|
||||
UpdateCoverAcrylicOverlay(control);
|
||||
}
|
||||
|
||||
if (_isDisplayTypeChanged || _isCanvasWidthChanged)
|
||||
@@ -92,8 +103,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_lyricsXTransition.StartTransition((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 + _leftMargin + _middleMargin, jumpTo);
|
||||
_albumArtXTransition.StartTransition(_leftMargin + ((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 - _albumArtSize) / 2, jumpTo);
|
||||
break;
|
||||
case LyricsDisplayType.PlaceholderOnly:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -196,48 +205,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
_canvasYScrollTransition.Update(_elapsedTime);
|
||||
|
||||
int startVisibleLineIndex = -1;
|
||||
int endVisibleLineIndex = -1;
|
||||
|
||||
// Update visible line indices
|
||||
for (int i = startLineIndex; i <= endLineIndex; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
var lines = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines;
|
||||
if (lines == null || lines.Count == 0) return;
|
||||
|
||||
if (line == null || line.CanvasTextLayout == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var textLayout = line.CanvasTextLayout;
|
||||
|
||||
if (
|
||||
_canvasYScrollTransition.Value
|
||||
+ _canvasHeight / 2
|
||||
+ line.Position.Y
|
||||
+ textLayout.LayoutBounds.Height
|
||||
>= 0
|
||||
)
|
||||
{
|
||||
if (startVisibleLineIndex == -1)
|
||||
{
|
||||
startVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
if (
|
||||
_canvasYScrollTransition.Value
|
||||
+ _canvasHeight / 2
|
||||
+ line.Position.Y
|
||||
+ textLayout.LayoutBounds.Height
|
||||
>= control.Size.Height
|
||||
)
|
||||
{
|
||||
if (endVisibleLineIndex == -1)
|
||||
{
|
||||
endVisibleLineIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
float offset = _canvasYScrollTransition.Value + _canvasHeight / 2;
|
||||
int startVisibleLineIndex = FindFirstVisibleLine(lines, offset);
|
||||
int endVisibleLineIndex = FindLastVisibleLine(lines, offset, _canvasHeight);
|
||||
|
||||
if (startVisibleLineIndex != -1 && endVisibleLineIndex == -1)
|
||||
{
|
||||
@@ -250,6 +224,52 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_endVisibleLineIndex = endVisibleLineIndex;
|
||||
}
|
||||
|
||||
private int FindFirstVisibleLine(IList<LyricsLine> lines, float offset)
|
||||
{
|
||||
int left = 0, right = lines.Count - 1, result = -1;
|
||||
while (left <= right)
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
var layout = line.CanvasTextLayout;
|
||||
if (layout == null) break;
|
||||
float value = offset + line.Position.Y + (float)layout.LayoutBounds.Height;
|
||||
if (value >= 0)
|
||||
{
|
||||
result = mid;
|
||||
right = mid - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int FindLastVisibleLine(IList<LyricsLine> lines, float offset, float canvasHeight)
|
||||
{
|
||||
int left = 0, right = lines.Count - 1, result = -1;
|
||||
while (left <= right)
|
||||
{
|
||||
int mid = (left + right) / 2;
|
||||
var line = lines[mid];
|
||||
var layout = line.CanvasTextLayout;
|
||||
if (layout == null) break;
|
||||
float value = offset + line.Position.Y + (float)layout.LayoutBounds.Height;
|
||||
if (value >= canvasHeight)
|
||||
{
|
||||
result = mid;
|
||||
right = mid - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void UpdateColorConfig()
|
||||
{
|
||||
if (_isDesktopMode || _isDockMode)
|
||||
@@ -297,7 +317,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (_adaptiveGrayedFontColor == _lightColor)
|
||||
{
|
||||
grayedEnvironmentalColor = _darkColor;
|
||||
} else if (_adaptiveGrayedFontColor == _darkColor)
|
||||
}
|
||||
else if (_adaptiveGrayedFontColor == _darkColor)
|
||||
{
|
||||
grayedEnvironmentalColor = _lightColor;
|
||||
}
|
||||
@@ -399,5 +420,49 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
line.HighlightOpacityTransition.Update(_elapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateImmersiveBackgroundOpacity()
|
||||
{
|
||||
float targetOpacity;
|
||||
if (_isDesktopMode)
|
||||
{
|
||||
if (_isLyricsWindowLocked)
|
||||
{
|
||||
targetOpacity = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_isMouseWithinWindow)
|
||||
{
|
||||
targetOpacity = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetOpacity = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetOpacity = 1f;
|
||||
}
|
||||
_immersiveBgOpacityTransition.StartTransition(targetOpacity);
|
||||
}
|
||||
|
||||
private void UpdateCoverAcrylicOverlay(ICanvasAnimatedControl control)
|
||||
{
|
||||
if (_coverAcrylicEffectAmount > 0)
|
||||
{
|
||||
var ret = NoiseOverlayHelper.GenerateNoiseBitmapBGRA((int)_canvasWidth, (int)_canvasHeight);
|
||||
_coverAcrylicNoiseCanvasBitmap = CanvasBitmap.CreateFromBytes(
|
||||
control,
|
||||
ret,
|
||||
(int)_canvasWidth,
|
||||
(int)_canvasHeight,
|
||||
Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized
|
||||
);
|
||||
}
|
||||
_isCoverAcrylicEffectAmountChanged = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Helpers.General;
|
||||
using Lyricify.Lyrics.Providers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
@@ -30,15 +31,24 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial class LyricsRendererViewModel : BaseViewModel
|
||||
{
|
||||
private TimeSpan _elapsedTime = TimeSpan.Zero;
|
||||
private TimeSpan _totalTime = TimeSpan.Zero;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
|
||||
|
||||
private TimeSpan _positionOffset = TimeSpan.Zero;
|
||||
|
||||
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
|
||||
|
||||
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
|
||||
private SoftwareBitmap? _albumArtSwBitmap = null;
|
||||
|
||||
private CanvasBitmap? _lastAlbumArtCanvasBitmap = null;
|
||||
private CanvasBitmap? _albumArtCanvasBitmap = null;
|
||||
|
||||
private CanvasBitmap? _coverAcrylicNoiseCanvasBitmap = null;
|
||||
private bool _isCoverAcrylicEffectAmountChanged = false;
|
||||
|
||||
private float _albumArtSize = 0f;
|
||||
private int _albumArtCornerRadius = 0;
|
||||
|
||||
@@ -55,7 +65,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private float _canvasWidth = 0f;
|
||||
private float _canvasHeight = 0f;
|
||||
|
||||
private readonly float _defaultOpacity = 0.3f;
|
||||
private float _defaultOpacity;
|
||||
private readonly float _highlightedOpacity = 1.0f;
|
||||
|
||||
private readonly float _defaultScale = 0.75f;
|
||||
@@ -131,14 +141,18 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private bool _isDynamicCoverOverlayEnabled;
|
||||
private bool _isLyricsGlowEffectEnabled;
|
||||
|
||||
private bool _isLyricsFloatAnimationEnabled;
|
||||
|
||||
private bool _isLayoutChanged = true;
|
||||
|
||||
private int _langIndex = 0;
|
||||
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
private List<string> _translationList = [];
|
||||
private bool _isTranslationEnabled = false;
|
||||
private int _targetLanguageIndex = 6;
|
||||
private bool _isTranslationEnabled;
|
||||
private bool _showTranslationOnly;
|
||||
private int _targetLanguageIndex;
|
||||
private bool _isLibreTranslateEnabled;
|
||||
|
||||
private int _timelineSyncThreshold;
|
||||
|
||||
@@ -169,12 +183,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
|
||||
private LatestOnlyTaskRunner _showTranslationsRunner = new();
|
||||
|
||||
private LyricsDisplayType _displayTypeReceived = LyricsDisplayType.PlaceholderOnly;
|
||||
private LyricsDisplayType _displayType = LyricsDisplayType.PlaceholderOnly;
|
||||
private LyricsDisplayType _displayTypeReceived;
|
||||
private LyricsDisplayType _displayType;
|
||||
|
||||
private int _albumArtBgBlurAmount;
|
||||
private int _albumArtBgOpacity;
|
||||
|
||||
private int _coverAcrylicEffectAmount;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsTranslating { get; set; } = false;
|
||||
|
||||
@@ -187,32 +203,43 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private int GetCurrentPlayingLineIndex()
|
||||
{
|
||||
var totalMs = TotalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds;
|
||||
if (totalMs < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
|
||||
|
||||
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines[i];
|
||||
if (line == null)
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i + 1);
|
||||
if (nextLine != null && line.StartMs <= totalMs && totalMs < nextLine.StartMs)
|
||||
{
|
||||
continue;
|
||||
return i;
|
||||
}
|
||||
if (
|
||||
line.StartMs <= _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds
|
||||
&& _totalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds <= line.EndMs
|
||||
)
|
||||
else if (nextLine == null && line.StartMs <= totalMs)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
return GetMaxLyricsLineIndexBoundaries().Item2;
|
||||
}
|
||||
|
||||
private void GetLinePlayingProgress(LyricsLine line, out int charStartIndex, out int charLength, out float charProgress)
|
||||
private void GetLinePlayingProgress(int lineIndex, out int charStartIndex, out int charLength, out float charProgress)
|
||||
{
|
||||
charStartIndex = 0;
|
||||
charLength = 0;
|
||||
charProgress = 0f;
|
||||
|
||||
float now = (float)_totalTime.TotalMilliseconds + (float)_positionOffset.TotalMilliseconds;
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex);
|
||||
if (line == null) return;
|
||||
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
|
||||
|
||||
int lineEndMs;
|
||||
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
|
||||
else if (nextLine != null) lineEndMs = nextLine.StartMs;
|
||||
else lineEndMs = _songDurationMs;
|
||||
|
||||
float now = (float)TotalTime.TotalMilliseconds + (float)_positionOffset.TotalMilliseconds;
|
||||
|
||||
// 1. 还没到本句
|
||||
if (now < line.StartMs)
|
||||
@@ -221,27 +248,37 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
|
||||
// 2. 已经超过本句
|
||||
if (now > line.EndMs)
|
||||
if (now > lineEndMs)
|
||||
{
|
||||
charProgress = 1f;
|
||||
charStartIndex = line.OriginalText.Length - 1;
|
||||
charLength = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 有逐字时间轴
|
||||
if (line.CharTimings != null && line.CharTimings.Count > 0)
|
||||
if (line.LyricsChars != null && line.LyricsChars.Count > 1)
|
||||
{
|
||||
int charTimingsCount = line.CharTimings.Count;
|
||||
int charTimingsCount = line.LyricsChars.Count;
|
||||
for (int i = 0; i < charTimingsCount; i++)
|
||||
{
|
||||
var timing = line.CharTimings[i];
|
||||
var timing = line.LyricsChars[i];
|
||||
var nextTiming = line.LyricsChars.ElementAtOrDefault(i + 1);
|
||||
|
||||
int timingEndMs;
|
||||
if (timing.EndMs != null) timingEndMs = timing.EndMs.Value;
|
||||
else if (nextTiming != null) timingEndMs = nextTiming.StartMs;
|
||||
else timingEndMs = lineEndMs;
|
||||
|
||||
charStartIndex = timing.StartIndex;
|
||||
charLength = timing.Text.Length;
|
||||
|
||||
// 当前时间在某个字的高亮区间
|
||||
if (now >= timing.StartMs && now <= timing.EndMs)
|
||||
if (now >= timing.StartMs && now <= timingEndMs)
|
||||
{
|
||||
charStartIndex = timing.StartIndex;
|
||||
charLength = timing.Text.Length;
|
||||
if (timing.EndMs != timing.StartMs)
|
||||
if (timingEndMs != timing.StartMs)
|
||||
{
|
||||
charProgress = (now - timing.StartMs) / (timing.EndMs - timing.StartMs);
|
||||
charProgress = (now - timing.StartMs) / (timingEndMs - timing.StartMs);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -249,15 +286,30 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (now > timingEndMs && (nextTiming == null || now < nextTiming?.StartMs))
|
||||
{
|
||||
charProgress = 1f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有逐字时间轴,直接线性
|
||||
charProgress = (now - line.StartMs) / line.DurationMs;
|
||||
charProgress = Math.Clamp(charProgress, 0f, 1f);
|
||||
charStartIndex = 0;
|
||||
charLength = line.OriginalText.Length;
|
||||
// 没有逐字时间轴,均匀分配每个字的高亮时间
|
||||
int textLength = line.OriginalText.Length;
|
||||
if (textLength == 0) return;
|
||||
|
||||
float lineProgress = (now - line.StartMs) / (lineEndMs - line.StartMs);
|
||||
lineProgress = Math.Clamp(lineProgress, 0f, 1f);
|
||||
|
||||
// 计算当前高亮到第几个字
|
||||
float charFloatIndex = lineProgress * textLength;
|
||||
int charIndex = (int)charFloatIndex;
|
||||
charStartIndex = Math.Clamp(charIndex, 0, textLength - 1);
|
||||
charLength = 1;
|
||||
|
||||
// 当前字的进度(0~1)
|
||||
charProgress = charFloatIndex - charIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,11 +341,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isPlaying = e.IsPlaying;
|
||||
}
|
||||
|
||||
private void PlaybackService_PositionChanged(object? sender, PositionChangedEventArgs e)
|
||||
private void PlaybackService_TimelineChanged(object? sender, TimelineChangedEventArgs e)
|
||||
{
|
||||
if (Math.Abs(_totalTime.TotalMilliseconds - e.Position.TotalMilliseconds) >= _timelineSyncThreshold)
|
||||
if (Math.Abs(TotalTime.TotalMilliseconds - e.Position.TotalMilliseconds) >= _timelineSyncThreshold)
|
||||
{
|
||||
_totalTime = e.Position;
|
||||
TotalTime = e.Position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +361,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_lastSongArtist = _songArtist;
|
||||
_songArtist = SongInfo?.Artist;
|
||||
|
||||
_songDurationMs = (int)(SongInfo?.DurationMs ?? TimeSpan.FromMinutes(99).TotalMilliseconds);
|
||||
|
||||
_songInfoOpacityTransition.Reset(0f);
|
||||
_songInfoOpacityTransition.StartTransition(1f);
|
||||
|
||||
@@ -317,7 +371,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
_totalTime = TimeSpan.Zero;
|
||||
TotalTime = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +396,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private void UpdateTranslations()
|
||||
{
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
|
||||
_isLayoutChanged = true;
|
||||
|
||||
IsTranslating = true;
|
||||
if (_isTranslationEnabled)
|
||||
{
|
||||
@@ -354,7 +411,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
|
||||
_langIndex = 0;
|
||||
IsTranslating = false;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
@@ -364,7 +422,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_logger.LogInformation("Showing translation for lyrics...");
|
||||
string targetLangCode = LanguageHelper.GetUserTargetLanguageCode();
|
||||
string originalText = _lyricsDataArr[0].WrappedOriginalText;
|
||||
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
|
||||
if (originalText == null) return;
|
||||
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
|
||||
|
||||
if (originalLangCode == targetLangCode)
|
||||
@@ -378,14 +438,42 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr);
|
||||
if (found >= 0)
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found]);
|
||||
if (_showTranslationOnly)
|
||||
{
|
||||
_lyricsDataArr[found].SetDisplayedTextInOriginalText();
|
||||
_langIndex = found;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found], 50);
|
||||
_langIndex = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (_isLibreTranslateEnabled)
|
||||
{
|
||||
var translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
string translated = string.Empty;
|
||||
try
|
||||
{
|
||||
translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
|
||||
if (translated == string.Empty) return;
|
||||
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
|
||||
if (_showTranslationOnly)
|
||||
{
|
||||
_lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated);
|
||||
_lyricsDataArr[^1].SetDisplayedTextInOriginalText();
|
||||
_langIndex = _lyricsDataArr.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
|
||||
_langIndex = 0;
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("LibreTranslateFailed")!, Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,10 +486,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isLayoutChanged = true;
|
||||
|
||||
string? lyricsRaw = null;
|
||||
LyricsSearchProvider? provider = null;
|
||||
|
||||
if (SongInfo != null)
|
||||
{
|
||||
lyricsRaw = await _lyrcsSearchService.SearchAsync(
|
||||
(lyricsRaw, provider) = await _lyrcsSearchService.SearchAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album ?? "",
|
||||
@@ -410,13 +499,14 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
_logger.LogInformation("Lyrics search result: {LyricsRaw}", lyricsRaw ?? "null");
|
||||
token.ThrowIfCancellationRequested();
|
||||
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
|
||||
FillTranslationFromCache(provider);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
|
||||
}
|
||||
|
||||
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
|
||||
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
|
||||
|
||||
// This ensures that original lyrics are always shown while waiting for translations
|
||||
@@ -425,5 +515,54 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
UpdateTranslations();
|
||||
}
|
||||
|
||||
private void FillTranslationFromCache(LyricsSearchProvider? provider)
|
||||
{
|
||||
string? translationRaw = null;
|
||||
switch (provider)
|
||||
{
|
||||
case LyricsSearchProvider.QQ:
|
||||
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
|
||||
break;
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
break;
|
||||
case LyricsSearchProvider.LocalMusicFile:
|
||||
break;
|
||||
case LyricsSearchProvider.LocalLrcFile:
|
||||
break;
|
||||
case LyricsSearchProvider.LocalEslrcFile:
|
||||
break;
|
||||
case LyricsSearchProvider.LocalTtmlFile:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (translationRaw != null)
|
||||
{
|
||||
var translationData = new LyricsParser().Parse(translationRaw, (int?)SongInfo?.DurationMs);
|
||||
if (provider == LyricsSearchProvider.QQ)
|
||||
{
|
||||
foreach (var data in translationData)
|
||||
{
|
||||
foreach (var item in data.LyricsLines)
|
||||
{
|
||||
if (item.OriginalText == "//")
|
||||
{
|
||||
item.OriginalText = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lyricsDataArr = _lyricsDataArr.Concat(translationData).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,19 @@ using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
using Windows.UI;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
@@ -24,15 +29,33 @@ namespace BetterLyrics.WinUI3
|
||||
public partial class LyricsWindowViewModel
|
||||
: BaseWindowViewModel,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>
|
||||
IRecipient<PropertyChangedMessage<DockPlacement>>
|
||||
{
|
||||
private readonly IPlaybackService _playbackService = Ioc.Default.GetRequiredService<IPlaybackService>();
|
||||
private ForegroundWindowWatcher? _windowWatcher = null;
|
||||
private bool _ignoreFullscreenWindow = false;
|
||||
private bool _ignoreFullscreenWindow;
|
||||
private bool _hideWindowWhenNotPlaying;
|
||||
|
||||
private DockPlacement _dockPlacement;
|
||||
private int _dockWindowHeight;
|
||||
|
||||
public LyricsWindowViewModel(ISettingsService settingsService) : base(settingsService)
|
||||
{
|
||||
_ignoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
|
||||
_hideWindowWhenNotPlaying = _settingsService.HideWindowWhenNotPlaying;
|
||||
IsImmersiveMode = _settingsService.IsImmersiveMode;
|
||||
_dockPlacement = _settingsService.DockPlacement;
|
||||
_dockWindowHeight = _settingsService.DockWindowHeight;
|
||||
OnIsImmersiveModeChanged(_settingsService.IsImmersiveMode);
|
||||
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
}
|
||||
|
||||
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
|
||||
{
|
||||
AutoHideOrShowWindow();
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -51,6 +74,13 @@ namespace BetterLyrics.WinUI3
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsWindowLocked { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsImmersiveMode { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial float TopCommandGridOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ElementTheme ThemeType { get; set; } = ElementTheme.Default;
|
||||
|
||||
@@ -61,6 +91,60 @@ namespace BetterLyrics.WinUI3
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsMouseWithinWindow { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string LockHotKey { get; set; } = "";
|
||||
|
||||
private void AutoHideOrShowWindow()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
if (IsDockMode || IsDesktopMode)
|
||||
{
|
||||
if (_hideWindowWhenNotPlaying && !_playbackService.IsPlaying)
|
||||
{
|
||||
if (IsDockMode)
|
||||
{
|
||||
DockModeHelper.UpdateAppBarHeight(hwnd, 0, _dockPlacement);
|
||||
}
|
||||
window.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsDockMode)
|
||||
{
|
||||
DockModeHelper.UpdateAppBarHeight(hwnd, _dockWindowHeight, _dockPlacement);
|
||||
}
|
||||
window.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDockWindow()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
if (!_hideWindowWhenNotPlaying || _playbackService.SongInfo != null)
|
||||
{
|
||||
DockModeHelper.UpdateAppBarHeight(WindowNative.GetWindowHandle(window), _dockWindowHeight, _dockPlacement);
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnIsImmersiveModeChanged(bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
TopCommandGridOpacity = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
TopCommandGridOpacity = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is SystemTrayViewModel)
|
||||
@@ -79,6 +163,11 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
_ignoreFullscreenWindow = message.NewValue;
|
||||
}
|
||||
else if (message.PropertyName == nameof(SettingsPageViewModel.HideWindowWhenNotPlaying))
|
||||
{
|
||||
_hideWindowWhenNotPlaying = message.NewValue;
|
||||
AutoHideOrShowWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,20 +186,43 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontSize))
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.DockWindowHeight))
|
||||
{
|
||||
if (IsDockMode)
|
||||
_dockWindowHeight = message.NewValue;
|
||||
UpdateDockWindow();
|
||||
}
|
||||
else if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LockHotKeyIndex))
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
DockModeHelper.UpdateAppBarHeight(WindowNative.GetWindowHandle(window), message.NewValue * 4);
|
||||
UpdateLockHotKey(message.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartWatchWindowColorChange(WindowPixelSampleMode mode)
|
||||
private void UpdateLockHotKey(int hotKeyIndex)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
GlobalHotKeyHelper.UnregisterAllHotKeys(window);
|
||||
GlobalHotKeyHelper.RegisterHotKey(
|
||||
window,
|
||||
User32.HotKeyModifiers.MOD_CONTROL | User32.HotKeyModifiers.MOD_ALT,
|
||||
(uint)(hotKeyIndex + (int)VirtualKey.A),
|
||||
() =>
|
||||
{
|
||||
if (IsDesktopMode)
|
||||
{
|
||||
ToggleLockWindowCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
);
|
||||
LockHotKey = ((VirtualKey)(hotKeyIndex + (int)VirtualKey.A)).ToString();
|
||||
}
|
||||
|
||||
public void StartWatchWindowColorChange()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
@@ -120,15 +232,18 @@ namespace BetterLyrics.WinUI3
|
||||
hwnd,
|
||||
onWindowChanged =>
|
||||
{
|
||||
if (_ignoreFullscreenWindow && window.AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
_dispatcherQueueTimer.Debounce(() =>
|
||||
{
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
}
|
||||
UpdateAccentColor(hwnd, mode);
|
||||
if (_ignoreFullscreenWindow && window.AppWindow.Presenter is OverlappedPresenter presenter)
|
||||
{
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
}
|
||||
UpdateAccentColor(hwnd);
|
||||
}, TimeSpan.FromMilliseconds(300));
|
||||
}
|
||||
);
|
||||
_windowWatcher.Start();
|
||||
UpdateAccentColor(hwnd, mode);
|
||||
UpdateAccentColor(hwnd);
|
||||
}
|
||||
|
||||
private void StopWatchWindowColorChange()
|
||||
@@ -137,19 +252,37 @@ namespace BetterLyrics.WinUI3
|
||||
_windowWatcher = null;
|
||||
}
|
||||
|
||||
public void UpdateAccentColor(nint hwnd, WindowPixelSampleMode mode)
|
||||
public void UpdateAccentColor(nint hwnd)
|
||||
{
|
||||
WindowPixelSampleMode mode = IsDesktopMode ? WindowPixelSampleMode.WindowEdge : _dockPlacement.ToWindowPixelSampleMode();
|
||||
ActivatedWindowAccentColor = Helper.ColorHelper.GetAccentColor(hwnd, mode).ToColor();
|
||||
}
|
||||
|
||||
public void InitLockHotKey()
|
||||
{
|
||||
UpdateLockHotKey(_settingsService.LockHotKeyIndex);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void LockWindow()
|
||||
private void ToggleLockWindow()
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
DesktopModeHelper.SetClickThrough(window, true);
|
||||
IsLyricsWindowLocked = true;
|
||||
if (IsLyricsWindowLocked)
|
||||
{
|
||||
DesktopModeHelper.SetClickThrough(window, false);
|
||||
IsLyricsWindowLocked = false;
|
||||
IsImmersiveMode = _settingsService.IsImmersiveMode;
|
||||
}
|
||||
else
|
||||
{
|
||||
DesktopModeHelper.SetClickThrough(window, true);
|
||||
IsLyricsWindowLocked = true;
|
||||
IsImmersiveMode = true;
|
||||
}
|
||||
|
||||
AutoHideOrShowWindow();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -164,7 +297,7 @@ namespace BetterLyrics.WinUI3
|
||||
if (IsDesktopMode)
|
||||
{
|
||||
DesktopModeHelper.Enable(window);
|
||||
StartWatchWindowColorChange(WindowPixelSampleMode.WindowEdge);
|
||||
StartWatchWindowColorChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,13 +316,34 @@ namespace BetterLyrics.WinUI3
|
||||
IsDockMode = !IsDockMode;
|
||||
if (IsDockMode)
|
||||
{
|
||||
DockModeHelper.Enable(window, _settingsService.LyricsFontSize * 4);
|
||||
StartWatchWindowColorChange(WindowPixelSampleMode.BelowWindow);
|
||||
window.Restore();
|
||||
DockModeHelper.Enable(window, _dockWindowHeight, _dockPlacement);
|
||||
StartWatchWindowColorChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
DockModeHelper.Disable(window);
|
||||
}
|
||||
|
||||
AutoHideOrShowWindow();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OnImmersiveToggleButtonEnabledChanged()
|
||||
{
|
||||
_settingsService.IsImmersiveMode = IsImmersiveMode;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<DockPlacement> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.DockPlacement))
|
||||
{
|
||||
_dockPlacement = message.NewValue;
|
||||
UpdateDockWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Media;
|
||||
using Windows.Media.Core;
|
||||
using Windows.Media.Playback;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class MusicGalleryViewModel : BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LocalMediaFolder>>>
|
||||
{
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly MediaPlayer _mediaPlayer = new();
|
||||
private readonly MediaTimelineController _timelineController = new();
|
||||
private readonly SystemMediaTransportControls _smtc;
|
||||
// All songs
|
||||
private List<Track> _tracks = [];
|
||||
// Songs in current playlist
|
||||
private List<Track> _playlistTracks = [];
|
||||
// Filtered songs based on search query for current playlist
|
||||
private List<Track> _filteredTracks = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLocalMediaNotFound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Grouped tracks after filtering and sorting for current playlist
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<GroupInfoList> GroupedTracks { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial List<Track> SelectedTracks { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<PlayQueueItem> TrackPlayingQueue { get; set; } = [];
|
||||
|
||||
public PlayQueueItem? PlayingQueueItem => TrackPlayingQueue.ElementAtOrDefault(PlayingSongIndex);
|
||||
|
||||
[ObservableProperty]
|
||||
public partial PlaybackOrder PlaybackOrder { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial CommonSongProperty SongOrderType { get; set; } = CommonSongProperty.Title;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<SongsTabInfo> SongsTabInfoList { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SelectedSongsTabInfoIndex { get; set; } = 0;
|
||||
|
||||
public SongsTabInfo? SelectedSongsTabInfo => SongsTabInfoList.ElementAtOrDefault(SelectedSongsTabInfoIndex);
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsDataLoading { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Track TrackRightTapped { get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int PlayingSongIndex { get; set; } = -1;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int DisplayedPlayingSongIndex { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string SongSearchQuery { get; set; } = string.Empty;
|
||||
|
||||
public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService) : base(settingsService)
|
||||
{
|
||||
SongsTabInfoList.Add(new SongsTabInfo(App.ResourceLoader!.GetString("MusicGalleryPageAllSongs"), "\uE8A9", false, CommonSongProperty.Title, string.Empty));
|
||||
|
||||
RefreshSongs();
|
||||
|
||||
PlaybackOrder = _settingsService.PlaybackOrder;
|
||||
|
||||
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
|
||||
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
|
||||
_timelineController = _mediaPlayer.TimelineController = new();
|
||||
_timelineController.PositionChanged += TimelineController_PositionChanged;
|
||||
_smtc = _mediaPlayer.SystemMediaTransportControls;
|
||||
_mediaPlayer.CommandManager.IsEnabled = false;
|
||||
_smtc.IsPlayEnabled = true;
|
||||
_smtc.IsPauseEnabled = true;
|
||||
_smtc.IsNextEnabled = true;
|
||||
_smtc.IsPreviousEnabled = true;
|
||||
_smtc.ButtonPressed += Smtc_ButtonPressed;
|
||||
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
|
||||
|
||||
_libWatcherService = libWatcherService;
|
||||
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
|
||||
}
|
||||
|
||||
private void MediaPlayer_MediaEnded(MediaPlayer sender, object args)
|
||||
{
|
||||
PlayNextTrack();
|
||||
}
|
||||
|
||||
public void PlayNextTrack()
|
||||
{
|
||||
switch (PlaybackOrder)
|
||||
{
|
||||
case PlaybackOrder.RepeatAll:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (PlayingSongIndex < TrackPlayingQueue.Count - 1)
|
||||
{
|
||||
PlayingSongIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayingSongIndex = 0;
|
||||
}
|
||||
PlayTrack(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
case PlaybackOrder.RepeatOne:
|
||||
_timelineController.Position = TimeSpan.Zero;
|
||||
break;
|
||||
case PlaybackOrder.Shuffle:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (TrackPlayingQueue.Count > 0)
|
||||
{
|
||||
PlayingSongIndex = new Random().Next(0, TrackPlayingQueue.Count);
|
||||
}
|
||||
PlayTrack(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayPreviousTrack()
|
||||
{
|
||||
switch (PlaybackOrder)
|
||||
{
|
||||
case PlaybackOrder.RepeatAll:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (PlayingSongIndex > 0)
|
||||
{
|
||||
PlayingSongIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayingSongIndex = TrackPlayingQueue.Count - 1;
|
||||
}
|
||||
PlayTrack(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
case PlaybackOrder.RepeatOne:
|
||||
_timelineController.Position = TimeSpan.Zero;
|
||||
break;
|
||||
case PlaybackOrder.Shuffle:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (TrackPlayingQueue.Count > 0)
|
||||
{
|
||||
PlayingSongIndex = new Random().Next(0, TrackPlayingQueue.Count);
|
||||
}
|
||||
PlayTrack(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Smtc_PlaybackPositionChangeRequested(SystemMediaTransportControls sender, PlaybackPositionChangeRequestedEventArgs args)
|
||||
{
|
||||
_timelineController.Position = args.RequestedPlaybackPosition;
|
||||
}
|
||||
|
||||
private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
|
||||
{
|
||||
_timelineController.Start();
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
|
||||
}
|
||||
|
||||
private void TimelineController_PositionChanged(MediaTimelineController sender, object args)
|
||||
{
|
||||
_smtc.UpdateTimelineProperties(new SystemMediaTransportControlsTimelineProperties()
|
||||
{
|
||||
Position = sender.Position,
|
||||
EndTime = _mediaPlayer.PlaybackSession.NaturalDuration
|
||||
});
|
||||
}
|
||||
|
||||
private void Smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
|
||||
{
|
||||
switch (args.Button)
|
||||
{
|
||||
case SystemMediaTransportControlsButton.Play:
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
|
||||
_timelineController.Resume();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Pause:
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Paused;
|
||||
_timelineController.Pause();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Next:
|
||||
PlayNextTrack();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Previous:
|
||||
PlayPreviousTrack();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, Events.LibChangedEventArgs e)
|
||||
{
|
||||
RefreshSongs();
|
||||
}
|
||||
|
||||
public void RefreshSongs()
|
||||
{
|
||||
_dispatcherQueueTimer.Debounce(() =>
|
||||
{
|
||||
IsDataLoading = true;
|
||||
_tracks.Clear();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
foreach (var folder in _settingsService.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(folder.Path, $"*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
Track track = new(file);
|
||||
if (track.Duration <= 0) continue;
|
||||
_tracks.Add(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
ApplyPlaylist();
|
||||
ApplySongSearchQuery();
|
||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||
ApplySongOrderType();
|
||||
IsDataLoading = false;
|
||||
});
|
||||
});
|
||||
}, TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
|
||||
public void ApplyPlaylist()
|
||||
{
|
||||
if (SelectedSongsTabInfo?.FilterValue == string.Empty)
|
||||
{
|
||||
_playlistTracks = _tracks;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (SelectedSongsTabInfo?.FilterProperty)
|
||||
{
|
||||
case CommonSongProperty.Title:
|
||||
_playlistTracks = _tracks.Where(t => t.Title.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
break;
|
||||
case CommonSongProperty.Album:
|
||||
_playlistTracks = _tracks.Where(t => t.Album.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
break;
|
||||
case CommonSongProperty.Artist:
|
||||
_playlistTracks = _tracks.Where(t => t.Artist.Equals(SelectedSongsTabInfo.FilterValue, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ApplySongSearchQuery();
|
||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||
ApplySongOrderType();
|
||||
}
|
||||
|
||||
public void ApplySongSearchQuery()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SongSearchQuery))
|
||||
{
|
||||
_filteredTracks = _playlistTracks;
|
||||
return;
|
||||
}
|
||||
_filteredTracks = _playlistTracks.Where(t =>
|
||||
t.Title.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
t.Artist.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase) ||
|
||||
t.Album.Contains(SongSearchQuery, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
|
||||
private void ApplySongOrderType()
|
||||
{
|
||||
switch (SongOrderType)
|
||||
{
|
||||
case CommonSongProperty.Title:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Title),
|
||||
o => ((Track)o).Title
|
||||
);
|
||||
break;
|
||||
case CommonSongProperty.Artist:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Artist),
|
||||
o => ((Track)o).Artist
|
||||
);
|
||||
break;
|
||||
case CommonSongProperty.Album:
|
||||
GroupedTracks = _filteredTracks.GetGroupedBy(
|
||||
t => LanguageHelper.GetOrderChar(t.Album),
|
||||
o => ((Track)o).Album
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSelectedPlaylist(SongsTabInfo playlist)
|
||||
{
|
||||
var found = SongsTabInfoList.Where(x => x.FilterProperty == playlist.FilterProperty && x.FilterValue == playlist.FilterValue)
|
||||
.ToList().FirstOrDefault();
|
||||
if (found == null)
|
||||
{
|
||||
SongsTabInfoList.Add(playlist);
|
||||
SelectedSongsTabInfoIndex = SongsTabInfoList.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedSongsTabInfoIndex = SongsTabInfoList.IndexOf(found);
|
||||
}
|
||||
ApplyPlaylist();
|
||||
}
|
||||
|
||||
public void PlayTrackAt(int index)
|
||||
{
|
||||
PlayTrack(TrackPlayingQueue.ElementAtOrDefault(index));
|
||||
}
|
||||
|
||||
public void PlayTrack(PlayQueueItem? playQueueItem)
|
||||
{
|
||||
_timelineController.Pause();
|
||||
_mediaPlayer.Source = null;
|
||||
if (playQueueItem == null)
|
||||
{
|
||||
_smtc.IsEnabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var track = playQueueItem.Track;
|
||||
var updater = _smtc.DisplayUpdater;
|
||||
_smtc.IsEnabled = true;
|
||||
_mediaPlayer.Source = MediaSource.CreateFromUri(new Uri(track.Path));
|
||||
updater.AppMediaId = Package.Current.Id.FullName;
|
||||
updater.Type = MediaPlaybackType.Music;
|
||||
updater.MusicProperties.Title = track.Title;
|
||||
updater.MusicProperties.Artist = track.Artist;
|
||||
updater.MusicProperties.AlbumTitle = track.Album;
|
||||
if (track.EmbeddedPictures.FirstOrDefault()?.PictureData is byte[] pictureData)
|
||||
{
|
||||
updater.Thumbnail = ImageHelper.ByteArrayToRandomAccessStreamReference(pictureData);
|
||||
}
|
||||
else
|
||||
{
|
||||
updater.Thumbnail = null;
|
||||
}
|
||||
updater.Update();
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSongOrderTypeChanged(CommonSongProperty value)
|
||||
{
|
||||
ApplySongOrderType();
|
||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||
}
|
||||
|
||||
partial void OnSongSearchQueryChanged(string value)
|
||||
{
|
||||
ApplySongSearchQuery();
|
||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||
ApplySongOrderType();
|
||||
}
|
||||
|
||||
partial void OnPlayingSongIndexChanged(int value)
|
||||
{
|
||||
DisplayedPlayingSongIndex = value + 1;
|
||||
}
|
||||
|
||||
partial void OnPlaybackOrderChanged(PlaybackOrder value)
|
||||
{
|
||||
_settingsService.PlaybackOrder = value;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)
|
||||
{
|
||||
if (message.Sender is SettingsPageViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsPageViewModel.LocalMediaFolders))
|
||||
{
|
||||
RefreshSongs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,23 +7,17 @@ using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using ShadowViewer.Controls;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Globalization;
|
||||
using Windows.Media.Playback;
|
||||
using Windows.System;
|
||||
using Windows.UI;
|
||||
using Windows.UI.Popups;
|
||||
using WinRT.Interop;
|
||||
using MetadataHelper = BetterLyrics.WinUI3.Helper.MetadataHelper;
|
||||
|
||||
@@ -43,10 +37,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_playbackService = playbackService;
|
||||
_libreTranslateService = libreTranslateService;
|
||||
|
||||
IsLibreTranslateEnabled = _settingsService.IsLibreTranslateEnabled;
|
||||
LibreTranslateServer = _settingsService.LibreTranslateServer;
|
||||
SelectedTargetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
|
||||
|
||||
LocalLyricsFolders = [.. _settingsService.LocalLyricsFolders];
|
||||
LocalMediaFolders = [.. _settingsService.LocalMediaFolders];
|
||||
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
|
||||
AlbumArtSearchProvidersInfo = [.. _settingsService.AlbumArtSearchProvidersInfo];
|
||||
|
||||
@@ -60,6 +55,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
||||
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
|
||||
CoverAcrylicEffectAmount = _settingsService.CoverAcrylicEffectAmount;
|
||||
|
||||
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
|
||||
SongInfoAlignmentType = _settingsService.SongInfoAlignmentType;
|
||||
LyricsFontWeight = _settingsService.LyricsFontWeight;
|
||||
@@ -89,12 +86,22 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
LyricsScrollDuration = _settingsService.LyricsScrollDuration;
|
||||
TimelineSyncThreshold = _settingsService.TimelineSyncThreshold;
|
||||
|
||||
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
|
||||
IsLyricsFloatAnimationEnabled = _settingsService.IsLyricsFloatAnimationEnabled;
|
||||
ResetPositionOffsetOnSongChanged = _settingsService.ResetPositionOffsetOnSongChanged;
|
||||
LockHotKeyIndex = _settingsService.LockHotKeyIndex;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
BuildDate = (await Helper.MetadataHelper.GetBuildDate()).ToString("(yyyy/MM/dd HH:mm:ss)");
|
||||
});
|
||||
LXMusicServer = _settingsService.LXMusicServer;
|
||||
DockPlacement = _settingsService.DockPlacement;
|
||||
LyricsBgFontOpacity = _settingsService.LyricsBgFontOpacity;
|
||||
HideWindowWhenNotPlaying = _settingsService.HideWindowWhenNotPlaying;
|
||||
DockWindowHeight = _settingsService.DockWindowHeight;
|
||||
|
||||
SystemFontNames = [.. FontHelper.SystemFontFamilies];
|
||||
SelectedFontFamilyIndex = _settingsService.SelectedFontFamilyIndex;
|
||||
LyricsFontFamily = _settingsService.LyricsFontFamily;
|
||||
IsDragEverywhereEnabled = _settingsService.IsDragEverywhereEnabled;
|
||||
|
||||
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
|
||||
}
|
||||
|
||||
private void PlaybackService_SessionIdsChanged(object? sender, Events.MediaSourceProvidersInfoEventArgs e)
|
||||
@@ -102,6 +109,33 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
MediaSourceProvidersInfo = [.. e.MediaSourceProviersInfo];
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLibreTranslateEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsDragEverywhereEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial string LyricsFontFamily { get; set; }
|
||||
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<string> SystemFontNames { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int SelectedFontFamilyIndex { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial DockPlacement DockPlacement { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LockHotKeyIndex { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ElementTheme LyricsBackgroundTheme { get; set; }
|
||||
@@ -136,11 +170,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsDynamicCoverOverlayEnabled { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverAcrylicEffectAmount { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Enums.Language Language { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<LocalLyricsFolder> LocalLyricsFolders { get; set; }
|
||||
public partial ObservableCollection<LocalMediaFolder> LocalMediaFolders { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
@@ -185,6 +223,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial Color LyricsCustomStrokeFontColor { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int LyricsBgFontOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsFontColorType LyricsBgFontColorType { get; set; }
|
||||
@@ -222,12 +264,18 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial int LyricsVerticalEdgeOpacity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial object NavViewSelectedItemTag { get; set; }
|
||||
public partial object NavViewSelectedItemTag { get; set; } = "App";
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsLyricsFloatAnimationEnabled { get; set; }
|
||||
|
||||
public string Version { get; set; } = MetadataHelper.AppVersion;
|
||||
|
||||
public string BuildDate { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string LibreTranslateServer { get; set; }
|
||||
|
||||
@@ -258,6 +306,21 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int TimelineSyncThreshold { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLXMusicServerTesting { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial string LXMusicServer { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool HideWindowWhenNotPlaying { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int DockWindowHeight { get; set; }
|
||||
|
||||
public void OnLyricsSearchProvidersReordered()
|
||||
{
|
||||
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
|
||||
@@ -278,21 +341,21 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
}
|
||||
|
||||
public void RemoveFolderAsync(LocalLyricsFolder folder)
|
||||
public void RemoveFolderAsync(LocalMediaFolder folder)
|
||||
{
|
||||
LocalLyricsFolders.Remove(folder);
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
LocalMediaFolders.Remove(folder);
|
||||
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalMediaFolders]);
|
||||
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
|
||||
}
|
||||
|
||||
public void ToggleLocalLyricsFolder(LocalLyricsFolder folder)
|
||||
public void ToggleLocalLyricsFolder()
|
||||
{
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
|
||||
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
|
||||
}
|
||||
|
||||
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
|
||||
public void ToggleLyricsSearchProvider()
|
||||
{
|
||||
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
|
||||
Broadcast(
|
||||
@@ -325,16 +388,16 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
var normalizedPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
|
||||
if (LocalLyricsFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
|
||||
if (LocalMediaFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"));
|
||||
}
|
||||
else if (LocalLyricsFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
|
||||
else if (LocalMediaFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
// 添加的文件夹是现有文件夹的子文件夹
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"));
|
||||
}
|
||||
else if (LocalLyricsFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
|
||||
else if (LocalMediaFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
|
||||
)
|
||||
{
|
||||
// 添加的文件夹是现有文件夹的父文件夹
|
||||
@@ -342,23 +405,23 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
else
|
||||
{
|
||||
LocalLyricsFolders.Add(new LocalLyricsFolder(path, true));
|
||||
_settingsService.LocalLyricsFolders = [.. LocalLyricsFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalLyricsFolders]);
|
||||
Broadcast(LocalLyricsFolders, LocalLyricsFolders, nameof(LocalLyricsFolders));
|
||||
LocalMediaFolders.Add(new LocalMediaFolder(path, true));
|
||||
_settingsService.LocalMediaFolders = [.. LocalMediaFolders];
|
||||
_libWatcherService.UpdateWatchers([.. LocalMediaFolders]);
|
||||
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task LaunchProjectGitHubPageAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri(MetadataHelper.GithubUrl));
|
||||
await Windows.System.Launcher.LaunchUriAsync(new Uri(MetadataHelper.GithubUrl));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static async Task OpenCacheFolderAsync()
|
||||
{
|
||||
await Launcher.LaunchFolderPathAsync(PathHelper.CacheFolder);
|
||||
await Windows.System.Launcher.LaunchFolderPathAsync(PathHelper.CacheFolder);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -397,23 +460,40 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
string targetLangCode = LanguageHelper.SupportedTargetLanguages[SelectedTargetLanguageIndex].Code;
|
||||
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, null);
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
|
||||
IsLibreTranslateServerTesting = false;
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageLibreTranslateTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
IsLibreTranslateServerTesting = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void LXMusicServerTest()
|
||||
{
|
||||
IsLXMusicServerTesting = true;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
bool testResult = await NetHelper.CheckConnectivity($"{LXMusicServer}/status");
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString($"SettingsPageServerTest{(testResult ? "Success" : "Failed")}Info"),
|
||||
testResult ? InfoBarSeverity.Success : InfoBarSeverity.Error);
|
||||
IsLXMusicServerTesting = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<bool> ToggleAutoStartupAsync(bool target)
|
||||
{
|
||||
StartupTask startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
|
||||
@@ -446,6 +526,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
return result;
|
||||
}
|
||||
|
||||
partial void OnDockPlacementChanged(DockPlacement value)
|
||||
{
|
||||
_settingsService.DockPlacement = value;
|
||||
}
|
||||
partial void OnLyricsScrollEasingTypeChanged(EasingType value)
|
||||
{
|
||||
_settingsService.LyricsScrollEasingType = value;
|
||||
@@ -474,6 +558,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_settingsService.LibreTranslateServer = value;
|
||||
}
|
||||
partial void OnLXMusicServerChanged(string value)
|
||||
{
|
||||
_settingsService.LXMusicServer = value;
|
||||
}
|
||||
partial void OnAutoStartWindowTypeChanged(AutoStartWindowType value)
|
||||
{
|
||||
_settingsService.AutoStartWindowType = value;
|
||||
@@ -490,6 +578,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_settingsService.CoverOverlayBlurAmount = value;
|
||||
}
|
||||
partial void OnCoverAcrylicEffectAmountChanged(int value)
|
||||
{
|
||||
_settingsService.CoverAcrylicEffectAmount = value;
|
||||
}
|
||||
partial void OnCoverOverlayOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.CoverOverlayOpacity = value;
|
||||
@@ -597,6 +689,48 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
_settingsService.TimelineSyncThreshold = value;
|
||||
}
|
||||
partial void OnIsLyricsFloatAnimationEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsLyricsFloatAnimationEnabled = value;
|
||||
}
|
||||
partial void OnResetPositionOffsetOnSongChangedChanged(bool value)
|
||||
{
|
||||
_settingsService.ResetPositionOffsetOnSongChanged = value;
|
||||
}
|
||||
partial void OnLyricsBgFontOpacityChanged(int value)
|
||||
{
|
||||
_settingsService.LyricsBgFontOpacity = value;
|
||||
}
|
||||
partial void OnHideWindowWhenNotPlayingChanged(bool value)
|
||||
{
|
||||
_settingsService.HideWindowWhenNotPlaying = value;
|
||||
}
|
||||
partial void OnDockWindowHeightChanged(int value)
|
||||
{
|
||||
_settingsService.DockWindowHeight = value;
|
||||
}
|
||||
partial void OnSelectedFontFamilyIndexChanged(int value)
|
||||
{
|
||||
_settingsService.SelectedFontFamilyIndex = value;
|
||||
LyricsFontFamily = SystemFontNames[value];
|
||||
}
|
||||
partial void OnLyricsFontFamilyChanged(string value)
|
||||
{
|
||||
_settingsService.LyricsFontFamily = value;
|
||||
}
|
||||
partial void OnIsDragEverywhereEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsDragEverywhereEnabled = value;
|
||||
|
||||
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow != null)
|
||||
{
|
||||
lyricsWindow.UpdateTitleBarArea();
|
||||
}
|
||||
}
|
||||
partial void OnIsLibreTranslateEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsLibreTranslateEnabled = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
@@ -34,13 +36,18 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[RelayCommand]
|
||||
private static void ExitApp()
|
||||
{
|
||||
WindowHelper.ExitAllWindows();
|
||||
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (lyricsWindow != null)
|
||||
{
|
||||
DockModeHelper.Disable(lyricsWindow);
|
||||
}
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void OpenSettings()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
WindowHelper.OpenWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -18,160 +18,379 @@
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid">
|
||||
<Grid x:Name="RootGrid" SizeChanged="RootGrid_SizeChanged">
|
||||
<!-- Lyrics area -->
|
||||
<renderer:LyricsRenderer />
|
||||
|
||||
<!-- No music playing placeholder -->
|
||||
<Grid x:Name="NoMusicPlayingGrid" Background="{ThemeResource SolidBackgroundFillColorBaseBrush}">
|
||||
<Grid x:Name="NoMusicPlayingGrid" Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}">
|
||||
<TextBlock
|
||||
x:Uid="MainPageNoMusicPlaying"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource TitleTextBlockStyle}" />
|
||||
FontFamily="{x:Bind ViewModel.LyricsFontFamily, Mode=OneWay}"
|
||||
FontSize="{x:Bind ViewModel.LyricsFontSize, Mode=OneWay}" />
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="{x:Null}">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="{x:Null}">
|
||||
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Grid>
|
||||
|
||||
<!-- Bottom command area -->
|
||||
<Grid
|
||||
x:Name="BottomCommandGrid"
|
||||
Padding="12"
|
||||
Margin="12"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"
|
||||
Opacity="0"
|
||||
Opacity="{x:Bind ViewModel.BottomCommandGridOpacity, Mode=OneWay}"
|
||||
PointerEntered="BottomCommandGrid_PointerEntered"
|
||||
PointerExited="BottomCommandGrid_PointerExited">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
<StackPanel
|
||||
x:Name="BottomLeftCommandStackPanel"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
</StackPanel>
|
||||
<Grid x:Name="BottomCommandContent">
|
||||
<Grid Padding="3" HorizontalAlignment="Left">
|
||||
<StackPanel
|
||||
x:Name="BottomLeftCommandStackPanel"
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
|
||||
<StackPanel
|
||||
x:Name="BottomCenterCommandStackPanel"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Margin="0,0,0,2"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="2">
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{Binding ElementName=TimelineSlider, Path=Value, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
||||
<TextBlock Text="{Binding ElementName=TimelineSlider, Path=Maximum, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
x:Name="BottomRightCommandStackPanel"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
<!-- Position offset -->
|
||||
<Button Click="TimelineOffsetButton_Click" Style="{StaticResource GhostButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph=""
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5" />
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout x:Name="TimelineOffsetFlyout" ShouldConstrainToRootBounds="False">
|
||||
<StackPanel>
|
||||
<Slider
|
||||
x:Uid="MainPagePositionOffsetSlider"
|
||||
Maximum="5000"
|
||||
Minimum="-5000"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="100"
|
||||
TickFrequency="100"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.PositionOffset, Mode=TwoWay}" />
|
||||
<RelativePanel>
|
||||
<TextBlock
|
||||
RelativePanel.AlignLeftWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Text="{x:Bind ViewModel.PositionOffset, Mode=OneWay}" />
|
||||
<Button
|
||||
Click="PositionOffsetResetButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
RelativePanel.AlignRightWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</RelativePanel>
|
||||
<CheckBox IsChecked="{x:Bind ViewModel.ResetPositionOffsetOnSongChanged, Mode=TwoWay}">
|
||||
<TextBlock x:Uid="LyricsPagePositionOffsetHint" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.ContextFlyout>
|
||||
</Button>
|
||||
|
||||
<!-- Position offset -->
|
||||
<Button Style="{StaticResource GhostButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph=""
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5" />
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<StackPanel>
|
||||
<Slider
|
||||
x:Uid="MainPagePositionOffsetSlider"
|
||||
Maximum="5000"
|
||||
Minimum="-5000"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="100"
|
||||
TickFrequency="100"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.PositionOffset, Mode=TwoWay}" />
|
||||
<RelativePanel>
|
||||
<TextBlock
|
||||
RelativePanel.AlignLeftWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Text="{x:Bind ViewModel.PositionOffset, Mode=OneWay}" />
|
||||
<Button
|
||||
Click="PositionOffsetResetButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
RelativePanel.AlignRightWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</RelativePanel>
|
||||
<TextBlock x:Uid="LyricsPagePositionOffsetHint" Opacity="0.5" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Translation -->
|
||||
<ToggleButton
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsChecked="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}"
|
||||
Style="{StaticResource GhostToggleButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</ToggleButton>
|
||||
<Grid Padding="3" HorizontalAlignment="Center">
|
||||
<StackPanel
|
||||
x:Name="BottomCenterCommandStackPanel"
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.PreviousSongCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.PauseSongCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.PlaySongCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.IsSongPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.NextSongCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Display type -->
|
||||
<Button
|
||||
x:Name="DisplayTypeSwitchButton"
|
||||
x:Uid="MainPageDisplayTypeSwitcher"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Flyout>
|
||||
<Flyout>
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style TargetType="FlyoutPresenter">
|
||||
<Setter Property="Padding" Value="12,2,12,8" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<RadioButtons MaxColumns="1" SelectedIndex="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<RadioButton x:Uid="MainPageAlbumArtOnly" Click="AlbumArtOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageLyriscOnly" Click="LyricsOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
|
||||
</RadioButtons>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Grid Padding="3" HorizontalAlignment="Right">
|
||||
<StackPanel
|
||||
x:Name="BottomRightCommandStackPanel"
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
|
||||
<!-- Settings -->
|
||||
<Button
|
||||
x:Name="SettingsButton"
|
||||
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- Volume -->
|
||||
<!--<Button Click="VolumeButton_Click" Style="{StaticResource GhostButtonStyle}">
|
||||
<Grid>
|
||||
|
||||
</StackPanel>
|
||||
-->
|
||||
<!-- Volumn: 0 -->
|
||||
<!--
|
||||
<FontIcon
|
||||
x:Name="VolumeLevel0"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="">
|
||||
<FontIcon.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</FontIcon.OpacityTransition>
|
||||
</FontIcon>
|
||||
|
||||
-->
|
||||
<!-- Volumn: 1-32 -->
|
||||
<!--
|
||||
<FontIcon
|
||||
x:Name="VolumeLevel1"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="">
|
||||
<FontIcon.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</FontIcon.OpacityTransition>
|
||||
</FontIcon>
|
||||
|
||||
-->
|
||||
<!-- Volumn: 33-65 -->
|
||||
<!--
|
||||
<FontIcon
|
||||
x:Name="VolumeLevel2"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="">
|
||||
<FontIcon.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</FontIcon.OpacityTransition>
|
||||
</FontIcon>
|
||||
|
||||
-->
|
||||
<!-- Volumn: 66-100 -->
|
||||
<!--
|
||||
<FontIcon
|
||||
x:Name="VolumeLevel3"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="">
|
||||
<FontIcon.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</FontIcon.OpacityTransition>
|
||||
</FontIcon>
|
||||
</Grid>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout x:Name="VolumeFlyout" ShouldConstrainToRootBounds="False">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.Volume, Mode=OneWay}" />
|
||||
<TextBlock Margin="0,0,14,0" VerticalAlignment="Center" />
|
||||
<Slider
|
||||
Width="150"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="1"
|
||||
TickFrequency="1"
|
||||
TickPlacement="None"
|
||||
Value="{x:Bind ViewModel.Volume, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.DataContext>
|
||||
</Button>-->
|
||||
|
||||
<!-- Translation -->
|
||||
<Button
|
||||
Click="TranslationButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="TranslationToolTip" x:Uid="LyricsPageTranslationButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout x:Name="TranslationFlyout" ShouldConstrainToRootBounds="False">
|
||||
<StackPanel>
|
||||
<ToggleSwitch x:Uid="LyricsPageTranslationEnabled" IsOn="{x:Bind ViewModel.IsTranslationEnabled, Mode=TwoWay}" />
|
||||
<ToggleSwitch
|
||||
x:Uid="LyricsPageTranslationOnly"
|
||||
IsEnabled="{x:Bind ViewModel.IsTranslationEnabled, Mode=OneWay}"
|
||||
IsOn="{x:Bind ViewModel.ShowTranslationOnly, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.ContextFlyout>
|
||||
</Button>
|
||||
|
||||
<!-- Display type -->
|
||||
<Button
|
||||
x:Name="DisplayTypeSwitchButton"
|
||||
x:Uid="MainPageDisplayTypeSwitcher"
|
||||
Click="DisplayTypeSwitchButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="PresentationTypeToolTip" x:Uid="LyricsPageDisplayTypeButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout x:Name="DisplayTypeSwitchFlyout" ShouldConstrainToRootBounds="false">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style TargetType="FlyoutPresenter">
|
||||
<Setter Property="Padding" Value="12,2,12,8" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<RadioButtons MaxColumns="1" SelectedIndex="{x:Bind ViewModel.DisplayType, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<RadioButton x:Uid="MainPageAlbumArtOnly" Click="AlbumArtOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageLyriscOnly" Click="LyricsOnlyRadioButton_Click" />
|
||||
<RadioButton x:Uid="MainPageSplitView" Click="SplitViewRadioButton_Click" />
|
||||
</RadioButtons>
|
||||
</Flyout>
|
||||
</Button.ContextFlyout>
|
||||
</Button>
|
||||
|
||||
<!-- Settings -->
|
||||
<Button
|
||||
x:Name="SettingsButton"
|
||||
Command="{x:Bind ViewModel.OpenSettingsWindowCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="SettingsToolTip" x:Uid="LyricsPageSettingsButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Slider
|
||||
x:Name="TimelineSlider"
|
||||
Margin="0,-32,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Maximum="{x:Bind ViewModel.SongDurationSeconds, Mode=OneWay}"
|
||||
Minimum="0"
|
||||
Style="{StaticResource GhostSliderStyle}"
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
|
||||
Value="{x:Bind ViewModel.TimelinePositionSeconds, Mode=OneWay}" />
|
||||
|
||||
<Slider
|
||||
x:Name="TimelineSliderOverlay"
|
||||
Margin="0,-32,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Maximum="{Binding ElementName=TimelineSlider, Path=Maximum}"
|
||||
Minimum="0"
|
||||
Style="{StaticResource TransparentSliderStyle}"
|
||||
Tapped="TimelineSliderOverlay_Tapped"
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Bottom command flyout trigger -->
|
||||
<Grid
|
||||
x:Name="BottomCommandFlyoutTrigger"
|
||||
Height="12"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="Transparent"
|
||||
CornerRadius="3,3,0,0"
|
||||
Opacity="{x:Bind ViewModel.BottomCommandFlyoutTriggerOpacity, Mode=OneWay}"
|
||||
PointerEntered="BottomCommandFlyoutTrigger_PointerEntered"
|
||||
PointerExited="BottomCommandFlyoutTrigger_PointerExited"
|
||||
Tapped="BottomCommandFlyoutTrigger_Tapped">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<Grid
|
||||
x:Name="BottomCommandFlyoutTriggerHint"
|
||||
Width="150"
|
||||
Margin="4"
|
||||
Background="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
CornerRadius="2"
|
||||
Translation="0,0,0">
|
||||
<Grid.TranslationTransition>
|
||||
<Vector3Transition />
|
||||
</Grid.TranslationTransition>
|
||||
</Grid>
|
||||
<Grid.ContextFlyout>
|
||||
<Flyout x:Name="BottomCommandFlyout" ShouldConstrainToRootBounds="False">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style TargetType="FlyoutPresenter">
|
||||
<Setter Property="MinWidth" Value="600" />
|
||||
<Setter Property="MinHeight" Value="100" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<Grid x:Name="BottomCommandFlyoutContainer" VerticalAlignment="Bottom" />
|
||||
</Flyout>
|
||||
</Grid.ContextFlyout>
|
||||
</Grid>
|
||||
|
||||
<TeachingTip
|
||||
@@ -184,29 +403,64 @@
|
||||
<uc:SystemTray />
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="MusicPlayingStates">
|
||||
<VisualState x:Name="MusicPlaying">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsNotEqualStateTrigger Value="{x:Bind ViewModel.DisplayType, Converter={StaticResource EnumToIntConverter}, Mode=OneWay}" To="3" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="DisplayTypeSwitchButton.Visibility" Value="Visible" />
|
||||
<Setter Target="NoMusicPlayingGrid.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NoMusicPlaying">
|
||||
<!--<VisualStateGroup x:Name="VolumeState">
|
||||
<VisualState x:Name="Volume0">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="Equal"
|
||||
Value="{x:Bind ViewModel.DisplayType, Converter={StaticResource EnumToIntConverter}, Mode=OneWay}"
|
||||
To="3" />
|
||||
Value="{x:Bind ViewModel.Volume, Mode=OneWay}"
|
||||
To="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="DisplayTypeSwitchButton.Visibility" Value="Collapsed" />
|
||||
<Setter Target="NoMusicPlayingGrid.Opacity" Value="1" />
|
||||
<Setter Target="VolumeLevel0.Opacity" Value="1" />
|
||||
<Setter Target="VolumeLevel1.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel2.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel3.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualState x:Name="Volume1">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="LessThanOrEqual"
|
||||
Value="{x:Bind ViewModel.Volume, Mode=OneWay}"
|
||||
To="32" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="VolumeLevel0.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel1.Opacity" Value="1" />
|
||||
<Setter Target="VolumeLevel2.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel3.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Volume2">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="LessThanOrEqual"
|
||||
Value="{x:Bind ViewModel.Volume, Mode=OneWay}"
|
||||
To="65" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="VolumeLevel0.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel1.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel2.Opacity" Value="1" />
|
||||
<Setter Target="VolumeLevel3.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Volume3">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:CompareStateTrigger
|
||||
Comparison="LessThanOrEqual"
|
||||
Value="{x:Bind ViewModel.Volume, Mode=OneWay}"
|
||||
To="100" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="VolumeLevel0.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel1.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel2.Opacity" Value="0" />
|
||||
<Setter Target="VolumeLevel3.Opacity" Value="1" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>-->
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -12,15 +15,18 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
public sealed partial class LyricsPage : Page
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly IPlaybackService _playbackService = Ioc.Default.GetRequiredService<IPlaybackService>();
|
||||
|
||||
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
|
||||
|
||||
public LyricsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
DataContext = Ioc.Default.GetService<LyricsPageViewModel>();
|
||||
DataContext = Ioc.Default.GetRequiredService<LyricsPageViewModel>();
|
||||
}
|
||||
|
||||
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
|
||||
|
||||
private void WelcomeTeachingTip_Closed(TeachingTip sender, TeachingTipClosedEventArgs args)
|
||||
{
|
||||
ViewModel.IsFirstRun = false;
|
||||
@@ -28,17 +34,20 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void LyricsOnlyRadioButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.LyricsOnly;
|
||||
ViewModel.DisplayType = LyricsDisplayType.LyricsOnly;
|
||||
_settingsService.DisplayType = ViewModel.DisplayType;
|
||||
}
|
||||
|
||||
private void AlbumArtOnlyRadioButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.AlbumArtOnly;
|
||||
ViewModel.DisplayType = LyricsDisplayType.AlbumArtOnly;
|
||||
_settingsService.DisplayType = ViewModel.DisplayType;
|
||||
}
|
||||
|
||||
private void SplitViewRadioButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.PreferredDisplayType = ViewModel.DisplayType = LyricsDisplayType.SplitView;
|
||||
ViewModel.DisplayType = LyricsDisplayType.SplitView;
|
||||
_settingsService.DisplayType = ViewModel.DisplayType;
|
||||
}
|
||||
|
||||
private void PositionOffsetResetButton_Click(object sender, RoutedEventArgs e)
|
||||
@@ -48,12 +57,92 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
BottomCommandGrid.Opacity = 1;
|
||||
if (ViewModel.IsImmersiveMode && BottomCommandGrid.Children.Count != 0)
|
||||
{
|
||||
ViewModel.BottomCommandGridOpacity = 1f;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void BottomCommandGrid_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
BottomCommandGrid.Opacity = 0;
|
||||
if (ViewModel.IsImmersiveMode && BottomCommandGrid.Children.Count != 0)
|
||||
{
|
||||
ViewModel.BottomCommandGridOpacity = 0f;
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void DisplayTypeSwitchButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DisplayTypeSwitchFlyout.ShowAt(BottomRightCommandStackPanel);
|
||||
}
|
||||
|
||||
private void TimelineOffsetButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TimelineOffsetFlyout.ShowAt(BottomLeftCommandStackPanel);
|
||||
}
|
||||
|
||||
private void TranslationButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TranslationFlyout.ShowAt(BottomRightCommandStackPanel);
|
||||
}
|
||||
|
||||
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (e.NewSize.Width < 500 || e.NewSize.Height < 100)
|
||||
{
|
||||
if (BottomCommandGrid.Children.Count != 0)
|
||||
{
|
||||
BottomCommandGrid.Children.Remove(BottomCommandContent);
|
||||
BottomCommandFlyoutContainer.Children.Add(BottomCommandContent);
|
||||
}
|
||||
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BottomCommandFlyoutContainer.Children.Count != 0)
|
||||
{
|
||||
BottomCommandFlyout.Hide();
|
||||
BottomCommandFlyoutContainer.Children.Remove(BottomCommandContent);
|
||||
BottomCommandGrid.Children.Add(BottomCommandContent);
|
||||
}
|
||||
BottomCommandFlyoutTriggerHint.Translation = new Vector3(0, 12, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//private void VolumeButton_Click(object sender, RoutedEventArgs e)
|
||||
//{
|
||||
// VolumeFlyout.ShowAt(BottomRightCommandStackPanel);
|
||||
//}
|
||||
|
||||
private void BottomCommandFlyoutTrigger_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.IsImmersiveMode && BottomCommandFlyoutContainer.Children.Count != 0)
|
||||
{
|
||||
ViewModel.BottomCommandFlyoutTriggerOpacity = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
private void BottomCommandFlyoutTrigger_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.IsImmersiveMode && BottomCommandFlyoutContainer.Children.Count != 0)
|
||||
{
|
||||
ViewModel.BottomCommandFlyoutTriggerOpacity = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void BottomCommandFlyoutTrigger_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||
{
|
||||
if (BottomCommandFlyoutContainer.Children.Count != 0)
|
||||
{
|
||||
BottomCommandFlyout.ShowAt(BottomCommandFlyoutTrigger);
|
||||
}
|
||||
}
|
||||
|
||||
private void TimelineSliderOverlay_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||
{
|
||||
_playbackService.ChangePosition(TimelineSliderOverlay.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,137 +17,175 @@
|
||||
x:Name="RootGrid"
|
||||
PointerEntered="RootGrid_PointerEntered"
|
||||
PointerExited="RootGrid_PointerExited"
|
||||
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
|
||||
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}"
|
||||
SizeChanged="RootGrid_SizeChanged">
|
||||
|
||||
<local:LyricsPage />
|
||||
|
||||
<!-- Top command -->
|
||||
<Grid
|
||||
x:Name="TopCommandGrid"
|
||||
Margin="6"
|
||||
VerticalAlignment="Top"
|
||||
Opacity="0"
|
||||
Background="Transparent"
|
||||
Opacity="{x:Bind ViewModel.TopCommandGridOpacity, Mode=OneWay}"
|
||||
PointerEntered="TopCommandGrid_PointerEntered"
|
||||
PointerExited="TopCommandGrid_PointerExited">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
<StackPanel
|
||||
x:Name="TopLeftCommandStackPanel"
|
||||
Margin="12"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
</StackPanel>
|
||||
<Grid Padding="3" HorizontalAlignment="Left">
|
||||
<StackPanel
|
||||
x:Name="TopLeftCommandStackPanel"
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
|
||||
<StackPanel
|
||||
x:Name="TopRightCommandStackPanel"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
<!-- Music gallery -->
|
||||
<Button Click="MusicGalleryButton_Click" Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Immersive mode -->
|
||||
<ToggleButton
|
||||
x:Name="ImmersiveButton"
|
||||
Command="{x:Bind ViewModel.ImmersiveToggleButtonEnabledChangedCommand}"
|
||||
IsChecked="{x:Bind ViewModel.IsImmersiveMode, Mode=TwoWay}"
|
||||
Style="{StaticResource TitleBarToggleButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize}"
|
||||
Glyph="" />
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Look -->
|
||||
<Button
|
||||
x:Name="ClickThroughButton"
|
||||
Command="{x:Bind ViewModel.LockWindowCommand}"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="LockToolTip" x:Uid="HostWindowLockToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- More -->
|
||||
<Button x:Name="MoreButton" Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="MoreButtonToolTip" x:Uid="HostWindowMoreButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="AOTFlyoutItem"
|
||||
x:Uid="BaseWindowAOTFlyoutItem"
|
||||
Click="AOTFlyoutItem_Click" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="FullScreenFlyoutItem"
|
||||
x:Uid="BaseWindowFullScreenFlyoutItem"
|
||||
Click="FullScreenFlyoutItem_Click" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="DockFlyoutItem"
|
||||
x:Uid="HostWindowDockFlyoutItem"
|
||||
Command="{x:Bind ViewModel.ToggleDockModeCommand}"
|
||||
IsChecked="{x:Bind ViewModel.IsDockMode, Mode=OneWay}" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="DesktopFlyoutItem"
|
||||
x:Uid="HostWindowDesktopFlyoutItem"
|
||||
Command="{x:Bind ViewModel.ToggleDesktopModeCommand}"
|
||||
IsChecked="{x:Bind ViewModel.IsDesktopMode, Mode=OneWay}" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="MiniFlyoutItem"
|
||||
x:Uid="BaseWindowMiniFlyoutItem"
|
||||
Click="MiniFlyoutItem_Click" />
|
||||
<MenuFlyoutItem
|
||||
x:Name="SettingsFlyoutItem"
|
||||
x:Uid="HostWindowSettingsFlyoutItem"
|
||||
Click="SettingsMenuFlyoutItem_Click" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Grid Padding="3" HorizontalAlignment="Right">
|
||||
<StackPanel
|
||||
x:Name="TopRightCommandStackPanel"
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
|
||||
<!-- Window Minimise -->
|
||||
<Button
|
||||
x:Name="MinimiseButton"
|
||||
Click="MinimiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Maximise -->
|
||||
<Button
|
||||
x:Name="MaximiseButton"
|
||||
Click="MaximiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Restore -->
|
||||
<Button
|
||||
x:Name="RestoreButton"
|
||||
Click="RestoreButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Close -->
|
||||
<Button
|
||||
x:Name="CloseButton"
|
||||
Click="CloseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<!-- Look -->
|
||||
<Button
|
||||
x:Name="ClickThroughButton"
|
||||
Click="ClickThroughButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="LockToolTip">
|
||||
<ToolTip.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="HostWindowLockToolTip" />
|
||||
<TextBlock
|
||||
Margin="6,0"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0.7"
|
||||
Text="Ctrl + Alt + " />
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0.7"
|
||||
Text="{x:Bind ViewModel.LockHotKey, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</ToolTip.Content>
|
||||
</ToolTip>
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- More -->
|
||||
<Button x:Name="MoreButton" Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Name="MoreButtonToolTip" x:Uid="HostWindowMoreButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="AOTFlyoutItem"
|
||||
x:Uid="BaseWindowAOTFlyoutItem"
|
||||
Click="AOTFlyoutItem_Click" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="FullScreenFlyoutItem"
|
||||
x:Uid="BaseWindowFullScreenFlyoutItem"
|
||||
Click="FullScreenFlyoutItem_Click" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="DockFlyoutItem"
|
||||
x:Uid="HostWindowDockFlyoutItem"
|
||||
Click="DockFlyoutItem_Click"
|
||||
IsChecked="{x:Bind ViewModel.IsDockMode, Mode=OneWay}" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="DesktopFlyoutItem"
|
||||
x:Uid="HostWindowDesktopFlyoutItem"
|
||||
Click="DesktopFlyoutItem_Click"
|
||||
IsChecked="{x:Bind ViewModel.IsDesktopMode, Mode=OneWay}" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Name="MiniFlyoutItem"
|
||||
x:Uid="BaseWindowMiniFlyoutItem"
|
||||
Click="MiniFlyoutItem_Click" />
|
||||
<MenuFlyoutItem
|
||||
x:Name="SettingsFlyoutItem"
|
||||
x:Uid="HostWindowSettingsFlyoutItem"
|
||||
Click="SettingsMenuFlyoutItem_Click" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
|
||||
<!-- Window Minimise -->
|
||||
<Button
|
||||
x:Name="MinimiseButton"
|
||||
Click="MinimiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Maximise -->
|
||||
<Button
|
||||
x:Name="MaximiseButton"
|
||||
Click="MaximiseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Restore -->
|
||||
<Button
|
||||
x:Name="RestoreButton"
|
||||
Click="RestoreButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Visibility="Collapsed">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
<!-- Window Close -->
|
||||
<Button
|
||||
x:Name="CloseButton"
|
||||
Click="CloseButton_Click"
|
||||
Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -10,24 +10,58 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.System;
|
||||
using WinUIEx.Messaging;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
public sealed partial class LyricsWindow : Window
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly WindowMessageMonitor _wmm;
|
||||
|
||||
public LyricsWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
AppWindow.SetIcons();
|
||||
|
||||
AppWindow.Changed += AppWindow_Changed;
|
||||
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
|
||||
Title = App.ResourceLoader!.GetString("LyricsPageTitle");
|
||||
SetTitleBar(TopCommandGrid);
|
||||
|
||||
UpdateTitleBarArea();
|
||||
|
||||
_wmm = new WindowMessageMonitor(this);
|
||||
_wmm.WindowMessageReceived += Wmm_WindowMessageReceived;
|
||||
}
|
||||
|
||||
public void UpdateTitleBarArea()
|
||||
{
|
||||
if (_settingsService.IsDragEverywhereEnabled)
|
||||
{
|
||||
SetTitleBar(RootGrid);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTitleBar(TopCommandGrid);
|
||||
}
|
||||
}
|
||||
|
||||
private void Wmm_WindowMessageReceived(object? sender, WindowMessageEventArgs e)
|
||||
{
|
||||
if (e.Message.MessageId == (uint)User32.WindowMessage.WM_HOTKEY)
|
||||
{
|
||||
int id = (int)e.Message.WParam;
|
||||
GlobalHotKeyHelper.TryInvokeAction(id);
|
||||
}
|
||||
}
|
||||
|
||||
public LyricsWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<LyricsWindowViewModel>();
|
||||
@@ -38,6 +72,14 @@ namespace BetterLyrics.WinUI3.Views
|
||||
switch (type!)
|
||||
{
|
||||
case AutoStartWindowType.StandardMode:
|
||||
if (_settingsService.StandardWindowLeft < 0 || _settingsService.StandardWindowTop < 0 ||
|
||||
_settingsService.StandardWindowWidth <= 0 || _settingsService.StandardWindowHeight <= 0)
|
||||
{
|
||||
_settingsService.StandardWindowLeft = 200;
|
||||
_settingsService.StandardWindowTop = 200;
|
||||
_settingsService.StandardWindowWidth = 1600;
|
||||
_settingsService.StandardWindowHeight = 800;
|
||||
}
|
||||
AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(
|
||||
_settingsService.StandardWindowLeft,
|
||||
_settingsService.StandardWindowTop,
|
||||
@@ -54,7 +96,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
ViewModel.ToggleDesktopModeCommand.Execute(null);
|
||||
if (autoLook == null && _settingsService.AutoLockOnDesktopMode)
|
||||
{
|
||||
ViewModel.LockWindowCommand.Execute(null);
|
||||
ViewModel.ToggleLockWindowCommand.Execute(null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -78,25 +120,27 @@ namespace BetterLyrics.WinUI3.Views
|
||||
var rect = AppWindow.Position;
|
||||
var size = AppWindow.Size;
|
||||
|
||||
if (ViewModel.IsDesktopMode)
|
||||
{
|
||||
_settingsService.DesktopWindowLeft = rect.X;
|
||||
_settingsService.DesktopWindowTop = rect.Y;
|
||||
_settingsService.DesktopWindowWidth = size.Width;
|
||||
_settingsService.DesktopWindowHeight = size.Height;
|
||||
}
|
||||
else if (ViewModel.IsDockMode)
|
||||
if (rect.X >= 0 && rect.Y >= 0 && size.Width > 0 && size.Height > 0)
|
||||
{
|
||||
if (ViewModel.IsDesktopMode)
|
||||
{
|
||||
_settingsService.DesktopWindowLeft = rect.X;
|
||||
_settingsService.DesktopWindowTop = rect.Y;
|
||||
_settingsService.DesktopWindowWidth = size.Width;
|
||||
_settingsService.DesktopWindowHeight = size.Height;
|
||||
}
|
||||
else if (ViewModel.IsDockMode)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_settingsService.StandardWindowLeft = rect.X;
|
||||
_settingsService.StandardWindowTop = rect.Y;
|
||||
_settingsService.StandardWindowWidth = size.Width;
|
||||
_settingsService.StandardWindowHeight = size.Height;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_settingsService.StandardWindowLeft = rect.X;
|
||||
_settingsService.StandardWindowTop = rect.Y;
|
||||
_settingsService.StandardWindowWidth = size.Width;
|
||||
_settingsService.StandardWindowHeight = size.Height;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +177,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void SettingsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<SettingsWindow>();
|
||||
WindowHelper.OpenWindow<SettingsWindow>();
|
||||
}
|
||||
|
||||
private void UpdateTitleBarWindowButtonsVisibility()
|
||||
@@ -147,6 +191,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
AOTFlyoutItem.Visibility = DesktopFlyoutItem.Visibility = FullScreenFlyoutItem.Visibility = DockFlyoutItem.Visibility =
|
||||
ClickThroughButton.Visibility = Visibility.Collapsed;
|
||||
|
||||
ViewModel.IsImmersiveMode = true;
|
||||
break;
|
||||
case AppWindowPresenterKind.FullScreen:
|
||||
MinimiseButton.Visibility = MaximiseButton.Visibility = RestoreButton.Visibility =
|
||||
@@ -157,7 +202,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
DockFlyoutItem.Visibility =
|
||||
Visibility.Collapsed;
|
||||
FullScreenFlyoutItem.IsChecked = true;
|
||||
|
||||
break;
|
||||
case AppWindowPresenterKind.Overlapped:
|
||||
DockFlyoutItem.Visibility = Visibility.Visible;
|
||||
@@ -175,6 +219,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
MiniFlyoutItem.Visibility =
|
||||
Visibility.Collapsed;
|
||||
|
||||
ViewModel.IsImmersiveMode = true;
|
||||
}
|
||||
else if (DesktopFlyoutItem.IsChecked)
|
||||
{
|
||||
@@ -189,7 +234,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
Visibility.Collapsed;
|
||||
|
||||
ClickThroughButton.Visibility = Visibility.Visible;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -217,6 +261,8 @@ namespace BetterLyrics.WinUI3.Views
|
||||
MaximiseButton.Visibility = Visibility.Visible;
|
||||
RestoreButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
ViewModel.IsImmersiveMode = _settingsService.IsImmersiveMode;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -226,7 +272,8 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowHelper.ExitAllWindows();
|
||||
DockModeHelper.Disable(this);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
|
||||
@@ -255,12 +302,18 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void TopCommandGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
TopCommandGrid.Opacity = 1;
|
||||
if (ViewModel.IsImmersiveMode)
|
||||
{
|
||||
ViewModel.TopCommandGridOpacity = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
private void TopCommandGrid_PointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
TopCommandGrid.Opacity = 0;
|
||||
if (ViewModel.IsImmersiveMode)
|
||||
{
|
||||
ViewModel.TopCommandGridOpacity = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void TipContainerCenter_Loaded(object sender, RoutedEventArgs e)
|
||||
@@ -271,11 +324,38 @@ namespace BetterLyrics.WinUI3.Views
|
||||
private void RootGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
ViewModel.IsMouseWithinWindow = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void RootGrid_PointerExited(object sender, PointerRoutedEventArgs e)
|
||||
{
|
||||
ViewModel.IsMouseWithinWindow = false;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private void MusicGalleryButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowHelper.OpenWindow<MusicGalleryWindow>();
|
||||
}
|
||||
|
||||
private void ClickThroughButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.ToggleLockWindowCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void DockFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.ToggleDockModeCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void DesktopFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.ToggleDesktopModeCommand.Execute(null);
|
||||
UpdateTitleBarWindowButtonsVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="BetterLyrics.WinUI3.Views.MusicGalleryPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:atl="using:ATL"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:labs="using:CommunityToolkit.Labs.WinUI"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:media="using:CommunityToolkit.WinUI.Media"
|
||||
xmlns:models="using:BetterLyrics.WinUI3.Models"
|
||||
xmlns:templateselector="using:BetterLyrics.WinUI3.TemplateSelector"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<CollectionViewSource
|
||||
x:Name="GroupedTracksCVS"
|
||||
IsSourceGrouped="True"
|
||||
Source="{x:Bind ViewModel.GroupedTracks, Mode=OneWay}" />
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="3*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
x:Name="SongViewer"
|
||||
Grid.Column="0"
|
||||
Padding="12">
|
||||
<Grid.Tag>
|
||||
<Flyout
|
||||
x:Name="SongFileInfoFlyout"
|
||||
Placement="Bottom"
|
||||
ShouldConstrainToRootBounds="False">
|
||||
<ScrollViewer Width="300" Height="300">
|
||||
<StackPanel Spacing="12">
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoTitle" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Title, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileArtist" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Artist, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileAlbum" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Album, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoYear" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Year, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoDuration" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoBitrate" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Bitrate, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoSampleRate" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.SampleRate, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoBitDepth" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.BitDepth, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoFormat" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.AudioFormat.Name, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoEncoder" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackRightTapped.Encoder, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="MusicGalleryPageFileInfoPath" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
<HyperlinkButton
|
||||
Margin="-12,0,0,0"
|
||||
Click="SongPathHyperlinkButton_Click"
|
||||
Content="{x:Bind ViewModel.TrackRightTapped.Path, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Flyout>
|
||||
</Grid.Tag>
|
||||
|
||||
<ListView
|
||||
VerticalAlignment="Top"
|
||||
ItemsSource="{x:Bind ViewModel.SongsTabInfoList, Mode=OneWay}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||
SelectedIndex="{x:Bind ViewModel.SelectedSongsTabInfoIndex, Mode=TwoWay}">
|
||||
<ListView.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<ItemsStackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListView.ItemsPanel>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:SongsTabInfo">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
Grid.Column="0"
|
||||
Background="Transparent"
|
||||
ColumnSpacing="6"
|
||||
Tapped="PlaylistGrid_Tapped">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<FontIcon
|
||||
Grid.Column="0"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="16"
|
||||
Glyph="{Binding Icon}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}" />
|
||||
</Grid>
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Click="PlaylistCloseButton_Click"
|
||||
Content="{ui:FontIcon FontSize=10,
|
||||
FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource GhostButtonStyle}"
|
||||
Visibility="{Binding IsClosable, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<Grid Margin="0,48,0,0" VerticalAlignment="Top">
|
||||
<AutoSuggestBox
|
||||
x:Name="SongSearchBox"
|
||||
x:Uid="MusicGalleryPageSongSearchBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
QueryIcon="Find"
|
||||
Text="{x:Bind ViewModel.SongSearchQuery, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,96,0,0" VerticalAlignment="Top">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<ToggleButton
|
||||
x:Name="SelectAllToggleButton"
|
||||
x:Uid="MusicGalleryPageSelectAll"
|
||||
Click="SelectAllToggleButton_Click" />
|
||||
<Button x:Uid="MusicGalleryPageAddToPlayingQueue">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToNext" Click="AddSongToQueueNextMenuFlyoutItem_Click" />
|
||||
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToEnd" Click="AddSongToQueueEndMenuFlyoutItem_Click" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button x:Uid="MusicGalleryPageAddToCustomList" Visibility="Collapsed">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyoutItem x:Uid="MusicGalleryPageNewPlaylist" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPageSortType"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<controls:Segmented
|
||||
x:Name="Segmented"
|
||||
SelectedIndex="{x:Bind ViewModel.SongOrderType, Converter={StaticResource EnumToIntConverter}, Mode=TwoWay}"
|
||||
SelectionMode="Single">
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByTitle" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByAlbum" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByArtist" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
</controls:Segmented>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<SemanticZoom Margin="0,140,0,0">
|
||||
<SemanticZoom.ZoomedInView>
|
||||
<ListView
|
||||
x:Name="SongListView"
|
||||
ItemsSource="{x:Bind GroupedTracksCVS.View, Mode=OneWay}"
|
||||
SelectionChanged="SongListView_SelectionChanged"
|
||||
SelectionMode="Multiple">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="atl:Track">
|
||||
<Grid Padding="12" RightTapped="SongListViewItemGrid_RightTapped">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
<ColumnDefinition Width="1*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 歌曲名 -->
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Title}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- 歌手名 -->
|
||||
<HyperlinkButton
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Click="ArtistHyperlibkButton_Click"
|
||||
Tag="{Binding Artist}">
|
||||
<TextBlock Text="{Binding Artist}" TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
|
||||
<!-- 专辑名 -->
|
||||
<HyperlinkButton
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Click="AlbumHyperlibkButton_Click"
|
||||
Tag="{Binding Album}">
|
||||
<TextBlock Text="{Binding Album}" TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
|
||||
<!-- 年份 -->
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Year}"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- 歌曲时长 -->
|
||||
<TextBlock
|
||||
Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Duration, Converter={StaticResource SecondsToFormattedTimeConverter}}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
<ListView.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<ItemsStackPanel AreStickyGroupHeadersEnabled="True" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListView.ItemsPanel>
|
||||
<ListView.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.HeaderTemplate>
|
||||
<DataTemplate x:DataType="models:GroupInfoList">
|
||||
<Border AutomationProperties.AccessibilityView="Raw">
|
||||
<TextBlock
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding}" />
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</GroupStyle.HeaderTemplate>
|
||||
</GroupStyle>
|
||||
</ListView.GroupStyle>
|
||||
</ListView>
|
||||
</SemanticZoom.ZoomedInView>
|
||||
|
||||
<SemanticZoom.ZoomedOutView>
|
||||
<GridView
|
||||
MaxWidth="500"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind GroupedTracksCVS.View.CollectionGroups, Mode=OneWay}"
|
||||
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
|
||||
SelectionMode="None">
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:GroupInfoList">
|
||||
<TextBlock Style="{ThemeResource TitleTextBlockStyle}" Text="{Binding}" />
|
||||
</DataTemplate>
|
||||
</GridView.ItemTemplate>
|
||||
</GridView>
|
||||
</SemanticZoom.ZoomedOutView>
|
||||
</SemanticZoom>
|
||||
|
||||
<Grid Margin="0,140,0,0" Visibility="{x:Bind ViewModel.IsLocalMediaNotFound, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<Image MaxWidth="200" Source="/Assets/EmptyState.png" />
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPageFileNotFound"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="PlayQueue"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,12,0">
|
||||
|
||||
<StackPanel Margin="0,10,0,0" Spacing="12">
|
||||
<Grid Margin="0,6,0,0" VerticalAlignment="Top">
|
||||
<TextBlock x:Uid="MusicGalleryPagePlayingQueue" Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.DisplayedPlayingSongIndex, Mode=OneWay}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<Button x:Uid="MusicGalleryPageEmptyPlayingQueue" Click="EmptyPlayingQueueButton_Click" />
|
||||
<Button x:Uid="MusicGalleryPageScrollToPlayingItem" Click="ScrollToPlayingItemButton_Click" />
|
||||
</StackPanel>
|
||||
<controls:Segmented HorizontalAlignment="Stretch" SelectedIndex="{x:Bind ViewModel.PlaybackOrder, Converter={StaticResource EnumToIntConverter}, Mode=TwoWay}">
|
||||
<controls:Segmented.Items>
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageQueueLoop" />
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageSingleLoop" />
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageQueueRandom" />
|
||||
</controls:Segmented.Items>
|
||||
</controls:Segmented>
|
||||
</StackPanel>
|
||||
|
||||
<ListView
|
||||
x:Name="PlayingQueueListView"
|
||||
Margin="0,136,0,0"
|
||||
ItemsSource="{x:Bind ViewModel.TrackPlayingQueue, Mode=OneWay}"
|
||||
SelectedIndex="{x:Bind ViewModel.PlayingSongIndex, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="0,6">
|
||||
<Grid Tapped="PlayingQueueListVireItemGrid_Tapped">
|
||||
<StackPanel>
|
||||
<TextBlock Text="{Binding Track.Title}" TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Foreground="{StaticResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Track.Artist}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid HorizontalAlignment="Right">
|
||||
<Button
|
||||
Click="RemoveFromPlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=12,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageRemoveFromPlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<Grid Margin="0,136,0,0">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<Image MaxWidth="100" Source="/Assets/EmptyBox.png" />
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPagePlayingQueueEmpty"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" Visibility="{x:Bind ViewModel.IsDataLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="12" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="12" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="12" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<labs:Shimmer Grid.Row="0" CornerRadius="6" />
|
||||
<labs:Shimmer Grid.Row="2" CornerRadius="6" />
|
||||
<labs:Shimmer Grid.Row="4" CornerRadius="6" />
|
||||
<labs:Shimmer Grid.Row="6" CornerRadius="6" />
|
||||
</Grid>
|
||||
<ProgressRing IsActive="{x:Bind ViewModel.IsDataLoading, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,171 @@
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.System;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MusicGalleryPage : Page
|
||||
{
|
||||
public MusicGalleryViewModel ViewModel => (MusicGalleryViewModel)DataContext;
|
||||
|
||||
public MusicGalleryPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<MusicGalleryViewModel>();
|
||||
}
|
||||
|
||||
private void SongListViewItemGrid_RightTapped(object sender, RightTappedRoutedEventArgs e)
|
||||
{
|
||||
ViewModel.TrackRightTapped = (Track)((FrameworkElement)sender).DataContext;
|
||||
SongFileInfoFlyout.ShowAt(sender as FrameworkElement);
|
||||
}
|
||||
|
||||
private async void SongPathHyperlinkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await LauncherHelper.SelectAndShowFile($"{((HyperlinkButton)sender).Content}");
|
||||
}
|
||||
|
||||
private void PlayingQueueListVireItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
|
||||
ViewModel.PlayTrack(item);
|
||||
PlayingQueueListView.ScrollIntoView(item);
|
||||
}
|
||||
|
||||
private void EmptyPlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.TrackPlayingQueue.Clear();
|
||||
ViewModel.PlayingSongIndex = -1;
|
||||
ViewModel.PlayTrackAt(ViewModel.PlayingSongIndex);
|
||||
}
|
||||
|
||||
private void ScrollToPlayingItemButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ViewModel.PlayingQueueItem == null) return;
|
||||
PlayingQueueListView.ScrollIntoView(ViewModel.PlayingQueueItem);
|
||||
}
|
||||
|
||||
private void RemoveFromPlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool playNext = false;
|
||||
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
|
||||
int index = ViewModel.TrackPlayingQueue.IndexOf(item);
|
||||
if (item == ViewModel.PlayingQueueItem)
|
||||
{
|
||||
playNext = true;
|
||||
}
|
||||
ViewModel.TrackPlayingQueue.Remove(item);
|
||||
if (playNext)
|
||||
{
|
||||
if (ViewModel.TrackPlayingQueue.Count == 0)
|
||||
{
|
||||
index = -1;
|
||||
}
|
||||
else if (index >= ViewModel.TrackPlayingQueue.Count)
|
||||
{
|
||||
index = ViewModel.TrackPlayingQueue.Count - 1;
|
||||
}
|
||||
ViewModel.PlayingSongIndex = index;
|
||||
ViewModel.PlayTrackAt(ViewModel.PlayingSongIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void SongFileInfoMenuFlyoutSubItem_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
SongFileInfoFlyout.ShowAt(sender as FrameworkElement);
|
||||
}
|
||||
|
||||
private void AddSongToQueueNextMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool startPlaying = ViewModel.TrackPlayingQueue.Count == 0;
|
||||
ViewModel.TrackPlayingQueue.InsertRange(ViewModel.PlayingSongIndex + 1, SongListView.SelectedItems.Cast<Track>().Select(x => new PlayQueueItem(x)));
|
||||
if (startPlaying)
|
||||
{
|
||||
ViewModel.PlayingSongIndex = ViewModel.PlayingSongIndex + 1;
|
||||
ViewModel.PlayTrackAt(ViewModel.PlayingSongIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSongToQueueEndMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool startPlaying = ViewModel.TrackPlayingQueue.Count == 0;
|
||||
ViewModel.TrackPlayingQueue.AddRange(SongListView.SelectedItems.Cast<Track>().Select(x => new PlayQueueItem(x)));
|
||||
if (startPlaying)
|
||||
{
|
||||
ViewModel.PlayingSongIndex = ViewModel.PlayingSongIndex + 1;
|
||||
ViewModel.PlayTrackAt(ViewModel.PlayingSongIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void SongListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
ViewModel.SelectedTracks = SongListView.SelectedItems.Cast<Track>().ToList();
|
||||
SelectAllToggleButton.IsChecked = SongListView.SelectedItems.Count == SongListView.Items.Count;
|
||||
}
|
||||
|
||||
private void SelectAllToggleButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (SelectAllToggleButton.IsChecked == true)
|
||||
{
|
||||
SongListView.SelectAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
SongListView.SelectedItems.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void ArtistHyperlibkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var artist = (string)((HyperlinkButton)sender).Tag;
|
||||
var playlist = new SongsTabInfo(artist, "\uEFA9", true, CommonSongProperty.Artist, artist);
|
||||
ViewModel.UpdateSelectedPlaylist(playlist);
|
||||
}
|
||||
|
||||
private void AlbumHyperlibkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var album = (string)((HyperlinkButton)sender).Tag;
|
||||
var playlist = new SongsTabInfo(album, "\uE93C", true, CommonSongProperty.Album, album);
|
||||
ViewModel.UpdateSelectedPlaylist(playlist);
|
||||
}
|
||||
|
||||
private void PlaylistGrid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var playlist = (SongsTabInfo)((FrameworkElement)sender).DataContext;
|
||||
ViewModel.UpdateSelectedPlaylist(playlist);
|
||||
}
|
||||
|
||||
private void PlaylistCloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var playlist = (SongsTabInfo)((FrameworkElement)sender).DataContext;
|
||||
ViewModel.SongsTabInfoList.Remove(playlist);
|
||||
ViewModel.SelectedSongsTabInfoIndex = 0;
|
||||
ViewModel.ApplyPlaylist();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Window
|
||||
x:Class="BetterLyrics.WinUI3.Views.MusicGalleryWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="MusicGalleryWindow"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<local:MusicGalleryPage />
|
||||
|
||||
</Window>
|
||||
@@ -0,0 +1,45 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using WinUIEx;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MusicGalleryWindow : Window
|
||||
{
|
||||
public MusicGalleryWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Title = App.ResourceLoader?.GetString("MusicGalleryPageTitle");
|
||||
AppWindow.TitleBar.PreferredTheme = TitleBarTheme.UseDefaultAppMode;
|
||||
AppWindow.SetIcons();
|
||||
|
||||
AppWindow.Closing += AppWindow_Closing;
|
||||
}
|
||||
|
||||
private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,12 +134,91 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageIgnoreFullscreenWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IgnoreFullscreenWindow, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageHideWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.HideWindowWhenNotPlaying, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageGlobalDrag" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDragEverywhereEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Desktop mode -->
|
||||
|
||||
<TextBlock x:Uid="SettingsPageAppDesktop" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageAutoLock" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AutoLockOnDesktopMode, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageIgnoreFullscreenWindow" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IgnoreFullscreenWindow, Mode=TwoWay}" />
|
||||
<controls:SettingsCard x:Uid="SettingsPageLockHotKey" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
VerticalAlignment="Center"
|
||||
Text="Ctrl + Alt + " />
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LockHotKeyIndex, Mode=TwoWay}">
|
||||
<ComboBoxItem Content="A" />
|
||||
<ComboBoxItem Content="B" />
|
||||
<ComboBoxItem Content="C" />
|
||||
<ComboBoxItem Content="D" />
|
||||
<ComboBoxItem Content="E" />
|
||||
<ComboBoxItem Content="F" />
|
||||
<ComboBoxItem Content="G" />
|
||||
<ComboBoxItem Content="H" />
|
||||
<ComboBoxItem Content="I" />
|
||||
<ComboBoxItem Content="J" />
|
||||
<ComboBoxItem Content="K" />
|
||||
<ComboBoxItem Content="L" />
|
||||
<ComboBoxItem Content="M" />
|
||||
<ComboBoxItem Content="N" />
|
||||
<ComboBoxItem Content="O" />
|
||||
<ComboBoxItem Content="P" />
|
||||
<ComboBoxItem Content="Q" />
|
||||
<ComboBoxItem Content="R" />
|
||||
<ComboBoxItem Content="S" />
|
||||
<ComboBoxItem Content="T" />
|
||||
<ComboBoxItem Content="U" />
|
||||
<ComboBoxItem Content="V" />
|
||||
<ComboBoxItem Content="W" />
|
||||
<ComboBoxItem Content="X" />
|
||||
<ComboBoxItem Content="Y" />
|
||||
<ComboBoxItem Content="Z" />
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageDockWindowHeight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.DockWindowHeight, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=" px" />
|
||||
<Slider
|
||||
Maximum="200"
|
||||
Minimum="64"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="1"
|
||||
TickFrequency="1"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.DockWindowHeight, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- Dock mode -->
|
||||
|
||||
<TextBlock x:Uid="SettingsPageAppDock" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageDockPlacement" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.DockPlacement, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageDockPlacementTop" />
|
||||
<ComboBoxItem x:Uid="SettingsPageDockPlacementBottom" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
@@ -198,6 +277,24 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageBackgroundAcrylicEffectAmount" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind ViewModel.CoverAcrylicEffectAmount, Mode=OneWay}" />
|
||||
<Slider
|
||||
Maximum="10"
|
||||
Minimum="0"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="1"
|
||||
TickFrequency="1"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.CoverAcrylicEffectAmount, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
@@ -254,18 +351,12 @@
|
||||
CanDragItems="True"
|
||||
CanReorderItems="True"
|
||||
DragItemsCompleted="AlbumArtSearchProvidersListView_DragItemsCompleted"
|
||||
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
|
||||
ItemsSource="{x:Bind ViewModel.AlbumArtSearchProvidersInfo, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</ListView.OpacityTransition>
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:AlbumArtSearchProviderInfo">
|
||||
<controls:SettingsCard Padding="60,0,48,0" Header="{Binding Provider, Converter={StaticResource AlbumArtSearchProviderToDisplayNameConverter}, Mode=OneWay}">
|
||||
@@ -312,7 +403,7 @@
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True"
|
||||
ItemsSource="{x:Bind ViewModel.LocalLyricsFolders, Mode=OneWay}">
|
||||
ItemsSource="{x:Bind ViewModel.LocalMediaFolders, Mode=OneWay}">
|
||||
<controls:SettingsExpander.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:SettingsCard>
|
||||
@@ -347,13 +438,13 @@
|
||||
<interactivity:Interaction.Behaviors>
|
||||
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LocalLyricsFolders.Count, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.LocalMediaFolders.Count, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LocalLyricsFolders.Count, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.LocalMediaFolders.Count, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
@@ -432,6 +523,16 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontFamily" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox ItemsSource="{x:Bind ViewModel.SystemFontNames, Mode=OneWay}" SelectedIndex="{x:Bind ViewModel.SelectedFontFamilyIndex, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontWeight" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsFontWeight, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsThin" />
|
||||
@@ -448,6 +549,25 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsBgFontOpacity" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.LyricsBgFontOpacity, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,0,14,0"
|
||||
VerticalAlignment="Center"
|
||||
Text=" %" />
|
||||
<Slider
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
SnapsTo="Ticks"
|
||||
StepFrequency="1"
|
||||
TickFrequency="1"
|
||||
TickPlacement="Outside"
|
||||
Value="{x:Bind ViewModel.LyricsBgFontOpacity, Mode=TwoWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontStrokeWidth" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
|
||||
@@ -668,7 +788,7 @@
|
||||
IsExpanded="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=TwoWay}" />
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsGlowEffectScope" IsEnabled="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
|
||||
<controls:SettingsCard x:Uid="SettingsPageScope" IsEnabled="{x:Bind ViewModel.IsLyricsGlowEffectEnabled, Mode=OneWay}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LyricsGlowEffectScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
|
||||
@@ -678,6 +798,10 @@
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFloatAnimation" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsLyricsFloatAnimationEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageFan" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsFanLyricsEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
@@ -770,9 +894,10 @@
|
||||
x:Uid="SettingsPageTranslationConfig"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
IsExpanded="{x:Bind ViewModel.IsLibreTranslateEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsLibreTranslateEnabled, Mode=TwoWay}" />
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageLibreTranslateServer">
|
||||
<controls:SettingsCard x:Uid="SettingsPageLibreTranslateServer" IsEnabled="{x:Bind ViewModel.IsLibreTranslateEnabled, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<TextBox
|
||||
x:Name="LibreTranslateServerTextBox"
|
||||
@@ -810,7 +935,6 @@
|
||||
<Paragraph>
|
||||
<Run x:Uid="SettingsPageVersion" />
|
||||
<Run Text="{x:Bind ViewModel.Version, Mode=OneWay}" />
|
||||
<Run Text="{x:Bind ViewModel.BuildDate, Mode=OneWay}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
</controls:SettingsCard.Description>
|
||||
@@ -825,6 +949,10 @@
|
||||
Glyph=}"
|
||||
IsClickEnabled="True" />
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageFAQ" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<HyperlinkButton Content="https://github.com/jayfunc/BetterLyrics/blob/dev/FAQ/FAQ.md" NavigateUri="https://github.com/jayfunc/BetterLyrics/blob/dev/FAQ/FAQ.md" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageQQGroup" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/QQ.png}">
|
||||
<Button x:Uid="SettingsPageJoinNowButton" Click="QQGroupButton_Click" />
|
||||
</controls:SettingsCard>
|
||||
@@ -833,6 +961,10 @@
|
||||
<Button x:Uid="SettingsPageJoinNowButton" Click="DiscodGroupButton_Click" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageTelegram" HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/Telegram.png}">
|
||||
<Button x:Uid="SettingsPageJoinNowButton" Click="TelegramGroupButton_Click" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
@@ -871,6 +1003,20 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLXMusicServer">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<TextBox
|
||||
IsEnabled="{x:Bind ViewModel.IsLXMusicServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}"
|
||||
PlaceholderText="http://127.0.0.1:23330"
|
||||
Text="{x:Bind ViewModel.LXMusicServer, Mode=TwoWay}" />
|
||||
<Button
|
||||
x:Uid="SettingsPageServerTestButton"
|
||||
Command="{x:Bind ViewModel.LXMusicServerTestCommand}"
|
||||
IsEnabled="{x:Bind ViewModel.IsLXMusicServerTesting, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
|
||||
@@ -7,14 +7,13 @@ using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Windows.System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
public sealed partial class SettingsPage : Page
|
||||
{
|
||||
private bool _isUserToggle;
|
||||
|
||||
public SettingsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@@ -27,9 +26,9 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
if (sender is ToggleSwitch toggleSwitch)
|
||||
{
|
||||
if (toggleSwitch.DataContext is LocalLyricsFolder localLyricsFolder)
|
||||
if (toggleSwitch.DataContext is LocalMediaFolder localLyricsFolder)
|
||||
{
|
||||
ViewModel.ToggleLocalLyricsFolder(localLyricsFolder);
|
||||
ViewModel.ToggleLocalLyricsFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +47,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
if (toggleSwitch.DataContext is LyricsSearchProviderInfo providerInfo)
|
||||
{
|
||||
ViewModel.ToggleLyricsSearchProvider(providerInfo);
|
||||
ViewModel.ToggleLyricsSearchProvider();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +65,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
Microsoft.UI.Xaml.RoutedEventArgs e
|
||||
)
|
||||
{
|
||||
ViewModel.RemoveFolderAsync((LocalLyricsFolder)(sender as HyperlinkButton)!.Tag);
|
||||
ViewModel.RemoveFolderAsync((LocalMediaFolder)(sender as HyperlinkButton)!.Tag);
|
||||
}
|
||||
|
||||
private void MediaSourceProviderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
|
||||
@@ -132,5 +131,10 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
Launcher.LaunchUriAsync(new Uri(MetadataHelper.DiscordUrl));
|
||||
}
|
||||
|
||||
private void TelegramGroupButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Launcher.LaunchUriAsync(new Uri(MetadataHelper.TelegramUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<Grid>
|
||||
<Grid x:Name="RootGrid">
|
||||
<local:SettingsPage />
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using H.NotifyIcon;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using WinUIEx;
|
||||
|
||||
@@ -8,26 +9,14 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
public sealed partial class SettingsWindow : Window
|
||||
{
|
||||
public SettingsWindowViewModel ViewModel { get; set; } = Ioc.Default.GetRequiredService<SettingsWindowViewModel>();
|
||||
|
||||
public SettingsWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Title = App.ResourceLoader!.GetString("SettingsPageTitle");
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
|
||||
AppWindow.Closing += AppWindow_Closing;
|
||||
}
|
||||
|
||||
public SettingsWindowViewModel ViewModel { get; set; } =
|
||||
Ioc.Default.GetRequiredService<SettingsWindowViewModel>();
|
||||
|
||||
private void AppWindow_Closing(
|
||||
Microsoft.UI.Windowing.AppWindow sender,
|
||||
Microsoft.UI.Windowing.AppWindowClosingEventArgs args
|
||||
)
|
||||
{
|
||||
args.Cancel = true; // Prevent the window from closing
|
||||
this.Hide(true);
|
||||
Title = App.ResourceLoader?.GetString("SettingsPageTitle");
|
||||
AppWindow.TitleBar.PreferredTheme = TitleBarTheme.UseDefaultAppMode;
|
||||
AppWindow.SetIcons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
FAQ/FAQ.md
Normal file
@@ -0,0 +1,59 @@
|
||||
### I couldn't see any button that I can interact with / 我找不到任何可交互的按钮
|
||||
|
||||
By default, all the buttons are shown but if you click on the "Immersive" button (as shown in the picture), all the buttons will disappear. But don't worry, one you hover again, it will shown up to you!
|
||||
|
||||
默认情况下,所有按钮都会显示,但如果您点击 “沉浸” 按钮(如图左上位置所示),所有按钮都会消失。不过不用担心,当您再次将鼠标悬停在按钮上时,它们就会重新显示出来!
|
||||
|
||||

|
||||
|
||||
It is important to note that when you enter "Docked Mode", the action buttons are hidden. Hover your mouse over the top to access the "Immerse", "More", and "Close" buttons.
|
||||
|
||||
需要特别注意的是,当您进入 “停靠模式” 时,会隐藏操作按钮。将鼠标悬停在顶部以访问 “沉浸” 按钮、“更多” 按钮以及 “关闭” 按钮
|
||||
|
||||

|
||||
|
||||
Hover the mouse slightly above the bottom edge of the window to display the white control floating window at the bottom
|
||||
|
||||
将鼠标悬浮在窗口下边缘稍靠上位置以显示底部控制浮窗小白条
|
||||
|
||||

|
||||
|
||||
Tap the "little white bar" to display the bottom control bar in floating window form (including current playback progress view, timeline offset adjustment; previous song, pause/play, next song; translation, layout, settings)
|
||||
|
||||
点按 “小白条” 即可以浮窗形式显示底部控制栏(包括当前播放进度查看、时间轴偏移调整;上一首、暂停/播放、下一首; 翻译、布局、设置)
|
||||
|
||||

|
||||
|
||||
### I have set up all the settings related to translation but there is no translation at all
|
||||
|
||||

|
||||
|
||||
Please make sure that you have already enable "Translation" function (at the bottom-right of the lyrics page, click to toggle).
|
||||
|
||||
### How can I lock the window when switching to desktop mode
|
||||
|
||||

|
||||
|
||||
Again, hover you mouse on the top, click on the lock icon and you're good to go! Or, alternatively, press `Ctrl + Alt + U`.
|
||||
|
||||
### How can I unlock the window in desktop mode
|
||||
|
||||

|
||||
|
||||
It's in the system tray, right-click on the icon and you'll see "Unlock the window". Or, alternatively, press `Ctrl + Alt + U`.
|
||||
|
||||
### There's a delay in lyrics timeline
|
||||
|
||||
Hover you mouse on the very bottom of the app,
|
||||
|
||||

|
||||
|
||||
And then click on the first icon button (Lyrics timeline offset), here you can adjust the offset freely.
|
||||
|
||||
### The lyrics jump back and forth frequently / 歌词频繁前后跳动
|
||||
|
||||

|
||||
|
||||
Go to "Advanced options" section, increase the threshold value (marked with the bigger red rectangle) until the lyrics is working properly.
|
||||
|
||||
---
|
||||
|
Before Width: | Height: | Size: 510 KiB After Width: | Height: | Size: 510 KiB |
BIN
FAQ/image-10.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
FAQ/image-11.png
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
FAQ/image-12.png
Normal file
|
After Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 631 KiB After Width: | Height: | Size: 631 KiB |