mirror of
https://github.com/ppy/osu.git
synced 2025-02-13 15:03:13 +08:00
Merge branch 'master' into multiplayer-leaderboard-user-mods-2
This commit is contained in:
commit
cd649f7d97
@ -21,7 +21,7 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2022.417.0",
|
||||
"version": "2022.607.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
|
@ -52,10 +52,10 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.530.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.605.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
<PackageReference Include="Realm" Version="10.11.2" />
|
||||
<PackageReference Include="Realm" Version="10.14.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
|
||||
[TestCase(2.3449735700206298d, 151, "diffcalc-test")]
|
||||
[TestCase(2.3449735700206298d, 242, "diffcalc-test")]
|
||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||
|
||||
[TestCase(2.7879104989252959d, 151, "diffcalc-test")]
|
||||
[TestCase(2.7879104989252959d, 242, "diffcalc-test")]
|
||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
|
||||
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
foreach (var v in base.ToDatabaseAttributes())
|
||||
yield return v;
|
||||
|
||||
// Todo: osu!mania doesn't output MaxCombo attribute for some reason.
|
||||
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
||||
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
||||
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
||||
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
|
||||
@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
base.FromDatabaseAttributes(values);
|
||||
|
||||
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
||||
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
|
||||
|
@ -52,10 +52,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
||||
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
|
||||
ScoreMultiplier = getScoreMultiplier(mods),
|
||||
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
|
||||
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject)
|
||||
};
|
||||
}
|
||||
|
||||
private static int maxComboForObject(HitObject hitObject)
|
||||
{
|
||||
if (hitObject is HoldNote hold)
|
||||
return 1 + (int)((hold.EndTime - hold.StartTime) / 100);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
{
|
||||
var sortedObjects = beatmap.HitObjects.ToArray();
|
||||
|
@ -79,7 +79,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
Result = { BindTarget = SpinsPerMinute },
|
||||
},
|
||||
ticks = new Container<DrawableSpinnerTick>(),
|
||||
ticks = new Container<DrawableSpinnerTick>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new AspectContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSpinnerTick : DrawableOsuHitObject
|
||||
@ -10,13 +12,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject;
|
||||
|
||||
public DrawableSpinnerTick()
|
||||
: base(null)
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableSpinnerTick(SpinnerTick spinnerTick)
|
||||
: base(spinnerTick)
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
||||
|
@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
|
||||
|
||||
AddNested(i < SpinsRequired
|
||||
? new SpinnerTick { StartTime = startTime, Position = Position }
|
||||
: new SpinnerBonusTick { StartTime = startTime, Position = Position });
|
||||
? new SpinnerTick { StartTime = startTime }
|
||||
: new SpinnerBonusTick { StartTime = startTime });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
}
|
||||
});
|
||||
|
||||
if (!(source.FindProvider(s => s.GetTexture("spinner-top") != null) is DefaultLegacySkin))
|
||||
var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null);
|
||||
|
||||
if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin))
|
||||
{
|
||||
AddInternal(ApproachCircle = new Sprite
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
@ -321,12 +322,14 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
private class ProxyContainer : LifetimeManagementContainer
|
||||
{
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
set => base.Padding = value;
|
||||
}
|
||||
|
||||
public void Add(Drawable proxy) => AddInternal(proxy);
|
||||
|
||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
|
||||
{
|
||||
// DrawableHitObject disables masking.
|
||||
// Hitobject content is proxied and unproxied based on hit status and the IsMaskedAway value could get stuck because of this.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -710,7 +710,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
realm.Realm.Write(() =>
|
||||
await realm.Realm.WriteAsync(() =>
|
||||
{
|
||||
foreach (var b in imported.Beatmaps)
|
||||
b.OnlineID = -1;
|
||||
|
@ -59,12 +59,14 @@ namespace osu.Game.Tests.Gameplay
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
|
||||
|
||||
// No header shouldn't cause any change
|
||||
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame());
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
|
||||
|
||||
// Reset with a miss instead.
|
||||
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame
|
||||
@ -74,6 +76,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||
|
||||
// Reset with no judged hit.
|
||||
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame
|
||||
@ -83,6 +86,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
|
||||
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||
Assert.That(scoreProcessor.JudgedHits, Is.Zero);
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
|
@ -59,11 +59,13 @@ namespace osu.Game.Tests.Skins
|
||||
AddAssert("Check float parse lookup", () => requester.GetConfig<string, float>("FloatTest")?.Value == 1.1f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBoolLookup()
|
||||
[TestCase("0", false)]
|
||||
[TestCase("1", true)]
|
||||
[TestCase("2", true)] // https://github.com/ppy/osu/issues/18579
|
||||
public void TestBoolLookup(string originalValue, bool expectedParsedValue)
|
||||
{
|
||||
AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1");
|
||||
AddAssert("Check bool parse lookup", () => requester.GetConfig<string, bool>("BoolTest")?.Value == true);
|
||||
AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = originalValue);
|
||||
AddAssert("Check bool parse lookup", () => requester.GetConfig<string, bool>("BoolTest")?.Value == expectedParsedValue);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
48
osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs
Normal file
48
osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneTapButton : OsuManualInputManagerTestScene
|
||||
{
|
||||
private TapButton tapButton;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("create button", () =>
|
||||
{
|
||||
Child = tapButton = new TapButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(4),
|
||||
};
|
||||
});
|
||||
|
||||
bool pressed = false;
|
||||
|
||||
AddRepeatStep("Press button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(tapButton);
|
||||
if (!pressed)
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
else
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
|
||||
pressed = !pressed;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
@ -77,34 +77,6 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType<TimingControlPoint>().First().BPM:N2}";
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoop()
|
||||
{
|
||||
AddStep("do nothing", () => { });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTapThenReset()
|
||||
{
|
||||
AddStep("click tap button", () =>
|
||||
{
|
||||
control.ChildrenOfType<RoundedButton>()
|
||||
.Last()
|
||||
.TriggerClick();
|
||||
});
|
||||
|
||||
AddUntilStep("wait for track playing", () => Clock.IsRunning);
|
||||
|
||||
AddStep("click reset button", () =>
|
||||
{
|
||||
control.ChildrenOfType<RoundedButton>()
|
||||
.First()
|
||||
.TriggerClick();
|
||||
});
|
||||
|
||||
AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
@ -115,7 +87,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("click tap button", () =>
|
||||
{
|
||||
control.ChildrenOfType<RoundedButton>()
|
||||
control.ChildrenOfType<OsuButton>()
|
||||
.Last()
|
||||
.TriggerClick();
|
||||
});
|
||||
@ -129,6 +101,28 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTapThenReset()
|
||||
{
|
||||
AddStep("click tap button", () =>
|
||||
{
|
||||
control.ChildrenOfType<OsuButton>()
|
||||
.Last()
|
||||
.TriggerClick();
|
||||
});
|
||||
|
||||
AddUntilStep("wait for track playing", () => Clock.IsRunning);
|
||||
|
||||
AddStep("click reset button", () =>
|
||||
{
|
||||
control.ChildrenOfType<OsuButton>()
|
||||
.First()
|
||||
.TriggerClick();
|
||||
});
|
||||
|
||||
AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
Beatmap.Disabled = false;
|
||||
|
48
osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
Normal file
48
osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
public class TestSceneTimelineZoom : TimelineTestScene
|
||||
{
|
||||
public override Drawable CreateTestComponent() => Empty();
|
||||
|
||||
[Test]
|
||||
public void TestVisibleRangeUpdatesOnZoomChange()
|
||||
{
|
||||
double initialVisibleRange = 0;
|
||||
|
||||
AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||
|
||||
AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
|
||||
AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1));
|
||||
AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
|
||||
AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1));
|
||||
|
||||
AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||
AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibleRangeConstantOnSizeChange()
|
||||
{
|
||||
double initialVisibleRange = 0;
|
||||
|
||||
AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
|
||||
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||
|
||||
AddStep("scale timeline size", () => TimelineArea.Timeline.Width = 2);
|
||||
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||
AddStep("descale timeline size", () => TimelineArea.Timeline.Width = 0.5f);
|
||||
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||
|
||||
AddStep("restore timeline size", () => TimelineArea.Timeline.Width = 1);
|
||||
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||
}
|
||||
}
|
||||
}
|
@ -44,7 +44,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.Gray(30)
|
||||
},
|
||||
scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both }
|
||||
scrollContainer = new ZoomableScrollContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
},
|
||||
new MenuCursor()
|
||||
@ -62,7 +67,15 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Test]
|
||||
public void TestWidthInitialization()
|
||||
{
|
||||
AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0);
|
||||
AddAssert("Inner container width was initialized", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWidthUpdatesOnDrawSizeChanges()
|
||||
{
|
||||
AddStep("Shrink scroll container", () => scrollContainer.Width = 0.5f);
|
||||
AddAssert("Scroll container width shrunk", () => scrollContainer.DrawWidth == scrollContainer.Parent.DrawWidth / 2);
|
||||
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -178,6 +179,36 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that <see cref="MessageNotifier"/> handles channels which have not been or could not be resolved (i.e. <see cref="Channel.Id"/> = 0).
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSendInUnresolvedChannel()
|
||||
{
|
||||
int i = 1;
|
||||
Channel unresolved = null;
|
||||
|
||||
AddRepeatStep("join unresolved channels", () => testContainer.ChannelManager.JoinChannel(unresolved = new Channel(new APIUser
|
||||
{
|
||||
Id = 100 + i,
|
||||
Username = $"Foreign #{i++}",
|
||||
})), 5);
|
||||
|
||||
AddStep("send message in unresolved channel", () =>
|
||||
{
|
||||
Debug.Assert(unresolved.Id == 0);
|
||||
|
||||
unresolved.AddLocalEcho(new LocalEchoMessage
|
||||
{
|
||||
Sender = API.LocalUser.Value,
|
||||
ChannelId = unresolved.Id,
|
||||
Content = "Some message",
|
||||
});
|
||||
});
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
private void receiveMessage(APIUser sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content));
|
||||
|
||||
private Message createMessage(APIUser sender, Channel channel, string content) => new Message(messageIdCounter++)
|
||||
|
@ -128,11 +128,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
AddAssert("Ensure no adjacent day separators", () =>
|
||||
{
|
||||
var indices = chatDisplay.FillFlow.OfType<DrawableChannel.DaySeparator>().Select(ds => chatDisplay.FillFlow.IndexOf(ds));
|
||||
var indices = chatDisplay.FillFlow.OfType<DaySeparator>().Select(ds => chatDisplay.FillFlow.IndexOf(ds));
|
||||
|
||||
foreach (int i in indices)
|
||||
{
|
||||
if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator)
|
||||
if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DaySeparator)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
beatmap.Metadata.Author = author;
|
||||
beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title";
|
||||
beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist";
|
||||
beatmap.DifficultyName = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong difficulty name";
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
@ -319,6 +319,15 @@ namespace osu.Game.Beatmaps
|
||||
});
|
||||
}
|
||||
|
||||
public void DeleteAllVideos()
|
||||
{
|
||||
realm.Write(r =>
|
||||
{
|
||||
var items = r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected);
|
||||
beatmapModelManager.DeleteVideos(items.ToList());
|
||||
});
|
||||
}
|
||||
|
||||
public void UndeleteAll()
|
||||
{
|
||||
realm.Run(r => beatmapModelManager.Undelete(r.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Stores;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@ -33,6 +34,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
protected override string[] HashableFileTypes => new[] { ".osu" };
|
||||
|
||||
public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" };
|
||||
|
||||
public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null)
|
||||
: base(realm, storage, onlineLookupQueue)
|
||||
{
|
||||
@ -114,5 +117,50 @@ namespace osu.Game.Beatmaps
|
||||
item.CopyChangesToRealm(existing);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete videos from a list of beatmaps.
|
||||
/// This will post notifications tracking progress.
|
||||
/// </summary>
|
||||
public void DeleteVideos(List<BeatmapSetInfo> items, bool silent = false)
|
||||
{
|
||||
if (items.Count == 0) return;
|
||||
|
||||
var notification = new ProgressNotification
|
||||
{
|
||||
Progress = 0,
|
||||
Text = $"Preparing to delete all {HumanisedModelName} videos...",
|
||||
CompletionText = "No videos found to delete!",
|
||||
State = ProgressNotificationState.Active,
|
||||
};
|
||||
|
||||
if (!silent)
|
||||
PostNotification?.Invoke(notification);
|
||||
|
||||
int i = 0;
|
||||
int deleted = 0;
|
||||
|
||||
foreach (var b in items)
|
||||
{
|
||||
if (notification.State == ProgressNotificationState.Cancelled)
|
||||
// user requested abort
|
||||
return;
|
||||
|
||||
var video = b.Files.FirstOrDefault(f => VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal)));
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
DeleteFile(b, video);
|
||||
deleted++;
|
||||
notification.CompletionText = $"Deleted {deleted} {HumanisedModelName} video(s)!";
|
||||
}
|
||||
|
||||
notification.Text = $"Deleting videos from {HumanisedModelName}s ({deleted} deleted)";
|
||||
|
||||
notification.Progress = (float)++i / items.Count;
|
||||
}
|
||||
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
protected readonly BeatmapDownloadTracker DownloadTracker;
|
||||
|
||||
protected BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true)
|
||||
: base(HoverSampleSet.Submit)
|
||||
: base(HoverSampleSet.Button)
|
||||
{
|
||||
Expanded = new BindableBool { Disabled = !allowExpansion };
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||
using osu.Game.Graphics;
|
||||
@ -245,10 +244,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
});
|
||||
|
||||
if (BeatmapSet.HasVideo)
|
||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
||||
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
|
||||
|
||||
if (BeatmapSet.HasStoryboard)
|
||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
|
||||
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
|
||||
|
||||
if (BeatmapSet.FeaturedInSpotlight)
|
||||
{
|
||||
|
@ -7,7 +7,6 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||
using osu.Game.Graphics;
|
||||
@ -226,10 +225,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
});
|
||||
|
||||
if (BeatmapSet.HasVideo)
|
||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
||||
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
|
||||
|
||||
if (BeatmapSet.HasStoryboard)
|
||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
|
||||
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
|
||||
|
||||
if (BeatmapSet.FeaturedInSpotlight)
|
||||
{
|
||||
|
@ -3,14 +3,16 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
public class IconPill : CircularContainer
|
||||
public abstract class IconPill : CircularContainer, IHasTooltip
|
||||
{
|
||||
public Vector2 IconSize
|
||||
{
|
||||
@ -20,7 +22,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
||||
private readonly Container iconContainer;
|
||||
|
||||
public IconPill(IconUsage icon)
|
||||
protected IconPill(IconUsage icon)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Masking = true;
|
||||
@ -47,5 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public abstract LocalisableString TooltipText { get; }
|
||||
}
|
||||
}
|
||||
|
19
osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs
Normal file
19
osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// 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 osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
public class StoryboardIconPill : IconPill
|
||||
{
|
||||
public StoryboardIconPill()
|
||||
: base(FontAwesome.Solid.Image)
|
||||
{
|
||||
}
|
||||
|
||||
public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoStoryboard;
|
||||
}
|
||||
}
|
19
osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs
Normal file
19
osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// 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 osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
public class VideoIconPill : IconPill
|
||||
{
|
||||
public VideoIconPill()
|
||||
: base(FontAwesome.Solid.Film)
|
||||
{
|
||||
}
|
||||
|
||||
public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoVideo;
|
||||
}
|
||||
}
|
@ -392,7 +392,7 @@ namespace osu.Game.Database
|
||||
{
|
||||
total_writes_async.Value++;
|
||||
using (var realm = getRealmInstance())
|
||||
await realm.WriteAsync(action);
|
||||
await realm.WriteAsync(() => action(realm));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers
|
||||
/// </summary>
|
||||
public class ScalingContainer : Container
|
||||
{
|
||||
private const float duration = 500;
|
||||
internal const float TRANSITION_DURATION = 500;
|
||||
|
||||
private Bindable<float> sizeX;
|
||||
private Bindable<float> sizeY;
|
||||
@ -99,7 +99,7 @@ namespace osu.Game.Graphics.Containers
|
||||
if (applyUIScale)
|
||||
{
|
||||
uiScale = osuConfig.GetBindable<float>(OsuSetting.UIScale);
|
||||
uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, duration, Easing.OutQuart), true);
|
||||
uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, TRANSITION_DURATION, Easing.OutQuart), true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,10 +163,10 @@ namespace osu.Game.Graphics.Containers
|
||||
backgroundStack.Push(new ScalingBackgroundScreen());
|
||||
}
|
||||
|
||||
backgroundStack.FadeIn(duration);
|
||||
backgroundStack.FadeIn(TRANSITION_DURATION);
|
||||
}
|
||||
else
|
||||
backgroundStack?.FadeOut(duration);
|
||||
backgroundStack?.FadeOut(TRANSITION_DURATION);
|
||||
}
|
||||
|
||||
RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One);
|
||||
@ -195,13 +195,13 @@ namespace osu.Game.Graphics.Containers
|
||||
if (requiresMasking)
|
||||
sizableContainer.Masking = true;
|
||||
|
||||
sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart);
|
||||
sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart);
|
||||
sizableContainer.MoveTo(targetRect.Location, TRANSITION_DURATION, Easing.OutQuart);
|
||||
sizableContainer.ResizeTo(targetRect.Size, TRANSITION_DURATION, Easing.OutQuart);
|
||||
|
||||
// Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius.
|
||||
// Masking and corner radius should likely only be applied at one point in the full game stack to fix this.
|
||||
// An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything".
|
||||
sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None)
|
||||
sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, TRANSITION_DURATION, requiresMasking ? Easing.OutQuart : Easing.None)
|
||||
.OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
Size = TwoLayerButton.SIZE_EXTENDED;
|
||||
|
||||
Child = button = new TwoLayerButton(HoverSampleSet.Submit)
|
||||
Child = button = new TwoLayerButton
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
|
@ -56,8 +56,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private readonly SpriteText spriteText;
|
||||
private Vector2 hoverSpacing => new Vector2(3f, 0f);
|
||||
|
||||
public DialogButton()
|
||||
: base(HoverSampleSet.Submit)
|
||||
public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button)
|
||||
: base(sampleSet)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Icon = FontAwesome.Solid.ExternalLinkAlt,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new HoverClickSounds(HoverSampleSet.Submit)
|
||||
new HoverClickSounds()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
@ -37,7 +38,10 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
|
||||
sampleClick?.Play();
|
||||
{
|
||||
sampleClick.Frequency.Value = 0.99 + RNG.NextDouble(0.02);
|
||||
sampleClick.Play();
|
||||
}
|
||||
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
@ -10,9 +10,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[Description("default")]
|
||||
Default,
|
||||
|
||||
[Description("submit")]
|
||||
Submit,
|
||||
|
||||
[Description("button")]
|
||||
Button,
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class ShearedToggleButton : ShearedButton
|
||||
{
|
||||
private Sample? sampleClick;
|
||||
private Sample? sampleOff;
|
||||
private Sample? sampleOn;
|
||||
|
||||
@ -39,8 +40,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
||||
sampleClick = audio.Samples.Get(@"UI/default-select");
|
||||
sampleOn = audio.Samples.Get(@"UI/dropdown-open");
|
||||
sampleOff = audio.Samples.Get(@"UI/dropdown-close");
|
||||
}
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||
@ -67,6 +69,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private void playSample()
|
||||
{
|
||||
sampleClick?.Play();
|
||||
|
||||
if (Active.Value)
|
||||
sampleOn?.Play();
|
||||
else
|
||||
|
@ -2,11 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -65,6 +67,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
get
|
||||
{
|
||||
if (BeatmapModelManager.VIDEO_EXTENSIONS.Contains(File.Extension))
|
||||
return FontAwesome.Regular.FileVideo;
|
||||
|
||||
switch (File.Extension)
|
||||
{
|
||||
case @".ogg":
|
||||
@ -77,12 +82,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
case @".png":
|
||||
return FontAwesome.Regular.FileImage;
|
||||
|
||||
case @".mp4":
|
||||
case @".avi":
|
||||
case @".mov":
|
||||
case @".flv":
|
||||
return FontAwesome.Regular.FileVideo;
|
||||
|
||||
default:
|
||||
return FontAwesome.Regular.File;
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
||||
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
||||
new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay),
|
||||
new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
|
||||
@ -322,5 +323,8 @@ namespace osu.Game.Input.Bindings
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))]
|
||||
DeselectAllMods,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))]
|
||||
EditorTapForBPM,
|
||||
}
|
||||
}
|
||||
|
@ -30,12 +30,12 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device");
|
||||
|
||||
/// <summary>
|
||||
/// "Master"
|
||||
/// "Hitsound stereo separation"
|
||||
/// </summary>
|
||||
public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation");
|
||||
|
||||
/// <summary>
|
||||
/// "Level"
|
||||
/// "Master"
|
||||
/// </summary>
|
||||
public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
|
||||
|
||||
@ -69,6 +69,6 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +174,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
|
||||
|
||||
/// <summary>
|
||||
/// "Tap for BPM"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM");
|
||||
|
||||
/// <summary>
|
||||
/// "Cycle grid display mode"
|
||||
/// </summary>
|
||||
@ -205,7 +210,7 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface");
|
||||
|
||||
/// <summary>
|
||||
/// "Toggle Mod Select"
|
||||
/// "Toggle mod select"
|
||||
/// </summary>
|
||||
public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle mod select");
|
||||
|
||||
@ -294,6 +299,6 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,10 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString JoystickGamepad => new TranslatableString(getKey(@"joystick_gamepad"), @"Joystick / Gamepad");
|
||||
|
||||
/// <summary>
|
||||
/// "Deadzone Threshold"
|
||||
/// "Deadzone"
|
||||
/// </summary>
|
||||
public static LocalisableString DeadzoneThreshold => new TranslatableString(getKey(@"deadzone_threshold"), @"Deadzone");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency.");
|
||||
|
||||
/// <summary>
|
||||
/// "Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended."
|
||||
/// </summary>
|
||||
public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended.");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps");
|
||||
|
||||
/// <summary>
|
||||
/// "Delete ALL beatmap videos"
|
||||
/// </summary>
|
||||
public static LocalisableString DeleteAllBeatmapVideos => new TranslatableString(getKey(@"delete_all_beatmap_videos"), @"Delete ALL beatmap videos");
|
||||
|
||||
/// <summary>
|
||||
/// "Import scores from stable"
|
||||
/// </summary>
|
||||
|
@ -121,8 +121,16 @@ namespace osu.Game.Online.API
|
||||
|
||||
if (isFailing) return;
|
||||
|
||||
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
||||
WebRequest.Perform();
|
||||
try
|
||||
{
|
||||
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
||||
WebRequest.Perform();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignore this. internally Perform is running async and the fail state may have changed since
|
||||
// the last check of `isFailing` above.
|
||||
}
|
||||
|
||||
if (isFailing) return;
|
||||
|
||||
|
@ -38,7 +38,6 @@ namespace osu.Game.Online.Chat
|
||||
}
|
||||
|
||||
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
||||
: base(HoverSampleSet.Submit)
|
||||
{
|
||||
Parts = parts.ToList();
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Online.Chat
|
||||
if (!messages.Any())
|
||||
return;
|
||||
|
||||
var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId);
|
||||
var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id > 0 && c.Id == messages.First().ChannelId);
|
||||
|
||||
if (channel == null)
|
||||
return;
|
||||
|
@ -155,9 +155,6 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
public Func<Message, ChatLine> CreateChatLineAction;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
public StandAloneDrawableChannel(Channel channel)
|
||||
: base(channel)
|
||||
{
|
||||
@ -166,25 +163,40 @@ namespace osu.Game.Online.Chat
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// TODO: Remove once DrawableChannel & ChatLine padding is fixed
|
||||
ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 };
|
||||
}
|
||||
|
||||
protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m);
|
||||
|
||||
protected override Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time)
|
||||
protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time);
|
||||
}
|
||||
|
||||
protected class StandAloneDaySeparator : DaySeparator
|
||||
{
|
||||
protected override float TextSize => 14;
|
||||
protected override float LineHeight => 1;
|
||||
protected override float Spacing => 10;
|
||||
protected override float DateAlign => 120;
|
||||
|
||||
public StandAloneDaySeparator(DateTimeOffset time)
|
||||
: base(time)
|
||||
{
|
||||
TextSize = 14,
|
||||
Colour = colours.Yellow,
|
||||
LineHeight = 1,
|
||||
Padding = new MarginPadding { Horizontal = 10 },
|
||||
Margin = new MarginPadding { Vertical = 5 },
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Height = 25;
|
||||
Colour = colours.Yellow;
|
||||
// TODO: Remove once DrawableChannel & ChatLine padding is fixed
|
||||
Padding = new MarginPadding { Horizontal = 10 };
|
||||
}
|
||||
}
|
||||
|
||||
protected class StandAloneMessage : ChatLine
|
||||
{
|
||||
protected override float TextSize => 15;
|
||||
|
||||
protected override float HorizontalPadding => 10;
|
||||
protected override float MessagePadding => 120;
|
||||
protected override float TimestampPadding => 50;
|
||||
|
@ -197,6 +197,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
APIRoom.Playlist.Clear();
|
||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
||||
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
|
||||
|
||||
Debug.Assert(LocalUser != null);
|
||||
addUserToAPIRoom(LocalUser);
|
||||
@ -737,6 +738,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
APIRoom.Type.Value = Room.Settings.MatchType;
|
||||
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
||||
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
|
||||
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId);
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
}
|
||||
|
@ -61,7 +61,13 @@ namespace osu.Game.Online.Rooms
|
||||
/// Used for serialising to the API.
|
||||
/// </summary>
|
||||
[JsonProperty("beatmap_id")]
|
||||
private int onlineBeatmapId => Beatmap.OnlineID;
|
||||
private int onlineBeatmapId
|
||||
{
|
||||
get => Beatmap.OnlineID;
|
||||
// This setter is only required for client-side serialise-then-deserialise operations.
|
||||
// Serialisation is supposed to emit only a `beatmap_id`, but a (non-null) `beatmap` is required on deserialise.
|
||||
set => Beatmap = new APIBeatmap { OnlineID = value };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A beatmap representing this playlist item.
|
||||
|
@ -162,6 +162,13 @@ namespace osu.Game.Online.Rooms
|
||||
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies values from another <see cref="Room"/> into this one.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// **Beware**: This will store references between <see cref="Room"/>s.
|
||||
/// </remarks>
|
||||
/// <param name="other">The <see cref="Room"/> to copy values from.</param>
|
||||
public void CopyFrom(Room other)
|
||||
{
|
||||
RoomID.Value = other.RoomID.Value;
|
||||
|
@ -9,6 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
@ -658,11 +659,14 @@ namespace osu.Game
|
||||
}
|
||||
|
||||
protected override IDictionary<FrameworkSetting, object> GetFrameworkConfigDefaults()
|
||||
=> new Dictionary<FrameworkSetting, object>
|
||||
{
|
||||
return new Dictionary<FrameworkSetting, object>
|
||||
{
|
||||
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance)
|
||||
{ FrameworkSetting.WindowMode, WindowMode.Fullscreen }
|
||||
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance).
|
||||
// However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there.
|
||||
{ FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
|
@ -80,6 +80,9 @@ namespace osu.Game
|
||||
|
||||
public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
|
||||
|
||||
internal EndpointConfiguration CreateEndpoints() =>
|
||||
UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
||||
|
||||
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
||||
|
||||
/// <summary>
|
||||
@ -268,7 +271,7 @@ namespace osu.Game
|
||||
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
|
||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||
|
||||
EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
||||
EndpointConfiguration endpoints = CreateEndpoints();
|
||||
|
||||
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
@ -69,7 +70,7 @@ namespace osu.Game.Overlays.AccountCreation
|
||||
},
|
||||
usernameTextBox = new OsuTextBox
|
||||
{
|
||||
PlaceholderText = UsersStrings.LoginUsername,
|
||||
PlaceholderText = UsersStrings.LoginUsername.ToLower(),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
@ -91,7 +92,7 @@ namespace osu.Game.Overlays.AccountCreation
|
||||
},
|
||||
passwordTextBox = new OsuPasswordTextBox
|
||||
{
|
||||
PlaceholderText = "password",
|
||||
PlaceholderText = UsersStrings.LoginPassword.ToLower(),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular),
|
||||
Text = LabelFor(Value)
|
||||
},
|
||||
new HoverClickSounds()
|
||||
new HoverClickSounds(HoverSampleSet.TabSelect)
|
||||
});
|
||||
|
||||
Enabled.Value = true;
|
||||
|
@ -6,10 +6,6 @@
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
@ -24,85 +20,19 @@ namespace osu.Game.Overlays.Chat
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// TODO: Remove once DrawableChannel & ChatLine padding is fixed
|
||||
ChatLineFlow.Padding = new MarginPadding(0);
|
||||
}
|
||||
|
||||
protected override Drawable CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time);
|
||||
protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time);
|
||||
|
||||
private class ChatOverlayDaySeparator : Container
|
||||
private class ChatOverlayDaySeparator : DaySeparator
|
||||
{
|
||||
private readonly DateTimeOffset time;
|
||||
|
||||
public ChatOverlayDaySeparator(DateTimeOffset time)
|
||||
: base(time)
|
||||
{
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Padding = new MarginPadding { Horizontal = 15, Vertical = 20 };
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 200),
|
||||
new Dimension(GridSizeMode.Absolute, 15),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 15),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Colour = colourProvider.Background5,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 2,
|
||||
},
|
||||
Drawable.Empty(),
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(),
|
||||
Font = OsuFont.Torus.With(size: 15, weight: FontWeight.SemiBold),
|
||||
Colour = colourProvider.Content1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Drawable.Empty(),
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = colourProvider.Background5,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
// TODO: Remove once DrawableChannel & ChatLine padding is fixed
|
||||
Padding = new MarginPadding { Horizontal = 15 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
105
osu.Game/Overlays/Chat/DaySeparator.cs
Normal file
105
osu.Game/Overlays/Chat/DaySeparator.cs
Normal file
@ -0,0 +1,105 @@
|
||||
// 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 enable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class DaySeparator : Container
|
||||
{
|
||||
protected virtual float TextSize => 15;
|
||||
|
||||
protected virtual float LineHeight => 2;
|
||||
|
||||
protected virtual float DateAlign => 200;
|
||||
|
||||
protected virtual float Spacing => 15;
|
||||
|
||||
private readonly DateTimeOffset time;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private OverlayColourProvider? colourProvider { get; set; }
|
||||
|
||||
public DaySeparator(DateTimeOffset time)
|
||||
{
|
||||
this.time = time;
|
||||
Height = 40;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RowDimensions = new[] { new Dimension() },
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, DateAlign),
|
||||
new Dimension(GridSizeMode.Absolute, Spacing),
|
||||
new Dimension(),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[] { new Dimension() },
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, Spacing),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = LineHeight,
|
||||
Colour = colourProvider?.Background5 ?? Colour4.White,
|
||||
},
|
||||
Drawable.Empty(),
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(),
|
||||
Font = OsuFont.Torus.With(size: TextSize, weight: FontWeight.SemiBold),
|
||||
Colour = colourProvider?.Content1 ?? Colour4.White,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
Drawable.Empty(),
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = LineHeight,
|
||||
Colour = colourProvider?.Background5 ?? Colour4.White,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -7,14 +7,9 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Chat;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -40,9 +35,6 @@ namespace osu.Game.Overlays.Chat
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
public DrawableChannel(Channel channel)
|
||||
{
|
||||
Channel = channel;
|
||||
@ -67,7 +59,7 @@ namespace osu.Game.Overlays.Chat
|
||||
Padding = new MarginPadding { Bottom = 5 },
|
||||
Child = ChatLineFlow = new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding { Left = 20, Right = 20 },
|
||||
Padding = new MarginPadding { Horizontal = 15 },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
@ -121,11 +113,7 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m);
|
||||
|
||||
protected virtual Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time)
|
||||
{
|
||||
Colour = colours.ChatBlue.Lighten(0.7f),
|
||||
Margin = new MarginPadding { Vertical = 10 },
|
||||
};
|
||||
protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time);
|
||||
|
||||
private void newMessagesArrived(IEnumerable<Message> newMessages) => Schedule(() =>
|
||||
{
|
||||
@ -203,69 +191,5 @@ namespace osu.Game.Overlays.Chat
|
||||
});
|
||||
|
||||
private IEnumerable<ChatLine> chatLines => ChatLineFlow.Children.OfType<ChatLine>();
|
||||
|
||||
public class DaySeparator : Container
|
||||
{
|
||||
public float TextSize
|
||||
{
|
||||
get => text.Font.Size;
|
||||
set => text.Font = text.Font.With(size: value);
|
||||
}
|
||||
|
||||
private float lineHeight = 2;
|
||||
|
||||
public float LineHeight
|
||||
{
|
||||
get => lineHeight;
|
||||
set => lineHeight = leftBox.Height = rightBox.Height = value;
|
||||
}
|
||||
|
||||
private readonly SpriteText text;
|
||||
private readonly Box leftBox;
|
||||
private readonly Box rightBox;
|
||||
|
||||
public DaySeparator(DateTimeOffset time)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
},
|
||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), },
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
leftBox = new Box
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = lineHeight,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding { Horizontal = 10 },
|
||||
Text = time.ToLocalTime().ToString("dd MMM yyyy"),
|
||||
},
|
||||
rightBox = new Box
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = lineHeight,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -16,6 +18,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Chat;
|
||||
using osuTK;
|
||||
|
||||
@ -32,6 +35,8 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
public IEnumerable<LocalisableString> FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty };
|
||||
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||
|
||||
private Box hoverBox = null!;
|
||||
private SpriteIcon checkbox = null!;
|
||||
private OsuSpriteText channelText = null!;
|
||||
@ -46,14 +51,20 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
|
||||
private const float vertical_margin = 1.5f;
|
||||
|
||||
private Sample? sampleJoin;
|
||||
private Sample? sampleLeave;
|
||||
|
||||
public ChannelListingItem(Channel channel)
|
||||
{
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleJoin = audio.Samples.Get(@"UI/check-on");
|
||||
sampleLeave = audio.Samples.Get(@"UI/check-off");
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -156,7 +167,19 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
}
|
||||
}, true);
|
||||
|
||||
Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(Channel);
|
||||
Action = () =>
|
||||
{
|
||||
if (channelJoined.Value)
|
||||
{
|
||||
OnRequestLeave?.Invoke(Channel);
|
||||
sampleLeave?.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
OnRequestJoin?.Invoke(Channel);
|
||||
sampleJoin?.Play();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
|
@ -14,7 +14,6 @@ using osu.Framework.Platform;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Overlays.News
|
||||
@ -29,7 +28,6 @@ namespace osu.Game.Overlays.News
|
||||
private TextFlowContainer main;
|
||||
|
||||
public NewsCard(APINewsPost post)
|
||||
: base(HoverSampleSet.Submit)
|
||||
{
|
||||
this.post = post;
|
||||
|
||||
|
@ -18,7 +18,6 @@ using System.Diagnostics;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.News.Sidebar
|
||||
{
|
||||
@ -129,7 +128,6 @@ namespace osu.Game.Overlays.News.Sidebar
|
||||
private readonly APINewsPost post;
|
||||
|
||||
public PostButton(APINewsPost post)
|
||||
: base(HoverSampleSet.Submit)
|
||||
{
|
||||
this.post = post;
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
private Sample sampleOpen;
|
||||
private Sample sampleClose;
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds();
|
||||
|
||||
public ExpandDetailsButton()
|
||||
{
|
||||
|
@ -6,7 +6,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Sections
|
||||
{
|
||||
@ -18,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections
|
||||
private readonly IBeatmapInfo beatmapInfo;
|
||||
|
||||
protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo)
|
||||
: base(HoverSampleSet.Submit)
|
||||
{
|
||||
this.beatmapInfo = beatmapInfo;
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
@ -224,6 +225,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
|
||||
private void updateScreenModeWarning()
|
||||
{
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS)
|
||||
{
|
||||
if (windowModeDropdown.Current.Value == WindowMode.Fullscreen)
|
||||
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true);
|
||||
else
|
||||
windowModeDropdown.ClearNoticeText();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (windowModeDropdown.Current.Value != WindowMode.Fullscreen)
|
||||
{
|
||||
windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true);
|
||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
private SettingsButton deleteSkinsButton;
|
||||
private SettingsButton restoreButton;
|
||||
private SettingsButton undeleteButton;
|
||||
private SettingsButton deleteBeatmapVideosButton;
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay)
|
||||
@ -58,6 +59,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
}
|
||||
});
|
||||
|
||||
Add(deleteBeatmapVideosButton = new DangerousSettingsButton
|
||||
{
|
||||
Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos,
|
||||
Action = () =>
|
||||
{
|
||||
dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() =>
|
||||
{
|
||||
deleteBeatmapVideosButton.Enabled.Value = false;
|
||||
Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true));
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
if (legacyImportManager?.SupportsImportFromStable == true)
|
||||
{
|
||||
Add(importScoresButton = new SettingsButton
|
||||
|
@ -0,0 +1,16 @@
|
||||
// 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;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
public class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog
|
||||
{
|
||||
public MassVideoDeleteConfirmationDialog(Action deleteAction)
|
||||
: base(deleteAction)
|
||||
{
|
||||
BodyText = "All beatmap videos? This cannot be undone!";
|
||||
}
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -83,7 +82,6 @@ namespace osu.Game.Overlays.Toolbar
|
||||
private RealmAccess realm { get; set; }
|
||||
|
||||
protected ToolbarButton()
|
||||
: base(HoverSampleSet.Toolbar)
|
||||
{
|
||||
Width = Toolbar.HEIGHT;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
@ -11,7 +11,6 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -29,7 +28,6 @@ namespace osu.Game.Overlays.Toolbar
|
||||
private AnalogClockDisplay analog;
|
||||
|
||||
public ToolbarClock()
|
||||
: base(HoverSampleSet.Toolbar)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
AutoSizeAxes = Axes.X;
|
||||
|
@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
@ -28,7 +27,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>, IStateful<SelectionState>
|
||||
public class VolumeMeter : Container, IStateful<SelectionState>
|
||||
{
|
||||
private CircularProgress volumeCircle;
|
||||
private CircularProgress volumeCircleGlow;
|
||||
@ -80,7 +79,7 @@ namespace osu.Game.Overlays.Volume
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, AudioManager audio)
|
||||
{
|
||||
hoverSample = audio.Samples.Get($"UI/{HoverSampleSet.Button.GetDescription()}-hover");
|
||||
hoverSample = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-hover");
|
||||
notchSample = audio.Samples.Get(@"UI/notch-tick");
|
||||
sampleLastPlaybackTime = Time.Current;
|
||||
|
||||
@ -132,7 +131,7 @@ namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Name = "Progress under covers for smoothing",
|
||||
Name = @"Progress under covers for smoothing",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = 180,
|
||||
Child = volumeCircle = new CircularProgress
|
||||
@ -144,7 +143,7 @@ namespace osu.Game.Overlays.Volume
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Name = "Inner Cover",
|
||||
Name = @"Inner Cover",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -153,7 +152,7 @@ namespace osu.Game.Overlays.Volume
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = "Progress overlay for glow",
|
||||
Name = @"Progress overlay for glow",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -365,27 +364,6 @@ namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (!IsHovered)
|
||||
return false;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.SelectPreviousGroup:
|
||||
State = SelectionState.Selected;
|
||||
adjust(1, false);
|
||||
return true;
|
||||
|
||||
case GlobalAction.SelectNextGroup:
|
||||
State = SelectionState.Selected;
|
||||
adjust(-1, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays.Volume;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@ -171,6 +172,30 @@ namespace osu.Game.Overlays
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Left:
|
||||
Adjust(GlobalAction.PreviousVolumeMeter);
|
||||
return true;
|
||||
|
||||
case Key.Right:
|
||||
Adjust(GlobalAction.NextVolumeMeter);
|
||||
return true;
|
||||
|
||||
case Key.Down:
|
||||
Adjust(GlobalAction.DecreaseVolume);
|
||||
return true;
|
||||
|
||||
case Key.Up:
|
||||
Adjust(GlobalAction.IncreaseVolume);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
schedulePopOut();
|
||||
|
@ -460,6 +460,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
currentMaximumScoringValues.BaseScore = maximum.BaseScore;
|
||||
currentMaximumScoringValues.MaxCombo = maximum.MaxCombo;
|
||||
|
||||
Combo.Value = frame.Header.Combo;
|
||||
HighestCombo.Value = frame.Header.MaxCombo;
|
||||
|
||||
scoreResultCounts.Clear();
|
||||
|
@ -16,13 +16,15 @@ namespace osu.Game.Screens.Edit.Components
|
||||
/// </summary>
|
||||
internal class EditorSidebar : Container<EditorSidebarSection>
|
||||
{
|
||||
public const float WIDTH = 250;
|
||||
|
||||
private readonly Box background;
|
||||
|
||||
protected override Container<EditorSidebarSection> Content { get; }
|
||||
|
||||
public EditorSidebar()
|
||||
{
|
||||
Width = 250;
|
||||
Width = WIDTH;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
|
@ -1,99 +1,29 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
public class TimelineButton : CompositeDrawable
|
||||
public class TimelineButton : IconButton
|
||||
{
|
||||
public Action Action;
|
||||
public readonly BindableBool Enabled = new BindableBool(true);
|
||||
|
||||
public IconUsage Icon
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
get => button.Icon;
|
||||
set => button.Icon = value;
|
||||
// These are using colourProvider but don't match the design.
|
||||
// Just something to fit until someone implements the updated design.
|
||||
IconColour = colourProvider.Background1;
|
||||
IconHoverColour = colourProvider.Content2;
|
||||
|
||||
HoverColour = colourProvider.Background1;
|
||||
FlashColour = colourProvider.Content2;
|
||||
|
||||
Add(new RepeatingButtonBehaviour(this));
|
||||
}
|
||||
|
||||
private readonly IconButton button;
|
||||
|
||||
public TimelineButton()
|
||||
{
|
||||
InternalChild = button = new TimelineIconButton { Action = () => Action?.Invoke() };
|
||||
|
||||
button.Enabled.BindTo(Enabled);
|
||||
Width = button.Width;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
button.Size = new Vector2(button.Width, DrawHeight);
|
||||
}
|
||||
|
||||
private class TimelineIconButton : IconButton
|
||||
{
|
||||
public TimelineIconButton()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
IconColour = OsuColour.Gray(0.35f);
|
||||
IconHoverColour = Color4.White;
|
||||
HoverColour = OsuColour.Gray(0.25f);
|
||||
FlashColour = OsuColour.Gray(0.5f);
|
||||
}
|
||||
|
||||
private ScheduledDelegate repeatSchedule;
|
||||
|
||||
/// <summary>
|
||||
/// The initial delay before mouse down repeat begins.
|
||||
/// </summary>
|
||||
private const int repeat_initial_delay = 250;
|
||||
|
||||
/// <summary>
|
||||
/// The delay between mouse down repeats after the initial repeat.
|
||||
/// </summary>
|
||||
private const int repeat_tick_rate = 70;
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
// don't actuate a click since we are manually handling repeats.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
Action clickAction = () => base.OnClick(new ClickEvent(e.CurrentState, e.Button));
|
||||
|
||||
// run once for initial down
|
||||
clickAction();
|
||||
|
||||
Scheduler.Add(repeatSchedule = new ScheduledDelegate(clickAction, Clock.CurrentTime + repeat_initial_delay, repeat_tick_rate));
|
||||
}
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
repeatSchedule?.Cancel();
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
}
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Layout;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -40,10 +41,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
[Resolved(canBeNull: true)]
|
||||
private IFrameBasedClock editorClock { get; set; }
|
||||
|
||||
private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize);
|
||||
|
||||
public ZoomableScrollContainer()
|
||||
: base(Direction.Horizontal)
|
||||
{
|
||||
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
|
||||
|
||||
AddLayout(zoomedContentWidthCache);
|
||||
}
|
||||
|
||||
private float minZoom = 1;
|
||||
@ -103,12 +108,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
protected override void Update()
|
||||
{
|
||||
base.LoadComplete();
|
||||
base.Update();
|
||||
|
||||
// This width only gets updated on the application of a transform, so this needs to be initialized here.
|
||||
updateZoomedContentWidth();
|
||||
if (!zoomedContentWidthCache.IsValid)
|
||||
updateZoomedContentWidth();
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
@ -128,7 +133,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
return base.OnScroll(e);
|
||||
}
|
||||
|
||||
private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom;
|
||||
private void updateZoomedContentWidth()
|
||||
{
|
||||
zoomedContent.Width = DrawWidth * currentZoom;
|
||||
zoomedContentWidthCache.Validate();
|
||||
}
|
||||
|
||||
private float zoomTarget = 1;
|
||||
|
||||
@ -199,8 +208,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
|
||||
|
||||
d.currentZoom = newZoom;
|
||||
|
||||
d.updateZoomedContentWidth();
|
||||
|
||||
// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
|
||||
// TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is.
|
||||
d.Invalidate(Invalidation.DrawSize);
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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 enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@ -9,6 +11,7 @@ using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@ -27,10 +30,10 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
public IEnumerable<string> HandledExtensions => handledExtensions;
|
||||
|
||||
private readonly Bindable<FileInfo> currentFile = new Bindable<FileInfo>();
|
||||
private readonly Bindable<FileInfo?> currentFile = new Bindable<FileInfo?>();
|
||||
|
||||
[Resolved]
|
||||
private OsuGameBase game { get; set; }
|
||||
private OsuGameBase game { get; set; } = null!;
|
||||
|
||||
public FileChooserLabelledTextBox(params string[] handledExtensions)
|
||||
{
|
||||
@ -45,7 +48,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
currentFile.BindValueChanged(onFileSelected);
|
||||
}
|
||||
|
||||
private void onFileSelected(ValueChangedEvent<FileInfo> file)
|
||||
private void onFileSelected(ValueChangedEvent<FileInfo?> file)
|
||||
{
|
||||
if (file.NewValue == null)
|
||||
return;
|
||||
@ -65,14 +68,16 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
game.UnregisterImportHandler(this);
|
||||
|
||||
if (game.IsNotNull())
|
||||
game.UnregisterImportHandler(this);
|
||||
}
|
||||
|
||||
public override Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile);
|
||||
|
||||
private class FileChooserPopover : OsuPopover
|
||||
{
|
||||
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo> currentFile)
|
||||
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo?> currentFile)
|
||||
{
|
||||
Child = new Container
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Padding = new MarginPadding(10);
|
||||
Padding = new MarginPadding(10) { Bottom = 0 };
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
|
@ -38,6 +38,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
[Resolved]
|
||||
private OverlayColourProvider overlayColourProvider { get; set; }
|
||||
|
||||
public bool EnableClicking { get; set; } = true;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
@ -281,6 +283,9 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
if (!EnableClicking)
|
||||
return;
|
||||
|
||||
var channel = clunk?.GetChannel();
|
||||
|
||||
if (channel != null)
|
||||
|
92
osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs
Normal file
92
osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs
Normal file
@ -0,0 +1,92 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a component that provides the behaviour of triggering button clicks repeatedly while holding with mouse.
|
||||
/// </summary>
|
||||
public class RepeatingButtonBehaviour : Component
|
||||
{
|
||||
private const double initial_delay = 300;
|
||||
private const double minimum_delay = 80;
|
||||
|
||||
private readonly Drawable button;
|
||||
|
||||
private Sample sample;
|
||||
|
||||
/// <summary>
|
||||
/// An additive modifier for the frequency of the sample played on next actuation.
|
||||
/// This can be adjusted during the button's <see cref="Drawable.OnClick"/> event to affect the repeat sample playback of that click.
|
||||
/// </summary>
|
||||
public double SampleFrequencyModifier { get; set; }
|
||||
|
||||
public RepeatingButtonBehaviour(Drawable button)
|
||||
{
|
||||
this.button = button;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sample = audio.Samples.Get(@"UI/notch-tick");
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
beginRepeat();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
adjustDelegate?.Cancel();
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
private ScheduledDelegate adjustDelegate;
|
||||
private double adjustDelay = initial_delay;
|
||||
|
||||
private void beginRepeat()
|
||||
{
|
||||
adjustDelegate?.Cancel();
|
||||
|
||||
adjustDelay = initial_delay;
|
||||
adjustNext();
|
||||
|
||||
void adjustNext()
|
||||
{
|
||||
if (IsHovered)
|
||||
{
|
||||
button.TriggerClick();
|
||||
adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f);
|
||||
|
||||
var channel = sample?.GetChannel();
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay);
|
||||
channel.Frequency.Value = 1 + repeatModifier + SampleFrequencyModifier;
|
||||
channel.Play();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
adjustDelay = initial_delay;
|
||||
}
|
||||
|
||||
adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
Flow = new FillFlowContainer
|
||||
{
|
||||
Padding = new MarginPadding(20),
|
||||
Padding = new MarginPadding(10) { Top = 0 },
|
||||
Spacing = new Vector2(20),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
|
418
osu.Game/Screens/Edit/Timing/TapButton.cs
Normal file
418
osu.Game/Screens/Edit/Timing/TapButton.cs
Normal file
@ -0,0 +1,418 @@
|
||||
// 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 enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
internal class TapButton : CircularContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
public const float SIZE = 140;
|
||||
|
||||
public readonly BindableBool IsHandlingTapping = new BindableBool();
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private Bindable<ControlPointGroup>? selectedGroup { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private IBeatSyncProvider? beatSyncSource { get; set; }
|
||||
|
||||
private Circle hoverLayer = null!;
|
||||
|
||||
private CircularContainer innerCircle = null!;
|
||||
private Box innerCircleHighlight = null!;
|
||||
|
||||
private int currentLight;
|
||||
|
||||
private Container scaleContainer = null!;
|
||||
private Container lights = null!;
|
||||
private Container lightsGlow = null!;
|
||||
private OsuSpriteText bpmText = null!;
|
||||
private Container textContainer = null!;
|
||||
|
||||
private bool grabbedMouseDown;
|
||||
|
||||
private ScheduledDelegate? resetDelegate;
|
||||
|
||||
private const int light_count = 8;
|
||||
|
||||
private const int initial_taps_to_ignore = 4;
|
||||
|
||||
private const int max_taps_to_consider = 128;
|
||||
|
||||
private const double transition_length = 500;
|
||||
|
||||
private const float angular_light_gap = 0.007f;
|
||||
|
||||
private readonly List<double> tapTimings = new List<double>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Size = new Vector2(SIZE);
|
||||
|
||||
const float ring_width = 22;
|
||||
const float light_padding = 3;
|
||||
|
||||
InternalChild = scaleContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4
|
||||
},
|
||||
lights = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Name = @"outer masking",
|
||||
Masking = true,
|
||||
BorderThickness = light_padding,
|
||||
BorderColour = colourProvider.Background4,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Black,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
}
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Name = @"inner masking",
|
||||
Size = new Vector2(SIZE - ring_width * 2 + light_padding * 2),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = colourProvider.Background4,
|
||||
},
|
||||
lightsGlow = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
innerCircle = new CircularContainer
|
||||
{
|
||||
Size = new Vector2(SIZE - ring_width * 2),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colourProvider.Background2,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
innerCircleHighlight = new Box
|
||||
{
|
||||
Colour = colourProvider.Colour3,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
},
|
||||
textContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background1,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Torus.With(size: 34, weight: FontWeight.SemiBold),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Y = 5,
|
||||
Text = "Tap",
|
||||
},
|
||||
bpmText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Torus.With(size: 23, weight: FontWeight.Regular),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Y = -1,
|
||||
},
|
||||
}
|
||||
},
|
||||
hoverLayer = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background1.Opacity(0.3f),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; i < light_count; i++)
|
||||
{
|
||||
var light = new Light
|
||||
{
|
||||
Rotation = (i + 1) * (360f / light_count) + 360 * angular_light_gap / 2,
|
||||
};
|
||||
|
||||
lights.Add(light);
|
||||
lightsGlow.Add(light.Glow.CreateProxy());
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
hoverLayer.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
private ColourInfo textColour
|
||||
{
|
||||
get
|
||||
{
|
||||
if (grabbedMouseDown)
|
||||
return colourProvider.Background4;
|
||||
|
||||
if (IsHovered)
|
||||
return colourProvider.Content2;
|
||||
|
||||
return colourProvider.Background1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
hoverLayer.FadeIn(transition_length, Easing.OutQuint);
|
||||
textContainer.FadeColour(textColour, transition_length, Easing.OutQuint);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
hoverLayer.FadeOut(transition_length, Easing.OutQuint);
|
||||
textContainer.FadeColour(textColour, transition_length, Easing.OutQuint);
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
const double in_duration = 100;
|
||||
|
||||
grabbedMouseDown = true;
|
||||
IsHandlingTapping.Value = true;
|
||||
|
||||
resetDelegate?.Cancel();
|
||||
|
||||
handleTap();
|
||||
|
||||
textContainer.FadeColour(textColour, in_duration, Easing.OutQuint);
|
||||
|
||||
scaleContainer.ScaleTo(0.99f, in_duration, Easing.OutQuint);
|
||||
innerCircle.ScaleTo(0.96f, in_duration, Easing.OutQuint);
|
||||
|
||||
innerCircleHighlight
|
||||
.FadeIn(50, Easing.OutQuint)
|
||||
.FlashColour(Color4.White, 1000, Easing.OutQuint);
|
||||
|
||||
lights[currentLight % light_count].Hide();
|
||||
lights[(currentLight + light_count / 2) % light_count].Hide();
|
||||
|
||||
currentLight++;
|
||||
|
||||
lights[currentLight % light_count].Show();
|
||||
lights[(currentLight + light_count / 2) % light_count].Show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
const double out_duration = 800;
|
||||
|
||||
grabbedMouseDown = false;
|
||||
|
||||
textContainer.FadeColour(textColour, out_duration, Easing.OutQuint);
|
||||
|
||||
scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint);
|
||||
innerCircle.ScaleTo(1, out_duration, Easing.OutQuint);
|
||||
|
||||
innerCircleHighlight.FadeOut(out_duration, Easing.OutQuint);
|
||||
|
||||
resetDelegate = Scheduler.AddDelayed(reset, 1000);
|
||||
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Action == GlobalAction.EditorTapForBPM && !e.Repeat)
|
||||
{
|
||||
// Direct through mouse handling to achieve animation
|
||||
OnMouseDown(new MouseDownEvent(e.CurrentState, MouseButton.Left));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Action == GlobalAction.EditorTapForBPM)
|
||||
OnMouseUp(new MouseUpEvent(e.CurrentState, MouseButton.Left));
|
||||
}
|
||||
|
||||
private void handleTap()
|
||||
{
|
||||
tapTimings.Add(Clock.CurrentTime);
|
||||
|
||||
if (tapTimings.Count > initial_taps_to_ignore + max_taps_to_consider)
|
||||
tapTimings.RemoveAt(0);
|
||||
|
||||
if (tapTimings.Count < initial_taps_to_ignore * 2)
|
||||
{
|
||||
bpmText.Text = new string('.', tapTimings.Count);
|
||||
return;
|
||||
}
|
||||
|
||||
double averageBeatLength = (tapTimings.Last() - tapTimings.Skip(initial_taps_to_ignore).First()) / (tapTimings.Count - initial_taps_to_ignore - 1);
|
||||
double clockRate = beatSyncSource?.Clock?.Rate ?? 1;
|
||||
|
||||
double bpm = Math.Round(60000 / averageBeatLength / clockRate);
|
||||
|
||||
bpmText.Text = $"{bpm} BPM";
|
||||
|
||||
var timingPoint = selectedGroup?.Value.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
||||
|
||||
if (timingPoint != null)
|
||||
{
|
||||
// Intentionally use the rounded BPM here.
|
||||
timingPoint.BeatLength = 60000 / bpm;
|
||||
}
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
bpmText.FadeOut(transition_length, Easing.OutQuint);
|
||||
|
||||
using (BeginDelayedSequence(tapTimings.Count > 0 ? transition_length : 0))
|
||||
{
|
||||
Schedule(() => bpmText.Text = "the beat!");
|
||||
bpmText.FadeIn(800, Easing.OutQuint);
|
||||
}
|
||||
|
||||
foreach (var light in lights)
|
||||
light.Hide();
|
||||
|
||||
tapTimings.Clear();
|
||||
currentLight = 0;
|
||||
IsHandlingTapping.Value = false;
|
||||
}
|
||||
|
||||
private class Light : CompositeDrawable
|
||||
{
|
||||
public Drawable Glow { get; private set; } = null!;
|
||||
|
||||
private Container fillContent = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
Size = new Vector2(0.98f); // Avoid bleed into masking edge.
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Current = { Value = 1f / light_count - angular_light_gap },
|
||||
Colour = colourProvider.Background2,
|
||||
},
|
||||
fillContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Colour = colourProvider.Colour1,
|
||||
Children = new[]
|
||||
{
|
||||
new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Current = { Value = 1f / light_count - angular_light_gap },
|
||||
Blending = BlendingParameters.Additive
|
||||
},
|
||||
// Please do not try and make sense of this.
|
||||
// Getting the visual effect I was going for relies on what I can only imagine is broken implementation
|
||||
// of `PadExtent`. If that's ever fixed in the future this will likely need to be adjusted.
|
||||
Glow = new CircularProgress
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Current = { Value = 1f / light_count - 0.01f },
|
||||
Blending = BlendingParameters.Additive
|
||||
}.WithEffect(new GlowEffect
|
||||
{
|
||||
Colour = colourProvider.Colour1.Opacity(0.4f),
|
||||
BlurSigma = new Vector2(9f),
|
||||
Strength = 10,
|
||||
PadExtent = true
|
||||
}),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public override void Show()
|
||||
{
|
||||
fillContent
|
||||
.FadeIn(50, Easing.OutQuint)
|
||||
.FlashColour(Color4.White, 1000, Easing.OutQuint);
|
||||
}
|
||||
|
||||
public override void Hide()
|
||||
{
|
||||
fillContent
|
||||
.FadeOut(300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +1,45 @@
|
||||
// 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 enable
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
public class TapTimingControl : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private EditorClock editorClock { get; set; }
|
||||
private EditorClock editorClock { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap beatmap { get; set; }
|
||||
private EditorBeatmap beatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
||||
private Bindable<ControlPointGroup> selectedGroup { get; set; } = null!;
|
||||
|
||||
private readonly BindableBool isHandlingTapping = new BindableBool();
|
||||
|
||||
private MetronomeDisplay metronome = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
||||
{
|
||||
const float padding = 10;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
@ -48,8 +60,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 200),
|
||||
new Dimension(GridSizeMode.Absolute, 60),
|
||||
new Dimension(GridSizeMode.Absolute, 60),
|
||||
new Dimension(GridSizeMode.Absolute, 50),
|
||||
new Dimension(GridSizeMode.Absolute, TapButton.SIZE + padding),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
@ -58,6 +70,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(padding),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
@ -72,7 +85,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new MetronomeDisplay
|
||||
metronome = new MetronomeDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
@ -89,15 +102,14 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(10),
|
||||
Padding = new MarginPadding { Bottom = padding, Horizontal = padding },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TimingAdjustButton(1)
|
||||
{
|
||||
Text = "Offset",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.48f,
|
||||
Height = 50,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.48f, 1),
|
||||
Action = adjustOffset,
|
||||
},
|
||||
new TimingAdjustButton(0.1)
|
||||
@ -105,9 +117,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Text = "BPM",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.48f,
|
||||
Height = 50,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.48f, 1),
|
||||
Action = adjustBpm,
|
||||
}
|
||||
}
|
||||
@ -118,33 +129,70 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(10),
|
||||
Padding = new MarginPadding { Bottom = padding, Horizontal = padding },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoundedButton
|
||||
new Container
|
||||
{
|
||||
Text = "Reset",
|
||||
BackgroundColour = colours.Pink,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.3f,
|
||||
Action = reset,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
Height = 0.98f,
|
||||
Width = TapButton.SIZE / 1.3f,
|
||||
Masking = true,
|
||||
CornerRadius = 15,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new InlineButton(FontAwesome.Solid.Stop, Anchor.TopLeft)
|
||||
{
|
||||
BackgroundColour = colourProvider.Background1,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.49f,
|
||||
Action = reset,
|
||||
},
|
||||
new InlineButton(FontAwesome.Solid.Play, Anchor.BottomLeft)
|
||||
{
|
||||
BackgroundColour = colourProvider.Background1,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.49f,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Action = start,
|
||||
},
|
||||
},
|
||||
},
|
||||
new RoundedButton
|
||||
new TapButton
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Text = "Play from start",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
BackgroundColour = colourProvider.Background1,
|
||||
Width = 0.68f,
|
||||
Action = tap,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
IsHandlingTapping = { BindTarget = isHandlingTapping }
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
isHandlingTapping.BindValueChanged(handling =>
|
||||
{
|
||||
metronome.EnableClicking = !handling.NewValue;
|
||||
|
||||
if (handling.NewValue)
|
||||
start();
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void start()
|
||||
{
|
||||
editorClock.Seek(selectedGroup.Value.Time);
|
||||
editorClock.Start();
|
||||
}
|
||||
|
||||
private void reset()
|
||||
{
|
||||
editorClock.Stop();
|
||||
editorClock.Seek(selectedGroup.Value.Time);
|
||||
}
|
||||
|
||||
private void adjustOffset(double adjust)
|
||||
@ -176,16 +224,66 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
timing.BeatLength = 60000 / (timing.BPM + adjust);
|
||||
}
|
||||
|
||||
private void tap()
|
||||
private class InlineButton : OsuButton
|
||||
{
|
||||
editorClock.Seek(selectedGroup.Value.Time);
|
||||
editorClock.Start();
|
||||
}
|
||||
private readonly IconUsage icon;
|
||||
private readonly Anchor anchor;
|
||||
|
||||
private void reset()
|
||||
{
|
||||
editorClock.Stop();
|
||||
editorClock.Seek(selectedGroup.Value.Time);
|
||||
private SpriteIcon spriteIcon = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public InlineButton(IconUsage icon, Anchor anchor)
|
||||
{
|
||||
this.icon = icon;
|
||||
this.anchor = anchor;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Content.CornerRadius = 0;
|
||||
Content.Masking = false;
|
||||
|
||||
BackgroundColour = colourProvider.Background2;
|
||||
|
||||
Content.Add(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(15),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
spriteIcon = new SpriteIcon
|
||||
{
|
||||
Icon = icon,
|
||||
Size = new Vector2(22),
|
||||
Anchor = anchor,
|
||||
Origin = anchor,
|
||||
Colour = colourProvider.Background1,
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
// scale looks bad so don't call base.
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
spriteIcon.FadeColour(colourProvider.Content2, 200, Easing.OutQuint);
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
spriteIcon.FadeColour(colourProvider.Background1, 200, Easing.OutQuint);
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,11 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
@ -26,32 +23,24 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
public Action<double> Action;
|
||||
|
||||
private readonly double adjustAmount;
|
||||
private ScheduledDelegate adjustDelegate;
|
||||
|
||||
private const int max_multiplier = 10;
|
||||
|
||||
private const int adjust_levels = 4;
|
||||
|
||||
private const double initial_delay = 300;
|
||||
|
||||
private const double minimum_delay = 80;
|
||||
|
||||
public Container Content { get; set; }
|
||||
|
||||
private double adjustDelay = initial_delay;
|
||||
|
||||
private readonly Box background;
|
||||
|
||||
private readonly OsuSpriteText text;
|
||||
|
||||
private Sample sample;
|
||||
|
||||
public LocalisableString Text
|
||||
{
|
||||
get => text.Text;
|
||||
set => text.Text = value;
|
||||
}
|
||||
|
||||
private readonly RepeatingButtonBehaviour repeatBehaviour;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; }
|
||||
|
||||
@ -82,13 +71,13 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddInternal(repeatBehaviour = new RepeatingButtonBehaviour(this));
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
private void load()
|
||||
{
|
||||
sample = audio.Samples.Get(@"UI/notch-tick");
|
||||
|
||||
background.Colour = colourProvider.Background3;
|
||||
|
||||
for (int i = 1; i <= adjust_levels; i++)
|
||||
@ -98,57 +87,22 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
beginRepeat();
|
||||
var hoveredBox = Content.OfType<IncrementBox>().FirstOrDefault(d => d.IsHovered);
|
||||
if (hoveredBox == null)
|
||||
return false;
|
||||
|
||||
Action(adjustAmount * hoveredBox.Multiplier);
|
||||
|
||||
hoveredBox.Flash();
|
||||
|
||||
repeatBehaviour.SampleFrequencyModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
adjustDelegate?.Cancel();
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
private void beginRepeat()
|
||||
{
|
||||
adjustDelegate?.Cancel();
|
||||
|
||||
adjustDelay = initial_delay;
|
||||
adjustNext();
|
||||
|
||||
void adjustNext()
|
||||
{
|
||||
var hoveredBox = Content.OfType<IncrementBox>().FirstOrDefault(d => d.IsHovered);
|
||||
|
||||
if (hoveredBox != null)
|
||||
{
|
||||
Action(adjustAmount * hoveredBox.Multiplier);
|
||||
|
||||
adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f);
|
||||
|
||||
hoveredBox.Flash();
|
||||
|
||||
var channel = sample?.GetChannel();
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay);
|
||||
double multiplierModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2f;
|
||||
|
||||
channel.Frequency.Value = 1 + multiplierModifier + repeatModifier;
|
||||
channel.Play();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
adjustDelay = initial_delay;
|
||||
}
|
||||
|
||||
adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay);
|
||||
}
|
||||
}
|
||||
|
||||
private class IncrementBox : CompositeDrawable
|
||||
{
|
||||
public readonly float Multiplier;
|
||||
@ -187,7 +141,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
Origin = direction,
|
||||
Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold),
|
||||
Text = $"{(index > 0 ? "+" : "-")}{Math.Abs(Multiplier * amount)}",
|
||||
Padding = new MarginPadding(5),
|
||||
Padding = new MarginPadding(2),
|
||||
Alpha = 0,
|
||||
}
|
||||
};
|
||||
|
@ -1,12 +1,14 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -51,6 +53,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
|
||||
private readonly IBindableList<ControlPointGroup> controlPointGroups = new BindableList<ControlPointGroup>();
|
||||
|
||||
private RoundedButton addButton;
|
||||
|
||||
[Resolved]
|
||||
private EditorClock clock { get; set; }
|
||||
|
||||
@ -105,9 +109,8 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
new RoundedButton
|
||||
addButton = new RoundedButton
|
||||
{
|
||||
Text = "+ Add at current time",
|
||||
Action = addNew,
|
||||
Size = new Vector2(160, 30),
|
||||
Anchor = Anchor.BottomRight,
|
||||
@ -122,7 +125,14 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true);
|
||||
selectedGroup.BindValueChanged(selected =>
|
||||
{
|
||||
deleteButton.Enabled.Value = selected.NewValue != null;
|
||||
|
||||
addButton.Text = selected.NewValue != null
|
||||
? "+ Clone to current time"
|
||||
: "+ Add at current time";
|
||||
}, true);
|
||||
|
||||
controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups);
|
||||
controlPointGroups.BindCollectionChanged((sender, args) =>
|
||||
@ -132,13 +142,23 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
selectedGroup.Value = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
trackActivePoint();
|
||||
|
||||
addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time;
|
||||
}
|
||||
|
||||
private Type trackedType;
|
||||
|
||||
/// <summary>
|
||||
/// Given the user has selected a control point group, we want to track any group which is
|
||||
/// active at the current point in time which matches the type the user has selected.
|
||||
@ -149,16 +169,27 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
private void trackActivePoint()
|
||||
{
|
||||
// For simplicity only match on the first type of the active control point.
|
||||
var selectedPointType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
||||
if (selectedGroup.Value == null)
|
||||
trackedType = null;
|
||||
else
|
||||
{
|
||||
// If the selected group only has one control point, update the tracking type.
|
||||
if (selectedGroup.Value.ControlPoints.Count == 1)
|
||||
trackedType = selectedGroup.Value?.ControlPoints.Single().GetType();
|
||||
// If the selected group has more than one control point, choose the first as the tracking type
|
||||
// if we don't already have a singular tracked type.
|
||||
else if (trackedType == null)
|
||||
trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType();
|
||||
}
|
||||
|
||||
if (selectedPointType != null)
|
||||
if (trackedType != null)
|
||||
{
|
||||
// We don't have an efficient way of looking up groups currently, only individual point types.
|
||||
// To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo.
|
||||
|
||||
// Find the next group which has the same type as the selected one.
|
||||
var found = Beatmap.ControlPointInfo.Groups
|
||||
.Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType))
|
||||
.Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType))
|
||||
.LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate);
|
||||
|
||||
if (found != null)
|
||||
@ -189,7 +220,7 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
// Try and create matching types from the currently selected control point.
|
||||
var selected = selectedGroup.Value;
|
||||
|
||||
if (selected != null)
|
||||
if (selected != null && selected != group)
|
||||
{
|
||||
foreach (var controlPoint in selected.ControlPoints)
|
||||
{
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
||||
sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select");
|
||||
sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-select");
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
|
@ -159,6 +159,8 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = beatmap.DifficultyName,
|
||||
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
|
||||
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
|
||||
Truncate = true,
|
||||
},
|
||||
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
|
||||
{
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Extensions.EnumExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
@ -633,7 +634,7 @@ namespace osu.Game.Screens.Select
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
// handles the vertical size of the carousel changing (ie. on window resize when aspect ratio has changed).
|
||||
if ((invalidation & Invalidation.Layout) > 0)
|
||||
if (invalidation.HasFlagFast(Invalidation.DrawSize))
|
||||
itemsCache.Invalidate();
|
||||
|
||||
return base.OnInvalidate(invalidation, source);
|
||||
|
@ -8,6 +8,7 @@ using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -244,7 +245,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
}
|
||||
|
||||
if (hideRequested != null)
|
||||
items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
|
||||
items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo)));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Screens.Select.Filter
|
||||
[Description("Author")]
|
||||
Author,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatsBpm))]
|
||||
[LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksBpm))]
|
||||
BPM,
|
||||
|
||||
[Description("Date Added")]
|
||||
@ -28,10 +28,10 @@ namespace osu.Game.Screens.Select.Filter
|
||||
Length,
|
||||
|
||||
// todo: pending support (https://github.com/ppy/osu/issues/4917)
|
||||
// [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))]
|
||||
// [Description("Rank Achieved")]
|
||||
// RankAchieved,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
|
||||
[Description("Source")]
|
||||
Source,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingTitle))]
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select
|
||||
private readonly Box light;
|
||||
|
||||
public FooterButton()
|
||||
: base(HoverSampleSet.Button)
|
||||
: base(HoverSampleSet.Toolbar)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Shear = SHEAR;
|
||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Options
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
public BeatmapOptionsButton()
|
||||
: base(HoverSampleSet.Submit)
|
||||
: base(HoverSampleSet.Button)
|
||||
{
|
||||
Width = width;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
@ -29,6 +29,8 @@ namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public const double TRANSITION_DURATION = 500;
|
||||
|
||||
public const float MENU_HEIGHT = 40;
|
||||
|
||||
public readonly BindableList<ISkinnableDrawable> SelectedComponents = new BindableList<ISkinnableDrawable>();
|
||||
|
||||
protected override bool StartHidden => true;
|
||||
@ -78,8 +80,6 @@ namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
const float menu_height = 40;
|
||||
|
||||
InternalChild = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -102,7 +102,7 @@ namespace osu.Game.Skinning.Editor
|
||||
Name = "Menu container",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MinValue,
|
||||
Height = menu_height,
|
||||
Height = MENU_HEIGHT,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new EditorMenuBar
|
||||
@ -322,7 +322,10 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.FadeIn(TRANSITION_DURATION, Easing.OutQuint);
|
||||
this
|
||||
// align animation to happen after the majority of the ScalingContainer animation completes.
|
||||
.Delay(ScalingContainer.TRANSITION_DURATION * 0.3f)
|
||||
.FadeIn(TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
|
@ -12,6 +12,8 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
@ -33,6 +35,8 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
private OsuScreen lastTargetScreen;
|
||||
|
||||
private Vector2 lastDrawSize;
|
||||
|
||||
public SkinEditorOverlay(ScalingContainer scalingContainer)
|
||||
{
|
||||
this.scalingContainer = scalingContainer;
|
||||
@ -81,15 +85,42 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
protected override void PopOut() => skinEditor?.Hide();
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (game.DrawSize != lastDrawSize)
|
||||
{
|
||||
lastDrawSize = game.DrawSize;
|
||||
updateScreenSizing();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateScreenSizing()
|
||||
{
|
||||
if (skinEditor?.State.Value != Visibility.Visible) return;
|
||||
|
||||
const float padding = 10;
|
||||
|
||||
float relativeSidebarWidth = (EditorSidebar.WIDTH + padding) / DrawWidth;
|
||||
float relativeToolbarHeight = (SkinEditorSceneLibrary.HEIGHT + SkinEditor.MENU_HEIGHT + padding) / DrawHeight;
|
||||
|
||||
var rect = new RectangleF(
|
||||
relativeSidebarWidth,
|
||||
relativeToolbarHeight,
|
||||
1 - relativeSidebarWidth * 2,
|
||||
1f - relativeToolbarHeight - padding / DrawHeight);
|
||||
|
||||
scalingContainer.SetCustomRect(rect, true);
|
||||
}
|
||||
|
||||
private void updateComponentVisibility()
|
||||
{
|
||||
Debug.Assert(skinEditor != null);
|
||||
|
||||
const float toolbar_padding_requirement = 0.18f;
|
||||
|
||||
if (skinEditor.State.Value == Visibility.Visible)
|
||||
{
|
||||
scalingContainer.SetCustomRect(new RectangleF(toolbar_padding_requirement, 0.2f, 0.8f - toolbar_padding_requirement, 0.7f), true);
|
||||
Scheduler.AddOnce(updateScreenSizing);
|
||||
|
||||
game?.Toolbar.Hide();
|
||||
game?.CloseAllOverlays();
|
||||
@ -127,6 +158,9 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
private void setTarget(OsuScreen target)
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(skinEditor != null);
|
||||
|
||||
if (!target.IsLoaded)
|
||||
|
@ -27,6 +27,8 @@ namespace osu.Game.Skinning.Editor
|
||||
{
|
||||
public class SkinEditorSceneLibrary : CompositeDrawable
|
||||
{
|
||||
public const float HEIGHT = BUTTON_HEIGHT + padding * 2;
|
||||
|
||||
public const float BUTTON_HEIGHT = 40;
|
||||
|
||||
private const float padding = 10;
|
||||
@ -42,7 +44,7 @@ namespace osu.Game.Skinning.Editor
|
||||
|
||||
public SkinEditorSceneLibrary()
|
||||
{
|
||||
Height = BUTTON_HEIGHT + padding * 2;
|
||||
Height = HEIGHT;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -303,8 +303,13 @@ namespace osu.Game.Skinning
|
||||
if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out string val))
|
||||
{
|
||||
// special case for handling skins which use 1 or 0 to signify a boolean state.
|
||||
// ..or in some cases 2 (https://github.com/ppy/osu/issues/18579).
|
||||
if (typeof(TValue) == typeof(bool))
|
||||
val = val == "1" ? "true" : "false";
|
||||
{
|
||||
val = bool.TryParse(val, out bool boolVal)
|
||||
? Convert.ChangeType(boolVal, typeof(bool)).ToString()
|
||||
: Convert.ChangeType(Convert.ToInt32(val), typeof(bool)).ToString();
|
||||
}
|
||||
|
||||
var bindable = new Bindable<TValue>();
|
||||
if (val != null)
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Skinning
|
||||
/// The <see cref="ISkin"/> which is being transformed.
|
||||
/// </summary>
|
||||
[NotNull]
|
||||
protected internal ISkin Skin { get; }
|
||||
public ISkin Skin { get; }
|
||||
|
||||
protected LegacySkinTransformer([NotNull] ISkin skin)
|
||||
{
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
@ -44,9 +46,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
switch (request)
|
||||
{
|
||||
case CreateRoomRequest createRoomRequest:
|
||||
var apiRoom = new Room();
|
||||
|
||||
apiRoom.CopyFrom(createRoomRequest.Room);
|
||||
var apiRoom = cloneRoom(createRoomRequest.Room);
|
||||
|
||||
// Passwords are explicitly not copied between rooms.
|
||||
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
|
||||
@ -178,12 +178,31 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
|
||||
private Room createResponseRoom(Room room, bool withParticipants)
|
||||
{
|
||||
var responseRoom = new Room();
|
||||
responseRoom.CopyFrom(room);
|
||||
var responseRoom = cloneRoom(room);
|
||||
|
||||
// Password is hidden from the response, and is only propagated via HasPassword.
|
||||
bool hadPassword = responseRoom.HasPassword.Value;
|
||||
responseRoom.Password.Value = null;
|
||||
responseRoom.HasPassword.Value = hadPassword;
|
||||
|
||||
if (!withParticipants)
|
||||
responseRoom.RecentParticipants.Clear();
|
||||
|
||||
return responseRoom;
|
||||
}
|
||||
|
||||
private Room cloneRoom(Room source)
|
||||
{
|
||||
var result = JsonConvert.DeserializeObject<Room>(JsonConvert.SerializeObject(source));
|
||||
Debug.Assert(result != null);
|
||||
|
||||
// Playlist item IDs aren't serialised.
|
||||
if (source.CurrentPlaylistItem.Value != null)
|
||||
result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID;
|
||||
for (int i = 0; i < source.Playlist.Count; i++)
|
||||
result.Playlist[i].ID = source.Playlist[i].ID;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Users.Drawables
|
||||
@ -74,11 +73,6 @@ namespace osu.Game.Users.Drawables
|
||||
{
|
||||
private LocalisableString tooltip = default_tooltip_text;
|
||||
|
||||
public ClickableArea()
|
||||
: base(HoverSampleSet.Submit)
|
||||
{
|
||||
}
|
||||
|
||||
public override LocalisableString TooltipText
|
||||
{
|
||||
get => Enabled.Value ? tooltip : default;
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Users.Drawables
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new HoverClickSounds(HoverSampleSet.Submit)
|
||||
new HoverClickSounds()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Users
|
||||
protected Drawable Background { get; private set; }
|
||||
|
||||
protected UserPanel(APIUser user)
|
||||
: base(HoverSampleSet.Submit)
|
||||
: base(HoverSampleSet.Button)
|
||||
{
|
||||
if (user == null)
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Utils
|
||||
sentrySession = SentrySdk.Init(options =>
|
||||
{
|
||||
// Not setting the dsn will completely disable sentry.
|
||||
if (game.IsDeployedBuild)
|
||||
if (game.IsDeployedBuild && game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal))
|
||||
options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2";
|
||||
|
||||
options.AutoSessionTracking = true;
|
||||
@ -159,6 +159,7 @@ namespace osu.Game.Utils
|
||||
Game = game.Clock.CurrentTime,
|
||||
};
|
||||
|
||||
scope.SetTag(@"beatmap", $"{beatmap.OnlineID}");
|
||||
scope.SetTag(@"ruleset", ruleset.ShortName);
|
||||
scope.SetTag(@"os", $"{RuntimeInfo.OS} ({Environment.OSVersion})");
|
||||
scope.SetTag(@"processor count", Environment.ProcessorCount.ToString());
|
||||
|
@ -31,12 +31,12 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2022.417.0">
|
||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2022.607.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.11.2" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.530.0" />
|
||||
<PackageReference Include="Realm" Version="10.14.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.605.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
||||
<PackageReference Include="Sentry" Version="3.17.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user