mirror of
https://github.com/ppy/osu.git
synced 2025-02-20 20:33:21 +08:00
Merge branch 'master' into replay-analysis-settings
This commit is contained in:
commit
0e16508fd6
@ -80,7 +80,7 @@ namespace osu.Android
|
||||
host.Window.CursorState |= CursorState.Hidden;
|
||||
}
|
||||
|
||||
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
||||
protected override UpdateManager CreateUpdateManager() => new MobileUpdateNotifier();
|
||||
|
||||
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
||||
|
||||
|
@ -2,10 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Win32;
|
||||
using osu.Desktop.Performance;
|
||||
using osu.Desktop.Security;
|
||||
@ -102,35 +102,13 @@ namespace osu.Desktop
|
||||
if (!string.IsNullOrEmpty(packageManaged))
|
||||
return new NoActionUpdateManager();
|
||||
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
Debug.Assert(OperatingSystem.IsWindows());
|
||||
|
||||
return new SquirrelUpdateManager();
|
||||
|
||||
default:
|
||||
return new SimpleUpdateManager();
|
||||
}
|
||||
return new VelopackUpdateManager();
|
||||
}
|
||||
|
||||
public override bool RestartAppWhenExited()
|
||||
{
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
Debug.Assert(OperatingSystem.IsWindows());
|
||||
|
||||
// Of note, this is an async method in squirrel that adds an arbitrary delay before returning
|
||||
// likely to ensure the external process is in a good state.
|
||||
//
|
||||
// We're not waiting on that here, but the outro playing before the actual exit should be enough
|
||||
// to cover this.
|
||||
Squirrel.UpdateManager.RestartAppWhenExited().FireAndForget();
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.RestartAppWhenExited();
|
||||
Task.Run(() => Velopack.UpdateExe.Start()).FireAndForget();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using osu.Desktop.LegacyIpc;
|
||||
using osu.Desktop.Windows;
|
||||
using osu.Framework;
|
||||
@ -14,7 +13,7 @@ using osu.Game;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Tournament;
|
||||
using SDL;
|
||||
using Squirrel;
|
||||
using Velopack;
|
||||
|
||||
namespace osu.Desktop
|
||||
{
|
||||
@ -31,19 +30,11 @@ namespace osu.Desktop
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
/*
|
||||
* WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK!
|
||||
*
|
||||
* Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it.
|
||||
* To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit,
|
||||
* namely by checking loaded assemblies:
|
||||
* https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32
|
||||
*
|
||||
* If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded -
|
||||
* the app will then do completely broken things like:
|
||||
* - not creating system shortcuts (as the logic is if'd out if "running tests")
|
||||
* - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests")
|
||||
*/
|
||||
// IMPORTANT DON'T IGNORE: For general sanity, velopack's setup needs to run before anything else.
|
||||
// This has bitten us in the rear before (bricked updater), and although the underlying issue from
|
||||
// last time has been fixed, let's not tempt fate.
|
||||
setupVelopack();
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
var windowsVersion = Environment.OSVersion.Version;
|
||||
@ -66,8 +57,6 @@ namespace osu.Desktop
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setupSquirrel();
|
||||
}
|
||||
|
||||
// NVIDIA profiles are based on the executable name of a process.
|
||||
@ -177,32 +166,14 @@ namespace osu.Desktop
|
||||
return false;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static void setupSquirrel()
|
||||
private static void setupVelopack()
|
||||
{
|
||||
SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) =>
|
||||
{
|
||||
tools.CreateShortcutForThisExe();
|
||||
tools.CreateUninstallerRegistryEntry();
|
||||
WindowsAssociationManager.InstallAssociations();
|
||||
}, onAppUpdate: (_, tools) =>
|
||||
{
|
||||
tools.CreateUninstallerRegistryEntry();
|
||||
WindowsAssociationManager.UpdateAssociations();
|
||||
}, onAppUninstall: (_, tools) =>
|
||||
{
|
||||
tools.RemoveShortcutForThisExe();
|
||||
tools.RemoveUninstallerRegistryEntry();
|
||||
WindowsAssociationManager.UninstallAssociations();
|
||||
}, onEveryRun: (_, _, _) =>
|
||||
{
|
||||
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
|
||||
// causes the right-click context menu to function incorrectly.
|
||||
//
|
||||
// This may turn out to be non-required after an alternative solution is implemented.
|
||||
// see https://github.com/clowd/Clowd.Squirrel/issues/24
|
||||
// tools.SetProcessAppUserModelId();
|
||||
});
|
||||
VelopackApp
|
||||
.Build()
|
||||
.WithFirstRun(v =>
|
||||
{
|
||||
if (OperatingSystem.IsWindows()) WindowsAssociationManager.InstallAssociations();
|
||||
}).Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,180 +0,0 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Play;
|
||||
using Squirrel.SimpleSplat;
|
||||
using Squirrel.Sources;
|
||||
using LogLevel = Squirrel.SimpleSplat.LogLevel;
|
||||
using UpdateManager = osu.Game.Updater.UpdateManager;
|
||||
|
||||
namespace osu.Desktop.Updater
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public partial class SquirrelUpdateManager : UpdateManager
|
||||
{
|
||||
private Squirrel.UpdateManager? updateManager;
|
||||
private INotificationOverlay notificationOverlay = null!;
|
||||
|
||||
public Task PrepareUpdateAsync() => Squirrel.UpdateManager.RestartAppWhenExited();
|
||||
|
||||
private static readonly Logger logger = Logger.GetLogger("updater");
|
||||
|
||||
/// <summary>
|
||||
/// Whether an update has been downloaded but not yet applied.
|
||||
/// </summary>
|
||||
private bool updatePending;
|
||||
|
||||
private readonly SquirrelLogger squirrelLogger = new SquirrelLogger();
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(INotificationOverlay notifications)
|
||||
{
|
||||
notificationOverlay = notifications;
|
||||
|
||||
SquirrelLocator.CurrentMutable.Register(() => squirrelLogger, typeof(ILogger));
|
||||
}
|
||||
|
||||
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
||||
|
||||
private async Task<bool> checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification? notification = null)
|
||||
{
|
||||
// should we schedule a retry on completion of this check?
|
||||
bool scheduleRecheck = true;
|
||||
|
||||
const string? github_token = null; // TODO: populate.
|
||||
|
||||
try
|
||||
{
|
||||
// Avoid any kind of update checking while gameplay is running.
|
||||
if (localUserInfo?.IsPlaying.Value == true)
|
||||
return false;
|
||||
|
||||
updateManager ??= new Squirrel.UpdateManager(new GithubSource(@"https://github.com/ppy/osu", github_token, false), @"osulazer");
|
||||
|
||||
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
|
||||
|
||||
if (info.ReleasesToApply.Count == 0)
|
||||
{
|
||||
if (updatePending)
|
||||
{
|
||||
// the user may have dismissed the completion notice, so show it again.
|
||||
notificationOverlay.Post(new UpdateApplicationCompleteNotification
|
||||
{
|
||||
Activated = () =>
|
||||
{
|
||||
restartToApplyUpdate();
|
||||
return true;
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// no updates available. bail and retry later.
|
||||
return false;
|
||||
}
|
||||
|
||||
scheduleRecheck = false;
|
||||
|
||||
if (notification == null)
|
||||
{
|
||||
notification = new UpdateProgressNotification
|
||||
{
|
||||
CompletionClickAction = restartToApplyUpdate,
|
||||
};
|
||||
|
||||
Schedule(() => notificationOverlay.Post(notification));
|
||||
}
|
||||
|
||||
notification.StartDownload();
|
||||
|
||||
try
|
||||
{
|
||||
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||
|
||||
notification.StartInstall();
|
||||
|
||||
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
updatePending = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (useDeltaPatching)
|
||||
{
|
||||
logger.Add(@"delta patching failed; will attempt full download!");
|
||||
|
||||
// could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
|
||||
// try again without deltas.
|
||||
await checkForUpdateAsync(false, notification).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// In the case of an error, a separate notification will be displayed.
|
||||
notification.FailDownload();
|
||||
Logger.Error(e, @"update failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
||||
scheduleRecheck = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (scheduleRecheck)
|
||||
{
|
||||
// check again in 30 minutes.
|
||||
Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool restartToApplyUpdate()
|
||||
{
|
||||
PrepareUpdateAsync()
|
||||
.ContinueWith(_ => Schedule(() => game.AttemptExit()));
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
updateManager?.Dispose();
|
||||
}
|
||||
|
||||
private class SquirrelLogger : ILogger, IDisposable
|
||||
{
|
||||
public LogLevel Level { get; set; } = LogLevel.Info;
|
||||
|
||||
public void Write(string message, LogLevel logLevel)
|
||||
{
|
||||
if (logLevel < Level)
|
||||
return;
|
||||
|
||||
logger.Add(message);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
131
osu.Desktop/Updater/VelopackUpdateManager.cs
Normal file
131
osu.Desktop/Updater/VelopackUpdateManager.cs
Normal file
@ -0,0 +1,131 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Play;
|
||||
using Velopack;
|
||||
using Velopack.Sources;
|
||||
|
||||
namespace osu.Desktop.Updater
|
||||
{
|
||||
public partial class VelopackUpdateManager : Game.Updater.UpdateManager
|
||||
{
|
||||
private readonly UpdateManager updateManager;
|
||||
private INotificationOverlay notificationOverlay = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
||||
|
||||
private UpdateInfo? pendingUpdate;
|
||||
|
||||
public VelopackUpdateManager()
|
||||
{
|
||||
updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", null, false), new UpdateOptions
|
||||
{
|
||||
AllowVersionDowngrade = true,
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(INotificationOverlay notifications)
|
||||
{
|
||||
notificationOverlay = notifications;
|
||||
}
|
||||
|
||||
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
||||
|
||||
private async Task<bool> checkForUpdateAsync(UpdateProgressNotification? notification = null)
|
||||
{
|
||||
// should we schedule a retry on completion of this check?
|
||||
bool scheduleRecheck = true;
|
||||
|
||||
try
|
||||
{
|
||||
// Avoid any kind of update checking while gameplay is running.
|
||||
if (localUserInfo?.IsPlaying.Value == true)
|
||||
return false;
|
||||
|
||||
if (pendingUpdate != null)
|
||||
{
|
||||
// If there is an update pending restart, show the notification to restart again.
|
||||
notificationOverlay.Post(new UpdateApplicationCompleteNotification
|
||||
{
|
||||
Activated = () =>
|
||||
{
|
||||
restartToApplyUpdate();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
pendingUpdate = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
|
||||
|
||||
// Handle no updates available.
|
||||
if (pendingUpdate == null)
|
||||
return false;
|
||||
|
||||
scheduleRecheck = false;
|
||||
|
||||
if (notification == null)
|
||||
{
|
||||
notification = new UpdateProgressNotification
|
||||
{
|
||||
CompletionClickAction = restartToApplyUpdate,
|
||||
};
|
||||
|
||||
Schedule(() => notificationOverlay.Post(notification));
|
||||
}
|
||||
|
||||
notification.StartDownload();
|
||||
|
||||
try
|
||||
{
|
||||
await updateManager.DownloadUpdatesAsync(pendingUpdate, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// In the case of an error, a separate notification will be displayed.
|
||||
notification.FailDownload();
|
||||
Logger.Error(e, @"update failed!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
||||
scheduleRecheck = true;
|
||||
Logger.Log($@"update check failed ({e.Message})");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (scheduleRecheck)
|
||||
{
|
||||
// check again in 30 minutes.
|
||||
Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool restartToApplyUpdate()
|
||||
{
|
||||
// TODO: Migrate this to async flow whenever available (see https://github.com/ppy/osu/pull/28743#discussion_r1740505665).
|
||||
// Currently there's an internal Thread.Sleep(300) which will cause a stutter when the user clicks to restart.
|
||||
updateManager.WaitExitThenApplyUpdates(pendingUpdate?.TargetFullRelease);
|
||||
Schedule(() => game.AttemptExit());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -23,9 +23,9 @@
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Clowd.Squirrel" Version="2.11.1" />
|
||||
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="Velopack" Version="0.0.598-g933b2ab" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
|
@ -18,7 +18,9 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
// The implementation below is probably correct but should be checked if/when exposed via controls.
|
||||
|
||||
float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime());
|
||||
float actualDistance = Math.Abs(((CatchHitObject)before).EffectiveX - ((CatchHitObject)after).EffectiveX);
|
||||
|
||||
float previousEndX = (before as JuiceStream)?.EndX ?? ((CatchHitObject)before).EffectiveX;
|
||||
float actualDistance = Math.Abs(previousEndX - ((CatchHitObject)after).EffectiveX);
|
||||
|
||||
return actualDistance / expectedDistance;
|
||||
}
|
||||
|
@ -85,9 +85,25 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
protected void SetTexture(Texture? texture, Texture? overlayTexture)
|
||||
{
|
||||
colouredSprite.Texture = texture;
|
||||
overlaySprite.Texture = overlayTexture;
|
||||
hyperSprite.Texture = texture;
|
||||
// Sizes are reset due to an arguable osu!framework bug where Sprite retains the size of the first set texture.
|
||||
|
||||
if (colouredSprite.Texture != texture)
|
||||
{
|
||||
colouredSprite.Size = Vector2.Zero;
|
||||
colouredSprite.Texture = texture;
|
||||
}
|
||||
|
||||
if (overlaySprite.Texture != overlayTexture)
|
||||
{
|
||||
overlaySprite.Size = Vector2.Zero;
|
||||
overlaySprite.Texture = overlayTexture;
|
||||
}
|
||||
|
||||
if (hyperSprite.Texture != texture)
|
||||
{
|
||||
hyperSprite.Size = Vector2.Zero;
|
||||
hyperSprite.Texture = texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
// 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.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
{
|
||||
public partial class TestSceneSliderChangeStates : TestSceneOsuEditor
|
||||
{
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||
|
||||
[TestCase(SplineType.Catmull)]
|
||||
[TestCase(SplineType.BSpline)]
|
||||
[TestCase(SplineType.Linear)]
|
||||
[TestCase(SplineType.PerfectCurve)]
|
||||
public void TestSliderRetainsCurveTypes(SplineType splineType)
|
||||
{
|
||||
Slider? slider = null;
|
||||
PathType pathType = new PathType(splineType);
|
||||
|
||||
AddStep("add slider", () => EditorBeatmap.Add(slider = new Slider
|
||||
{
|
||||
StartTime = 500,
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(Vector2.Zero, pathType),
|
||||
new PathControlPoint(new Vector2(200, 0), pathType),
|
||||
})
|
||||
}));
|
||||
AddAssert("slider has correct spline type", () => ((Slider)EditorBeatmap.HitObjects[0]).Path.ControlPoints.All(p => p.Type == pathType));
|
||||
AddStep("remove object", () => EditorBeatmap.Remove(slider));
|
||||
AddAssert("slider removed", () => EditorBeatmap.HitObjects.Count == 0);
|
||||
addUndoSteps();
|
||||
AddAssert("slider not removed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
AddAssert("slider has correct spline type", () => ((Slider)EditorBeatmap.HitObjects[0]).Path.ControlPoints.All(p => p.Type == pathType));
|
||||
}
|
||||
|
||||
private void addUndoSteps() => AddStep("undo", () => Editor.Undo());
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
@ -58,19 +59,19 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.ExpandedByMod;
|
||||
});
|
||||
AddStep("set DA", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() };
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.ExpandedByMod;
|
||||
});
|
||||
AddStep("set FL+WU+DA+AD", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() };
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.ExpandedByMod;
|
||||
});
|
||||
AddStep("set empty", () =>
|
||||
{
|
||||
@ -120,7 +121,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpandedStatePersistsWhenClicked()
|
||||
public void TestHoverExpandsAndCollapsesWhenHeaderTouched()
|
||||
{
|
||||
AddStep("add customisable mod", () =>
|
||||
{
|
||||
@ -128,34 +129,20 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
panel.Enabled.Value = true;
|
||||
});
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(false);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoverExpandsAndCollapsesWhenHeaderClicked()
|
||||
{
|
||||
AddStep("add customisable mod", () =>
|
||||
AddStep("touch header", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = true;
|
||||
var touch = new Touch(TouchSource.Touch1, header.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.BeginTouch(touch);
|
||||
Schedule(() => InputManager.EndTouch(touch));
|
||||
});
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("touch away from header", () =>
|
||||
{
|
||||
var touch = new Touch(TouchSource.Touch1, header.ScreenSpaceDrawQuad.TopLeft - new Vector2(10));
|
||||
InputManager.BeginTouch(touch);
|
||||
Schedule(() => InputManager.EndTouch(touch));
|
||||
});
|
||||
checkExpanded(false);
|
||||
}
|
||||
|
||||
|
@ -241,12 +241,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
|
||||
AddStep("dismiss mod customisation via toggle", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType<ModCustomisationHeader>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
assertCustomisationToggleState(disabled: false, active: false);
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero));
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
|
||||
@ -664,7 +660,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
||||
AddAssert("DT selected", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value), () => Is.EqualTo(1));
|
||||
|
||||
AddStep("open customisation area", () => modSelectOverlay.ChildrenOfType<ModCustomisationHeader>().Single().TriggerClick());
|
||||
AddStep("open customisation area", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType<ModCustomisationHeader>().Single()));
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
|
||||
AddStep("hover over mod settings slider", () =>
|
||||
@ -976,7 +972,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
createScreen();
|
||||
|
||||
AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
||||
AddStep("open customisation panel", () => this.ChildrenOfType<ModCustomisationHeader>().Single().TriggerClick());
|
||||
AddStep("open customisation panel", () => InputManager.MoveMouseTo(this.ChildrenOfType<ModCustomisationHeader>().Single()));
|
||||
AddAssert("search lost focus", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
|
||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||
@ -991,15 +987,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("press backspace", () => InputManager.Key(Key.BackSpace));
|
||||
AddAssert("mods not deselected", () => SelectedMods.Value.Single() is OsuModDoubleTime);
|
||||
|
||||
AddStep("move mouse to scroll bar", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType<ModSelectOverlay.ColumnScrollContainer>().Single().ScreenSpaceDrawQuad.BottomLeft + new Vector2(10f, -5f)));
|
||||
AddStep("move mouse to customisation panel", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType<ModCustomisationSection>().First()));
|
||||
|
||||
AddStep("scroll down", () => InputManager.ScrollVerticalBy(-10f));
|
||||
AddAssert("column not scrolled", () => modSelectOverlay.ChildrenOfType<ModSelectOverlay.ColumnScrollContainer>().Single().IsScrolledToStart());
|
||||
|
||||
AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddAssert("search still not focused", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("customisation panel closed by click",
|
||||
AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero));
|
||||
AddAssert("customisation panel closed",
|
||||
() => this.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
|
||||
|
@ -450,7 +450,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
// Explicit segments have a new format in which the type is injected into the middle of the control point string.
|
||||
// To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point.
|
||||
// One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments
|
||||
bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECT_CURVE;
|
||||
bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECT_CURVE || i == pathData.Path.ControlPoints.Count - 1;
|
||||
|
||||
// Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable.
|
||||
// Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder.
|
||||
|
@ -135,11 +135,6 @@ Click to see what's new!", version);
|
||||
/// </summary>
|
||||
public static LocalisableString DownloadingUpdate => new TranslatableString(getKey(@"downloading_update"), @"Downloading update...");
|
||||
|
||||
/// <summary>
|
||||
/// "Installing update..."
|
||||
/// </summary>
|
||||
public static LocalisableString InstallingUpdate => new TranslatableString(getKey(@"installing_update"), @"Installing update...");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -112,40 +112,10 @@ namespace osu.Game.Overlays.Mods
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
ExpandedState.Value = ExpandedState.Value switch
|
||||
{
|
||||
ModCustomisationPanelState.Collapsed => ModCustomisationPanelState.Expanded,
|
||||
_ => ModCustomisationPanelState.Collapsed
|
||||
};
|
||||
}
|
||||
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
private bool touchedThisFrame;
|
||||
|
||||
protected override bool OnTouchDown(TouchDownEvent e)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
touchedThisFrame = true;
|
||||
Schedule(() => touchedThisFrame = false);
|
||||
}
|
||||
|
||||
return base.OnTouchDown(e);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
if (!touchedThisFrame && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed)
|
||||
panel.ExpandedState.Value = ModCustomisationPanelState.ExpandedByHover;
|
||||
}
|
||||
if (Enabled.Value && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed)
|
||||
panel.ExpandedState.Value = ModCustomisationPanelState.Expanded;
|
||||
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (ExpandedState.Value == ModCustomisationPanelState.ExpandedByHover
|
||||
if (ExpandedState.Value == ModCustomisationPanelState.Expanded
|
||||
&& !ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position)
|
||||
&& inputManager.DraggedDrawable == null)
|
||||
{
|
||||
@ -239,8 +239,8 @@ namespace osu.Game.Overlays.Mods
|
||||
public enum ModCustomisationPanelState
|
||||
{
|
||||
Collapsed = 0,
|
||||
ExpandedByHover = 1,
|
||||
Expanded = 2,
|
||||
Expanded = 1,
|
||||
ExpandedByMod = 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +368,7 @@ namespace osu.Game.Overlays.Mods
|
||||
customisationPanel.Enabled.Value = true;
|
||||
|
||||
if (anyModPendingConfiguration)
|
||||
customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.ExpandedByMod;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -153,8 +153,22 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.Update();
|
||||
|
||||
float height = toastFlow.Count > 0 ? toastFlow.DrawHeight + 120 : 0;
|
||||
float alpha = toastFlow.Count > 0 ? MathHelper.Clamp(toastFlow.DrawHeight / 41, 0, 1) * toastFlow.Children.Max(n => n.Alpha) : 0;
|
||||
float height = 0;
|
||||
float alpha = 0;
|
||||
|
||||
if (toastFlow.Count > 0)
|
||||
{
|
||||
float maxNotificationAlpha = 0;
|
||||
|
||||
foreach (var t in toastFlow)
|
||||
{
|
||||
if (t.Alpha > maxNotificationAlpha)
|
||||
maxNotificationAlpha = t.Alpha;
|
||||
}
|
||||
|
||||
height = toastFlow.DrawHeight + 120;
|
||||
alpha = MathHelper.Clamp(toastFlow.DrawHeight / 41, 0, 1) * maxNotificationAlpha;
|
||||
}
|
||||
|
||||
toastContentBackground.Height = (float)Interpolation.DampContinuously(toastContentBackground.Height, height, 10, Clock.ElapsedFrameTime);
|
||||
toastContentBackground.Alpha = (float)Interpolation.DampContinuously(toastContentBackground.Alpha, alpha, 10, Clock.ElapsedFrameTime);
|
||||
|
@ -351,13 +351,19 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
int endPointLength = endPoint == null ? 0 : 1;
|
||||
|
||||
if (vertices.Length + endPointLength != 3)
|
||||
type = PathType.BEZIER;
|
||||
else if (isLinear(points[0], points[1], endPoint ?? points[2]))
|
||||
if (FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
|
||||
{
|
||||
// osu-stable special-cased colinear perfect curves to a linear path
|
||||
type = PathType.LINEAR;
|
||||
if (vertices.Length + endPointLength != 3)
|
||||
type = PathType.BEZIER;
|
||||
else if (isLinear(points[0], points[1], endPoint ?? points[2]))
|
||||
{
|
||||
// osu-stable special-cased colinear perfect curves to a linear path
|
||||
type = PathType.LINEAR;
|
||||
}
|
||||
}
|
||||
else if (vertices.Length + endPointLength > 3)
|
||||
// Lazer supports perfect curves with less than 3 points and colinear points
|
||||
type = PathType.BEZIER;
|
||||
}
|
||||
|
||||
// The first control point must have a definite type.
|
||||
|
@ -1,14 +1,18 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
@ -16,12 +20,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
public partial class MultiplayerSpectateButton : MultiplayerRoomComposite
|
||||
{
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private IBindable<bool> operationInProgress;
|
||||
private IBindable<bool> operationInProgress = null!;
|
||||
|
||||
private readonly RoundedButton button;
|
||||
|
||||
@ -46,10 +50,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy();
|
||||
operationInProgress.BindValueChanged(_ => updateState());
|
||||
|
||||
automaticallyDownload = config.GetBindable<bool>(OsuSetting.AutomaticallyDownloadMissingBeatmaps);
|
||||
automaticallyDownload.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
CurrentPlaylistItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true);
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
@ -77,6 +91,64 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
button.Enabled.Value = Client.Room != null
|
||||
&& Client.Room.State != MultiplayerRoomState.Closed
|
||||
&& !operationInProgress.Value;
|
||||
|
||||
Scheduler.AddOnce(checkForAutomaticDownload);
|
||||
}
|
||||
|
||||
#region Automatic download handling
|
||||
|
||||
[Resolved]
|
||||
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapModelDownloader beatmapDownloader { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private Bindable<bool> automaticallyDownload = null!;
|
||||
|
||||
private CancellationTokenSource? downloadCheckCancellation;
|
||||
|
||||
private void checkForAutomaticDownload()
|
||||
{
|
||||
PlaylistItem? currentItem = CurrentPlaylistItem.Value;
|
||||
|
||||
downloadCheckCancellation?.Cancel();
|
||||
|
||||
if (currentItem == null)
|
||||
return;
|
||||
|
||||
if (!automaticallyDownload.Value)
|
||||
return;
|
||||
|
||||
// While we can support automatic downloads when not spectating, there are some usability concerns.
|
||||
// - In host rotate mode, this could potentially be unwanted by some users (even though they want automatic downloads everywhere else).
|
||||
// - When first joining a room, the expectation should be that the user is checking out the room, and they may not immediately want to download the selected beatmap.
|
||||
//
|
||||
// Rather than over-complicating this flow, let's only auto-download when spectating for the time being.
|
||||
// A potential path forward would be to have a local auto-download checkbox above the playlist item list area.
|
||||
if (Client.LocalUser?.State != MultiplayerUserState.Spectating)
|
||||
return;
|
||||
|
||||
// In a perfect world we'd use BeatmapAvailability, but there's no event-driven flow for when a selection changes.
|
||||
// ie. if selection changes from "not downloaded" to another "not downloaded" we wouldn't get a value changed raised.
|
||||
beatmapLookupCache
|
||||
.GetBeatmapAsync(currentItem.Beatmap.OnlineID, (downloadCheckCancellation = new CancellationTokenSource()).Token)
|
||||
.ContinueWith(resolved => Schedule(() =>
|
||||
{
|
||||
var beatmapSet = resolved.GetResultSafely()?.BeatmapSet;
|
||||
|
||||
if (beatmapSet == null)
|
||||
return;
|
||||
|
||||
if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID }))
|
||||
return;
|
||||
|
||||
beatmapDownloader.Download(beatmapSet);
|
||||
}));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -857,8 +857,9 @@ namespace osu.Game.Screens.Select
|
||||
// Add those items within the previously found index range that should be displayed.
|
||||
foreach (var item in toDisplay)
|
||||
{
|
||||
var panel = setPool.Get(p => p.Item = item);
|
||||
var panel = setPool.Get();
|
||||
|
||||
panel.Item = item;
|
||||
panel.Y = item.CarouselYPosition;
|
||||
|
||||
Scroll.Add(panel);
|
||||
@ -900,8 +901,8 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
if (item is DrawableCarouselBeatmapSet set)
|
||||
{
|
||||
foreach (var diff in set.DrawableBeatmaps)
|
||||
updateItem(diff, item);
|
||||
for (int i = 0; i < set.DrawableBeatmaps.Count; i++)
|
||||
updateItem(set.DrawableBeatmaps[i], item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1101,7 +1102,7 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update a item's x position and multiplicative alpha based on its y position and
|
||||
/// Update an item's x position and multiplicative alpha based on its y position and
|
||||
/// the current scroll position.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to be updated.</param>
|
||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
public IEnumerable<DrawableCarouselItem> DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty<DrawableCarouselItem>() : beatmapContainer.AliveChildren;
|
||||
public IReadOnlyList<DrawableCarouselItem> DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Array.Empty<DrawableCarouselItem>() : beatmapContainer;
|
||||
|
||||
private Container<DrawableCarouselItem>? beatmapContainer;
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -16,10 +15,10 @@ using osu.Game.Overlays.Notifications;
|
||||
namespace osu.Game.Updater
|
||||
{
|
||||
/// <summary>
|
||||
/// An update manager that shows notifications if a newer release is detected.
|
||||
/// An update manager that shows notifications if a newer release is detected for mobile platforms.
|
||||
/// Installation is left up to the user.
|
||||
/// </summary>
|
||||
public partial class SimpleUpdateManager : UpdateManager
|
||||
public partial class MobileUpdateNotifier : UpdateManager
|
||||
{
|
||||
private string version = null!;
|
||||
|
||||
@ -80,19 +79,6 @@ namespace osu.Game.Updater
|
||||
|
||||
switch (RuntimeInfo.OS)
|
||||
{
|
||||
case RuntimeInfo.Platform.Windows:
|
||||
bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe", StringComparison.Ordinal));
|
||||
break;
|
||||
|
||||
case RuntimeInfo.Platform.macOS:
|
||||
string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "Apple.Silicon" : "Intel";
|
||||
bestAsset = release.Assets?.Find(f => f.Name.EndsWith($".app.{arch}.zip", StringComparison.Ordinal));
|
||||
break;
|
||||
|
||||
case RuntimeInfo.Platform.Linux:
|
||||
bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".AppImage", StringComparison.Ordinal));
|
||||
break;
|
||||
|
||||
case RuntimeInfo.Platform.iOS:
|
||||
if (release.Assets?.Exists(f => f.Name.EndsWith(".ipa", StringComparison.Ordinal)) == true)
|
||||
// iOS releases are available via testflight. this link seems to work well enough for now.
|
@ -176,12 +176,6 @@ namespace osu.Game.Updater
|
||||
Text = NotificationsStrings.DownloadingUpdate;
|
||||
}
|
||||
|
||||
public void StartInstall()
|
||||
{
|
||||
Progress = 0;
|
||||
Text = NotificationsStrings.InstallingUpdate;
|
||||
}
|
||||
|
||||
public void FailDownload()
|
||||
{
|
||||
State = ProgressNotificationState.Cancelled;
|
||||
|
@ -15,7 +15,7 @@ namespace osu.iOS
|
||||
{
|
||||
public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString());
|
||||
|
||||
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
|
||||
protected override UpdateManager CreateUpdateManager() => new MobileUpdateNotifier();
|
||||
|
||||
protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo();
|
||||
|
||||
|
@ -853,6 +853,7 @@ See the LICENCE file in the repository root for full licence text.
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpRenamePlacementToArrangementMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
@ -1060,5 +1061,6 @@ private void load()
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unplayed/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unproxy/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Unranked/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=velopack/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Welford_0027s/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Zoomable/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
|
Loading…
Reference in New Issue
Block a user