mirror of
https://github.com/osukey/osukey.git
synced 2025-07-01 16:29:58 +09:00
Merge branch 'master' into notification-fling
This commit is contained in:
@ -5,26 +5,24 @@ using System;
|
|||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osuTK;
|
|
||||||
using Squirrel;
|
using Squirrel;
|
||||||
using Squirrel.SimpleSplat;
|
using Squirrel.SimpleSplat;
|
||||||
|
using LogLevel = Squirrel.SimpleSplat.LogLevel;
|
||||||
|
using UpdateManager = osu.Game.Updater.UpdateManager;
|
||||||
|
|
||||||
namespace osu.Desktop.Updater
|
namespace osu.Desktop.Updater
|
||||||
{
|
{
|
||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
|
public class SquirrelUpdateManager : UpdateManager
|
||||||
{
|
{
|
||||||
private UpdateManager? updateManager;
|
private Squirrel.UpdateManager? updateManager;
|
||||||
private INotificationOverlay notificationOverlay = null!;
|
private INotificationOverlay notificationOverlay = null!;
|
||||||
|
|
||||||
public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited();
|
public Task PrepareUpdateAsync() => Squirrel.UpdateManager.RestartAppWhenExited();
|
||||||
|
|
||||||
private static readonly Logger logger = Logger.GetLogger("updater");
|
private static readonly Logger logger = Logger.GetLogger("updater");
|
||||||
|
|
||||||
@ -35,6 +33,9 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
|
private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGameBase game { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(INotificationOverlay notifications)
|
private void load(INotificationOverlay notifications)
|
||||||
{
|
{
|
||||||
@ -63,7 +64,14 @@ namespace osu.Desktop.Updater
|
|||||||
if (updatePending)
|
if (updatePending)
|
||||||
{
|
{
|
||||||
// the user may have dismissed the completion notice, so show it again.
|
// the user may have dismissed the completion notice, so show it again.
|
||||||
notificationOverlay.Post(new UpdateCompleteNotification(this));
|
notificationOverlay.Post(new UpdateApplicationCompleteNotification
|
||||||
|
{
|
||||||
|
Activated = () =>
|
||||||
|
{
|
||||||
|
restartToApplyUpdate();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,19 +83,21 @@ namespace osu.Desktop.Updater
|
|||||||
|
|
||||||
if (notification == null)
|
if (notification == null)
|
||||||
{
|
{
|
||||||
notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active };
|
notification = new UpdateProgressNotification
|
||||||
|
{
|
||||||
|
CompletionClickAction = restartToApplyUpdate,
|
||||||
|
};
|
||||||
|
|
||||||
Schedule(() => notificationOverlay.Post(notification));
|
Schedule(() => notificationOverlay.Post(notification));
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.Progress = 0;
|
notification.StartDownload();
|
||||||
notification.Text = @"Downloading update...";
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||||
|
|
||||||
notification.Progress = 0;
|
notification.StartInstall();
|
||||||
notification.Text = @"Installing update...";
|
|
||||||
|
|
||||||
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||||
|
|
||||||
@ -107,9 +117,7 @@ namespace osu.Desktop.Updater
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// In the case of an error, a separate notification will be displayed.
|
// In the case of an error, a separate notification will be displayed.
|
||||||
notification.State = ProgressNotificationState.Cancelled;
|
notification.FailDownload();
|
||||||
notification.Close();
|
|
||||||
|
|
||||||
Logger.Error(e, @"update failed!");
|
Logger.Error(e, @"update failed!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,78 +139,24 @@ namespace osu.Desktop.Updater
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool restartToApplyUpdate()
|
||||||
|
{
|
||||||
|
PrepareUpdateAsync()
|
||||||
|
.ContinueWith(_ => Schedule(() => game.AttemptExit()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
updateManager?.Dispose();
|
updateManager?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UpdateCompleteNotification : ProgressCompletionNotification
|
|
||||||
{
|
|
||||||
[Resolved]
|
|
||||||
private OsuGame game { get; set; } = null!;
|
|
||||||
|
|
||||||
public UpdateCompleteNotification(SquirrelUpdateManager updateManager)
|
|
||||||
{
|
|
||||||
Text = @"Update ready to install. Click to restart!";
|
|
||||||
|
|
||||||
Activated = () =>
|
|
||||||
{
|
|
||||||
updateManager.PrepareUpdateAsync()
|
|
||||||
.ContinueWith(_ => updateManager.Schedule(() => game.AttemptExit()));
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UpdateProgressNotification : ProgressNotification
|
|
||||||
{
|
|
||||||
private readonly SquirrelUpdateManager updateManager;
|
|
||||||
|
|
||||||
public UpdateProgressNotification(SquirrelUpdateManager updateManager)
|
|
||||||
{
|
|
||||||
this.updateManager = updateManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Notification CreateCompletionNotification()
|
|
||||||
{
|
|
||||||
return new UpdateCompleteNotification(updateManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
IconContent.AddRange(new Drawable[]
|
|
||||||
{
|
|
||||||
new SpriteIcon
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Icon = FontAwesome.Solid.Upload,
|
|
||||||
Size = new Vector2(20),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
// cancelling updates is not currently supported by the underlying updater.
|
|
||||||
// only allow dismissing for now.
|
|
||||||
|
|
||||||
switch (State)
|
|
||||||
{
|
|
||||||
case ProgressNotificationState.Cancelled:
|
|
||||||
base.Close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SquirrelLogger : ILogger, IDisposable
|
private class SquirrelLogger : ILogger, IDisposable
|
||||||
{
|
{
|
||||||
public Squirrel.SimpleSplat.LogLevel Level { get; set; } = Squirrel.SimpleSplat.LogLevel.Info;
|
public LogLevel Level { get; set; } = LogLevel.Info;
|
||||||
|
|
||||||
public void Write(string message, Squirrel.SimpleSplat.LogLevel logLevel)
|
public void Write(string message, LogLevel logLevel)
|
||||||
{
|
{
|
||||||
if (logLevel < Level)
|
if (logLevel < Level)
|
||||||
return;
|
return;
|
||||||
|
@ -97,6 +97,25 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCorrectAnimationStartTime()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyStoryboardDecoder();
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("animation-starts-before-alpha.osb"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var storyboard = decoder.Decode(stream);
|
||||||
|
|
||||||
|
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||||
|
Assert.AreEqual(1, background.Elements.Count);
|
||||||
|
|
||||||
|
Assert.AreEqual(2000, background.Elements[0].StartTime);
|
||||||
|
// This property should be used in DrawableStoryboardAnimation as a starting point for animation playback.
|
||||||
|
Assert.AreEqual(1000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestOutOfOrderStartTimes()
|
public void TestOutOfOrderStartTimes()
|
||||||
{
|
{
|
||||||
|
@ -118,17 +118,31 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.IsNull(filterCriteria.BPM.Max);
|
Assert.IsNull(filterCriteria.BPM.Max);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly object[] length_query_examples =
|
private static readonly object[] correct_length_query_examples =
|
||||||
{
|
{
|
||||||
new object[] { "6ms", TimeSpan.FromMilliseconds(6), TimeSpan.FromMilliseconds(1) },
|
|
||||||
new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) },
|
new object[] { "23s", TimeSpan.FromSeconds(23), TimeSpan.FromSeconds(1) },
|
||||||
new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) },
|
new object[] { "9m", TimeSpan.FromMinutes(9), TimeSpan.FromMinutes(1) },
|
||||||
new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) },
|
new object[] { "0.25h", TimeSpan.FromHours(0.25), TimeSpan.FromHours(1) },
|
||||||
new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) },
|
new object[] { "70", TimeSpan.FromSeconds(70), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "7m27s", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "7:27", TimeSpan.FromSeconds(447), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "1h2m3s", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "1h2m3.5s", TimeSpan.FromSeconds(3723.5), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "1:2:3", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "1:02:03", TimeSpan.FromSeconds(3723), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "6", TimeSpan.FromSeconds(6), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "6.5", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "6.5s", TimeSpan.FromSeconds(6.5), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "6.5m", TimeSpan.FromMinutes(6.5), TimeSpan.FromMinutes(1) },
|
||||||
|
new object[] { "6h5m", TimeSpan.FromMinutes(365), TimeSpan.FromMinutes(1) },
|
||||||
|
new object[] { "65m", TimeSpan.FromMinutes(65), TimeSpan.FromMinutes(1) },
|
||||||
|
new object[] { "90s", TimeSpan.FromSeconds(90), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "80m20s", TimeSpan.FromSeconds(4820), TimeSpan.FromSeconds(1) },
|
||||||
|
new object[] { "1h20s", TimeSpan.FromSeconds(3620), TimeSpan.FromSeconds(1) },
|
||||||
};
|
};
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
[TestCaseSource(nameof(length_query_examples))]
|
[TestCaseSource(nameof(correct_length_query_examples))]
|
||||||
public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale)
|
public void TestApplyLengthQueries(string lengthQuery, TimeSpan expectedLength, TimeSpan scale)
|
||||||
{
|
{
|
||||||
string query = $"length={lengthQuery} time";
|
string query = $"length={lengthQuery} time";
|
||||||
@ -140,6 +154,29 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
|||||||
Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max);
|
Assert.AreEqual(expectedLength.TotalMilliseconds + scale.TotalMilliseconds / 2.0, filterCriteria.Length.Max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly object[] incorrect_length_query_examples =
|
||||||
|
{
|
||||||
|
new object[] { "7.5m27s" },
|
||||||
|
new object[] { "7m27" },
|
||||||
|
new object[] { "7m7m7m" },
|
||||||
|
new object[] { "7m70s" },
|
||||||
|
new object[] { "5s6m" },
|
||||||
|
new object[] { "0:" },
|
||||||
|
new object[] { ":0" },
|
||||||
|
new object[] { "0:3:" },
|
||||||
|
new object[] { "3:15.5" },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[TestCaseSource(nameof(incorrect_length_query_examples))]
|
||||||
|
public void TestInvalidLengthQueries(string lengthQuery)
|
||||||
|
{
|
||||||
|
string query = $"length={lengthQuery} time";
|
||||||
|
var filterCriteria = new FilterCriteria();
|
||||||
|
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||||
|
Assert.AreEqual(false, filterCriteria.Length.HasFilter);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestApplyDivisorQueries()
|
public void TestApplyDivisorQueries()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
[Events]
|
||||||
|
//Storyboard Layer 0 (Background)
|
||||||
|
Animation,Background,Centre,"img.jpg",320,240,2,150,LoopForever
|
||||||
|
S,0,1000,1500,0.08 // animation should start playing from this point in time..
|
||||||
|
F,0,2000,,0,1 // .. not this point in time
|
@ -12,6 +12,7 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Updater;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -239,6 +240,35 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddUntilStep("light is not visible", () => notification.ChildrenOfType<Notification.NotificationLight>().Single().Alpha == 0);
|
AddUntilStep("light is not visible", () => notification.ChildrenOfType<Notification.NotificationLight>().Single().Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUpdateNotificationFlow()
|
||||||
|
{
|
||||||
|
bool applyUpdate = false;
|
||||||
|
|
||||||
|
AddStep(@"post update", () =>
|
||||||
|
{
|
||||||
|
applyUpdate = false;
|
||||||
|
|
||||||
|
var updateNotification = new UpdateManager.UpdateProgressNotification
|
||||||
|
{
|
||||||
|
CompletionClickAction = () => applyUpdate = true
|
||||||
|
};
|
||||||
|
|
||||||
|
notificationOverlay.Post(updateNotification);
|
||||||
|
progressingNotifications.Add(updateNotification);
|
||||||
|
});
|
||||||
|
|
||||||
|
checkProgressingCount(1);
|
||||||
|
waitForCompletion();
|
||||||
|
|
||||||
|
UpdateManager.UpdateApplicationCompleteNotification? completionNotification = null;
|
||||||
|
AddUntilStep("wait for completion notification",
|
||||||
|
() => (completionNotification = notificationOverlay.ChildrenOfType<UpdateManager.UpdateApplicationCompleteNotification>().SingleOrDefault()) != null);
|
||||||
|
AddStep("click notification", () => completionNotification?.TriggerClick());
|
||||||
|
|
||||||
|
AddUntilStep("wait for update applied", () => applyUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasicFlow()
|
public void TestBasicFlow()
|
||||||
{
|
{
|
||||||
|
@ -33,6 +33,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether only a single instance of this <see cref="MultiplayerCountdown"/> type may be active at any one time.
|
/// Whether only a single instance of this <see cref="MultiplayerCountdown"/> type may be active at any one time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[IgnoreMember]
|
||||||
public virtual bool IsExclusive => true;
|
public virtual bool IsExclusive => true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,6 +226,7 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background5,
|
Colour = colourProvider.Background5,
|
||||||
|
Depth = float.MaxValue,
|
||||||
},
|
},
|
||||||
loadingSpinner = new LoadingSpinner
|
loadingSpinner = new LoadingSpinner
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@ -125,10 +124,24 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
if (Enum.TryParse(value, true, out result)) return true;
|
if (Enum.TryParse(value, true, out result)) return true;
|
||||||
|
|
||||||
value = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture));
|
string? prefixMatch = Enum.GetNames(typeof(TEnum)).FirstOrDefault(name => name.StartsWith(value, true, CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
if (prefixMatch == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
return Enum.TryParse(value, true, out result);
|
return Enum.TryParse(value, true, out result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static GroupCollection? tryMatchRegex(string value, string regex)
|
||||||
|
{
|
||||||
|
Match matches = Regex.Match(value, regex);
|
||||||
|
|
||||||
|
if (matches.Success)
|
||||||
|
return matches.Groups;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to parse a keyword filter with the specified <paramref name="op"/> and textual <paramref name="value"/>.
|
/// Attempts to parse a keyword filter with the specified <paramref name="op"/> and textual <paramref name="value"/>.
|
||||||
/// If the value indicates a valid textual filter, the function returns <c>true</c> and the resulting data is stored into
|
/// If the value indicates a valid textual filter, the function returns <c>true</c> and the resulting data is stored into
|
||||||
@ -312,11 +325,45 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val)
|
private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val)
|
||||||
{
|
{
|
||||||
if (!tryParseDoubleWithPoint(val.TrimEnd('m', 's', 'h'), out double length))
|
List<string> parts = new List<string>();
|
||||||
|
|
||||||
|
GroupCollection? match = null;
|
||||||
|
|
||||||
|
match ??= tryMatchRegex(val, @"^((?<hours>\d+):)?(?<minutes>\d+):(?<seconds>\d+)$");
|
||||||
|
match ??= tryMatchRegex(val, @"^((?<hours>\d+(\.\d+)?)h)?((?<minutes>\d+(\.\d+)?)m)?((?<seconds>\d+(\.\d+)?)s)?$");
|
||||||
|
match ??= tryMatchRegex(val, @"^(?<seconds>\d+(\.\d+)?)$");
|
||||||
|
|
||||||
|
if (match == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int scale = getLengthScale(val);
|
if (match["seconds"].Success)
|
||||||
return tryUpdateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0);
|
parts.Add(match["seconds"].Value + "s");
|
||||||
|
if (match["minutes"].Success)
|
||||||
|
parts.Add(match["minutes"].Value + "m");
|
||||||
|
if (match["hours"].Success)
|
||||||
|
parts.Add(match["hours"].Value + "h");
|
||||||
|
|
||||||
|
double totalLength = 0;
|
||||||
|
int minScale = 3600000;
|
||||||
|
|
||||||
|
for (int i = 0; i < parts.Count; i++)
|
||||||
|
{
|
||||||
|
string part = parts[i];
|
||||||
|
string partNoUnit = part.TrimEnd('m', 's', 'h');
|
||||||
|
if (!tryParseDoubleWithPoint(partNoUnit, out double length))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (i != parts.Count - 1 && length >= 60)
|
||||||
|
return false;
|
||||||
|
if (i != 0 && partNoUnit.Contains('.'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int scale = getLengthScale(part);
|
||||||
|
totalLength += length * scale;
|
||||||
|
minScale = Math.Min(minScale, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -115,6 +116,21 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
Animation.ApplyTransforms(this);
|
Animation.ApplyTransforms(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IGameplayClock gameplayClock { get; set; }
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
// Framework animation class tries its best to synchronise the animation at LoadComplete,
|
||||||
|
// but in some cases (such as fast forward) this results in an incorrect start offset.
|
||||||
|
//
|
||||||
|
// In the case of storyboard animations, we want to synchronise with game time perfectly
|
||||||
|
// so let's get a correct time based on gameplay clock and earliest transform.
|
||||||
|
PlaybackPosition = gameplayClock.CurrentTime - Animation.EarliestTransformTime;
|
||||||
|
}
|
||||||
|
|
||||||
private void skinSourceChanged()
|
private void skinSourceChanged()
|
||||||
{
|
{
|
||||||
ClearFrames();
|
ClearFrames();
|
||||||
|
@ -54,6 +54,14 @@ namespace osu.Game.Storyboards
|
|||||||
return firstAlpha.startTime;
|
return firstAlpha.startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return EarliestTransformTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double EarliestTransformTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
// If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value.
|
// If we got to this point, either no alpha commands were present, or the earliest had a non-zero start value.
|
||||||
// The sprite's StartTime will be determined by the earliest command, regardless of type.
|
// The sprite's StartTime will be determined by the earliest command, regardless of type.
|
||||||
double earliestStartTime = TimelineGroup.StartTime;
|
double earliestStartTime = TimelineGroup.StartTime;
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Updater
|
namespace osu.Game.Updater
|
||||||
{
|
{
|
||||||
@ -27,13 +27,13 @@ namespace osu.Game.Updater
|
|||||||
GetType() != typeof(UpdateManager);
|
GetType() != typeof(UpdateManager);
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase game { get; set; }
|
private OsuGameBase game { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected INotificationOverlay Notifications { get; private set; }
|
protected INotificationOverlay Notifications { get; private set; } = null!;
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Updater
|
|||||||
|
|
||||||
private readonly object updateTaskLock = new object();
|
private readonly object updateTaskLock = new object();
|
||||||
|
|
||||||
private Task<bool> updateCheckTask;
|
private Task<bool>? updateCheckTask;
|
||||||
|
|
||||||
public async Task<bool> CheckForUpdateAsync()
|
public async Task<bool> CheckForUpdateAsync()
|
||||||
{
|
{
|
||||||
@ -109,5 +109,76 @@ namespace osu.Game.Updater
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class UpdateApplicationCompleteNotification : ProgressCompletionNotification
|
||||||
|
{
|
||||||
|
public UpdateApplicationCompleteNotification()
|
||||||
|
{
|
||||||
|
Text = @"Update ready to install. Click to restart!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UpdateProgressNotification : ProgressNotification
|
||||||
|
{
|
||||||
|
protected override Notification CreateCompletionNotification() => new UpdateApplicationCompleteNotification
|
||||||
|
{
|
||||||
|
Activated = CompletionClickAction
|
||||||
|
};
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
IconContent.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Icon = FontAwesome.Solid.Upload,
|
||||||
|
Size = new Vector2(34),
|
||||||
|
Colour = OsuColour.Gray(0.2f),
|
||||||
|
Depth = float.MaxValue,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
StartDownload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Close()
|
||||||
|
{
|
||||||
|
// cancelling updates is not currently supported by the underlying updater.
|
||||||
|
// only allow dismissing for now.
|
||||||
|
|
||||||
|
switch (State)
|
||||||
|
{
|
||||||
|
case ProgressNotificationState.Cancelled:
|
||||||
|
base.Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartDownload()
|
||||||
|
{
|
||||||
|
State = ProgressNotificationState.Active;
|
||||||
|
Progress = 0;
|
||||||
|
Text = @"Downloading update...";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartInstall()
|
||||||
|
{
|
||||||
|
Progress = 0;
|
||||||
|
Text = @"Installing update...";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FailDownload()
|
||||||
|
{
|
||||||
|
State = ProgressNotificationState.Cancelled;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user