1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-18 03:05:10 +08:00

Merge remote-tracking branch 'upstream/master' into grids-4

This commit is contained in:
OliBomby 2024-09-26 14:59:52 +02:00
commit b179e086c6
36 changed files with 766 additions and 155 deletions

View File

@ -66,7 +66,7 @@ namespace osu.Desktop.Updater
{ {
Activated = () => Activated = () =>
{ {
restartToApplyUpdate(); Task.Run(restartToApplyUpdate);
return true; return true;
} }
}); });
@ -88,7 +88,11 @@ namespace osu.Desktop.Updater
{ {
notification = new UpdateProgressNotification notification = new UpdateProgressNotification
{ {
CompletionClickAction = restartToApplyUpdate, CompletionClickAction = () =>
{
Task.Run(restartToApplyUpdate);
return true;
},
}; };
Schedule(() => notificationOverlay.Post(notification)); Schedule(() => notificationOverlay.Post(notification));
@ -127,13 +131,10 @@ namespace osu.Desktop.Updater
return true; return true;
} }
private bool restartToApplyUpdate() private async Task restartToApplyUpdate()
{ {
// TODO: Migrate this to async flow whenever available (see https://github.com/ppy/osu/pull/28743#discussion_r1740505665). await updateManager.WaitExitThenApplyUpdatesAsync(pendingUpdate?.TargetFullRelease).ConfigureAwait(false);
// 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()); Schedule(() => game.AttemptExit());
return true;
} }
} }
} }

View File

@ -26,7 +26,7 @@
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="System.IO.Packaging" Version="8.0.0" /> <PackageReference Include="System.IO.Packaging" Version="8.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="Velopack" Version="0.0.598-g933b2ab" /> <PackageReference Include="Velopack" Version="0.0.626" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" /> <EmbeddedResource Include="lazer.ico" />

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier, StarRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier,
Mods = mods, Mods = mods,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType<JuiceStream>().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)), MaxCombo = beatmap.GetMaxCombo(),
}; };
return attributes; return attributes;

View File

@ -81,7 +81,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
double drainRate = beatmap.Difficulty.DrainRate; double drainRate = beatmap.Difficulty.DrainRate;
int maxCombo = beatmap.GetMaxCombo();
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle); int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
int sliderCount = beatmap.HitObjects.Count(h => h is Slider); int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6, OverallDifficulty = (80 - hitWindowGreat) / 6,
DrainRate = drainRate, DrainRate = drainRate,
MaxCombo = maxCombo, MaxCombo = beatmap.GetMaxCombo(),
HitCircleCount = hitCirclesCount, HitCircleCount = hitCirclesCount,
SliderCount = sliderCount, SliderCount = sliderCount,
SpinnerCount = spinnerCount, SpinnerCount = spinnerCount,

View File

@ -105,9 +105,7 @@ namespace osu.Game.Rulesets.Osu.Edit
// is not looking to change the duration of the slider but expand the whole pattern. // is not looking to change the duration of the slider but expand the whole pattern.
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
{ {
var originalInfo = objectsInScale[slider]; scaleSlider(slider, scale, actualOrigin, objectsInScale[slider], axisRotation);
Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null);
scaleSlider(slider, scale, originalInfo.PathControlPointPositions, originalInfo.PathControlPointTypes, axisRotation);
} }
else else
{ {
@ -159,21 +157,25 @@ namespace osu.Game.Rulesets.Osu.Edit
return scale; return scale;
} }
private void scaleSlider(Slider slider, Vector2 scale, Vector2[] originalPathPositions, PathType?[] originalPathTypes, float axisRotation = 0) private void scaleSlider(Slider slider, Vector2 scale, Vector2 origin, OriginalHitObjectState originalInfo, float axisRotation = 0)
{ {
Debug.Assert(originalInfo.PathControlPointPositions != null && originalInfo.PathControlPointTypes != null);
scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); scale = Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
// Maintain the path types in case they were defaulted to bezier at some point during scaling // Maintain the path types in case they were defaulted to bezier at some point during scaling
for (int i = 0; i < slider.Path.ControlPoints.Count; i++) for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
{ {
slider.Path.ControlPoints[i].Position = GeometryUtils.GetScaledPosition(scale, Vector2.Zero, originalPathPositions[i], axisRotation); slider.Path.ControlPoints[i].Position = GeometryUtils.GetScaledPosition(scale, Vector2.Zero, originalInfo.PathControlPointPositions[i], axisRotation);
slider.Path.ControlPoints[i].Type = originalPathTypes[i]; slider.Path.ControlPoints[i].Type = originalInfo.PathControlPointTypes[i];
} }
// Snap the slider's length to the current beat divisor // Snap the slider's length to the current beat divisor
// to calculate the final resulting duration / bounding box before the final checks. // to calculate the final resulting duration / bounding box before the final checks.
slider.SnapTo(snapProvider); slider.SnapTo(snapProvider);
slider.Position = GeometryUtils.GetScaledPosition(scale, origin, originalInfo.Position, axisRotation);
//if sliderhead or sliderend end up outside playfield, revert scaling. //if sliderhead or sliderend end up outside playfield, revert scaling.
Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider }); Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider });
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
@ -182,7 +184,9 @@ namespace osu.Game.Rulesets.Osu.Edit
return; return;
for (int i = 0; i < slider.Path.ControlPoints.Count; i++) for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position = originalPathPositions[i]; slider.Path.ControlPoints[i].Position = originalInfo.PathControlPointPositions[i];
slider.Position = originalInfo.Position;
// Snap the slider's length again to undo the potentially-invalid length applied by the previous snap. // Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
slider.SnapTo(snapProvider); slider.SnapTo(snapProvider);

View File

@ -10,7 +10,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Components.RadioButtons;
using osuTK; using osuTK;
@ -35,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Edit
private OsuCheckbox xCheckBox = null!; private OsuCheckbox xCheckBox = null!;
private OsuCheckbox yCheckBox = null!; private OsuCheckbox yCheckBox = null!;
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox) public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler, OsuGridToolboxGroup gridToolbox)
{ {
this.scaleHandler = scaleHandler; this.scaleHandler = scaleHandler;
@ -44,8 +49,10 @@ namespace osu.Game.Rulesets.Osu.Edit
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(EditorBeatmap editorBeatmap)
{ {
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
Child = new FillFlowContainer Child = new FillFlowContainer
{ {
Width = 220, Width = 220,
@ -191,14 +198,26 @@ namespace osu.Game.Rulesets.Osu.Edit
updateAxisCheckBoxesEnabled(); updateAxisCheckBoxesEnabled();
} }
private Vector2? getOriginPosition(PreciseScaleInfo scale) => private Vector2? getOriginPosition(PreciseScaleInfo scale)
scale.Origin switch
{ {
ScaleOrigin.GridCentre => gridToolbox.StartPosition.Value, switch (scale.Origin)
ScaleOrigin.PlayfieldCentre => OsuPlayfield.BASE_SIZE / 2, {
ScaleOrigin.SelectionCentre => null, case ScaleOrigin.GridCentre:
_ => throw new ArgumentOutOfRangeException(nameof(scale)) return gridToolbox.StartPosition.Value;
};
case ScaleOrigin.PlayfieldCentre:
return OsuPlayfield.BASE_SIZE / 2;
case ScaleOrigin.SelectionCentre:
if (selectedItems.Count == 1 && selectedItems.First() is Slider slider)
return slider.Position;
return null;
default:
throw new ArgumentOutOfRangeException(nameof(scale));
}
}
private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y; private Axes getAdjustAxis(PreciseScaleInfo scale) => scale.XAxis ? scale.YAxis ? Axes.Both : Axes.X : Axes.Y;

View File

@ -91,19 +91,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
drawableObjectPiece.ApplyCustomUpdateState -= applyDimToDrawableHitObject; drawableObjectPiece.ApplyCustomUpdateState -= applyDimToDrawableHitObject;
drawableObjectPiece.ApplyCustomUpdateState += applyDimToDrawableHitObject; drawableObjectPiece.ApplyCustomUpdateState += applyDimToDrawableHitObject;
} }
else
// but at the end apply the transforms now regardless of whether this is a DHO or not.
// the above is just to ensure they don't get overwritten later.
applyDim(piece); applyDim(piece);
} }
}
void applyDim(Drawable piece) protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
// any dimmable pieces that are DHOs will be pooled separately.
// `applyDimToDrawableHitObject` is a closure that implicitly captures `this`,
// and because of separate pooling of parent and child objects, there is no guarantee that the pieces will be associated with `this` again on re-use.
// therefore, clean up the subscription here to avoid crosstalk.
// not doing so can result in the callback attempting to read things from `this` when it is in a completely bogus state (not in use or similar).
foreach (var piece in DimmablePieces.OfType<DrawableHitObject>())
piece.ApplyCustomUpdateState -= applyDimToDrawableHitObject;
}
private void applyDim(Drawable piece)
{ {
piece.FadeColour(new Color4(195, 195, 195, 255)); piece.FadeColour(new Color4(195, 195, 195, 255));
using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW)) using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
piece.FadeColour(Color4.White, 100); piece.FadeColour(Color4.White, 100);
} }
void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho); private void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho);
}
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;

View File

@ -14,7 +14,6 @@ using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
using osu.Game.Rulesets.Taiko.Difficulty.Skills; using osu.Game.Rulesets.Taiko.Difficulty.Skills;
using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Rulesets.Taiko.Scoring;
namespace osu.Game.Rulesets.Taiko.Difficulty namespace osu.Game.Rulesets.Taiko.Difficulty
@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
ColourDifficulty = colourRating, ColourDifficulty = colourRating,
PeakDifficulty = combinedRating, PeakDifficulty = combinedRating,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit), MaxCombo = beatmap.GetMaxCombo(),
}; };
return attributes; return attributes;

View File

@ -0,0 +1,52 @@
// 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 NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Utils;
namespace osu.Game.Tests.Utils
{
[TestFixture]
public class BindableValueAccessorTest
{
[Test]
public void GetValue()
{
const int value = 1337;
BindableInt bindable = new BindableInt(value);
Assert.That(BindableValueAccessor.GetValue(bindable), Is.EqualTo(value));
}
[Test]
public void SetValue()
{
const int value = 1337;
BindableInt bindable = new BindableInt();
BindableValueAccessor.SetValue(bindable, value);
Assert.That(bindable.Value, Is.EqualTo(value));
}
[Test]
public void GetInvalidBindable()
{
BindableList<object> list = new BindableList<object>();
Assert.That(BindableValueAccessor.GetValue(list), Is.EqualTo(list));
}
[Test]
public void SetInvalidBindable()
{
const int value = 1337;
BindableList<int> list = new BindableList<int> { value };
BindableValueAccessor.SetValue(list, 2);
Assert.That(list, Has.Exactly(1).Items);
Assert.That(list[0], Is.EqualTo(value));
}
}
}

View File

@ -5,14 +5,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
@ -23,6 +27,7 @@ using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Ranking.Statistics.User;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
@ -80,6 +85,69 @@ namespace osu.Game.Tests.Visual.Ranking
loadPanel(null); loadPanel(null);
} }
[Test]
public void TestStatisticsShownCorrectlyIfUpdateDeliveredBeforeLoad()
{
UserStatisticsWatcher userStatisticsWatcher = null!;
ScoreInfo score = null!;
AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher()));
AddStep("set user statistics update", () =>
{
score = TestResources.CreateTestScoreInfo();
score.OnlineID = 1234;
((Bindable<UserStatisticsUpdate>)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score,
new UserStatistics
{
Level = new UserStatistics.LevelInfo
{
Current = 5,
Progress = 20,
},
GlobalRank = 38000,
CountryRank = 12006,
PP = 2134,
RankedScore = 21123849,
Accuracy = 0.985,
PlayCount = 13375,
PlayTime = 354490,
TotalScore = 128749597,
TotalHits = 0,
MaxCombo = 1233,
}, new UserStatistics
{
Level = new UserStatistics.LevelInfo
{
Current = 5,
Progress = 30,
},
GlobalRank = 36000,
CountryRank = 12000,
PP = (decimal)2134.5,
RankedScore = 23897015,
Accuracy = 0.984,
PlayCount = 13376,
PlayTime = 35789,
TotalScore = 132218497,
TotalHits = 0,
MaxCombo = 1233,
});
});
AddStep("load user statistics panel", () => Child = new DependencyProvidingContainer
{
CachedDependencies = [(typeof(UserStatisticsWatcher), userStatisticsWatcher)],
RelativeSizeAxes = Axes.Both,
Child = new UserStatisticsPanel(score)
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
Score = { Value = score, }
}
});
AddUntilStep("overall ranking present", () => this.ChildrenOfType<OverallRanking>().Any());
AddUntilStep("loading spinner not visible", () => this.ChildrenOfType<LoadingLayer>().All(l => l.State.Value == Visibility.Hidden));
}
private void loadPanel(ScoreInfo score) => AddStep("load panel", () => private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
{ {
Child = new UserStatisticsPanel(score) Child = new UserStatisticsPanel(score)

View File

@ -9,6 +9,11 @@ namespace osu.Game.Tests.Visual.Settings
{ {
public partial class TestSceneDirectorySelector : ThemeComparisonTestScene public partial class TestSceneDirectorySelector : ThemeComparisonTestScene
{ {
public TestSceneDirectorySelector()
: base(false)
{
}
protected override Drawable CreateContent() => new OsuDirectorySelector protected override Drawable CreateContent() => new OsuDirectorySelector
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both

View File

@ -1,37 +1,49 @@
// 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.
using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays;
using osu.Game.Tests.Visual.UserInterface; using osu.Game.Tests.Visual.UserInterface;
namespace osu.Game.Tests.Visual.Settings namespace osu.Game.Tests.Visual.Settings
{ {
public partial class TestSceneFileSelector : ThemeComparisonTestScene public partial class TestSceneFileSelector : ThemeComparisonTestScene
{ {
[Resolved] public TestSceneFileSelector()
private OsuColour colours { get; set; } = null!; : base(false)
{
}
[Test] [Test]
public void TestJpgFilesOnly() public void TestJpgFilesOnly()
{ {
AddStep("create", () => AddStep("create", () =>
{ {
ContentContainer.Children = new Drawable[] var colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
ContentContainer.Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(OverlayColourProvider), colourProvider)
},
Children = new Drawable[]
{ {
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeaFoam Colour = colourProvider.Background3
}, },
new OsuFileSelector(validFileExtensions: new[] { ".jpg" }) new OsuFileSelector(validFileExtensions: new[] { ".jpg" })
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
}
}; };
}); });
} }

View File

@ -5,6 +5,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation; using osu.Game.Localisation;
@ -94,6 +95,11 @@ namespace osu.Game.Tests.Visual.UserInterface
Instantaneous = false, Instantaneous = false,
TabbableContentContainer = this, TabbableContentContainer = this,
}, },
new FormEnumDropdown<CountdownType>
{
Caption = EditorSetupStrings.EnableCountdown,
HintText = EditorSetupStrings.CountdownDescription,
},
}, },
}, },
} }

View File

@ -27,6 +27,9 @@ namespace osu.Game.Tournament.Screens.Setup
[Resolved] [Resolved]
private MatchIPCInfo ipc { get; set; } = null!; private MatchIPCInfo ipc { get; set; } = null!;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
private OsuDirectorySelector directorySelector = null!; private OsuDirectorySelector directorySelector = null!;
private DialogOverlay? overlay; private DialogOverlay? overlay;

View File

@ -198,8 +198,11 @@ namespace osu.Game.Beatmaps
if (beatmapSet.OnlineID > 0) if (beatmapSet.OnlineID > 0)
{ {
// Required local for iOS. Will cause runtime crash if inlined.
int onlineId = beatmapSet.OnlineID;
// OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure. // OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure.
foreach (var existingSetWithSameOnlineID in realm.All<BeatmapSetInfo>().Where(b => b.OnlineID == beatmapSet.OnlineID)) foreach (var existingSetWithSameOnlineID in realm.All<BeatmapSetInfo>().Where(b => b.OnlineID == onlineId))
{ {
existingSetWithSameOnlineID.DeletePending = true; existingSetWithSameOnlineID.DeletePending = true;
existingSetWithSameOnlineID.OnlineID = -1; existingSetWithSameOnlineID.OnlineID = -1;

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -15,6 +14,7 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Utils;
namespace osu.Game.Configuration namespace osu.Game.Configuration
{ {
@ -228,10 +228,7 @@ namespace osu.Game.Configuration
return b.Value; return b.Value;
case IBindable u: case IBindable u:
// An unknown (e.g. enum) generic type. return BindableValueAccessor.GetValue(u);
var valueMethod = u.GetType().GetProperty(nameof(IBindable<int>.Value));
Debug.Assert(valueMethod != null);
return valueMethod.GetValue(u)!;
default: default:
// fall back for non-bindable cases. // fall back for non-bindable cases.

View File

@ -0,0 +1,251 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2
{
public partial class FormDropdown<T> : OsuDropdown<T>
{
/// <summary>
/// Caption describing this slider bar, displayed on top of the controls.
/// </summary>
public LocalisableString Caption { get; init; }
/// <summary>
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
/// </summary>
public LocalisableString HintText { get; init; }
private FormDropdownHeader header = null!;
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.X;
header.Caption = Caption;
header.HintText = HintText;
}
protected override DropdownHeader CreateHeader() => header = new FormDropdownHeader
{
Dropdown = this,
};
protected override DropdownMenu CreateMenu() => new FormDropdownMenu();
private partial class FormDropdownHeader : DropdownHeader
{
public FormDropdown<T> Dropdown { get; set; } = null!;
protected override DropdownSearchBar CreateSearchBar() => SearchBar = new FormDropdownSearchBar();
private LocalisableString captionText;
private LocalisableString hintText;
private LocalisableString labelText;
public LocalisableString Caption
{
get => captionText;
set
{
captionText = value;
if (caption.IsNotNull())
caption.Caption = value;
}
}
public LocalisableString HintText
{
get => hintText;
set
{
hintText = value;
if (caption.IsNotNull())
caption.TooltipText = value;
}
}
protected override LocalisableString Label
{
get => labelText;
set
{
labelText = value;
if (label.IsNotNull())
label.Text = labelText;
}
}
protected new FormDropdownSearchBar SearchBar { get; set; } = null!;
private FormFieldCaption caption = null!;
private OsuSpriteText label = null!;
private SpriteIcon chevron = null!;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.None;
Height = 50;
Masking = true;
CornerRadius = 5;
Foreground.AutoSizeAxes = Axes.None;
Foreground.RelativeSizeAxes = Axes.Both;
Foreground.Padding = new MarginPadding(9);
Foreground.Children = new Drawable[]
{
caption = new FormFieldCaption
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Caption = Caption,
TooltipText = HintText,
},
label = new OsuSpriteText
{
RelativeSizeAxes = Axes.X,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
},
chevron = new SpriteIcon
{
Icon = FontAwesome.Solid.ChevronDown,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Size = new Vector2(16),
},
};
AddInternal(new HoverClickSounds());
}
protected override void LoadComplete()
{
base.LoadComplete();
Dropdown.Current.BindDisabledChanged(_ => updateState());
SearchBar.SearchTerm.BindValueChanged(_ => updateState(), true);
Dropdown.Menu.StateChanged += _ =>
{
updateState();
updateChevron();
};
SearchBar.TextBox.OnCommit += (_, _) =>
{
Background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
};
}
protected override bool OnHover(HoverEvent e)
{
updateState();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
updateState();
}
private void updateState()
{
label.Alpha = string.IsNullOrEmpty(SearchBar.SearchTerm.Value) ? 1 : 0;
caption.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
label.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
chevron.Colour = Dropdown.Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content1;
DisabledColour = Colour4.White;
bool dropdownOpen = Dropdown.Menu.State == MenuState.Open;
if (!Dropdown.Current.Disabled)
{
BorderThickness = IsHovered || dropdownOpen ? 2 : 0;
BorderColour = dropdownOpen ? colourProvider.Highlight1 : colourProvider.Light4;
if (dropdownOpen)
Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
else if (IsHovered)
Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
else
Background.Colour = colourProvider.Background5;
}
else
{
Background.Colour = colourProvider.Background4;
}
}
private void updateChevron()
{
bool open = Dropdown.Menu.State == MenuState.Open;
chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
}
}
private partial class FormDropdownSearchBar : DropdownSearchBar
{
public FormTextBox.InnerTextBox TextBox { get; private set; } = null!;
protected override void PopIn() => this.FadeIn();
protected override void PopOut() => this.FadeOut();
protected override TextBox CreateTextBox() => TextBox = new FormTextBox.InnerTextBox();
[BackgroundDependencyLoader]
private void load()
{
TextBox.Anchor = Anchor.BottomLeft;
TextBox.Origin = Anchor.BottomLeft;
TextBox.RelativeSizeAxes = Axes.X;
TextBox.Margin = new MarginPadding(9);
}
}
private partial class FormDropdownMenu : OsuDropdownMenu
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
ItemsContainer.Padding = new MarginPadding(9);
Margin = new MarginPadding { Top = 5 };
MaskingContainer.BorderThickness = 2;
MaskingContainer.BorderColour = colourProvider.Highlight1;
}
}
}
public partial class FormEnumDropdown<T> : FormDropdown<T>
where T : struct, Enum
{
public FormEnumDropdown()
{
Items = Enum.GetValues<T>();
}
}
}

View File

@ -1,41 +1,72 @@
// 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.IO; using System.IO;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
public partial class OsuDirectorySelector : DirectorySelector public partial class OsuDirectorySelector : DirectorySelector
{ {
public const float ITEM_HEIGHT = 20; public const float ITEM_HEIGHT = 16;
public OsuDirectorySelector(string initialPath = null) private Box hiddenToggleBackground = null!;
public OsuDirectorySelector(string? initialPath = null)
: base(initialPath) : base(initialPath)
{ {
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OverlayColourProvider colourProvider)
{ {
Padding = new MarginPadding(10); AddInternal(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5,
Depth = float.MaxValue,
});
hiddenToggleBackground.Colour = colourProvider.Background4;
} }
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer(); protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer
{
Padding = new MarginPadding
{
Horizontal = 20,
Vertical = 15,
}
};
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay(); protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
protected override Drawable CreateHiddenToggleButton() => new OsuDirectorySelectorHiddenToggle { Current = { BindTarget = ShowHiddenItems } }; protected override Drawable CreateHiddenToggleButton() => new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
hiddenToggleBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new OsuDirectorySelectorHiddenToggle
{
Current = { BindTarget = ShowHiddenItems },
},
}
};
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300); protected override void NotifySelectionError() => this.FlashColour(Colour4.Red, 300);
} }

View File

@ -1,33 +1,51 @@
// 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.IO; using System.IO;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK; using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
internal partial class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay internal partial class OsuDirectorySelectorBreadcrumbDisplay : DirectorySelectorBreadcrumbDisplay
{ {
protected override Drawable CreateCaption() => new OsuSpriteText public const float HEIGHT = 45;
public const float HORIZONTAL_PADDING = 20;
protected override Drawable CreateCaption() => Empty().With(d =>
{ {
Text = "Current Directory: ", d.Origin = Anchor.CentreLeft;
Font = OsuFont.Default.With(size: OsuDirectorySelector.ITEM_HEIGHT), d.Anchor = Anchor.CentreLeft;
}; d.Alpha = 0;
});
protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer(); protected override DirectorySelectorDirectory CreateRootDirectoryItem() => new OsuBreadcrumbDisplayComputer();
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName); protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuBreadcrumbDisplayDirectory(directory, displayName);
public OsuDirectorySelectorBreadcrumbDisplay() [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{ {
Padding = new MarginPadding(15); ((FillFlowContainer)InternalChild).Padding = new MarginPadding
{
Horizontal = HORIZONTAL_PADDING,
Vertical = 10,
};
AddInternal(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
Depth = 1,
});
} }
private partial class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory private partial class OsuBreadcrumbDisplayComputer : OsuBreadcrumbDisplayDirectory
@ -40,26 +58,67 @@ namespace osu.Game.Graphics.UserInterfaceV2
} }
} }
private partial class OsuBreadcrumbDisplayDirectory : OsuDirectorySelectorDirectory private partial class OsuBreadcrumbDisplayDirectory : DirectorySelectorDirectory
{ {
public OsuBreadcrumbDisplayDirectory(DirectoryInfo directory, string displayName = null) public OsuBreadcrumbDisplayDirectory(DirectoryInfo? directory, string? displayName = null)
: base(directory, displayName) : base(directory, displayName)
{ {
} }
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
Flow.AutoSizeAxes = Axes.X;
Flow.Height = 25;
Flow.Margin = new MarginPadding { Horizontal = 10, };
AddRangeInternal(new Drawable[]
{
new Background
{
Depth = 1
},
new HoverClickSounds(),
});
Flow.Add(new SpriteIcon Flow.Add(new SpriteIcon
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Icon = FontAwesome.Solid.ChevronRight, Icon = FontAwesome.Solid.ChevronRight,
Size = new Vector2(FONT_SIZE / 2) Size = new Vector2(FONT_SIZE / 2),
Margin = new MarginPadding { Left = 5, },
}); });
Flow.Colour = colourProvider.Light3;
} }
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? base.Icon : null; protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold));
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) ? FontAwesome.Solid.Database : null;
internal partial class Background : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider overlayColourProvider)
{
RelativeSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 5;
InternalChild = new Box
{
Colour = overlayColourProvider.Background3,
RelativeSizeAxes = Axes.Both,
};
}
}
} }
} }
} }

View File

@ -1,66 +1,41 @@
// 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.IO; using System.IO;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
internal partial class OsuDirectorySelectorDirectory : DirectorySelectorDirectory internal partial class OsuDirectorySelectorDirectory : DirectorySelectorDirectory
{ {
public OsuDirectorySelectorDirectory(DirectoryInfo directory, string displayName = null) public OsuDirectorySelectorDirectory(DirectoryInfo directory, string? displayName = null)
: base(directory, displayName) : base(directory, displayName)
{ {
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OsuColour colours)
{ {
Flow.AutoSizeAxes = Axes.X; Flow.AutoSizeAxes = Axes.X;
Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new Background
{
Depth = 1
},
new HoverClickSounds() new HoverClickSounds()
}); });
Colour = colours.Orange1;
} }
protected override SpriteText CreateSpriteText() => new OsuSpriteText(); protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.Bold));
protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar) protected override IconUsage? Icon => Directory.Name.Contains(Path.DirectorySeparatorChar)
? FontAwesome.Solid.Database ? FontAwesome.Solid.Database
: FontAwesome.Regular.Folder; : FontAwesome.Regular.Folder;
internal partial class Background : CompositeDrawable
{
[BackgroundDependencyLoader(true)]
private void load(OverlayColourProvider overlayColourProvider, OsuColour colours)
{
RelativeSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 5;
InternalChild = new Box
{
Colour = overlayColourProvider?.Background5 ?? colours.GreySeaFoamDarker,
RelativeSizeAxes = Axes.Both,
};
}
}
} }
} }

View File

@ -16,12 +16,15 @@ namespace osu.Game.Graphics.UserInterfaceV2
{ {
RelativeSizeAxes = Axes.None; RelativeSizeAxes = Axes.None;
AutoSizeAxes = Axes.None; AutoSizeAxes = Axes.None;
Size = new Vector2(100, 50); Size = new Vector2(140, OsuDirectorySelectorBreadcrumbDisplay.HEIGHT);
Margin = new MarginPadding { Right = OsuDirectorySelectorBreadcrumbDisplay.HORIZONTAL_PADDING, };
Anchor = Anchor.CentreLeft; Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft; Origin = Anchor.CentreLeft;
LabelTextFlowContainer.Anchor = Anchor.CentreLeft; LabelTextFlowContainer.Anchor = Anchor.CentreLeft;
LabelTextFlowContainer.Origin = Anchor.CentreLeft; LabelTextFlowContainer.Origin = Anchor.CentreLeft;
LabelText = @"Show hidden"; LabelText = @"Show hidden";
Scale = new Vector2(0.8f);
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]

View File

@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.IO; using System.IO;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
@ -14,5 +16,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
: base(directory, "..") : base(directory, "..")
{ {
} }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Colour = colourProvider.Content1;
}
} }
} }

View File

@ -1,43 +1,74 @@
// 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.IO; using System.IO;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
{ {
public partial class OsuFileSelector : FileSelector public partial class OsuFileSelector : FileSelector
{ {
public OsuFileSelector(string initialPath = null, string[] validFileExtensions = null) private Box hiddenToggleBackground = null!;
public OsuFileSelector(string? initialPath = null, string[]? validFileExtensions = null)
: base(initialPath, validFileExtensions) : base(initialPath, validFileExtensions)
{ {
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OverlayColourProvider colourProvider)
{ {
Padding = new MarginPadding(10); AddInternal(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5,
Depth = float.MaxValue,
});
hiddenToggleBackground.Colour = colourProvider.Background4;
} }
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer(); protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer
{
Padding = new MarginPadding
{
Horizontal = 20,
Vertical = 15,
}
};
protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay(); protected override DirectorySelectorBreadcrumbDisplay CreateBreadcrumb() => new OsuDirectorySelectorBreadcrumbDisplay();
protected override Drawable CreateHiddenToggleButton() => new OsuDirectorySelectorHiddenToggle { Current = { BindTarget = ShowHiddenItems } }; protected override Drawable CreateHiddenToggleButton() => new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
hiddenToggleBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new OsuDirectorySelectorHiddenToggle
{
Current = { BindTarget = ShowHiddenItems },
},
}
};
protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory); protected override DirectorySelectorDirectory CreateParentDirectoryItem(DirectoryInfo directory) => new OsuDirectorySelectorParentDirectory(directory);
protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName); protected override DirectorySelectorDirectory CreateDirectoryItem(DirectoryInfo directory, string? displayName = null) => new OsuDirectorySelectorDirectory(directory, displayName);
protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file); protected override DirectoryListingFile CreateFileItem(FileInfo file) => new OsuDirectoryListingFile(file);
@ -51,19 +82,17 @@ namespace osu.Game.Graphics.UserInterfaceV2
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OverlayColourProvider colourProvider)
{ {
Flow.AutoSizeAxes = Axes.X; Flow.AutoSizeAxes = Axes.X;
Flow.Height = OsuDirectorySelector.ITEM_HEIGHT; Flow.Height = OsuDirectorySelector.ITEM_HEIGHT;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new OsuDirectorySelectorDirectory.Background
{
Depth = 1
},
new HoverClickSounds() new HoverClickSounds()
}); });
Colour = colourProvider.Light3;
} }
protected override IconUsage? Icon protected override IconUsage? Icon
@ -91,7 +120,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
} }
} }
protected override SpriteText CreateSpriteText() => new OsuSpriteText(); protected override SpriteText CreateSpriteText() => new OsuSpriteText().With(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold));
} }
} }
} }

View File

@ -40,7 +40,10 @@ namespace osu.Game.Online
// Used to interact with manager classes that don't support interface types. Will eventually be replaced. // Used to interact with manager classes that don't support interface types. Will eventually be replaced.
var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID };
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => s.OnlineID == TrackedItem.OnlineID && !s.DeletePending), (items, _) => // Required local for iOS. Will cause runtime crash if inlined.
int onlineId = TrackedItem.OnlineID;
realmSubscription = realm.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => s.OnlineID == onlineId && !s.DeletePending), (items, _) =>
{ {
if (items.Any()) if (items.Any())
Schedule(() => UpdateState(DownloadState.LocallyAvailable)); Schedule(() => UpdateState(DownloadState.LocallyAvailable));

View File

@ -46,10 +46,15 @@ namespace osu.Game.Online
Downloader.DownloadBegan += downloadBegan; Downloader.DownloadBegan += downloadBegan;
Downloader.DownloadFailed += downloadFailed; Downloader.DownloadFailed += downloadFailed;
// Required local for iOS. Will cause runtime crash if inlined.
long onlineId = TrackedItem.OnlineID;
long legacyOnlineId = TrackedItem.LegacyOnlineID;
string hash = TrackedItem.Hash;
realmSubscription = realm.RegisterForNotifications(r => r.All<ScoreInfo>().Where(s => realmSubscription = realm.RegisterForNotifications(r => r.All<ScoreInfo>().Where(s =>
((s.OnlineID > 0 && s.OnlineID == TrackedItem.OnlineID) ((s.OnlineID > 0 && s.OnlineID == onlineId)
|| (s.LegacyOnlineID > 0 && s.LegacyOnlineID == TrackedItem.LegacyOnlineID) || (s.LegacyOnlineID > 0 && s.LegacyOnlineID == legacyOnlineId)
|| (!string.IsNullOrEmpty(s.Hash) && s.Hash == TrackedItem.Hash)) || (!string.IsNullOrEmpty(s.Hash) && s.Hash == hash))
&& !s.DeletePending), (items, _) => && !s.DeletePending), (items, _) =>
{ {
if (items.Any()) if (items.Any())

View File

@ -314,6 +314,7 @@ namespace osu.Game.Overlays.FirstRunSetup
private partial class DirectoryChooserPopover : OsuPopover private partial class DirectoryChooserPopover : OsuPopover
{ {
public DirectoryChooserPopover(Bindable<DirectoryInfo?> currentDirectory) public DirectoryChooserPopover(Bindable<DirectoryInfo?> currentDirectory)
: base(false)
{ {
Child = new Container Child = new Container
{ {
@ -325,6 +326,13 @@ namespace osu.Game.Overlays.FirstRunSetup
}, },
}; };
} }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Body.BorderColour = colourProvider.Highlight1;
Body.BorderThickness = 2;
}
} }
} }
} }

View File

@ -48,8 +48,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
/// </summary> /// </summary>
protected virtual DirectoryInfo InitialPath => null; protected virtual DirectoryInfo InitialPath => null;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load()
{ {
InternalChild = new Container InternalChild = new Container
{ {
@ -64,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeaFoamDark Colour = colourProvider.Background4,
}, },
new GridContainer new GridContainer
{ {

View File

@ -266,8 +266,7 @@ namespace osu.Game.Rulesets.Mods
// TODO: special case for handling number types // TODO: special case for handling number types
PropertyInfo property = targetSetting.GetType().GetProperty(nameof(Bindable<bool>.Value))!; BindableValueAccessor.SetValue(targetSetting, BindableValueAccessor.GetValue(sourceSetting));
property.SetValue(targetSetting, property.GetValue(sourceSetting));
} }
} }

View File

@ -18,6 +18,7 @@ using osu.Framework.Localisation;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Setup namespace osu.Game.Screens.Edit.Setup
@ -118,6 +119,7 @@ namespace osu.Game.Screens.Edit.Setup
protected override string PopOutSampleName => "UI/overlay-big-pop-out"; protected override string PopOutSampleName => "UI/overlay-big-pop-out";
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile, string? chooserPath) public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile, string? chooserPath)
: base(false)
{ {
Child = new Container Child = new Container
{ {
@ -129,6 +131,13 @@ namespace osu.Game.Screens.Edit.Setup
}, },
}; };
} }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Body.BorderColour = colourProvider.Highlight1;
Body.BorderThickness = 2;
}
} }
} }
} }

View File

@ -15,6 +15,7 @@ using osu.Framework.Screens;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays;
using osuTK; using osuTK;
namespace osu.Game.Screens.Import namespace osu.Game.Screens.Import
@ -36,8 +37,8 @@ namespace osu.Game.Screens.Import
[Resolved] [Resolved]
private OsuGameBase game { get; set; } private OsuGameBase game { get; set; }
[Resolved] [Cached]
private OsuColour colours { get; set; } private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load() private void load()
@ -52,11 +53,6 @@ namespace osu.Game.Screens.Import
Size = new Vector2(0.9f, 0.8f), Size = new Vector2(0.9f, 0.8f),
Children = new Drawable[] Children = new Drawable[]
{ {
new Box
{
Colour = colours.GreySeaFoamDark,
RelativeSizeAxes = Axes.Both,
},
fileSelector = new OsuFileSelector(validFileExtensions: game.HandledExtensions.ToArray()) fileSelector = new OsuFileSelector(validFileExtensions: game.HandledExtensions.ToArray())
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -72,7 +68,7 @@ namespace osu.Game.Screens.Import
{ {
new Box new Box
{ {
Colour = colours.GreySeaFoamDarker, Colour = colourProvider.Background4,
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}, },
new Container new Container

View File

@ -3,6 +3,7 @@
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -13,12 +14,11 @@ using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Spectator; using osu.Game.Online.Spectator;
using osu.Game.Overlays; using osu.Game.Overlays;
@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play
public partial class SoloSpectatorScreen : SpectatorScreen, IPreviewTrackOwner public partial class SoloSpectatorScreen : SpectatorScreen, IPreviewTrackOwner
{ {
[Resolved] [Resolved]
private IAPIProvider api { get; set; } = null!; private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
[Resolved] [Resolved]
private PreviewTrackManager previewTrackManager { get; set; } = null!; private PreviewTrackManager previewTrackManager { get; set; } = null!;
@ -60,7 +60,7 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
private SpectatorGameplayState? immediateSpectatorGameplayState; private SpectatorGameplayState? immediateSpectatorGameplayState;
private GetBeatmapSetRequest? onlineBeatmapRequest; private ScheduledDelegate? beatmapFetchCallback;
private APIBeatmapSet? beatmapSet; private APIBeatmapSet? beatmapSet;
@ -210,7 +210,7 @@ namespace osu.Game.Screens.Play
private void clearDisplay() private void clearDisplay()
{ {
watchButton.Enabled.Value = false; watchButton.Enabled.Value = false;
onlineBeatmapRequest?.Cancel(); beatmapFetchCallback?.Cancel();
beatmapPanelContainer.Clear(); beatmapPanelContainer.Clear();
previewTrackManager.StopAnyPlaying(this); previewTrackManager.StopAnyPlaying(this);
} }
@ -244,15 +244,17 @@ namespace osu.Game.Screens.Play
{ {
Debug.Assert(state.BeatmapID != null); Debug.Assert(state.BeatmapID != null);
onlineBeatmapRequest = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId); beatmapLookupCache.GetBeatmapAsync(state.BeatmapID.Value).ContinueWith(t => beatmapFetchCallback = Schedule(() =>
onlineBeatmapRequest.Success += beatmapSet => Schedule(() =>
{ {
this.beatmapSet = beatmapSet; var beatmap = t.GetResultSafely();
beatmapPanelContainer.Child = new BeatmapCardNormal(this.beatmapSet, allowExpansion: false);
checkForAutomaticDownload();
});
api.Queue(onlineBeatmapRequest); if (beatmap?.BeatmapSet == null)
return;
beatmapSet = beatmap.BeatmapSet;
beatmapPanelContainer.Child = new BeatmapCardNormal(beatmapSet, allowExpansion: false);
checkForAutomaticDownload();
}));
} }
private void checkForAutomaticDownload() private void checkForAutomaticDownload()

View File

@ -234,9 +234,12 @@ namespace osu.Game.Screens.Play
{ {
if (LoadedBeatmapSuccessfully) if (LoadedBeatmapSuccessfully)
{ {
// compare: https://github.com/ppy/osu/blob/ccf1acce56798497edfaf92d3ece933469edcf0a/osu.Game/Screens/Play/Player.cs#L848-L851
var scoreCopy = Score.DeepClone();
Task.Run(async () => Task.Run(async () =>
{ {
await submitScore(Score.DeepClone()).ConfigureAwait(false); await submitScore(scoreCopy).ConfigureAwait(false);
spectatorClient.EndPlaying(GameplayState); spectatorClient.EndPlaying(GameplayState);
}).FireAndForget(); }).FireAndForget();
} }

View File

@ -37,7 +37,7 @@ namespace osu.Game.Screens.Ranking.Statistics
{ {
if (update.NewValue?.Score.MatchesOnlineID(achievedScore) == true) if (update.NewValue?.Score.MatchesOnlineID(achievedScore) == true)
DisplayedUserStatisticsUpdate.Value = update.NewValue; DisplayedUserStatisticsUpdate.Value = update.NewValue;
}); }, true);
} }
} }

View File

@ -29,7 +29,10 @@ namespace osu.Game.Skinning
invalidateCache(); invalidateCache();
Debug.Assert(fileToStoragePathMapping != null); Debug.Assert(fileToStoragePathMapping != null);
realmSubscription = realm?.RegisterForNotifications(r => r.All<T>().Where(s => s.ID == source.ID), skinChanged); // Required local for iOS. Will cause runtime crash if inlined.
Guid id = source.ID;
realmSubscription = realm?.RegisterForNotifications(r => r.All<T>().Where(s => s.ID == id), skinChanged);
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)

View File

@ -131,9 +131,12 @@ namespace osu.Game.Skinning
{ {
Realm.Run(r => Realm.Run(r =>
{ {
// Required local for iOS. Will cause runtime crash if inlined.
Guid currentSkinId = CurrentSkinInfo.Value.ID;
// choose from only user skins, removing the current selection to ensure a new one is chosen. // choose from only user skins, removing the current selection to ensure a new one is chosen.
var randomChoices = r.All<SkinInfo>() var randomChoices = r.All<SkinInfo>()
.Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID) .Where(s => !s.DeletePending && s.ID != currentSkinId)
.ToArray(); .ToArray();
if (randomChoices.Length == 0) if (randomChoices.Length == 0)

View File

@ -0,0 +1,39 @@
// 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.Linq;
using System.Reflection;
using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
namespace osu.Game.Utils
{
internal static class BindableValueAccessor
{
private static readonly MethodInfo get_method = typeof(BindableValueAccessor).GetMethod(nameof(getValue), BindingFlags.Static | BindingFlags.NonPublic)!;
private static readonly MethodInfo set_method = typeof(BindableValueAccessor).GetMethod(nameof(setValue), BindingFlags.Static | BindingFlags.NonPublic)!;
public static object GetValue(IBindable bindable)
{
Type? bindableWithValueType = bindable.GetType().GetInterfaces().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IBindable<>));
if (bindableWithValueType == null)
return bindable;
return get_method.MakeGenericMethod(bindableWithValueType.GenericTypeArguments[0]).Invoke(null, [bindable])!;
}
public static void SetValue(IBindable bindable, object value)
{
Type? bindableWithValueType = bindable.GetType().EnumerateBaseTypes().SingleOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Bindable<>));
if (bindableWithValueType == null)
return;
set_method.MakeGenericMethod(bindableWithValueType.GenericTypeArguments[0]).Invoke(null, [bindable, value]);
}
private static object getValue<T>(object bindable) => ((IBindable<T>)bindable).Value!;
private static object setValue<T>(object bindable, object value) => ((Bindable<T>)bindable).Value = (T)value;
}
}