Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 72 additions & 24 deletions FluentFlyoutWPF/Controls/TaskbarWidgetControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,64 @@ public TaskbarWidgetControl()
public void ReorderControls()
{
// Remove ControlsStackPanel from MainStackPanel
// Remove media elements too before rebuilding the order
MainStackPanel.Children.Remove(ControlsStackPanel);
MainStackPanel.Children.Remove(SongImageBorder);
MainStackPanel.Children.Remove(SongInfoStackPanel);

bool controlsOnLeft = SettingsManager.Current.TaskbarWidgetControlsPosition == 0;
bool coverOnRight = IsCoverOnRight();

// Reorder based on position setting
if (SettingsManager.Current.TaskbarWidgetControlsPosition == 0)
if (controlsOnLeft)
{
// Left: Controls, Image, Info
MainStackPanel.Children.Insert(0, ControlsStackPanel);
// Left: Controls, Image/Info content
MainStackPanel.Children.Add(ControlsStackPanel);
ControlsStackPanel.Margin = new Thickness(2, 0, 6, 0); // for some reason margins are weird on left side
}

if (coverOnRight)
{
// Right cover: Info, Image
MainStackPanel.Children.Add(SongInfoStackPanel);
MainStackPanel.Children.Add(SongImageBorder);
}
else
{
// Right: Image, Info, Controls
// Left cover: Image, Info
MainStackPanel.Children.Add(SongImageBorder);
MainStackPanel.Children.Add(SongInfoStackPanel);
}

if (!controlsOnLeft)
{
// Right: Image/Info content, Controls
MainStackPanel.Children.Add(ControlsStackPanel);
ControlsStackPanel.Margin = new Thickness(8, 0, 0, 0);
}

ApplyCoverPositionLayout(coverOnRight);
UpdateMarquees();
}

private static bool IsCoverOnRight()
{
return SettingsManager.Current.TaskbarWidgetCoverPosition switch
{
1 => false,
2 => true,
_ => SettingsManager.Current.TaskbarWidgetPosition == 2
};
}

private void ApplyCoverPositionLayout(bool coverOnRight)
{
SongInfoStackPanel.Margin = coverOnRight
? new Thickness(0, 0, 8, 0)
: new Thickness(8, 0, 0, 0);

SongTitle.TextAlignment = coverOnRight ? TextAlignment.Right : TextAlignment.Left;
SongArtist.TextAlignment = coverOnRight ? TextAlignment.Right : TextAlignment.Left;
}

public void SetVerticalMode(bool isVertical)
Expand Down Expand Up @@ -364,8 +407,10 @@ private void UpdateMarquee(System.Windows.Controls.TextBlock textBlock, Canvas c

int speed = SettingsManager.Current.TaskbarWidgetScrollingTextSpeed;
bool loopForever = SettingsManager.Current.TaskbarWidgetScrollingTextLoopForever;
bool isRightAligned = IsCoverOnRight();
bool isTitle = textBlock == SongTitle;
double containerWidth = container.Width;
textBlock.TextAlignment = isRightAligned ? TextAlignment.Right : TextAlignment.Left;

// references moved outside so they may be called in the else block later
ref double cachedMaskWidth = ref (isTitle ? ref _cachedTitleOpacityMaskWidth : ref _cachedArtistOpacityMaskWidth);
Expand Down Expand Up @@ -416,12 +461,13 @@ private void UpdateMarquee(System.Windows.Controls.TextBlock textBlock, Canvas c

double spacerWidth = StringWidth.GetStringWidth(spacer, 400);
double scrollDistance = textWidth + spacerWidth;
double startOffset = isRightAligned ? containerWidth - textWidth : 0;

double durationToScroll = scrollDistance / speed;
var animation = new DoubleAnimation
{
From = 0,
To = -scrollDistance,
From = startOffset,
To = startOffset - scrollDistance,
Duration = TimeSpan.FromSeconds(durationToScroll),
RepeatBehavior = RepeatBehavior.Forever
};
Expand All @@ -434,6 +480,8 @@ private void UpdateMarquee(System.Windows.Controls.TextBlock textBlock, Canvas c
// resetting or reversing; this prevents abrupt cutoffs
double scrollDistance = textWidth - containerWidth + 10;
textBlock.Text = origText;
double startOffset = isRightAligned ? containerWidth - textWidth : 0;
double endOffset = isRightAligned ? 10 : -scrollDistance;

double durationSeconds = scrollDistance / speed;
double pauseDuration = 2.0; // wait 2 seconds at the start and end of the scroll
Expand All @@ -444,12 +492,12 @@ private void UpdateMarquee(System.Windows.Controls.TextBlock textBlock, Canvas c
double tTotalCycle = tScrollBackEnd + pauseDuration;

var animation = new DoubleAnimationUsingKeyFrames { RepeatBehavior = RepeatBehavior.Forever };
animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.Zero)));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitStart))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(-scrollDistance, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollEnd))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(-scrollDistance, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitEnd))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollBackEnd))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tTotalCycle))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(startOffset, KeyTime.FromTimeSpan(TimeSpan.Zero)));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(startOffset, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitStart))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(endOffset, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollEnd))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(endOffset, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitEnd))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(startOffset, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollBackEnd))));
animation.KeyFrames.Add(new LinearDoubleKeyFrame(startOffset, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tTotalCycle))));

// sync fades with the "ping pong" movement
Color transparentWhite = Color.FromArgb(0, 255, 255, 255);
Expand All @@ -460,20 +508,20 @@ private void UpdateMarquee(System.Windows.Controls.TextBlock textBlock, Canvas c
TimeSpan fadeTime = TimeSpan.FromMilliseconds(Math.Min(300, durationSeconds * 1000 / 2.0));

var leftColorAnim = new ColorAnimationUsingKeyFrames { RepeatBehavior = RepeatBehavior.Forever };
leftColorAnim.KeyFrames.Add(new DiscreteColorKeyFrame(solidWhite, KeyTime.FromTimeSpan(TimeSpan.Zero)));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitStart))));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitStart) + fadeTime)));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollBackEnd) - fadeTime)));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollBackEnd))));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tTotalCycle))));
leftColorAnim.KeyFrames.Add(new DiscreteColorKeyFrame(isRightAligned ? transparentWhite : solidWhite, KeyTime.FromTimeSpan(TimeSpan.Zero)));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? transparentWhite : solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitStart))));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? solidWhite : transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitStart) + fadeTime)));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? solidWhite : transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollBackEnd) - fadeTime)));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? transparentWhite : solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollBackEnd))));
leftColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? transparentWhite : solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tTotalCycle))));

var rightColorAnim = new ColorAnimationUsingKeyFrames { RepeatBehavior = RepeatBehavior.Forever };
rightColorAnim.KeyFrames.Add(new DiscreteColorKeyFrame(transparentWhite, KeyTime.FromTimeSpan(TimeSpan.Zero)));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollEnd) - fadeTime)));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollEnd))));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitEnd))));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitEnd) + fadeTime)));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tTotalCycle))));
rightColorAnim.KeyFrames.Add(new DiscreteColorKeyFrame(isRightAligned ? solidWhite : transparentWhite, KeyTime.FromTimeSpan(TimeSpan.Zero)));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? solidWhite : transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollEnd) - fadeTime)));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? transparentWhite : solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tScrollEnd))));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? transparentWhite : solidWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitEnd))));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? solidWhite : transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tWaitEnd) + fadeTime)));
rightColorAnim.KeyFrames.Add(new LinearColorKeyFrame(isRightAligned ? solidWhite : transparentWhite, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(tTotalCycle))));

cachedMask.GradientStops[0].BeginAnimation(GradientStop.ColorProperty, leftColorAnim);
cachedMask.GradientStops[3].BeginAnimation(GradientStop.ColorProperty, rightColorAnim);
Expand Down
13 changes: 13 additions & 0 deletions FluentFlyoutWPF/Pages/TaskbarWidgetPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@
<ComboBoxItem Content="{DynamicResource PositionBottomRight}" />
</ComboBox>
</ui:CardControl>
<ui:CardControl x:Name="TaskbarWidgetCoverPositionTitleCard" Tag="Indexable" Icon="{ui:SymbolIcon ArrowMove24}" Margin="0,0,0,3">
<ui:CardControl.Header>
<StackPanel Orientation="Vertical">
<TextBlock Text="{DynamicResource TaskbarWidgetCoverPositionTitle}" FontSize="14" FontWeight="Regular" VerticalAlignment="Center" />
<TextBlock Text="{DynamicResource TaskbarWidgetCoverPositionDescription}" FontSize="12" Opacity="0.5" />
</StackPanel>
</ui:CardControl.Header>
<ComboBox SelectedIndex="{Binding TaskbarWidgetCoverPosition}" IsEnabled="{Binding IsPremiumUnlocked}">
<ComboBoxItem Content="{DynamicResource PositionDefault}" />
<ComboBoxItem Content="{DynamicResource PositionLeft}" />
<ComboBoxItem Content="{DynamicResource PositionRight}" />
</ComboBox>
</ui:CardControl>
<ui:CardControl x:Name="TaskbarWidgetSelectedMonitorTitleCard" Tag="Indexable" Grid.Row="5" Icon="{ui:SymbolIcon ProjectionScreen24}" Margin="0,0,0,3">
<ui:CardControl.Header>
<StackPanel Orientation="Vertical">
Expand Down
3 changes: 3 additions & 0 deletions FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ hides repeat, shuffle and player info</System:String>
<System:String x:Key="TaskbarWidgetShowPauseOverlayDescription">When media is paused, display a pause icon over the album art</System:String>
<System:String x:Key="TaskbarWidgetControlsTitle">Widget Media Controls</System:String>
<System:String x:Key="TaskbarWidgetControlsDescription">Show media control buttons on the widget</System:String>
<System:String x:Key="PositionDefault">Default</System:String>
<System:String x:Key="TaskbarWidgetCoverPositionTitle">Album Art Position</System:String>
<System:String x:Key="TaskbarWidgetCoverPositionDescription">Default is left. If the widget position is set to bottom-right, default is right.</System:String>
<System:String x:Key="TaskbarWidgetControlsPositionTitle">Media Controls Position</System:String>
<System:String x:Key="TaskbarWidgetControlsPositionDescription">Choose where to display the media control buttons</System:String>
<System:String x:Key="PositionLeft">Left</System:String>
Expand Down
3 changes: 3 additions & 0 deletions FluentFlyoutWPF/Resources/Localization/Dictionary-zh-CN.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@
<system:String x:Key="ImportSuccessful">导入成功</system:String>
<system:String x:Key="ImportFailed">导入失败</system:String>
<system:String x:Key="SettingsExportedSuccessfully">设置导出成功!</system:String>
<system:String x:Key="PositionDefault">默认</system:String>
<system:String x:Key="TaskbarWidgetCoverPositionTitle">封面位置</system:String>
<system:String x:Key="TaskbarWidgetCoverPositionDescription">默认靠左,如果小组件位置设置为右下角,则默认靠右。</system:String>
<system:String x:Key="TaskbarWidgetControlsPositionTitle">媒体控制按键位置</system:String>
<system:String x:Key="TaskbarWidgetControlsPositionDescription">选择媒体控制按钮在小组件内的显示位置</system:String>
<system:String x:Key="BackupRestoreCardTitle">将你的设置进行备份和恢复</system:String>
Expand Down
34 changes: 32 additions & 2 deletions FluentFlyoutWPF/ViewModels/UserSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,12 @@ public string LockKeysDurationText
[ObservableProperty]
public partial int TaskbarWidgetPosition { get; set; }

/// <summary>
/// Position of the taskbar widget cover image. 0: Default, 1: Left, 2: Right
/// </summary>
[ObservableProperty]
public partial int TaskbarWidgetCoverPosition { get; set; }

/// <summary>
/// Determines whether padding should be applied to the taskbar widget for the native Windows Widgets button
/// </summary>
Expand Down Expand Up @@ -751,6 +757,7 @@ public UserSettings()
TaskbarWidgetEnabled = false;
TaskbarWidgetSelectedMonitor = 0;
TaskbarWidgetPosition = 0;
TaskbarWidgetCoverPosition = 0;
TaskbarWidgetPadding = true;
TaskbarWidgetManualPadding = 0;
TaskbarWidgetBackgroundBlur = false;
Expand Down Expand Up @@ -903,9 +910,21 @@ partial void OnTaskbarWidgetEnabledChanged(bool oldValue, bool newValue)
partial void OnTaskbarWidgetPositionChanged(int oldValue, int newValue)
{
if (oldValue == newValue || _initializing) return;
if (TaskbarWidgetCoverPosition == 0)
{
UpdateTaskbarWidgetLayout();
return;
}

UpdateTaskbar();
}

partial void OnTaskbarWidgetCoverPositionChanged(int oldValue, int newValue)
{
if (oldValue == newValue || _initializing) return;
UpdateTaskbarWidgetLayout();
}

partial void OnTaskbarWidgetManualPaddingChanged(int oldValue, int newValue)
{
if (oldValue == newValue || _initializing) return;
Expand Down Expand Up @@ -946,8 +965,7 @@ partial void OnTaskbarWidgetControlsPositionChanged(int oldValue, int newValue)
{
if (oldValue == newValue || _initializing) return;

MainWindow mainWindow = (MainWindow)Application.Current.MainWindow;
mainWindow.taskbarWindow?.Widget?.ReorderControls();
UpdateTaskbarWidgetLayout();
}

partial void OnTaskbarVisualizerPositionChanged(int oldValue, int newValue)
Expand All @@ -968,6 +986,18 @@ private void UpdateTaskbar()
mainWindow.UpdateTaskbar();
}

private void UpdateTaskbarWidgetLayout()
{
MainWindow mainWindow = (MainWindow)Application.Current.MainWindow;
var widget = mainWindow.taskbarWindow?.Widget;
if (widget != null)
{
widget.Dispatcher.Invoke(widget.ReorderControls);
}

mainWindow.UpdateTaskbar();
}

partial void OnTaskbarWidgetScrollingEnabledChanged(bool oldValue, bool newValue)
{
if (oldValue == newValue || _initializing) return;
Expand Down
Loading