mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +08:00
Merge branch 'master' into fix-spectator-frame-conversion
This commit is contained in:
commit
23096c58bb
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Utils;
|
||||
@ -17,8 +18,11 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
Seed.Value ??= RNG.Next();
|
||||
var rng = new Random((int)Seed.Value);
|
||||
|
||||
var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList();
|
||||
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => rng.Next()).ToList();
|
||||
|
||||
beatmap.HitObjects.OfType<ManiaHitObject>().ForEach(h => h.Column = shuffledColumns[h.Column]);
|
||||
}
|
||||
|
@ -3,15 +3,9 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
@ -42,13 +36,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
private Random rng;
|
||||
|
||||
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(OsuModRandomSettingsControl))]
|
||||
public Bindable<int?> Seed { get; } = new Bindable<int?>
|
||||
{
|
||||
Default = null,
|
||||
Value = null
|
||||
};
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
if (!(beatmap is OsuBeatmap osuBeatmap))
|
||||
@ -289,82 +276,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
AngleRad = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public class OsuModRandomSettingsControl : SettingsItem<int?>
|
||||
{
|
||||
protected override Drawable CreateControl() => new SeedControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Top = 5 }
|
||||
};
|
||||
|
||||
private sealed class SeedControl : CompositeDrawable, IHasCurrentValue<int?>
|
||||
{
|
||||
private readonly BindableWithCurrent<int?> current = new BindableWithCurrent<int?>();
|
||||
|
||||
public Bindable<int?> Current
|
||||
{
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
current.Current = value;
|
||||
seedNumberBox.Text = value.Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly OsuNumberBox seedNumberBox;
|
||||
|
||||
public SeedControl()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 2),
|
||||
new Dimension(GridSizeMode.Relative, 0.25f)
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
seedNumberBox = new OsuNumberBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
CommitOnFocusLost = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
seedNumberBox.Current.BindValueChanged(e =>
|
||||
{
|
||||
int? value = null;
|
||||
|
||||
if (int.TryParse(e.NewValue, out var intVal))
|
||||
value = intVal;
|
||||
|
||||
current.Value = value;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
if (current.Value == null)
|
||||
seedNumberBox.Text = current.Current.Value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,10 +20,13 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
var taikoBeatmap = (TaikoBeatmap)beatmap;
|
||||
|
||||
Seed.Value ??= RNG.Next();
|
||||
var rng = new Random((int)Seed.Value);
|
||||
|
||||
foreach (var obj in taikoBeatmap.HitObjects)
|
||||
{
|
||||
if (obj is Hit hit)
|
||||
hit.Type = RNG.Next(2) == 0 ? HitType.Centre : HitType.Rim;
|
||||
hit.Type = rng.Next(2) == 0 ? HitType.Centre : HitType.Rim;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
@ -36,6 +38,10 @@ namespace osu.Game.Tests.Visual.Online
|
||||
private Channel previousChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) - 1);
|
||||
private Channel channel1 => channels[0];
|
||||
private Channel channel2 => channels[1];
|
||||
private Channel channel3 => channels[2];
|
||||
|
||||
[Resolved]
|
||||
private GameHost host { get; set; }
|
||||
|
||||
public TestSceneChatOverlay()
|
||||
{
|
||||
@ -44,7 +50,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Name = $"Channel no. {index}",
|
||||
Topic = index == 3 ? null : $"We talk about the number {index} here",
|
||||
Type = index % 2 == 0 ? ChannelType.PM : ChannelType.Temporary
|
||||
Type = index % 2 == 0 ? ChannelType.PM : ChannelType.Temporary,
|
||||
Id = index
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
@ -228,6 +235,92 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCloseTabShortcut()
|
||||
{
|
||||
AddStep("Join 2 channels", () =>
|
||||
{
|
||||
channelManager.JoinChannel(channel1);
|
||||
channelManager.JoinChannel(channel2);
|
||||
});
|
||||
|
||||
// Want to close channel 2
|
||||
AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
|
||||
AddStep("Close tab via shortcut", pressCloseDocumentKeys);
|
||||
|
||||
// Channel 2 should be closed
|
||||
AddAssert("Channel 1 open", () => channelManager.JoinedChannels.Contains(channel1));
|
||||
AddAssert("Channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2));
|
||||
|
||||
// Want to close channel 1
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
|
||||
AddStep("Close tab via shortcut", pressCloseDocumentKeys);
|
||||
// Channel 1 and channel 2 should be closed
|
||||
AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNewTabShortcut()
|
||||
{
|
||||
AddStep("Join 2 channels", () =>
|
||||
{
|
||||
channelManager.JoinChannel(channel1);
|
||||
channelManager.JoinChannel(channel2);
|
||||
});
|
||||
|
||||
// Want to join another channel
|
||||
AddStep("Press new tab shortcut", pressNewTabKeys);
|
||||
|
||||
// Selector should be visible
|
||||
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRestoreTabShortcut()
|
||||
{
|
||||
AddStep("Join 3 channels", () =>
|
||||
{
|
||||
channelManager.JoinChannel(channel1);
|
||||
channelManager.JoinChannel(channel2);
|
||||
channelManager.JoinChannel(channel3);
|
||||
});
|
||||
|
||||
// Should do nothing
|
||||
AddStep("Restore tab via shortcut", pressRestoreTabKeys);
|
||||
AddAssert("All channels still open", () => channelManager.JoinedChannels.Count == 3);
|
||||
|
||||
// Close channel 1
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
|
||||
AddAssert("Channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1));
|
||||
AddAssert("Other channels still open", () => channelManager.JoinedChannels.Count == 2);
|
||||
|
||||
// Reopen channel 1
|
||||
AddStep("Restore tab via shortcut", pressRestoreTabKeys);
|
||||
AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3);
|
||||
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
|
||||
|
||||
// Close two channels
|
||||
AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
|
||||
AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
|
||||
AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
|
||||
AddStep("Close channel 2", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
|
||||
AddAssert("Only one channel open", () => channelManager.JoinedChannels.Count == 1);
|
||||
AddAssert("Current channel is channel 3", () => currentChannel == channel3);
|
||||
|
||||
// Should first re-open channel 2
|
||||
AddStep("Restore tab via shortcut", pressRestoreTabKeys);
|
||||
AddAssert("Channel 1 still closed", () => !channelManager.JoinedChannels.Contains(channel1));
|
||||
AddAssert("Channel 2 now open", () => channelManager.JoinedChannels.Contains(channel2));
|
||||
AddAssert("Current channel is channel 2", () => currentChannel == channel2);
|
||||
|
||||
// Should then re-open channel 1
|
||||
AddStep("Restore tab via shortcut", pressRestoreTabKeys);
|
||||
AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3);
|
||||
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
|
||||
}
|
||||
|
||||
private void pressChannelHotkey(int number)
|
||||
{
|
||||
var channelKey = Key.Number0 + number;
|
||||
@ -236,6 +329,23 @@ namespace osu.Game.Tests.Visual.Online
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
}
|
||||
|
||||
private void pressCloseDocumentKeys() => pressKeysFor(PlatformActionType.DocumentClose);
|
||||
|
||||
private void pressNewTabKeys() => pressKeysFor(PlatformActionType.TabNew);
|
||||
|
||||
private void pressRestoreTabKeys() => pressKeysFor(PlatformActionType.TabRestore);
|
||||
|
||||
private void pressKeysFor(PlatformActionType type)
|
||||
{
|
||||
var binding = host.PlatformKeyBindings.First(b => ((PlatformAction)b.Action).ActionType == type);
|
||||
|
||||
foreach (var k in binding.KeyCombination.Keys)
|
||||
InputManager.PressKey((Key)k);
|
||||
|
||||
foreach (var k in binding.KeyCombination.Keys)
|
||||
InputManager.ReleaseKey((Key)k);
|
||||
}
|
||||
|
||||
private void clickDrawable(Drawable d)
|
||||
{
|
||||
InputManager.MoveMouseTo(d);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Net;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
@ -32,7 +33,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("Show Article Page", () => wiki.ShowPage("Interface"));
|
||||
}
|
||||
|
||||
private void setUpWikiResponse(APIWikiPage r)
|
||||
[Test]
|
||||
public void TestErrorPage()
|
||||
{
|
||||
setUpWikiResponse(null, true);
|
||||
AddStep("Show Error Page", () => wiki.ShowPage("Error"));
|
||||
}
|
||||
|
||||
private void setUpWikiResponse(APIWikiPage r, bool isFailed = false)
|
||||
=> AddStep("set up response", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
@ -40,7 +48,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
if (!(request is GetWikiRequest getWikiRequest))
|
||||
return false;
|
||||
|
||||
getWikiRequest.TriggerSuccess(r);
|
||||
if (isFailed)
|
||||
getWikiRequest.TriggerFailure(new WebException());
|
||||
else
|
||||
getWikiRequest.TriggerSuccess(r);
|
||||
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.Chat.Tabs;
|
||||
@ -33,6 +34,16 @@ namespace osu.Game.Online.Chat
|
||||
private readonly BindableList<Channel> availableChannels = new BindableList<Channel>();
|
||||
private readonly BindableList<Channel> joinedChannels = new BindableList<Channel>();
|
||||
|
||||
/// <summary>
|
||||
/// Keeps a stack of recently closed channels
|
||||
/// </summary>
|
||||
private readonly List<ClosedChannel> closedChannels = new List<ClosedChannel>();
|
||||
|
||||
// For efficiency purposes, this constant bounds the number of closed channels we store.
|
||||
// This number is somewhat arbitrary; future developers are free to modify it.
|
||||
// Must be a positive number.
|
||||
private const int closed_channels_max_size = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The currently opened channel
|
||||
/// </summary>
|
||||
@ -51,6 +62,9 @@ namespace osu.Game.Online.Chat
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private UserLookupCache users { get; set; }
|
||||
|
||||
public readonly BindableBool HighPollRate = new BindableBool();
|
||||
|
||||
public ChannelManager()
|
||||
@ -420,6 +434,18 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
joinedChannels.Remove(channel);
|
||||
|
||||
// Prevent the closedChannel list from exceeding the max size
|
||||
// by removing the oldest element
|
||||
if (closedChannels.Count >= closed_channels_max_size)
|
||||
{
|
||||
closedChannels.RemoveAt(0);
|
||||
}
|
||||
|
||||
// For PM channels, we store the user ID; else, we store the channel ID
|
||||
closedChannels.Add(channel.Type == ChannelType.PM
|
||||
? new ClosedChannel(ChannelType.PM, channel.Users.Single().Id)
|
||||
: new ClosedChannel(channel.Type, channel.Id));
|
||||
|
||||
if (channel.Joined.Value)
|
||||
{
|
||||
api.Queue(new LeaveChannelRequest(channel));
|
||||
@ -427,6 +453,46 @@ namespace osu.Game.Online.Chat
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Opens the most recently closed channel that has not already been reopened,
|
||||
/// Works similarly to reopening the last closed tab on a web browser.
|
||||
/// </summary>
|
||||
public void JoinLastClosedChannel()
|
||||
{
|
||||
// This loop could be eliminated if the join channel operation ensured that every channel joined
|
||||
// is removed from the closedChannels list, but it'd require a linear scan of closed channels on every join.
|
||||
// To keep the overhead of joining channels low, just lazily scan the list of closed channels locally.
|
||||
while (closedChannels.Count > 0)
|
||||
{
|
||||
ClosedChannel lastClosedChannel = closedChannels.Last();
|
||||
closedChannels.RemoveAt(closedChannels.Count - 1);
|
||||
|
||||
// If the user has already joined the channel, try the next one
|
||||
if (joinedChannels.FirstOrDefault(lastClosedChannel.Matches) != null)
|
||||
continue;
|
||||
|
||||
Channel lastChannel = AvailableChannels.FirstOrDefault(lastClosedChannel.Matches);
|
||||
|
||||
if (lastChannel != null)
|
||||
{
|
||||
// Channel exists as an available channel, directly join it
|
||||
CurrentChannel.Value = JoinChannel(lastChannel);
|
||||
}
|
||||
else if (lastClosedChannel.Type == ChannelType.PM)
|
||||
{
|
||||
// Try to get user in order to open PM chat
|
||||
users.GetUserAsync((int)lastClosedChannel.Id).ContinueWith(u =>
|
||||
{
|
||||
if (u.Result == null) return;
|
||||
|
||||
Schedule(() => CurrentChannel.Value = JoinChannel(new Channel(u.Result)));
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private long lastMessageId;
|
||||
|
||||
private bool channelsInitialised;
|
||||
@ -511,4 +577,28 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores information about a closed channel
|
||||
/// </summary>
|
||||
public class ClosedChannel
|
||||
{
|
||||
public readonly ChannelType Type;
|
||||
public readonly long Id;
|
||||
|
||||
public ClosedChannel(ChannelType type, long id)
|
||||
{
|
||||
Type = type;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public bool Matches(Channel channel)
|
||||
{
|
||||
if (channel.Type != Type) return false;
|
||||
|
||||
return Type == ChannelType.PM
|
||||
? channel.Users.Single().Id == Id
|
||||
: channel.Id == Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,9 +81,11 @@ namespace osu.Game.Overlays.Chat.Tabs
|
||||
RemoveItem(channel);
|
||||
|
||||
if (SelectedTab == null)
|
||||
SelectTab(selectorTab);
|
||||
SelectChannelSelectorTab();
|
||||
}
|
||||
|
||||
public void SelectChannelSelectorTab() => SelectTab(selectorTab);
|
||||
|
||||
protected override void SelectTab(TabItem<Channel> tab)
|
||||
{
|
||||
if (tab is ChannelSelectorTabItem)
|
||||
|
@ -24,13 +24,15 @@ using osu.Game.Overlays.Chat.Tabs;
|
||||
using osuTK.Input;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
|
||||
public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler<PlatformAction>
|
||||
{
|
||||
public string IconTexture => "Icons/Hexacons/messaging";
|
||||
public LocalisableString Title => ChatStrings.HeaderTitle;
|
||||
@ -370,6 +372,30 @@ namespace osu.Game.Overlays
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
public bool OnPressed(PlatformAction action)
|
||||
{
|
||||
switch (action.ActionType)
|
||||
{
|
||||
case PlatformActionType.TabNew:
|
||||
ChannelTabControl.SelectChannelSelectorTab();
|
||||
return true;
|
||||
|
||||
case PlatformActionType.TabRestore:
|
||||
channelManager.JoinLastClosedChannel();
|
||||
return true;
|
||||
|
||||
case PlatformActionType.DocumentClose:
|
||||
channelManager.LeaveChannel(channelManager.CurrentChannel.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(PlatformAction action)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool AcceptsFocus => true;
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
|
@ -6,87 +6,36 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using System.Linq;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Overlays.News.Sidebar
|
||||
{
|
||||
public class NewsSidebar : CompositeDrawable
|
||||
public class NewsSidebar : OverlaySidebar
|
||||
{
|
||||
[Cached]
|
||||
public readonly Bindable<APINewsSidebar> Metadata = new Bindable<APINewsSidebar>();
|
||||
|
||||
private FillFlowContainer<MonthSection> monthsFlow;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
protected override Drawable CreateContent() => new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = 250;
|
||||
InternalChildren = new Drawable[]
|
||||
Direction = FillDirection.Vertical,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0, 20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
new YearsPanel(),
|
||||
monthsFlow = new FillFlowContainer<MonthSection>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background4
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = OsuScrollContainer.SCROLL_BAR_HEIGHT,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Colour = colourProvider.Background3,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin
|
||||
Child = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Right = 3 }, // Addeded 3px back
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 20,
|
||||
Left = 50,
|
||||
Right = 30
|
||||
},
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0, 20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new YearsPanel(),
|
||||
monthsFlow = new FillFlowContainer<MonthSection>
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
|
76
osu.Game/Overlays/OverlaySidebar.cs
Normal file
76
osu.Game/Overlays/OverlaySidebar.cs
Normal file
@ -0,0 +1,76 @@
|
||||
// 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 JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public abstract class OverlaySidebar : CompositeDrawable
|
||||
{
|
||||
private readonly Box sidebarBackground;
|
||||
private readonly Box scrollbarBackground;
|
||||
|
||||
protected OverlaySidebar()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = 250;
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
sidebarBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
scrollbarBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = OsuScrollContainer.SCROLL_BAR_HEIGHT,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin
|
||||
Child = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Right = 3 }, // Addeded 3px back
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 20,
|
||||
Left = 50,
|
||||
Right = 30
|
||||
},
|
||||
Child = CreateContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
sidebarBackground.Colour = colourProvider.Background4;
|
||||
scrollbarBackground.Colour = colourProvider.Background3;
|
||||
}
|
||||
|
||||
[NotNull]
|
||||
protected virtual Drawable CreateContent() => Empty();
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ namespace osu.Game.Overlays
|
||||
Loading.Show();
|
||||
|
||||
request.Success += response => Schedule(() => onSuccess(response));
|
||||
request.Failure += _ => Schedule(() => LoadDisplay(Empty()));
|
||||
request.Failure += _ => Schedule(onFail);
|
||||
|
||||
api.PerformAsync(request);
|
||||
}
|
||||
@ -132,6 +132,24 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
}
|
||||
|
||||
private void onFail()
|
||||
{
|
||||
LoadDisplay(new WikiMarkdownContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
CurrentPath = $@"{api.WebsiteRootUrl}/wiki/",
|
||||
Text = $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page).",
|
||||
DocumentMargin = new MarginPadding(0),
|
||||
DocumentPadding = new MarginPadding
|
||||
{
|
||||
Vertical = 20,
|
||||
Left = 30,
|
||||
Right = 50,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private void showParentPage()
|
||||
{
|
||||
var parentPath = string.Join("/", path.Value.Split('/').SkipLast(1));
|
||||
|
@ -1,8 +1,15 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays.Settings;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@ -13,5 +20,89 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override IconUsage? Icon => OsuIcon.Dice;
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(ModRandomSettingsControl))]
|
||||
public Bindable<int?> Seed { get; } = new Bindable<int?>
|
||||
{
|
||||
Default = null,
|
||||
Value = null
|
||||
};
|
||||
|
||||
private class ModRandomSettingsControl : SettingsItem<int?>
|
||||
{
|
||||
protected override Drawable CreateControl() => new SeedControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Top = 5 }
|
||||
};
|
||||
|
||||
private sealed class SeedControl : CompositeDrawable, IHasCurrentValue<int?>
|
||||
{
|
||||
private readonly BindableWithCurrent<int?> current = new BindableWithCurrent<int?>();
|
||||
|
||||
public Bindable<int?> Current
|
||||
{
|
||||
get => current;
|
||||
set
|
||||
{
|
||||
current.Current = value;
|
||||
seedNumberBox.Text = value.Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly OsuNumberBox seedNumberBox;
|
||||
|
||||
public SeedControl()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 2),
|
||||
new Dimension(GridSizeMode.Relative, 0.25f)
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
seedNumberBox = new OsuNumberBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
CommitOnFocusLost = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
seedNumberBox.Current.BindValueChanged(e =>
|
||||
{
|
||||
int? value = null;
|
||||
|
||||
if (int.TryParse(e.NewValue, out var intVal))
|
||||
value = intVal;
|
||||
|
||||
current.Value = value;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
if (current.Value == null)
|
||||
seedNumberBox.Text = current.Current.Value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -264,6 +264,12 @@ namespace osu.Game.Rulesets.UI
|
||||
if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager))
|
||||
throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available");
|
||||
|
||||
if (score == null)
|
||||
{
|
||||
recordingInputManager.Recorder = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var recorder = CreateReplayRecorder(score);
|
||||
|
||||
if (recorder == null)
|
||||
@ -513,7 +519,7 @@ namespace osu.Game.Rulesets.UI
|
||||
/// Sets a replay to be used to record gameplay.
|
||||
/// </summary>
|
||||
/// <param name="score">The target to be recorded to.</param>
|
||||
public abstract void SetRecordTarget(Score score);
|
||||
public abstract void SetRecordTarget([CanBeNull] Score score);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the interactive user requests resuming from a paused state.
|
||||
|
@ -30,12 +30,14 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
set
|
||||
{
|
||||
if (recorder != null)
|
||||
if (value != null && recorder != null)
|
||||
throw new InvalidOperationException("Cannot attach more than one recorder");
|
||||
|
||||
recorder?.Expire();
|
||||
recorder = value;
|
||||
|
||||
KeyBindingContainer.Add(recorder);
|
||||
if (recorder != null)
|
||||
KeyBindingContainer.Add(recorder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,6 +657,9 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
ValidForResume = false;
|
||||
|
||||
// ensure we are not writing to the replay any more, as we are about to consume and store the score.
|
||||
DrawableRuleset.SetRecordTarget(null);
|
||||
|
||||
if (!Configuration.ShowResults) return;
|
||||
|
||||
prepareScoreForDisplayTask ??= Task.Run(async () =>
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Users.Drawables
|
||||
private void load(LargeTextureStore textures)
|
||||
{
|
||||
if (user != null && user.Id > 1)
|
||||
Texture = textures.Get($@"https://a.ppy.sh/{user.Id}");
|
||||
Texture = textures.Get(user.AvatarUrl);
|
||||
|
||||
Texture ??= textures.Get(@"Online/avatar-guest");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user