1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:07:23 +08:00

Merge branch 'master' into beatmap-skin

This commit is contained in:
Dean Herbert 2021-05-29 15:36:43 +09:00
commit 9b239e308b
39 changed files with 911 additions and 309 deletions

View File

@ -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:**

View File

@ -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.

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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);
}
});
}

View File

@ -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

View File

@ -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();

View File

@ -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());
}
}
}

View File

@ -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());
}

View File

@ -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]

View File

@ -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]

View 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));
}
}
}
}

View File

@ -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()
{

View File

@ -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();
}
}
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View 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);
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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;

View 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));
}
}
}

View 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);
}
}
}

View 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;
}),
};
}
}
}

View 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);
}
}
}
}

View File

@ -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()
{

View File

@ -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;

View File

@ -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>

View File

@ -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);
});
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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 };
}
}

View 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;
}
}
}
}

View File

@ -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>