mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 13:22:55 +08:00
Merge branch 'master' into beatmap-skin
This commit is contained in:
commit
9b239e308b
@ -1,7 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Propose a feature you would like to see in the game!
|
||||
---
|
||||
**Describe the new feature:**
|
||||
|
||||
**Proposal designs of the feature:**
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,12 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Suggestions or feature request
|
||||
url: https://github.com/ppy/osu/discussions/categories/ideas
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||
- name: Help
|
||||
url: https://github.com/ppy/osu/discussions/categories/q-a
|
||||
about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
|
||||
- name: osu!stable issues
|
||||
url: https://github.com/ppy/osu-stable-issues
|
||||
about: For issues regarding osu!stable (not osu!lazer), open them here.
|
||||
about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports.
|
||||
|
||||
|
@ -7,13 +7,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece
|
||||
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, IHasMainCirclePiece
|
||||
{
|
||||
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
||||
|
||||
@ -111,7 +112,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||
}
|
||||
|
||||
public void UpdateSnakingPosition(Vector2 start, Vector2 end) =>
|
||||
Position = HitObject.RepeatIndex % 2 == 0 ? end : start;
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
|
||||
if (Slider != null)
|
||||
Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,8 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
// Old osu! used hit sounding to determine various hit type information
|
||||
IList<HitSampleInfo> samples = obj.Samples;
|
||||
|
||||
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case IHasDistance distanceData:
|
||||
@ -94,15 +92,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
||||
{
|
||||
IList<HitSampleInfo> currentSamples = allSamples[i];
|
||||
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
||||
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
||||
|
||||
yield return new Hit
|
||||
{
|
||||
StartTime = j,
|
||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
||||
Samples = currentSamples,
|
||||
IsStrong = strong
|
||||
};
|
||||
|
||||
i = (i + 1) % allSamples.Count;
|
||||
@ -117,7 +111,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
IsStrong = strong,
|
||||
Duration = taikoDuration,
|
||||
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
||||
};
|
||||
@ -143,16 +136,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
|
||||
default:
|
||||
{
|
||||
bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
|
||||
|
||||
bool isRim = samples.Any(isRimDefinition);
|
||||
|
||||
yield return new Hit
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
||||
Samples = samples,
|
||||
IsStrong = strong
|
||||
};
|
||||
|
||||
break;
|
||||
|
@ -69,7 +69,11 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
||||
{
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
||||
if (h is Hit taikoHit)
|
||||
{
|
||||
taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
||||
EditorBeatmap.Update(h);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,25 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public HitType Type
|
||||
{
|
||||
get => TypeBindable.Value;
|
||||
set
|
||||
{
|
||||
TypeBindable.Value = value;
|
||||
updateSamplesFromType();
|
||||
}
|
||||
set => TypeBindable.Value = value;
|
||||
}
|
||||
|
||||
public Hit()
|
||||
{
|
||||
TypeBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||
}
|
||||
|
||||
private void updateTypeFromSamples()
|
||||
{
|
||||
Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array of any samples which would cause this object to be a "rim" type hit.
|
||||
/// </summary>
|
||||
private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
||||
|
||||
private void updateSamplesFromType()
|
||||
{
|
||||
var rimSamples = getRimSamples();
|
||||
@ -42,11 +54,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an array of any samples which would cause this object to be a "rim" type hit.
|
||||
/// </summary>
|
||||
private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
|
@ -33,14 +33,21 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
public bool IsStrong
|
||||
{
|
||||
get => IsStrongBindable.Value;
|
||||
set
|
||||
{
|
||||
IsStrongBindable.Value = value;
|
||||
updateSamplesFromStrong();
|
||||
}
|
||||
set => IsStrongBindable.Value = value;
|
||||
}
|
||||
|
||||
private void updateSamplesFromStrong()
|
||||
protected TaikoStrongableHitObject()
|
||||
{
|
||||
IsStrongBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||
}
|
||||
|
||||
private void updateTypeFromSamples()
|
||||
{
|
||||
IsStrong = getStrongSamples().Any();
|
||||
}
|
||||
|
||||
private void updateSamplesFromType()
|
||||
{
|
||||
var strongSamples = getStrongSamples();
|
||||
|
||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
|
||||
await osu.CollectionManager.Import(new MemoryStream());
|
||||
await importCollectionsFromStream(osu, new MemoryStream());
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
|
||||
}
|
||||
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host);
|
||||
|
||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||
|
||||
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||
|
||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||
|
||||
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
|
||||
ms.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
await osu.CollectionManager.Import(ms);
|
||||
await importCollectionsFromStream(osu, ms);
|
||||
}
|
||||
|
||||
Assert.That(host.UpdateThread.Running, Is.True);
|
||||
@ -134,7 +134,7 @@ namespace osu.Game.Tests.Collections.IO
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||
|
||||
// Move first beatmap from second collection into the first.
|
||||
osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]);
|
||||
@ -169,5 +169,12 @@ namespace osu.Game.Tests.Collections.IO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task importCollectionsFromStream(TestOsuGameBase osu, Stream stream)
|
||||
{
|
||||
// intentionally spin this up on a separate task to avoid disposal deadlocks.
|
||||
// see https://github.com/EventStore/EventStore/issues/1179
|
||||
await Task.Run(() => osu.CollectionManager.Import(stream).Wait());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,13 +88,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
beforeLoadAction?.Invoke();
|
||||
|
||||
prepareBeatmap();
|
||||
|
||||
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
||||
}
|
||||
|
||||
private void prepareBeatmap()
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
|
||||
|
||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
||||
mod.ApplyToTrack(Beatmap.Value.Track);
|
||||
|
||||
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -178,10 +183,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("load slow dummy beatmap", () =>
|
||||
{
|
||||
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
|
||||
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
|
||||
prepareBeatmap();
|
||||
slowPlayer = new SlowLoadPlayer(false, false);
|
||||
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer));
|
||||
});
|
||||
|
||||
AddStep("schedule slow load", () => Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000));
|
||||
|
||||
AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen());
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
CreateTest(null);
|
||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
|
||||
AddAssert("score shown", () => Player.IsScoreShown);
|
||||
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||
AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -73,8 +73,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
for (int i = 0; i < users; i++)
|
||||
spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||
|
||||
Client.CurrentMatchPlayingUserIds.Clear();
|
||||
Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
|
||||
spectatorClient.Schedule(() =>
|
||||
{
|
||||
Client.CurrentMatchPlayingUserIds.Clear();
|
||||
Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
|
||||
});
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -91,6 +94,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||
AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
175
osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
Normal file
175
osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
Normal file
@ -0,0 +1,175 @@
|
||||
// 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 Markdig.Syntax.Inlines;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers.Markdown;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Wiki.Markdown;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneWikiMarkdownContainer : OsuTestScene
|
||||
{
|
||||
private TestMarkdownContainer markdownContainer;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = overlayColour.Background5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(20),
|
||||
Child = markdownContainer = new TestMarkdownContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestLink()
|
||||
{
|
||||
AddStep("set current path", () => markdownContainer.CurrentPath = "Article_styling_criteria/");
|
||||
|
||||
AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_Page");
|
||||
|
||||
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ");
|
||||
|
||||
AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing");
|
||||
|
||||
AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)");
|
||||
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOutdatedNoticeBox()
|
||||
{
|
||||
AddStep("Add outdated yaml header", () =>
|
||||
{
|
||||
markdownContainer.Text = @"---
|
||||
outdated: true
|
||||
---";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNeedsCleanupNoticeBox()
|
||||
{
|
||||
AddStep("Add needs cleanup yaml header", () =>
|
||||
{
|
||||
markdownContainer.Text = @"---
|
||||
needs_cleanup: true
|
||||
---";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlyShowOutdatedNoticeBox()
|
||||
{
|
||||
AddStep("Add outdated and needs cleanup yaml", () =>
|
||||
{
|
||||
markdownContainer.Text = @"---
|
||||
outdated: true
|
||||
needs_cleanup: true
|
||||
---";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAbsoluteImage()
|
||||
{
|
||||
AddStep("Add absolute image", () =>
|
||||
{
|
||||
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||
markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRelativeImage()
|
||||
{
|
||||
AddStep("Add relative image", () =>
|
||||
{
|
||||
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||
markdownContainer.CurrentPath = "Interface/";
|
||||
markdownContainer.Text = "![intro](img/intro-screen.jpg)";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBlockImage()
|
||||
{
|
||||
AddStep("Add paragraph with block image", () =>
|
||||
{
|
||||
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||
markdownContainer.CurrentPath = "Interface/";
|
||||
markdownContainer.Text = @"Line before image
|
||||
|
||||
![play menu](img/play-menu.jpg ""Main Menu in osu!"")
|
||||
|
||||
Line after image";
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInlineImage()
|
||||
{
|
||||
AddStep("Add inline image", () =>
|
||||
{
|
||||
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||
markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!";
|
||||
});
|
||||
}
|
||||
|
||||
private class TestMarkdownContainer : WikiMarkdownContainer
|
||||
{
|
||||
public LinkInline Link;
|
||||
|
||||
public new string DocumentUrl
|
||||
{
|
||||
set => base.DocumentUrl = value;
|
||||
}
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
|
||||
{
|
||||
UrlAdded = link => Link = link,
|
||||
};
|
||||
|
||||
private class TestMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||
{
|
||||
public Action<LinkInline> UrlAdded;
|
||||
|
||||
protected override void AddLinkText(string text, LinkInline linkInline)
|
||||
{
|
||||
base.AddLinkText(text, linkInline);
|
||||
|
||||
UrlAdded?.Invoke(linkInline);
|
||||
}
|
||||
|
||||
protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("click first row", () =>
|
||||
{
|
||||
firstRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||
|
||||
InputManager.MoveMouseTo(firstRow);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
@ -138,6 +139,64 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleBindingResetButton()
|
||||
{
|
||||
KeyBindingRow settingsKeyBindingRow = null;
|
||||
|
||||
AddStep("click first row", () =>
|
||||
{
|
||||
settingsKeyBindingRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||
|
||||
InputManager.MoveMouseTo(settingsKeyBindingRow);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.PressKey(Key.P);
|
||||
InputManager.ReleaseKey(Key.P);
|
||||
});
|
||||
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha > 0);
|
||||
|
||||
AddStep("click reset button for bindings", () =>
|
||||
{
|
||||
var resetButton = settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First();
|
||||
|
||||
resetButton.Click();
|
||||
});
|
||||
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
||||
|
||||
AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResetAllBindingsButton()
|
||||
{
|
||||
KeyBindingRow settingsKeyBindingRow = null;
|
||||
|
||||
AddStep("click first row", () =>
|
||||
{
|
||||
settingsKeyBindingRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||
|
||||
InputManager.MoveMouseTo(settingsKeyBindingRow);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.PressKey(Key.P);
|
||||
InputManager.ReleaseKey(Key.P);
|
||||
});
|
||||
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha > 0);
|
||||
|
||||
AddStep("click reset button for bindings", () =>
|
||||
{
|
||||
var resetButton = panel.ChildrenOfType<ResetButton>().First();
|
||||
|
||||
resetButton.Click();
|
||||
});
|
||||
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
||||
|
||||
AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClickRowSelectsFirstBinding()
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
private class TestSettingsTextBox : SettingsTextBox
|
||||
{
|
||||
public new Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton>().Single();
|
||||
public Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton<string>>().Single();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -786,9 +786,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}
|
||||
}
|
||||
|
||||
private void checkVisibleItemCount(bool diff, int count) =>
|
||||
AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () =>
|
||||
private void checkVisibleItemCount(bool diff, int count)
|
||||
{
|
||||
// until step required as we are querying against alive items, which are loaded asynchronously inside DrawableCarouselBeatmapSet.
|
||||
AddUntilStep($"{count} {(diff ? "diffs" : "sets")} visible", () =>
|
||||
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
|
||||
}
|
||||
|
||||
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
|
||||
|
||||
|
@ -58,8 +58,13 @@ namespace osu.Game.Collections
|
||||
|
||||
if (storage.Exists(database_name))
|
||||
{
|
||||
List<BeatmapCollection> beatmapCollections;
|
||||
|
||||
using (var stream = storage.GetStream(database_name))
|
||||
importCollections(readCollections(stream));
|
||||
beatmapCollections = readCollections(stream);
|
||||
|
||||
// intentionally fire-and-forget async.
|
||||
importCollections(beatmapCollections);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -46,12 +47,19 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
}
|
||||
}
|
||||
|
||||
private Container content;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
content.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
public bool FilteringActive { get; set; }
|
||||
|
||||
private OsuSpriteText text;
|
||||
private FillFlowContainer cancelAndClearButtons;
|
||||
private FillFlowContainer<KeyButton> buttons;
|
||||
|
||||
private Bindable<bool> isDefault { get; } = new BindableBool(true);
|
||||
|
||||
public IEnumerable<string> FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString());
|
||||
|
||||
public KeyBindingRow(object action, IEnumerable<Framework.Input.Bindings.KeyBinding> bindings)
|
||||
@ -61,9 +69,6 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = padding;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
@ -72,51 +77,72 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Radius = 2,
|
||||
Colour = colours.YellowDark.Opacity(0),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Hollow = true,
|
||||
};
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS };
|
||||
|
||||
Children = new Drawable[]
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
new RestoreDefaultValueButton<bool>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.6f,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Text = action.GetDescription(),
|
||||
Margin = new MarginPadding(padding),
|
||||
},
|
||||
buttons = new FillFlowContainer<KeyButton>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight
|
||||
},
|
||||
cancelAndClearButtons = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(padding) { Top = height + padding * 2 },
|
||||
Anchor = Anchor.TopRight,
|
||||
Current = isDefault,
|
||||
Action = RestoreDefaults,
|
||||
Origin = Anchor.TopRight,
|
||||
Alpha = 0,
|
||||
Spacing = new Vector2(5),
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Masking = true,
|
||||
CornerRadius = padding,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Radius = 2,
|
||||
Colour = colours.YellowDark.Opacity(0),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Hollow = true,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CancelButton { Action = finalise },
|
||||
new ClearButton { Action = clear },
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.6f,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Text = action.GetDescription(),
|
||||
Margin = new MarginPadding(padding),
|
||||
},
|
||||
buttons = new FillFlowContainer<KeyButton>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight
|
||||
},
|
||||
cancelAndClearButtons = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(padding) { Top = height + padding * 2 },
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Alpha = 0,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CancelButton { Action = finalise },
|
||||
new ClearButton { Action = clear },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var b in bindings)
|
||||
buttons.Add(new KeyButton(b));
|
||||
|
||||
updateIsDefaultValue();
|
||||
}
|
||||
|
||||
public void RestoreDefaults()
|
||||
@ -129,18 +155,20 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
button.UpdateKeyCombination(d);
|
||||
store.Update(button.KeyBinding);
|
||||
}
|
||||
|
||||
isDefault.Value = true;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
|
||||
content.FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
|
||||
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
|
||||
content.FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
|
||||
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
@ -288,6 +316,8 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
{
|
||||
store.Update(bindTarget.KeyBinding);
|
||||
|
||||
updateIsDefaultValue();
|
||||
|
||||
bindTarget.IsBinding = false;
|
||||
Schedule(() =>
|
||||
{
|
||||
@ -305,8 +335,8 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
AutoSizeDuration = 500;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
content.AutoSizeDuration = 500;
|
||||
content.AutoSizeEasing = Easing.OutQuint;
|
||||
|
||||
cancelAndClearButtons.FadeIn(300, Easing.OutQuint);
|
||||
cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y;
|
||||
@ -331,6 +361,11 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
if (bindTarget != null) bindTarget.IsBinding = true;
|
||||
}
|
||||
|
||||
private void updateIsDefaultValue()
|
||||
{
|
||||
isDefault.Value = bindings.Select(b => b.KeyCombination).SequenceEqual(Defaults);
|
||||
}
|
||||
|
||||
private class CancelButton : TriangleButton
|
||||
{
|
||||
public CancelButton()
|
||||
@ -379,9 +414,6 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
|
||||
Margin = new MarginPadding(padding);
|
||||
|
||||
// todo: use this in a meaningful way
|
||||
// var isDefault = keyBinding.Action is Enum;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = padding;
|
||||
|
||||
|
@ -61,8 +61,11 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
{
|
||||
Text = "Reset all bindings in section";
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Margin = new MarginPadding { Top = 5 };
|
||||
Height = 20;
|
||||
Width = 0.5f;
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
Margin = new MarginPadding { Top = 15 };
|
||||
Height = 30;
|
||||
|
||||
Content.CornerRadius = 5;
|
||||
}
|
||||
|
@ -2,14 +2,13 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
|
||||
@ -20,26 +19,16 @@ namespace osu.Game.Overlays.News.Displays
|
||||
/// </summary>
|
||||
public class ArticleListing : CompositeDrawable
|
||||
{
|
||||
public Action<APINewsSidebar> SidebarMetadataUpdated;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
private readonly Action fetchMorePosts;
|
||||
|
||||
private FillFlowContainer content;
|
||||
private ShowMoreButton showMore;
|
||||
|
||||
private GetNewsRequest request;
|
||||
private Cursor lastCursor;
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private readonly int? year;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiate a listing for the specified year.
|
||||
/// </summary>
|
||||
/// <param name="year">The year to load articles from. If null, will show the most recent articles.</param>
|
||||
public ArticleListing(int? year = null)
|
||||
public ArticleListing(Action fetchMorePosts)
|
||||
{
|
||||
this.year = year;
|
||||
this.fetchMorePosts = fetchMorePosts;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -47,6 +36,7 @@ namespace osu.Game.Overlays.News.Displays
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 20,
|
||||
@ -75,53 +65,25 @@ namespace osu.Game.Overlays.News.Displays
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Top = 15
|
||||
},
|
||||
Action = performFetch,
|
||||
Margin = new MarginPadding { Top = 15 },
|
||||
Action = fetchMorePosts,
|
||||
Alpha = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
performFetch();
|
||||
}
|
||||
|
||||
private void performFetch()
|
||||
{
|
||||
request?.Cancel();
|
||||
|
||||
request = new GetNewsRequest(year, lastCursor);
|
||||
request.Success += response => Schedule(() => onSuccess(response));
|
||||
api.PerformAsync(request);
|
||||
}
|
||||
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private void onSuccess(GetNewsResponse response)
|
||||
{
|
||||
cancellationToken?.Cancel();
|
||||
|
||||
// only needs to be updated on the initial load, as the content won't change during pagination.
|
||||
if (lastCursor == null)
|
||||
SidebarMetadataUpdated?.Invoke(response.SidebarMetadata);
|
||||
|
||||
// store cursor for next pagination request.
|
||||
lastCursor = response.Cursor;
|
||||
|
||||
LoadComponentsAsync(response.NewsPosts.Select(p => new NewsCard(p)).ToList(), loaded =>
|
||||
public void AddPosts(IEnumerable<APINewsPost> posts, bool morePostsAvailable) => Schedule(() =>
|
||||
LoadComponentsAsync(posts.Select(p => new NewsCard(p)).ToList(), loaded =>
|
||||
{
|
||||
content.AddRange(loaded);
|
||||
|
||||
showMore.IsLoading = false;
|
||||
showMore.Alpha = response.Cursor != null ? 1 : 0;
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
showMore.Alpha = morePostsAvailable ? 1 : 0;
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token)
|
||||
);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
request?.Cancel();
|
||||
cancellationToken?.Cancel();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.News;
|
||||
using osu.Game.Overlays.News.Displays;
|
||||
using osu.Game.Overlays.News.Sidebar;
|
||||
@ -14,13 +15,21 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
public class NewsOverlay : OnlineOverlay<NewsHeader>
|
||||
{
|
||||
private readonly Bindable<string> article = new Bindable<string>(null);
|
||||
private readonly Bindable<string> article = new Bindable<string>();
|
||||
|
||||
private readonly Container sidebarContainer;
|
||||
private readonly NewsSidebar sidebar;
|
||||
|
||||
private readonly Container content;
|
||||
|
||||
private GetNewsRequest request;
|
||||
|
||||
private Cursor lastCursor;
|
||||
|
||||
/// <summary>
|
||||
/// The year currently being displayed. If null, the main listing is being displayed.
|
||||
/// </summary>
|
||||
private int? displayedYear;
|
||||
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private bool displayUpdateRequired = true;
|
||||
@ -65,7 +74,13 @@ namespace osu.Game.Overlays
|
||||
base.LoadComplete();
|
||||
|
||||
// should not be run until first pop-in to avoid requesting data before user views.
|
||||
article.BindValueChanged(onArticleChanged);
|
||||
article.BindValueChanged(a =>
|
||||
{
|
||||
if (a.NewValue == null)
|
||||
loadListing();
|
||||
else
|
||||
loadArticle(a.NewValue);
|
||||
});
|
||||
}
|
||||
|
||||
protected override NewsHeader CreateHeader() => new NewsHeader { ShowFrontPage = ShowFrontPage };
|
||||
@ -95,7 +110,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
public void ShowYear(int year)
|
||||
{
|
||||
loadFrontPage(year);
|
||||
loadListing(year);
|
||||
Show();
|
||||
}
|
||||
|
||||
@ -108,7 +123,11 @@ namespace osu.Game.Overlays
|
||||
protected void LoadDisplay(Drawable display)
|
||||
{
|
||||
ScrollFlow.ScrollToStart();
|
||||
LoadComponentAsync(display, loaded => content.Child = loaded, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
LoadComponentAsync(display, loaded =>
|
||||
{
|
||||
content.Child = loaded;
|
||||
Loading.Hide();
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
@ -118,48 +137,65 @@ namespace osu.Game.Overlays
|
||||
sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0));
|
||||
}
|
||||
|
||||
private void onArticleChanged(ValueChangedEvent<string> article)
|
||||
private void loadListing(int? year = null)
|
||||
{
|
||||
if (article.NewValue == null)
|
||||
loadFrontPage();
|
||||
else
|
||||
loadArticle(article.NewValue);
|
||||
}
|
||||
|
||||
private void loadFrontPage(int? year = null)
|
||||
{
|
||||
beginLoading();
|
||||
|
||||
Header.SetFrontPage();
|
||||
|
||||
var page = new ArticleListing(year);
|
||||
page.SidebarMetadataUpdated += metadata => Schedule(() =>
|
||||
displayedYear = year;
|
||||
lastCursor = null;
|
||||
|
||||
beginLoading(true);
|
||||
|
||||
request = new GetNewsRequest(displayedYear);
|
||||
request.Success += response => Schedule(() =>
|
||||
{
|
||||
sidebar.Metadata.Value = metadata;
|
||||
Loading.Hide();
|
||||
lastCursor = response.Cursor;
|
||||
sidebar.Metadata.Value = response.SidebarMetadata;
|
||||
|
||||
var listing = new ArticleListing(getMorePosts);
|
||||
listing.AddPosts(response.NewsPosts, response.Cursor != null);
|
||||
LoadDisplay(listing);
|
||||
});
|
||||
LoadDisplay(page);
|
||||
|
||||
API.PerformAsync(request);
|
||||
}
|
||||
|
||||
private void getMorePosts()
|
||||
{
|
||||
beginLoading(false);
|
||||
|
||||
request = new GetNewsRequest(displayedYear, lastCursor);
|
||||
request.Success += response => Schedule(() =>
|
||||
{
|
||||
lastCursor = response.Cursor;
|
||||
if (content.Child is ArticleListing listing)
|
||||
listing.AddPosts(response.NewsPosts, response.Cursor != null);
|
||||
});
|
||||
|
||||
API.PerformAsync(request);
|
||||
}
|
||||
|
||||
private void loadArticle(string article)
|
||||
{
|
||||
beginLoading();
|
||||
// This is not yet implemented nor called from anywhere.
|
||||
beginLoading(true);
|
||||
|
||||
Header.SetArticle(article);
|
||||
|
||||
// Temporary, should be handled by ArticleDisplay later
|
||||
LoadDisplay(Empty());
|
||||
Loading.Hide();
|
||||
}
|
||||
|
||||
private void beginLoading()
|
||||
private void beginLoading(bool showLoadingOverlay)
|
||||
{
|
||||
request?.Cancel();
|
||||
cancellationToken?.Cancel();
|
||||
Loading.Show();
|
||||
|
||||
if (showLoadingOverlay)
|
||||
Loading.Show();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
request?.Cancel();
|
||||
cancellationToken?.Cancel();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
106
osu.Game/Overlays/RestoreDefaultValueButton.cs
Normal file
106
osu.Game/Overlays/RestoreDefaultValueButton.cs
Normal file
@ -0,0 +1,106 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class RestoreDefaultValueButton<T> : OsuButton, IHasTooltip, IHasCurrentValue<T>
|
||||
{
|
||||
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
||||
|
||||
private readonly BindableWithCurrent<T> current = new BindableWithCurrent<T>();
|
||||
|
||||
// this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button.
|
||||
public override bool AcceptsFocus => true;
|
||||
|
||||
public Bindable<T> Current
|
||||
{
|
||||
get => current.Current;
|
||||
set => current.Current = value;
|
||||
}
|
||||
|
||||
private Color4 buttonColour;
|
||||
|
||||
private bool hovering;
|
||||
|
||||
public RestoreDefaultValueButton()
|
||||
{
|
||||
Height = 1;
|
||||
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = SettingsPanel.CONTENT_MARGINS;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colour)
|
||||
{
|
||||
BackgroundColour = colour.Yellow;
|
||||
buttonColour = colour.Yellow;
|
||||
Content.Width = 0.33f;
|
||||
Content.CornerRadius = 3;
|
||||
Content.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = buttonColour.Opacity(0.1f),
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 2,
|
||||
};
|
||||
|
||||
Padding = new MarginPadding { Vertical = 1.5f };
|
||||
Alpha = 0f;
|
||||
|
||||
Action += () =>
|
||||
{
|
||||
if (!current.Disabled) current.SetDefault();
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.ValueChanged += _ => UpdateState();
|
||||
Current.DisabledChanged += _ => UpdateState();
|
||||
Current.DefaultChanged += _ => UpdateState();
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
public string TooltipText => "revert to default";
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
hovering = true;
|
||||
UpdateState();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
hovering = false;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
public void UpdateState() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (current == null)
|
||||
return;
|
||||
|
||||
this.FadeTo(current.IsDefault ? 0f :
|
||||
hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint);
|
||||
this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,16 +5,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -108,7 +103,7 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
protected SettingsItem()
|
||||
{
|
||||
RestoreDefaultValueButton restoreDefaultButton;
|
||||
RestoreDefaultValueButton<T> restoreDefaultButton;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
@ -116,7 +111,7 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
restoreDefaultButton = new RestoreDefaultValueButton(),
|
||||
restoreDefaultButton = new RestoreDefaultValueButton<T>(),
|
||||
FlowContent = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@ -137,7 +132,7 @@ namespace osu.Game.Overlays.Settings
|
||||
controlWithCurrent.Current.DisabledChanged += _ => updateDisabled();
|
||||
|
||||
if (ShowsDefaultIndicator)
|
||||
restoreDefaultButton.Bindable = controlWithCurrent.Current;
|
||||
restoreDefaultButton.Current = controlWithCurrent.Current;
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,101 +141,5 @@ namespace osu.Game.Overlays.Settings
|
||||
if (labelText != null)
|
||||
labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1;
|
||||
}
|
||||
|
||||
protected internal class RestoreDefaultValueButton : Container, IHasTooltip
|
||||
{
|
||||
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
||||
|
||||
private Bindable<T> bindable;
|
||||
|
||||
public Bindable<T> Bindable
|
||||
{
|
||||
get => bindable;
|
||||
set
|
||||
{
|
||||
bindable = value;
|
||||
bindable.ValueChanged += _ => UpdateState();
|
||||
bindable.DisabledChanged += _ => UpdateState();
|
||||
bindable.DefaultChanged += _ => UpdateState();
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 buttonColour;
|
||||
|
||||
private bool hovering;
|
||||
|
||||
public RestoreDefaultValueButton()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = SettingsPanel.CONTENT_MARGINS;
|
||||
Padding = new MarginPadding { Vertical = 1.5f };
|
||||
Alpha = 0f;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colour)
|
||||
{
|
||||
buttonColour = colour.Yellow;
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = 3,
|
||||
Masking = true,
|
||||
Colour = buttonColour,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = buttonColour.Opacity(0.1f),
|
||||
Type = EdgeEffectType.Glow,
|
||||
Radius = 2,
|
||||
},
|
||||
Width = 0.33f,
|
||||
Child = new Box { RelativeSizeAxes = Axes.Both },
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
public string TooltipText => "revert to default";
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (bindable != null && !bindable.Disabled)
|
||||
bindable.SetDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
hovering = true;
|
||||
UpdateState();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
hovering = false;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
public void UpdateState() => Scheduler.AddOnce(updateState);
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (bindable == null)
|
||||
return;
|
||||
|
||||
this.FadeTo(bindable.IsDefault ? 0f :
|
||||
hovering && !bindable.Disabled ? 1f : 0.65f, 200, Easing.OutQuint);
|
||||
this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
@ -49,8 +50,6 @@ namespace osu.Game.Overlays
|
||||
|
||||
private readonly bool showSidebar;
|
||||
|
||||
protected Box Background;
|
||||
|
||||
protected SettingsPanel(bool showSidebar)
|
||||
{
|
||||
this.showSidebar = showSidebar;
|
||||
@ -63,13 +62,13 @@ namespace osu.Game.Overlays
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = ContentContainer = new Container
|
||||
InternalChild = ContentContainer = new NonMaskedContent
|
||||
{
|
||||
Width = WIDTH,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Background = new Box
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
@ -165,7 +164,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.PopOut();
|
||||
|
||||
ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
|
||||
ContentContainer.MoveToX(-WIDTH + ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint);
|
||||
|
||||
Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint);
|
||||
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
|
||||
@ -191,6 +190,12 @@ namespace osu.Game.Overlays
|
||||
Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
|
||||
}
|
||||
|
||||
private class NonMaskedContent : Container<Drawable>
|
||||
{
|
||||
// masking breaks the pan-out transform with nested sub-settings panels.
|
||||
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||
}
|
||||
|
||||
public class SettingsSectionsContainer : SectionsContainer<SettingsSection>
|
||||
{
|
||||
public SearchContainer<SettingsSection> SearchContainer;
|
||||
|
50
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
Normal file
50
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using Markdig.Extensions.Yaml;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Game.Graphics.Containers.Markdown;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki.Markdown
|
||||
{
|
||||
public class WikiMarkdownContainer : OsuMarkdownContainer
|
||||
{
|
||||
public string CurrentPath
|
||||
{
|
||||
set => DocumentUrl = $"{DocumentUrl}wiki/{value}";
|
||||
}
|
||||
|
||||
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
|
||||
{
|
||||
switch (markdownObject)
|
||||
{
|
||||
case YamlFrontMatterBlock yamlFrontMatterBlock:
|
||||
container.Add(new WikiNoticeContainer(yamlFrontMatterBlock));
|
||||
break;
|
||||
|
||||
case ParagraphBlock paragraphBlock:
|
||||
// Check if paragraph only contains an image
|
||||
if (paragraphBlock.Inline.Count() == 1 && paragraphBlock.Inline.FirstChild is LinkInline { IsImage: true } linkInline)
|
||||
{
|
||||
container.Add(new WikiMarkdownImageBlock(linkInline));
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
base.AddMarkdownComponent(markdownObject, container, level);
|
||||
}
|
||||
|
||||
public override MarkdownTextFlowContainer CreateTextFlow() => new WikiMarkdownTextFlowContainer();
|
||||
|
||||
private class WikiMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||
{
|
||||
protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline));
|
||||
}
|
||||
}
|
||||
}
|
29
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs
Normal file
29
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImage.cs
Normal file
@ -0,0 +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 Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki.Markdown
|
||||
{
|
||||
public class WikiMarkdownImage : MarkdownImage, IHasTooltip
|
||||
{
|
||||
public string TooltipText { get; }
|
||||
|
||||
public WikiMarkdownImage(LinkInline linkInline)
|
||||
: base(linkInline.Url)
|
||||
{
|
||||
TooltipText = linkInline.Title;
|
||||
}
|
||||
|
||||
protected override ImageContainer CreateImageContainer(string url)
|
||||
{
|
||||
// The idea is replace "https://website.url/wiki/{path-to-image}" to "https://website.url/wiki/images/{path-to-image}"
|
||||
// "/wiki/images/*" is route to fetch wiki image from osu!web server (see: https://github.com/ppy/osu-web/blob/4205eb66a4da86bdee7835045e4bf28c35456e04/routes/web.php#L289)
|
||||
url = url.Replace("/wiki/", "/wiki/images/");
|
||||
|
||||
return base.CreateImageContainer(url);
|
||||
}
|
||||
}
|
||||
}
|
49
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
Normal file
49
osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// 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 Markdig.Syntax.Inlines;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki.Markdown
|
||||
{
|
||||
public class WikiMarkdownImageBlock : FillFlowContainer
|
||||
{
|
||||
[Resolved]
|
||||
private IMarkdownTextComponent parentTextComponent { get; set; }
|
||||
|
||||
private readonly LinkInline linkInline;
|
||||
|
||||
public WikiMarkdownImageBlock(LinkInline linkInline)
|
||||
{
|
||||
this.linkInline = linkInline;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Direction = FillDirection.Vertical;
|
||||
Spacing = new Vector2(0, 3);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new WikiMarkdownImage(linkInline)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
parentTextComponent.CreateSpriteText().With(t =>
|
||||
{
|
||||
t.Text = linkInline.Title;
|
||||
t.Anchor = Anchor.TopCentre;
|
||||
t.Origin = Anchor.TopCentre;
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
97
osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs
Normal file
97
osu.Game/Overlays/Wiki/Markdown/WikiNoticeContainer.cs
Normal file
@ -0,0 +1,97 @@
|
||||
// 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 Markdig.Extensions.Yaml;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Containers.Markdown;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Wiki.Markdown
|
||||
{
|
||||
public class WikiNoticeContainer : FillFlowContainer
|
||||
{
|
||||
private readonly bool isOutdated;
|
||||
private readonly bool needsCleanup;
|
||||
|
||||
public WikiNoticeContainer(YamlFrontMatterBlock yamlFrontMatterBlock)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
Direction = FillDirection.Vertical;
|
||||
|
||||
foreach (var line in yamlFrontMatterBlock.Lines)
|
||||
{
|
||||
switch (line.ToString())
|
||||
{
|
||||
case "outdated: true":
|
||||
isOutdated = true;
|
||||
break;
|
||||
|
||||
case "needs_cleanup: true":
|
||||
needsCleanup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// Reference : https://github.com/ppy/osu-web/blob/master/resources/views/wiki/_notice.blade.php and https://github.com/ppy/osu-web/blob/master/resources/lang/en/wiki.php
|
||||
// TODO : add notice box for fallback translation, legal translation and outdated translation after implement wiki locale in the future.
|
||||
if (isOutdated)
|
||||
{
|
||||
Add(new NoticeBox
|
||||
{
|
||||
Text = "The content on this page is incomplete or outdated. If you are able to help out, please consider updating the article!",
|
||||
});
|
||||
}
|
||||
else if (needsCleanup)
|
||||
{
|
||||
Add(new NoticeBox
|
||||
{
|
||||
Text = "This page does not meet the standards of the osu! wiki and needs to be cleaned up or rewritten. If you are able to help out, please consider updating the article!",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class NoticeBox : Container
|
||||
{
|
||||
[Resolved]
|
||||
private IMarkdownTextFlowComponent parentFlowComponent { get; set; }
|
||||
|
||||
public string Text { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider, OsuColour colour)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
MarkdownTextFlowContainer textFlow;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4,
|
||||
},
|
||||
textFlow = parentFlowComponent.CreateTextFlow().With(t =>
|
||||
{
|
||||
t.Colour = colour.Orange1;
|
||||
t.Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 10,
|
||||
Horizontal = 15,
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
textFlow.AddText(Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -311,6 +311,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// Invoked for this <see cref="DrawableHitObject"/> to take on any values from a newly-applied <see cref="HitObject"/>.
|
||||
/// This is also fired after any changes which occurred via an <see cref="osu.Game.Rulesets.Objects.HitObject.ApplyDefaults"/> call.
|
||||
/// </summary>
|
||||
protected virtual void OnApply()
|
||||
{
|
||||
@ -318,6 +319,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
|
||||
/// <summary>
|
||||
/// Invoked for this <see cref="DrawableHitObject"/> to revert any values previously taken on from the currently-applied <see cref="HitObject"/>.
|
||||
/// This is also fired after any changes which occurred via an <see cref="osu.Game.Rulesets.Objects.HitObject.ApplyDefaults"/> call.
|
||||
/// </summary>
|
||||
protected virtual void OnFree()
|
||||
{
|
||||
|
@ -77,7 +77,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
double offset = result.Time.Value - blueprints.First().Item.StartTime;
|
||||
|
||||
if (offset != 0)
|
||||
Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
|
||||
{
|
||||
Beatmap.PerformOnSelection(obj =>
|
||||
{
|
||||
obj.StartTime += offset;
|
||||
Beatmap.Update(obj);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -125,6 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
|
||||
h.Samples.Add(new HitSampleInfo(sampleName));
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
@ -134,7 +135,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <param name="sampleName">The name of the hit sample.</param>
|
||||
public void RemoveHitSample(string sampleName)
|
||||
{
|
||||
EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName));
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -276,7 +276,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime);
|
||||
double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount;
|
||||
|
||||
EditorBeatmap.PerformOnSelection(h => h.StartTime += adjustment);
|
||||
EditorBeatmap.PerformOnSelection(h =>
|
||||
{
|
||||
h.StartTime += adjustment;
|
||||
EditorBeatmap.Update(h);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -522,7 +522,10 @@ namespace osu.Game.Screens.Play
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
ValidForResume = false;
|
||||
this.MakeCurrent();
|
||||
|
||||
// in the potential case that this instance has already been exited, this is required to avoid a crash.
|
||||
if (this.GetChildScreen() != null)
|
||||
this.MakeCurrent();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||
|
||||
public IEnumerable<DrawableCarouselItem> DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty<DrawableCarouselItem>();
|
||||
public IEnumerable<DrawableCarouselItem> DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty<DrawableCarouselItem>() : beatmapContainer.AliveChildren;
|
||||
|
||||
[CanBeNull]
|
||||
private Container<DrawableCarouselItem> beatmapContainer;
|
||||
|
@ -57,7 +57,13 @@ namespace osu.Game.Storyboards.Drawables
|
||||
public DrawableStoryboard(Storyboard storyboard)
|
||||
{
|
||||
Storyboard = storyboard;
|
||||
|
||||
Size = new Vector2(640, 480);
|
||||
|
||||
bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).Any(e => !(e is StoryboardVideo));
|
||||
|
||||
Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f);
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
@ -15,6 +16,8 @@ namespace osu.Game.Storyboards.Drawables
|
||||
|
||||
public override bool IsPresent => Enabled && base.IsPresent;
|
||||
|
||||
protected LayerElementContainer ElementContainer { get; }
|
||||
|
||||
public DrawableStoryboardLayer(StoryboardLayer layer)
|
||||
{
|
||||
Layer = layer;
|
||||
@ -24,10 +27,10 @@ namespace osu.Game.Storyboards.Drawables
|
||||
Enabled = layer.VisibleWhenPassing;
|
||||
Masking = layer.Masking;
|
||||
|
||||
InternalChild = new LayerElementContainer(layer);
|
||||
InternalChild = ElementContainer = new LayerElementContainer(layer);
|
||||
}
|
||||
|
||||
private class LayerElementContainer : LifetimeManagementContainer
|
||||
protected class LayerElementContainer : LifetimeManagementContainer
|
||||
{
|
||||
private readonly StoryboardLayer storyboardLayer;
|
||||
|
||||
@ -35,8 +38,8 @@ namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
storyboardLayer = layer;
|
||||
|
||||
Width = 640;
|
||||
Height = 480;
|
||||
Size = new Vector2(640, 480);
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Storyboards
|
||||
|
||||
public Storyboard()
|
||||
{
|
||||
layers.Add("Video", new StoryboardLayer("Video", 4, false));
|
||||
layers.Add("Video", new StoryboardVideoLayer("Video", 4, false));
|
||||
layers.Add("Background", new StoryboardLayer("Background", 3));
|
||||
layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, });
|
||||
layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, });
|
||||
@ -85,12 +85,8 @@ namespace osu.Game.Storyboards
|
||||
}
|
||||
}
|
||||
|
||||
public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null)
|
||||
{
|
||||
var drawable = new DrawableStoryboard(this);
|
||||
drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f);
|
||||
return drawable;
|
||||
}
|
||||
public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) =>
|
||||
new DrawableStoryboard(this);
|
||||
|
||||
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Storyboards
|
||||
Elements.Add(element);
|
||||
}
|
||||
|
||||
public DrawableStoryboardLayer CreateDrawable()
|
||||
public virtual DrawableStoryboardLayer CreateDrawable()
|
||||
=> new DrawableStoryboardLayer(this) { Depth = Depth, Name = Name };
|
||||
}
|
||||
}
|
||||
|
32
osu.Game/Storyboards/StoryboardVideoLayer.cs
Normal file
32
osu.Game/Storyboards/StoryboardVideoLayer.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// 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;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
public class StoryboardVideoLayer : StoryboardLayer
|
||||
{
|
||||
public StoryboardVideoLayer(string name, int depth, bool masking)
|
||||
: base(name, depth, masking)
|
||||
{
|
||||
}
|
||||
|
||||
public override DrawableStoryboardLayer CreateDrawable()
|
||||
=> new DrawableStoryboardVideoLayer(this) { Depth = Depth, Name = Name };
|
||||
|
||||
public class DrawableStoryboardVideoLayer : DrawableStoryboardLayer
|
||||
{
|
||||
public DrawableStoryboardVideoLayer(StoryboardVideoLayer layer)
|
||||
: base(layer)
|
||||
{
|
||||
// for videos we want to take on the full size of the storyboard container hierarchy
|
||||
// to allow the video to fill the full available region.
|
||||
ElementContainer.RelativeSizeAxes = Axes.Both;
|
||||
ElementContainer.Size = Vector2.One;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@ -53,6 +54,8 @@ namespace osu.Game.Tests.Visual.Spectator
|
||||
});
|
||||
}
|
||||
|
||||
public new void Schedule(Action action) => base.Schedule(action);
|
||||
|
||||
/// <summary>
|
||||
/// Sends frames for an arbitrary user.
|
||||
/// </summary>
|
||||
|
Loading…
Reference in New Issue
Block a user