mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge branch 'master' into adjustable-map-settings
This commit is contained in:
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions;
|
||||
@ -42,10 +41,6 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
this.storyboard = storyboard;
|
||||
base.ParseStreamInto(stream, storyboard);
|
||||
|
||||
// OrderBy is used to guarantee that the parsing order of elements with equal start times is maintained (stably-sorted)
|
||||
foreach (StoryboardLayer layer in storyboard.Layers)
|
||||
layer.Elements = layer.Elements.OrderBy(h => h.StartTime).ToList();
|
||||
}
|
||||
|
||||
protected override void ParseLine(Storyboard storyboard, Section section, string line)
|
||||
|
@ -12,10 +12,12 @@ using osu.Framework.Input.Events;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class OsuTextBox : TextBox
|
||||
public class OsuTextBox : BasicTextBox
|
||||
{
|
||||
protected override float LeftRightPadding => 10;
|
||||
|
||||
protected override float CaretWidth => 3;
|
||||
|
||||
protected override SpriteText CreatePlaceholder() => new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(italics: true),
|
||||
@ -41,6 +43,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
BackgroundCommit = BorderColour = colour.Yellow;
|
||||
}
|
||||
|
||||
protected override Color4 SelectionColour => new Color4(249, 90, 255, 255);
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
BorderThickness = 3;
|
||||
|
@ -113,6 +113,7 @@ namespace osu.Game.Overlays.Settings
|
||||
bindable = value;
|
||||
bindable.ValueChanged += _ => UpdateState();
|
||||
bindable.DisabledChanged += _ => UpdateState();
|
||||
bindable.DefaultChanged += _ => UpdateState();
|
||||
UpdateState();
|
||||
}
|
||||
}
|
||||
|
@ -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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
@ -15,6 +16,8 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.SearchableList;
|
||||
using osu.Game.Overlays.Social;
|
||||
using osu.Game.Users;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Threading;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
@ -31,17 +34,20 @@ namespace osu.Game.Overlays
|
||||
protected override SearchableListHeader<SocialTab> CreateHeader() => new Header();
|
||||
protected override SearchableListFilterControl<SocialSortCriteria, SortDirection> CreateFilterControl() => new FilterControl();
|
||||
|
||||
private IEnumerable<User> users;
|
||||
private User[] users = Array.Empty<User>();
|
||||
|
||||
public IEnumerable<User> Users
|
||||
public User[] Users
|
||||
{
|
||||
get => users;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(users, value))
|
||||
if (users == value)
|
||||
return;
|
||||
|
||||
users = value?.ToList();
|
||||
users = value ?? Array.Empty<User>();
|
||||
|
||||
if (LoadState >= LoadState.Ready)
|
||||
recreatePanels();
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,11 +73,10 @@ namespace osu.Game.Overlays
|
||||
};
|
||||
|
||||
Header.Tabs.Current.ValueChanged += _ => queueUpdate();
|
||||
Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate();
|
||||
|
||||
Filter.Tabs.Current.ValueChanged += _ => queueUpdate();
|
||||
|
||||
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue);
|
||||
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdate();
|
||||
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels();
|
||||
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => recreatePanels();
|
||||
|
||||
currentQuery.BindTo(Filter.Search.Current);
|
||||
currentQuery.ValueChanged += query =>
|
||||
@ -85,6 +90,12 @@ namespace osu.Game.Overlays
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
recreatePanels();
|
||||
}
|
||||
|
||||
private APIRequest getUsersRequest;
|
||||
|
||||
private readonly Bindable<string> currentQuery = new Bindable<string>();
|
||||
@ -93,6 +104,8 @@ namespace osu.Game.Overlays
|
||||
|
||||
private void queueUpdate() => Scheduler.AddOnce(updateSearch);
|
||||
|
||||
private CancellationTokenSource loadCancellation;
|
||||
|
||||
private void updateSearch()
|
||||
{
|
||||
queryChangedDebounce?.Cancel();
|
||||
@ -102,7 +115,6 @@ namespace osu.Game.Overlays
|
||||
|
||||
Users = null;
|
||||
clearPanels();
|
||||
loading.Hide();
|
||||
getUsersRequest?.Cancel();
|
||||
|
||||
if (API?.IsLoggedIn != true)
|
||||
@ -112,26 +124,43 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
case SocialTab.Friends:
|
||||
var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
|
||||
friendRequest.Success += updateUsers;
|
||||
friendRequest.Success += users => Users = users.ToArray();
|
||||
API.Queue(getUsersRequest = friendRequest);
|
||||
break;
|
||||
|
||||
default:
|
||||
var userRequest = new GetUsersRequest(); // TODO filter arguments!
|
||||
userRequest.Success += res => updateUsers(res.Users.Select(r => r.User));
|
||||
userRequest.Success += res => Users = res.Users.Select(r => r.User).ToArray();
|
||||
API.Queue(getUsersRequest = userRequest);
|
||||
break;
|
||||
}
|
||||
|
||||
loading.Show();
|
||||
}
|
||||
|
||||
private void recreatePanels(PanelDisplayStyle displayStyle)
|
||||
private void recreatePanels()
|
||||
{
|
||||
clearPanels();
|
||||
|
||||
if (Users == null)
|
||||
{
|
||||
loading.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
IEnumerable<User> sortedUsers = Users;
|
||||
|
||||
switch (Filter.Tabs.Current.Value)
|
||||
{
|
||||
case SocialSortCriteria.Location:
|
||||
sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName);
|
||||
break;
|
||||
|
||||
case SocialSortCriteria.Name:
|
||||
sortedUsers = sortedUsers.OrderBy(u => u.Username);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Filter.DisplayStyleControl.Dropdown.Current.Value == SortDirection.Descending)
|
||||
sortedUsers = sortedUsers.Reverse();
|
||||
|
||||
var newPanels = new FillFlowContainer<SocialPanel>
|
||||
{
|
||||
@ -139,11 +168,11 @@ namespace osu.Game.Overlays
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(10f),
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
ChildrenEnumerable = Users.Select(u =>
|
||||
ChildrenEnumerable = sortedUsers.Select(u =>
|
||||
{
|
||||
SocialPanel panel;
|
||||
|
||||
switch (displayStyle)
|
||||
switch (Filter.DisplayStyleControl.DisplayStyle.Value)
|
||||
{
|
||||
case PanelDisplayStyle.Grid:
|
||||
panel = new SocialGridPanel(u)
|
||||
@ -169,19 +198,28 @@ namespace osu.Game.Overlays
|
||||
if (panels != null)
|
||||
ScrollFlow.Remove(panels);
|
||||
|
||||
loading.Hide();
|
||||
ScrollFlow.Add(panels = newPanels);
|
||||
});
|
||||
}, (loadCancellation = new CancellationTokenSource()).Token);
|
||||
}
|
||||
|
||||
private void updateUsers(IEnumerable<User> newUsers)
|
||||
private void onFilterUpdate()
|
||||
{
|
||||
Users = newUsers;
|
||||
loading.Hide();
|
||||
recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
|
||||
if (Filter.Tabs.Current.Value == SocialSortCriteria.Rank)
|
||||
{
|
||||
queueUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
recreatePanels();
|
||||
}
|
||||
|
||||
private void clearPanels()
|
||||
{
|
||||
loading.Show();
|
||||
|
||||
loadCancellation?.Cancel();
|
||||
|
||||
if (panels != null)
|
||||
{
|
||||
panels.Expire();
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -14,13 +15,45 @@ namespace osu.Game.Rulesets.UI
|
||||
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||
|
||||
private readonly Dictionary<DrawableHitObject, (IBindable<double> bindable, double timeAtAdd)> startTimeMap = new Dictionary<DrawableHitObject, (IBindable<double>, double)>();
|
||||
|
||||
public HitObjectContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject);
|
||||
public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject);
|
||||
public virtual void Add(DrawableHitObject hitObject)
|
||||
{
|
||||
// Added first for the comparer to remain ordered during AddInternal
|
||||
startTimeMap[hitObject] = (hitObject.HitObject.StartTimeBindable.GetBoundCopy(), hitObject.HitObject.StartTime);
|
||||
startTimeMap[hitObject].bindable.BindValueChanged(_ => onStartTimeChanged(hitObject));
|
||||
|
||||
AddInternal(hitObject);
|
||||
}
|
||||
|
||||
public virtual bool Remove(DrawableHitObject hitObject)
|
||||
{
|
||||
if (!RemoveInternal(hitObject))
|
||||
return false;
|
||||
|
||||
// Removed last for the comparer to remain ordered during RemoveInternal
|
||||
startTimeMap[hitObject].bindable.UnbindAll();
|
||||
startTimeMap.Remove(hitObject);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
|
||||
|
||||
private void onStartTimeChanged(DrawableHitObject hitObject)
|
||||
{
|
||||
if (!RemoveInternal(hitObject))
|
||||
return;
|
||||
|
||||
// Update the stored time, preserving the existing bindable
|
||||
startTimeMap[hitObject] = (startTimeMap[hitObject].bindable, hitObject.HitObject.StartTime);
|
||||
AddInternal(hitObject);
|
||||
}
|
||||
|
||||
protected override int Compare(Drawable x, Drawable y)
|
||||
{
|
||||
@ -28,7 +61,7 @@ namespace osu.Game.Rulesets.UI
|
||||
return base.Compare(x, y);
|
||||
|
||||
// Put earlier hitobjects towards the end of the list, so they handle input first
|
||||
int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime);
|
||||
int i = startTimeMap[yObj].timeAtAdd.CompareTo(startTimeMap[xObj].timeAtAdd);
|
||||
return i == 0 ? CompareReverseChildID(x, y) : i;
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Scoring
|
||||
if (mods == null)
|
||||
return null;
|
||||
|
||||
return modsJson = JsonConvert.SerializeObject(mods);
|
||||
return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym }));
|
||||
}
|
||||
set
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
@ -132,6 +133,8 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
private void confirmAndExit()
|
||||
{
|
||||
if (exitConfirmed) return;
|
||||
|
||||
exitConfirmed = true;
|
||||
this.Exit();
|
||||
}
|
||||
@ -244,10 +247,18 @@ namespace osu.Game.Screens.Menu
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
if (!exitConfirmed && dialogOverlay != null && !(dialogOverlay.CurrentDialog is ConfirmExitDialog))
|
||||
if (!exitConfirmed && dialogOverlay != null)
|
||||
{
|
||||
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
|
||||
return true;
|
||||
if (dialogOverlay.CurrentDialog is ConfirmExitDialog exitDialog)
|
||||
{
|
||||
exitConfirmed = true;
|
||||
exitDialog.Buttons.First().Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
dialogOverlay.Push(new ConfirmExitDialog(confirmAndExit, () => exitConfirmOverlay.Abort()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
buttons.State = ButtonSystemState.Exit;
|
||||
|
@ -43,6 +43,11 @@ namespace osu.Game.Screens.Play
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
config.BindWith(OsuSetting.KeyOverlay, configVisibility);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Visible.BindValueChanged(_ => updateVisibility());
|
||||
configVisibility.BindValueChanged(_ => updateVisibility(), true);
|
||||
|
@ -562,7 +562,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
|
||||
// as we are no longer the current screen, we cannot guarantee the track is still usable.
|
||||
GameplayClockContainer.StopUsingBeatmapClock();
|
||||
GameplayClockContainer?.StopUsingBeatmapClock();
|
||||
|
||||
fadeOut();
|
||||
return base.OnExiting(next);
|
||||
|
@ -7,11 +7,14 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
@ -24,19 +27,35 @@ namespace osu.Game.Screens.Select
|
||||
set => modDisplay.Current = value;
|
||||
}
|
||||
|
||||
protected readonly OsuSpriteText MultiplierText;
|
||||
private readonly FooterModDisplay modDisplay;
|
||||
private Color4 lowMultiplierColour;
|
||||
private Color4 highMultiplierColour;
|
||||
|
||||
public FooterButtonMods()
|
||||
{
|
||||
Add(new Container
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Shear = -SHEAR,
|
||||
Child = modDisplay = new FooterModDisplay
|
||||
Children = new Drawable[]
|
||||
{
|
||||
DisplayUnrankedText = false,
|
||||
Scale = new Vector2(0.8f)
|
||||
modDisplay = new FooterModDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
DisplayUnrankedText = false,
|
||||
Scale = new Vector2(0.8f)
|
||||
},
|
||||
MultiplierText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||
Margin = new MarginPadding { Right = 10 }
|
||||
}
|
||||
},
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Left = 70 }
|
||||
@ -48,10 +67,33 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
SelectedColour = colours.Yellow;
|
||||
DeselectedColour = SelectedColour.Opacity(0.5f);
|
||||
lowMultiplierColour = colours.Red;
|
||||
highMultiplierColour = colours.Green;
|
||||
Text = @"mods";
|
||||
Hotkey = Key.F1;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindValueChanged(_ => updateMultiplierText(), true);
|
||||
}
|
||||
|
||||
private void updateMultiplierText()
|
||||
{
|
||||
double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1;
|
||||
|
||||
MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
|
||||
|
||||
if (multiplier > 1.0)
|
||||
MultiplierText.FadeColour(highMultiplierColour, 200);
|
||||
else if (multiplier < 1.0)
|
||||
MultiplierText.FadeColour(lowMultiplierColour, 200);
|
||||
else
|
||||
MultiplierText.FadeColour(Color4.White, 200);
|
||||
}
|
||||
|
||||
private class FooterModDisplay : ModDisplay
|
||||
{
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false;
|
||||
|
@ -1,7 +1,6 @@
|
||||
// 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.Caching;
|
||||
using osu.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -12,27 +11,35 @@ namespace osu.Game.Storyboards
|
||||
public class CommandTimeline<T> : ICommandTimeline
|
||||
{
|
||||
private readonly List<TypedCommand> commands = new List<TypedCommand>();
|
||||
|
||||
public IEnumerable<TypedCommand> Commands => commands.OrderBy(c => c.StartTime);
|
||||
|
||||
public bool HasCommands => commands.Count > 0;
|
||||
|
||||
private readonly Cached<double> startTimeBacking = new Cached<double>();
|
||||
public double StartTime => startTimeBacking.IsValid ? startTimeBacking : startTimeBacking.Value = HasCommands ? commands.Min(c => c.StartTime) : double.MinValue;
|
||||
public double StartTime { get; private set; } = double.MaxValue;
|
||||
public double EndTime { get; private set; } = double.MinValue;
|
||||
|
||||
private readonly Cached<double> endTimeBacking = new Cached<double>();
|
||||
public double EndTime => endTimeBacking.IsValid ? endTimeBacking : endTimeBacking.Value = HasCommands ? commands.Max(c => c.EndTime) : double.MaxValue;
|
||||
|
||||
public T StartValue => HasCommands ? commands.OrderBy(c => c.StartTime).First().StartValue : default;
|
||||
public T EndValue => HasCommands ? commands.OrderByDescending(c => c.EndTime).First().EndValue : default;
|
||||
public T StartValue { get; private set; }
|
||||
public T EndValue { get; private set; }
|
||||
|
||||
public void Add(Easing easing, double startTime, double endTime, T startValue, T endValue)
|
||||
{
|
||||
if (endTime < startTime)
|
||||
return;
|
||||
|
||||
commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue, });
|
||||
commands.Add(new TypedCommand { Easing = easing, StartTime = startTime, EndTime = endTime, StartValue = startValue, EndValue = endValue });
|
||||
|
||||
startTimeBacking.Invalidate();
|
||||
endTimeBacking.Invalidate();
|
||||
if (startTime < StartTime)
|
||||
{
|
||||
StartValue = startValue;
|
||||
StartTime = startTime;
|
||||
}
|
||||
|
||||
if (endTime > EndTime)
|
||||
{
|
||||
EndValue = endValue;
|
||||
EndTime = endTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
@ -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 osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
@ -25,28 +26,52 @@ namespace osu.Game.Storyboards
|
||||
public CommandTimeline<bool> FlipH = new CommandTimeline<bool>();
|
||||
public CommandTimeline<bool> FlipV = new CommandTimeline<bool>();
|
||||
|
||||
private readonly ICommandTimeline[] timelines;
|
||||
|
||||
public CommandTimelineGroup()
|
||||
{
|
||||
timelines = new ICommandTimeline[]
|
||||
{
|
||||
X,
|
||||
Y,
|
||||
Scale,
|
||||
VectorScale,
|
||||
Rotation,
|
||||
Colour,
|
||||
Alpha,
|
||||
BlendingParameters,
|
||||
FlipH,
|
||||
FlipV
|
||||
};
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public IEnumerable<ICommandTimeline> Timelines
|
||||
public double CommandsStartTime
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return X;
|
||||
yield return Y;
|
||||
yield return Scale;
|
||||
yield return Rotation;
|
||||
yield return Colour;
|
||||
yield return Alpha;
|
||||
yield return BlendingParameters;
|
||||
yield return FlipH;
|
||||
yield return FlipV;
|
||||
double min = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
min = Math.Min(min, timelines[i].StartTime);
|
||||
|
||||
return min;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsStartTime => Timelines.Where(t => t.HasCommands).Min(t => t.StartTime);
|
||||
public double CommandsEndTime
|
||||
{
|
||||
get
|
||||
{
|
||||
double max = double.MinValue;
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsEndTime => Timelines.Where(t => t.HasCommands).Max(t => t.EndTime);
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
max = Math.Max(max, timelines[i].EndTime);
|
||||
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public double CommandsDuration => CommandsEndTime - CommandsStartTime;
|
||||
@ -61,7 +86,19 @@ namespace osu.Game.Storyboards
|
||||
public double Duration => EndTime - StartTime;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasCommands => Timelines.Any(t => t.HasCommands);
|
||||
public bool HasCommands
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < timelines.Length; i++)
|
||||
{
|
||||
if (timelines[i].HasCommands)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<CommandTimeline<T>.TypedCommand> GetCommands<T>(CommandTimelineSelector<T> timelineSelector, double offset = 0)
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ using osu.Game.Storyboards.Drawables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
{
|
||||
@ -63,50 +64,56 @@ namespace osu.Game.Storyboards
|
||||
|
||||
public void ApplyTransforms(Drawable drawable, IEnumerable<Tuple<CommandTimelineGroup, double>> triggeredGroups = null)
|
||||
{
|
||||
applyCommands(drawable, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing));
|
||||
applyCommands(drawable, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing));
|
||||
applyCommands(drawable, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing));
|
||||
applyCommands(drawable, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing));
|
||||
applyCommands(drawable, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing));
|
||||
applyCommands(drawable, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing));
|
||||
applyCommands(drawable, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration),
|
||||
// For performance reasons, we need to apply the commands in order by start time. Not doing so will cause many functions to be interleaved, resulting in O(n^2) complexity.
|
||||
// To achieve this, commands are "generated" as pairs of (command, initFunc, transformFunc) and batched into a contiguous list
|
||||
// The list is then stably-sorted (to preserve command order), and applied to the drawable sequentially.
|
||||
|
||||
List<IGeneratedCommand> generated = new List<IGeneratedCommand>();
|
||||
|
||||
generateCommands(generated, getCommands(g => g.X, triggeredGroups), (d, value) => d.X = value, (d, value, duration, easing) => d.MoveToX(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Y, triggeredGroups), (d, value) => d.Y = value, (d, value, duration, easing) => d.MoveToY(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Scale, triggeredGroups), (d, value) => d.Scale = new Vector2(value), (d, value, duration, easing) => d.ScaleTo(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Rotation, triggeredGroups), (d, value) => d.Rotation = value, (d, value, duration, easing) => d.RotateTo(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Colour, triggeredGroups), (d, value) => d.Colour = value, (d, value, duration, easing) => d.FadeColour(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing));
|
||||
generateCommands(generated, getCommands(g => g.BlendingParameters, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration),
|
||||
false);
|
||||
|
||||
if (drawable is IVectorScalable vectorScalable)
|
||||
{
|
||||
applyCommands(drawable, getCommands(g => g.VectorScale, triggeredGroups), (d, value) => vectorScalable.VectorScale = value,
|
||||
generateCommands(generated, getCommands(g => g.VectorScale, triggeredGroups), (d, value) => vectorScalable.VectorScale = value,
|
||||
(d, value, duration, easing) => vectorScalable.VectorScaleTo(value, duration, easing));
|
||||
}
|
||||
|
||||
if (drawable is IFlippable flippable)
|
||||
{
|
||||
applyCommands(drawable, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration),
|
||||
generateCommands(generated, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration),
|
||||
false);
|
||||
applyCommands(drawable, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration),
|
||||
generateCommands(generated, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration),
|
||||
false);
|
||||
}
|
||||
|
||||
foreach (var command in generated.OrderBy(g => g.StartTime))
|
||||
command.ApplyTo(drawable);
|
||||
}
|
||||
|
||||
private void applyCommands<T>(Drawable drawable, IEnumerable<CommandTimeline<T>.TypedCommand> commands, DrawablePropertyInitializer<T> initializeProperty, DrawableTransformer<T> transform,
|
||||
bool alwaysInitialize = true)
|
||||
where T : struct
|
||||
private void generateCommands<T>(List<IGeneratedCommand> resultList, IEnumerable<CommandTimeline<T>.TypedCommand> commands,
|
||||
DrawablePropertyInitializer<T> initializeProperty, DrawableTransformer<T> transform, bool alwaysInitialize = true)
|
||||
{
|
||||
var initialized = false;
|
||||
bool initialized = false;
|
||||
|
||||
foreach (var command in commands.OrderBy(l => l))
|
||||
foreach (var command in commands)
|
||||
{
|
||||
DrawablePropertyInitializer<T> initFunc = null;
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
if (alwaysInitialize || command.StartTime == command.EndTime)
|
||||
initializeProperty.Invoke(drawable, command.StartValue);
|
||||
initFunc = initializeProperty;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
{
|
||||
transform(drawable, command.StartValue, 0, Easing.None);
|
||||
transform(drawable, command.EndValue, command.Duration, command.Easing);
|
||||
}
|
||||
resultList.Add(new GeneratedCommand<T>(command, initFunc, transform));
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,5 +134,39 @@ namespace osu.Game.Storyboards
|
||||
|
||||
public override string ToString()
|
||||
=> $"{Path}, {Origin}, {InitialPosition}";
|
||||
|
||||
private interface IGeneratedCommand
|
||||
{
|
||||
double StartTime { get; }
|
||||
|
||||
void ApplyTo(Drawable drawable);
|
||||
}
|
||||
|
||||
private readonly struct GeneratedCommand<T> : IGeneratedCommand
|
||||
{
|
||||
public double StartTime => command.StartTime;
|
||||
|
||||
private readonly DrawablePropertyInitializer<T> initializeProperty;
|
||||
private readonly DrawableTransformer<T> transform;
|
||||
private readonly CommandTimeline<T>.TypedCommand command;
|
||||
|
||||
public GeneratedCommand([NotNull] CommandTimeline<T>.TypedCommand command, [CanBeNull] DrawablePropertyInitializer<T> initializeProperty, [NotNull] DrawableTransformer<T> transform)
|
||||
{
|
||||
this.command = command;
|
||||
this.initializeProperty = initializeProperty;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
public void ApplyTo(Drawable drawable)
|
||||
{
|
||||
initializeProperty?.Invoke(drawable, command.StartValue);
|
||||
|
||||
using (drawable.BeginAbsoluteSequence(command.StartTime))
|
||||
{
|
||||
transform(drawable, command.StartValue, 0, Easing.None);
|
||||
transform(drawable, command.EndValue, command.Duration, command.Easing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,10 @@ using osu.Game.Overlays.Notifications;
|
||||
|
||||
namespace osu.Game.Updater
|
||||
{
|
||||
public abstract class UpdateManager : CompositeDrawable
|
||||
/// <summary>
|
||||
/// An update manager which only shows notifications after an update completes.
|
||||
/// </summary>
|
||||
public class UpdateManager : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2019.1215.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2019.1224.0" />
|
||||
<PackageReference Include="Sentry" Version="1.2.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
|
Reference in New Issue
Block a user