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
71 changes: 71 additions & 0 deletions src/Filee.App/Controls/Spin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Turns an icon while Spin.IsActive is true (About: Check now, Engines: Check again). It is done in code, not with a
// XAML animation: a Style.Animations on RotateTransform.Angle in Soft.axaml stopped the ComboBox chevron from flipping.
// With "Reduce animations" on the icon does not turn.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Threading;
using Filee.App.Services;

namespace Filee.App.Controls;

public static class Spin
{
private const double TurnMilliseconds = 900;

public static readonly AttachedProperty<bool> IsActiveProperty =
AvaloniaProperty.RegisterAttached<Control, bool>("IsActive", typeof(Spin));

public static bool GetIsActive(Control control) => control.GetValue(IsActiveProperty);
public static void SetIsActive(Control control, bool value) => control.SetValue(IsActiveProperty, value);

private sealed class State
{
public DispatcherTimer? Timer;
}

private static readonly ConditionalWeakTable<Control, State> States = new();

static Spin()
{
IsActiveProperty.Changed.AddClassHandler<Control>((control, e) =>
{
if (e.NewValue is true)
Start(control);
else
Stop(control);
});
}

private static void Start(Control control)
{
if (!Motion.Enabled)
return;
var state = States.GetOrCreateValue(control);
if (state.Timer is not null)
return;

var clock = Stopwatch.StartNew();
var timer = new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(16) };
timer.Tick += (_, _) =>
{
var angle = clock.Elapsed.TotalMilliseconds % TurnMilliseconds / TurnMilliseconds * 360;
control.RenderTransform = new RotateTransform(angle); // the origin is the middle of the icon
};
state.Timer = timer;
control.DetachedFromVisualTree += (_, _) => Stop(control);
timer.Start();
}

private static void Stop(Control control)
{
if (!States.TryGetValue(control, out var state) || state.Timer is null)
return;
state.Timer.Stop();
state.Timer = null;
control.RenderTransform = null;
}
}
5 changes: 1 addition & 4 deletions src/Filee.App/Styles/Soft.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -427,17 +427,14 @@
<Setter Property="Foreground" Value="{DynamicResource AccentStrongBrush}" />
</Style>

<!-- ToggleSwitch: M3 track, knob and hover color -->
<!-- ToggleSwitch: M3 track, knob and hover color. No hover scale on the control itself: its label is part of it, so the text would grow with the switch. -->
<Style Selector="Window.material-you ToggleSwitch">
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.18" Easing="{StaticResource SoftEase}" />
</Transitions>
</Setter>
</Style>
<Style Selector="Window.material-you ToggleSwitch:pointerover">
<Setter Property="RenderTransform" Value="scale(1.02)" />
</Style>
<Style Selector="Window.material-you ToggleSwitch /template/ Border#SwitchKnobBounds">
<Setter Property="CornerRadius" Value="{DynamicResource SoftRadiusPill}" />
<Setter Property="Transitions">
Expand Down
12 changes: 11 additions & 1 deletion src/Filee.App/ViewModels/Pages/AboutPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,18 @@ private void OnUpdatesChanged(object? sender, System.ComponentModel.PropertyChan
ShowUpdateState(_updates.LatestVersion);
}

/// <summary>A check can finish in a blink; the arrow on "Check now" keeps turning for at least this long so it reads as checking.</summary>
private static readonly TimeSpan MinimumCheckTime = TimeSpan.FromMilliseconds(900);

[RelayCommand]
private async Task CheckNow() => ShowUpdateState(await _updates.CheckAsync(), afterCheck: true);
private async Task CheckNow()
{
// With "Reduce animations" on there is no arrow turning, so there is nothing to hold the result back for.
var minimumSpin = Task.Delay(Motion.Enabled ? MinimumCheckTime : TimeSpan.Zero);
var newer = await _updates.CheckAsync();
await minimumSpin;
ShowUpdateState(newer, afterCheck: true);
}

/// <summary>Downloads and runs the new installer, which updates Filee in place (a portable copy opens the download page).</summary>
[RelayCommand]
Expand Down
8 changes: 7 additions & 1 deletion src/Filee.App/ViewModels/Pages/EnginesPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,18 @@ private void Reload()
[RelayCommand]
private void MoveDown(EngineItemViewModel item) => Move(item, +1);

/// <summary>The check can be over in a blink; the arrow on "Check again" keeps turning for at least this long so it reads as checking.</summary>
private static readonly TimeSpan MinimumRecheckTime = TimeSpan.FromMilliseconds(900);

[RelayCommand]
private void Recheck()
private async Task Recheck()
{
// With "Reduce animations" on there is no arrow turning, so there is nothing to hold the result back for.
var minimumSpin = Task.Delay(Motion.Enabled ? MinimumRecheckTime : TimeSpan.Zero);
EngineEnvironment.ForgetSystemCopies(); // a tool installed meanwhile is found
_downloads.RefreshStatus();
_catalog.Refresh();
await minimumSpin;
}

private void Move(EngineItemViewModel item, int delta)
Expand Down
9 changes: 8 additions & 1 deletion src/Filee.App/Views/Pages/AboutPage.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@
<TextBlock Classes="h2" Text="{DynamicResource about.updates}" />
<ToggleSwitch Content="{DynamicResource general.check_updates}" IsChecked="{Binding CheckForUpdates}" />
<WrapPanel>
<Button Content="{DynamicResource general.check_now}" Command="{Binding CheckNowCommand}" Margin="0,0,10,0" />
<Button Command="{Binding CheckNowCommand}" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal" Spacing="6">
<!-- Turns while the check runs (Spin.IsActive follows CheckNowCommand.IsRunning; the code is in Controls/Spin.cs). -->
<PathIcon c:Spin.IsActive="{Binding CheckNowCommand.IsRunning}"
Width="13" Height="13" Data="{StaticResource Icon.Refresh}" />
<TextBlock Text="{DynamicResource general.check_now}" />
</StackPanel>
</Button>
<Button Classes="accent" Command="{Binding DownloadUpdateCommand}" IsVisible="{Binding UpdateAvailable}" Margin="0,0,10,0">
<StackPanel Orientation="Horizontal" Spacing="6">
<PathIcon Width="13" Height="13" Data="{StaticResource Icon.Download}" />
Expand Down
5 changes: 4 additions & 1 deletion src/Filee.App/Views/Pages/EnginesPage.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Filee.App.ViewModels.Pages"
xmlns:svc="using:Filee.App.Services"
xmlns:c="using:Filee.App.Controls"
x:Class="Filee.App.Views.Pages.EnginesPage"
x:DataType="vm:EnginesPageViewModel">
<UserControl.Styles>
Expand All @@ -25,7 +26,9 @@
<DockPanel>
<Button DockPanel.Dock="Right" Command="{Binding RecheckCommand}">
<StackPanel Orientation="Horizontal" Spacing="6">
<PathIcon Width="13" Height="13" Data="{StaticResource Icon.Refresh}" />
<!-- Turns while the recheck runs (Spin.IsActive follows RecheckCommand.IsRunning; the code is in Controls/Spin.cs). -->
<PathIcon c:Spin.IsActive="{Binding RecheckCommand.IsRunning}"
Width="13" Height="13" Data="{StaticResource Icon.Refresh}" />
<TextBlock Text="{DynamicResource engines.recheck}" />
</StackPanel>
</Button>
Expand Down
Loading