mirror of
https://github.com/ppy/osu.git
synced 2025-02-19 22:36:07 +08:00
Merge branch 'master' into ranks-section
This commit is contained in:
commit
5822a6cc96
@ -1 +1 @@
|
||||
Subproject commit 167d5cda8f3ddae702ffc8d8d22dac67e48b509c
|
||||
Subproject commit 14a33d110e2ed32e3a875bc2acd2bade244ba045
|
@ -1,17 +1,14 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Desktop.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game;
|
||||
|
||||
namespace osu.Desktop.Tests.Visual
|
||||
{
|
||||
[TestFixture]
|
||||
public abstract class OsuTestCase : TestCase
|
||||
{
|
||||
[Test]
|
||||
public override void RunTest()
|
||||
{
|
||||
using (var host = new HeadlessGameHost(realtime: false))
|
||||
|
@ -1,8 +1,11 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -14,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject, OsuJudgement>
|
||||
{
|
||||
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>(ScoringMode.Exponential);
|
||||
|
||||
public OsuScoreProcessor()
|
||||
{
|
||||
}
|
||||
@ -23,6 +28,35 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
}
|
||||
|
||||
private float hpDrainRate;
|
||||
|
||||
private int totalAccurateJudgements;
|
||||
|
||||
private readonly Dictionary<OsuScoreResult, int> scoreResultCounts = new Dictionary<OsuScoreResult, int>();
|
||||
private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>();
|
||||
|
||||
private double comboMaxScore;
|
||||
|
||||
protected override void ComputeTargets(Beatmap<OsuHitObject> beatmap)
|
||||
{
|
||||
hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate;
|
||||
totalAccurateJudgements = beatmap.HitObjects.Count;
|
||||
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
{
|
||||
if (h != null)
|
||||
{
|
||||
// TODO: add support for other object types.
|
||||
AddJudgement(new OsuJudgement
|
||||
{
|
||||
MaxScore = OsuScoreResult.Hit300,
|
||||
Score = OsuScoreResult.Hit300,
|
||||
Result = HitResult.Hit
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
@ -34,9 +68,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
comboResultCounts.Clear();
|
||||
}
|
||||
|
||||
private readonly Dictionary<OsuScoreResult, int> scoreResultCounts = new Dictionary<OsuScoreResult, int>();
|
||||
private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>();
|
||||
|
||||
public override void PopulateScore(Score score)
|
||||
{
|
||||
base.PopulateScore(score);
|
||||
@ -57,28 +88,75 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
comboResultCounts[judgement.Combo] = comboResultCounts.GetOrDefault(judgement.Combo) + 1;
|
||||
}
|
||||
|
||||
switch (judgement.Result)
|
||||
switch (judgement.Score)
|
||||
{
|
||||
case HitResult.Hit:
|
||||
Health.Value += 0.1f;
|
||||
case OsuScoreResult.Hit300:
|
||||
Health.Value += (10.2 - hpDrainRate) * 0.02;
|
||||
break;
|
||||
case HitResult.Miss:
|
||||
Health.Value -= 0.2f;
|
||||
|
||||
case OsuScoreResult.Hit100:
|
||||
Health.Value += (8 - hpDrainRate) * 0.02;
|
||||
break;
|
||||
|
||||
case OsuScoreResult.Hit50:
|
||||
Health.Value += (4 - hpDrainRate) * 0.02;
|
||||
break;
|
||||
|
||||
case OsuScoreResult.SliderTick:
|
||||
Health.Value += Math.Max(7 - hpDrainRate, 0) * 0.01;
|
||||
break;
|
||||
|
||||
case OsuScoreResult.Miss:
|
||||
Health.Value -= hpDrainRate * 0.04;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int score = 0;
|
||||
int maxScore = 0;
|
||||
calculateScore();
|
||||
}
|
||||
|
||||
private void calculateScore()
|
||||
{
|
||||
int baseScore = 0;
|
||||
double comboScore = 0;
|
||||
|
||||
int baseMaxScore = 0;
|
||||
|
||||
foreach (var j in Judgements)
|
||||
{
|
||||
score += j.ScoreValue;
|
||||
maxScore += j.MaxScoreValue;
|
||||
baseScore += j.ScoreValue;
|
||||
baseMaxScore += j.MaxScoreValue;
|
||||
|
||||
comboScore += j.ScoreValue * (1 + Combo.Value / 10d);
|
||||
}
|
||||
|
||||
TotalScore.Value = score;
|
||||
Accuracy.Value = (double)score / maxScore;
|
||||
Accuracy.Value = (double)baseScore / baseMaxScore;
|
||||
|
||||
if (comboScore > comboMaxScore)
|
||||
comboMaxScore = comboScore;
|
||||
|
||||
if (baseScore == 0)
|
||||
TotalScore.Value = 0;
|
||||
else
|
||||
{
|
||||
// temporary to make scoring feel more like score v1 without being score v1.
|
||||
float exponentialFactor = Mode.Value == ScoringMode.Exponential ? (float)Judgements.Count / 100 : 1;
|
||||
|
||||
TotalScore.Value =
|
||||
(int)
|
||||
(
|
||||
exponentialFactor *
|
||||
700000 * comboScore / comboMaxScore +
|
||||
300000 * Math.Pow(Accuracy.Value, 10) * ((double)Judgements.Count / totalAccurateJudgements) +
|
||||
0 /* bonusScore */
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ScoringMode
|
||||
{
|
||||
Standardised,
|
||||
Exponential
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,6 +268,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
||||
base.Reset();
|
||||
|
||||
Health.Value = 0;
|
||||
Accuracy.Value = 1;
|
||||
|
||||
bonusScore = 0;
|
||||
comboPortion = 0;
|
||||
|
@ -52,6 +52,8 @@ namespace osu.Game.Beatmaps
|
||||
[JsonProperty("file_sha2")]
|
||||
public string Hash { get; set; }
|
||||
|
||||
public bool Hidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.).
|
||||
/// </summary>
|
||||
|
@ -33,11 +33,21 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public event Action<BeatmapSetInfo> BeatmapSetAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a single difficulty has been hidden.
|
||||
/// </summary>
|
||||
public event Action<BeatmapInfo> BeatmapHidden;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database.
|
||||
/// </summary>
|
||||
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when a single difficulty has been restored.
|
||||
/// </summary>
|
||||
public event Action<BeatmapInfo> BeatmapRestored;
|
||||
|
||||
/// <summary>
|
||||
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
|
||||
/// </summary>
|
||||
@ -71,6 +81,8 @@ namespace osu.Game.Beatmaps
|
||||
beatmaps = new BeatmapStore(connection);
|
||||
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
|
||||
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
|
||||
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
||||
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
||||
|
||||
this.storage = storage;
|
||||
this.files = files;
|
||||
@ -162,24 +174,34 @@ namespace osu.Game.Beatmaps
|
||||
// If we have an ID then we already exist in the database.
|
||||
if (beatmapSetInfo.ID != 0) return;
|
||||
|
||||
lock (beatmaps)
|
||||
beatmaps.Add(beatmapSetInfo);
|
||||
beatmaps.Add(beatmapSetInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a beatmap from the manager.
|
||||
/// Is a no-op for already deleted beatmaps.
|
||||
/// </summary>
|
||||
/// <param name="beatmapSet">The beatmap to delete.</param>
|
||||
/// <param name="beatmapSet">The beatmap set to delete.</param>
|
||||
public void Delete(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
lock (beatmaps)
|
||||
if (!beatmaps.Delete(beatmapSet)) return;
|
||||
if (!beatmaps.Delete(beatmapSet)) return;
|
||||
|
||||
if (!beatmapSet.Protected)
|
||||
files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a beatmap difficulty.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap difficulty to hide.</param>
|
||||
public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap);
|
||||
|
||||
/// <summary>
|
||||
/// Restore a beatmap difficulty.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap difficulty to restore.</param>
|
||||
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="BeatmapSetInfo"/> to a usable state if it has previously been deleted but not yet purged.
|
||||
/// Is a no-op for already usable beatmaps.
|
||||
@ -187,8 +209,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <param name="beatmapSet">The beatmap to restore.</param>
|
||||
public void Undelete(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
lock (beatmaps)
|
||||
if (!beatmaps.Undelete(beatmapSet)) return;
|
||||
if (!beatmaps.Undelete(beatmapSet)) return;
|
||||
|
||||
if (!beatmapSet.Protected)
|
||||
files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
||||
@ -248,6 +269,13 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh an existing instance of a <see cref="BeatmapSetInfo"/> from the store.
|
||||
/// </summary>
|
||||
/// <param name="beatmapSet">A stale instance.</param>
|
||||
/// <returns>A fresh instance.</returns>
|
||||
public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID);
|
||||
|
||||
/// <summary>
|
||||
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
|
||||
/// </summary>
|
||||
@ -255,7 +283,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>Results from the provided query.</returns>
|
||||
public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query)
|
||||
{
|
||||
lock (beatmaps) return beatmaps.QueryAndPopulate(query);
|
||||
return beatmaps.QueryAndPopulate(query);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -265,15 +293,12 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||
public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query)
|
||||
{
|
||||
lock (beatmaps)
|
||||
{
|
||||
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
|
||||
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
|
||||
|
||||
if (set != null)
|
||||
beatmaps.Populate(set);
|
||||
if (set != null)
|
||||
beatmaps.Populate(set);
|
||||
|
||||
return set;
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -16,11 +16,14 @@ namespace osu.Game.Beatmaps
|
||||
public event Action<BeatmapSetInfo> BeatmapSetAdded;
|
||||
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
||||
|
||||
public event Action<BeatmapInfo> BeatmapHidden;
|
||||
public event Action<BeatmapInfo> BeatmapRestored;
|
||||
|
||||
/// <summary>
|
||||
/// The current version of this store. Used for migrations (see <see cref="PerformMigration(int, int)"/>).
|
||||
/// The initial version is 1.
|
||||
/// </summary>
|
||||
protected override int StoreVersion => 3;
|
||||
protected override int StoreVersion => 4;
|
||||
|
||||
public BeatmapStore(SQLiteConnection connection)
|
||||
: base(connection)
|
||||
@ -81,6 +84,10 @@ namespace osu.Game.Beatmaps
|
||||
// Added MD5Hash column to BeatmapInfo
|
||||
Connection.MigrateTable<BeatmapInfo>();
|
||||
break;
|
||||
case 4:
|
||||
// Added Hidden column to BeatmapInfo
|
||||
Connection.MigrateTable<BeatmapInfo>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,7 +107,7 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="BeatmapSetInfo"/> to the database.
|
||||
/// Delete a <see cref="BeatmapSetInfo"/> from the database.
|
||||
/// </summary>
|
||||
/// <param name="beatmapSet">The beatmap to delete.</param>
|
||||
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
|
||||
@ -131,6 +138,38 @@ namespace osu.Game.Beatmaps
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide a <see cref="BeatmapInfo"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to hide.</param>
|
||||
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
||||
public bool Hide(BeatmapInfo beatmap)
|
||||
{
|
||||
if (beatmap.Hidden) return false;
|
||||
|
||||
beatmap.Hidden = true;
|
||||
Connection.Update(beatmap);
|
||||
|
||||
BeatmapHidden?.Invoke(beatmap);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore a previously hidden <see cref="BeatmapInfo"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to restore.</param>
|
||||
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
||||
public bool Restore(BeatmapInfo beatmap)
|
||||
{
|
||||
if (!beatmap.Hidden) return false;
|
||||
|
||||
beatmap.Hidden = false;
|
||||
Connection.Update(beatmap);
|
||||
|
||||
BeatmapRestored?.Invoke(beatmap);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void cleanupPendingDeletions()
|
||||
{
|
||||
Connection.RunInTransaction(() =>
|
||||
|
@ -11,6 +11,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public class BeatmapGroup : IStateful<BeatmapGroupState>
|
||||
{
|
||||
public event Action<BeatmapGroupState> StateChanged;
|
||||
|
||||
public BeatmapPanel SelectedPanel;
|
||||
|
||||
/// <summary>
|
||||
@ -23,19 +25,26 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </summary>
|
||||
public Action<BeatmapInfo> StartRequested;
|
||||
|
||||
public BeatmapSetHeader Header;
|
||||
public Action<BeatmapSetInfo> DeleteRequested;
|
||||
|
||||
private BeatmapGroupState state;
|
||||
public Action<BeatmapSetInfo> RestoreHiddenRequested;
|
||||
|
||||
public Action<BeatmapInfo> HideDifficultyRequested;
|
||||
|
||||
public BeatmapSetHeader Header;
|
||||
|
||||
public List<BeatmapPanel> BeatmapPanels;
|
||||
|
||||
public BeatmapSetInfo BeatmapSet;
|
||||
|
||||
private BeatmapGroupState state;
|
||||
public BeatmapGroupState State
|
||||
{
|
||||
get { return state; }
|
||||
set
|
||||
{
|
||||
state = value;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case BeatmapGroupState.Expanded:
|
||||
@ -54,7 +63,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
panel.State = PanelSelectedState.Hidden;
|
||||
break;
|
||||
}
|
||||
state = value;
|
||||
|
||||
StateChanged?.Invoke(state);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,14 +76,17 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Header = new BeatmapSetHeader(beatmap)
|
||||
{
|
||||
GainedSelection = headerGainedSelection,
|
||||
DeleteRequested = b => DeleteRequested(b),
|
||||
RestoreHiddenRequested = b => RestoreHiddenRequested(b),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
};
|
||||
|
||||
BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList();
|
||||
BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).ToList();
|
||||
BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b)
|
||||
{
|
||||
Alpha = 0,
|
||||
GainedSelection = panelGainedSelection,
|
||||
HideRequested = p => HideDifficultyRequested?.Invoke(p),
|
||||
StartRequested = p => { StartRequested?.Invoke(p.Beatmap); },
|
||||
RelativeSizeAxes = Axes.X,
|
||||
}).ToList();
|
||||
@ -81,6 +94,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Header.AddDifficultyIcons(BeatmapPanels);
|
||||
}
|
||||
|
||||
|
||||
private void headerGainedSelection(BeatmapSetHeader panel)
|
||||
{
|
||||
State = BeatmapGroupState.Expanded;
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
@ -14,16 +15,20 @@ using OpenTK.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public class BeatmapPanel : Panel
|
||||
public class BeatmapPanel : Panel, IHasContextMenu
|
||||
{
|
||||
public BeatmapInfo Beatmap;
|
||||
private readonly Sprite background;
|
||||
|
||||
public Action<BeatmapPanel> GainedSelection;
|
||||
public Action<BeatmapPanel> StartRequested;
|
||||
public Action<BeatmapPanel> EditRequested;
|
||||
public Action<BeatmapInfo> HideRequested;
|
||||
|
||||
private readonly Triangles triangles;
|
||||
private readonly StarCounter starCounter;
|
||||
|
||||
@ -148,5 +153,12 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(this)),
|
||||
new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(this)),
|
||||
new OsuMenuItem("Hide", MenuItemType.Destructive, () => HideRequested?.Invoke(Beatmap)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,22 +3,31 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public class BeatmapSetHeader : Panel
|
||||
public class BeatmapSetHeader : Panel, IHasContextMenu
|
||||
{
|
||||
public Action<BeatmapSetHeader> GainedSelection;
|
||||
|
||||
public Action<BeatmapSetInfo> DeleteRequested;
|
||||
|
||||
public Action<BeatmapSetInfo> RestoreHiddenRequested;
|
||||
|
||||
private readonly SpriteText title;
|
||||
private readonly SpriteText artist;
|
||||
|
||||
@ -148,5 +157,23 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
foreach (var p in panels)
|
||||
difficultyIcons.Add(new DifficultyIcon(p.Beatmap));
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (State == PanelSelectedState.NotSelected)
|
||||
items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => State = PanelSelectedState.Selected));
|
||||
|
||||
if (beatmap.BeatmapSetInfo.Beatmaps.Any(b => b.Hidden))
|
||||
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmap.BeatmapSetInfo)));
|
||||
|
||||
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmap.BeatmapSetInfo)));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Normal,
|
||||
Hard,
|
||||
Insane,
|
||||
Expert
|
||||
Expert,
|
||||
ExpertPlus
|
||||
}
|
||||
|
||||
private DifficultyRating getDifficultyRating(BeatmapInfo beatmap)
|
||||
@ -44,7 +45,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
if (rating < 2.25) return DifficultyRating.Normal;
|
||||
if (rating < 3.75) return DifficultyRating.Hard;
|
||||
if (rating < 5.25) return DifficultyRating.Insane;
|
||||
return DifficultyRating.Expert;
|
||||
if (rating < 6.75) return DifficultyRating.Expert;
|
||||
return DifficultyRating.ExpertPlus;
|
||||
}
|
||||
|
||||
private Color4 getColour(BeatmapInfo beatmap)
|
||||
@ -55,12 +57,14 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
return palette.Green;
|
||||
default:
|
||||
case DifficultyRating.Normal:
|
||||
return palette.Yellow;
|
||||
return palette.Blue;
|
||||
case DifficultyRating.Hard:
|
||||
return palette.Pink;
|
||||
return palette.Yellow;
|
||||
case DifficultyRating.Insane:
|
||||
return palette.Purple;
|
||||
return palette.Pink;
|
||||
case DifficultyRating.Expert:
|
||||
return palette.Purple;
|
||||
case DifficultyRating.ExpertPlus:
|
||||
return palette.Gray0;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -15,6 +16,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public const float MAX_HEIGHT = 80;
|
||||
|
||||
public event Action<PanelSelectedState> StateChanged;
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
|
||||
private readonly Container nestedContainer;
|
||||
@ -77,11 +80,15 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
set
|
||||
{
|
||||
if (state == value) return;
|
||||
if (state == value)
|
||||
return;
|
||||
|
||||
var last = state;
|
||||
state = value;
|
||||
|
||||
ApplyState(last);
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,12 @@ namespace osu.Game.Graphics.Containers
|
||||
samplePopIn = audio.Sample.Get(@"UI/melodic-5");
|
||||
samplePopOut = audio.Sample.Get(@"UI/melodic-4");
|
||||
|
||||
StateChanged += OsuFocusedOverlayContainer_StateChanged;
|
||||
StateChanged += onStateChanged;
|
||||
}
|
||||
|
||||
private void OsuFocusedOverlayContainer_StateChanged(VisibilityContainer arg1, Visibility arg2)
|
||||
private void onStateChanged(Visibility visibility)
|
||||
{
|
||||
switch (arg2)
|
||||
switch (visibility)
|
||||
{
|
||||
case Visibility.Visible:
|
||||
samplePopIn?.Play();
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using OpenTK;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
@ -35,6 +36,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private class BreadcrumbTabItem : OsuTabItem, IStateful<Visibility>
|
||||
{
|
||||
public event Action<Visibility> StateChanged;
|
||||
|
||||
public readonly SpriteIcon Chevron;
|
||||
|
||||
//don't allow clicking between transitions and don't make the chevron clickable
|
||||
@ -42,6 +45,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
public override bool HandleInput => State == Visibility.Visible;
|
||||
|
||||
private Visibility state;
|
||||
|
||||
public Visibility State
|
||||
{
|
||||
get { return state; }
|
||||
@ -62,6 +66,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
this.FadeOut(transition_duration, Easing.OutQuint);
|
||||
this.ScaleTo(new Vector2(0.8f, 1f), transition_duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,14 +14,17 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private const int fade_duration = 250;
|
||||
|
||||
public OsuContextMenu()
|
||||
: base(Direction.Vertical)
|
||||
{
|
||||
CornerRadius = 5;
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
MaskingContainer.CornerRadius = 5;
|
||||
MaskingContainer.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(0.1f),
|
||||
Radius = 4,
|
||||
};
|
||||
|
||||
ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL };
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -32,7 +35,5 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint);
|
||||
protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint);
|
||||
|
||||
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL };
|
||||
}
|
||||
}
|
@ -57,6 +57,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
CornerRadius = 4;
|
||||
BackgroundColour = Color4.Black.Opacity(0.5f);
|
||||
|
||||
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
||||
ItemsContainer.Padding = new MarginPadding(5);
|
||||
}
|
||||
|
||||
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
||||
@ -64,13 +67,18 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint);
|
||||
|
||||
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
||||
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding(5);
|
||||
|
||||
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
||||
protected override void UpdateMenuHeight()
|
||||
protected override void UpdateSize(Vector2 newSize)
|
||||
{
|
||||
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
|
||||
this.ResizeHeightTo(State == MenuState.Opened ? actualHeight : 0, 300, Easing.OutQuint);
|
||||
if (Direction == Direction.Vertical)
|
||||
{
|
||||
Width = newSize.X;
|
||||
this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Height = newSize.Y;
|
||||
this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 accentColour;
|
||||
@ -141,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override Drawable CreateContent() => new Content();
|
||||
|
||||
protected class Content : FillFlowContainer, IHasText
|
||||
protected new class Content : FillFlowContainer, IHasText
|
||||
{
|
||||
public string Text
|
||||
{
|
||||
|
@ -12,28 +12,38 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public class OsuMenu : Menu
|
||||
{
|
||||
public OsuMenu()
|
||||
public OsuMenu(Direction direction)
|
||||
: base(direction)
|
||||
{
|
||||
CornerRadius = 4;
|
||||
BackgroundColour = Color4.Black.Opacity(0.5f);
|
||||
|
||||
MaskingContainer.CornerRadius = 4;
|
||||
ItemsContainer.Padding = new MarginPadding(5);
|
||||
}
|
||||
|
||||
protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint);
|
||||
protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint);
|
||||
|
||||
protected override void UpdateMenuHeight()
|
||||
protected override void UpdateSize(Vector2 newSize)
|
||||
{
|
||||
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
|
||||
this.ResizeHeightTo(State == MenuState.Opened ? actualHeight : 0, 300, Easing.OutQuint);
|
||||
if (Direction == Direction.Vertical)
|
||||
{
|
||||
Width = newSize.X;
|
||||
this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Height = newSize.Y;
|
||||
this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding(5);
|
||||
|
||||
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item);
|
||||
|
||||
protected class DrawableOsuMenuItem : DrawableMenuItem
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
public Bindable<bool> Joined = new Bindable<bool>();
|
||||
|
||||
public bool ReadOnly => Name != "#lazer";
|
||||
public bool ReadOnly => false;
|
||||
|
||||
public const int MAX_HISTORY = 300;
|
||||
|
||||
|
@ -230,13 +230,13 @@ namespace osu.Game
|
||||
var singleDisplayOverlays = new OverlayContainer[] { chat, social, direct };
|
||||
foreach (var overlay in singleDisplayOverlays)
|
||||
{
|
||||
overlay.StateChanged += (container, state) =>
|
||||
overlay.StateChanged += state =>
|
||||
{
|
||||
if (state == Visibility.Hidden) return;
|
||||
|
||||
foreach (var c in singleDisplayOverlays)
|
||||
{
|
||||
if (c == container) continue;
|
||||
if (c == overlay) continue;
|
||||
c.State = Visibility.Hidden;
|
||||
}
|
||||
};
|
||||
|
@ -16,15 +16,16 @@ using osu.Game.Online.Chat;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Configuration;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class ChatTabControl : OsuTabControl<Channel>
|
||||
{
|
||||
protected override TabItem<Channel> CreateTabItem(Channel value) => new ChannelTabItem(value);
|
||||
|
||||
private const float shear_width = 10;
|
||||
|
||||
public Action<Channel> OnRequestLeave;
|
||||
|
||||
public readonly Bindable<bool> ChannelSelectorActive = new Bindable<bool>();
|
||||
|
||||
private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab;
|
||||
@ -49,6 +50,20 @@ namespace osu.Game.Overlays.Chat
|
||||
ChannelSelectorActive.BindTo(selectorTab.Active);
|
||||
}
|
||||
|
||||
protected override void AddTabItem(TabItem<Channel> item, bool addToDropdown = true)
|
||||
{
|
||||
if (selectorTab.Depth < float.MaxValue)
|
||||
// performTabSort might've made selectorTab's position wonky, fix it
|
||||
TabContainer.ChangeChildDepth(selectorTab, float.MaxValue);
|
||||
|
||||
base.AddTabItem(item, addToDropdown);
|
||||
|
||||
if (SelectedTab == null)
|
||||
SelectTab(item);
|
||||
}
|
||||
|
||||
protected override TabItem<Channel> CreateTabItem(Channel value) => new ChannelTabItem(value) { OnRequestClose = tabCloseRequested };
|
||||
|
||||
protected override void SelectTab(TabItem<Channel> tab)
|
||||
{
|
||||
if (tab is ChannelTabItem.ChannelSelectorTabItem)
|
||||
@ -62,18 +77,38 @@ namespace osu.Game.Overlays.Chat
|
||||
base.SelectTab(tab);
|
||||
}
|
||||
|
||||
private void tabCloseRequested(TabItem<Channel> tab)
|
||||
{
|
||||
int totalTabs = TabContainer.Count - 1; // account for selectorTab
|
||||
int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs);
|
||||
|
||||
if (tab == SelectedTab && totalTabs > 1)
|
||||
// Select the tab after tab-to-be-removed's index, or the tab before if current == last
|
||||
SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]);
|
||||
else if (totalTabs == 1 && !selectorTab.Active)
|
||||
// Open channel selection overlay if all channel tabs will be closed after removing this tab
|
||||
SelectTab(selectorTab);
|
||||
|
||||
OnRequestLeave?.Invoke(tab.Value);
|
||||
}
|
||||
|
||||
private class ChannelTabItem : TabItem<Channel>
|
||||
{
|
||||
private Color4 backgroundInactive;
|
||||
private Color4 backgroundHover;
|
||||
private Color4 backgroundActive;
|
||||
|
||||
public override bool IsRemovable => !Pinned;
|
||||
|
||||
private readonly SpriteText text;
|
||||
private readonly SpriteText textBold;
|
||||
private readonly ClickableContainer closeButton;
|
||||
private readonly Box box;
|
||||
private readonly Box highlightBox;
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public Action<ChannelTabItem> OnRequestClose;
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (Active)
|
||||
@ -108,6 +143,9 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
if (IsRemovable)
|
||||
closeButton.FadeIn(200, Easing.OutQuint);
|
||||
|
||||
if (!Active)
|
||||
box.FadeColour(backgroundHover, transition_length, Easing.OutQuint);
|
||||
return true;
|
||||
@ -115,6 +153,7 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
closeButton.FadeOut(200, Easing.OutQuint);
|
||||
updateState();
|
||||
}
|
||||
|
||||
@ -204,13 +243,69 @@ namespace osu.Game.Overlays.Chat
|
||||
Font = @"Exo2.0-Bold",
|
||||
TextSize = 18,
|
||||
},
|
||||
}
|
||||
}
|
||||
closeButton = new CloseButton
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Right = 20 },
|
||||
Origin = Anchor.CentreRight,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Action = delegate
|
||||
{
|
||||
if (IsRemovable) OnRequestClose?.Invoke(this);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public class CloseButton : ClickableContainer
|
||||
{
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public CloseButton()
|
||||
{
|
||||
Size = new Vector2(20);
|
||||
|
||||
Child = icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(0.75f),
|
||||
Icon = FontAwesome.fa_close,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.5f, 1000, Easing.OutQuint);
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.75f, 1000, Easing.OutElastic);
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.Red, 200, Easing.OutQuint);
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.White, 200, Easing.OutQuint);
|
||||
base.OnHoverLost(state);
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelSelectorTabItem : ChannelTabItem
|
||||
{
|
||||
public override bool IsRemovable => false;
|
||||
|
||||
public ChannelSelectorTabItem(Channel value) : base(value)
|
||||
{
|
||||
Depth = float.MaxValue;
|
||||
|
@ -160,6 +160,7 @@ namespace osu.Game.Overlays
|
||||
channelTabs = new ChatTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
OnRequestLeave = removeChannel,
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -169,7 +170,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
channelTabs.Current.ValueChanged += newChannel => CurrentChannel = newChannel;
|
||||
channelTabs.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden;
|
||||
channelSelection.StateChanged += (overlay, state) =>
|
||||
channelSelection.StateChanged += state =>
|
||||
{
|
||||
channelTabs.ChannelSelectorActive.Value = state == Visibility.Visible;
|
||||
|
||||
@ -305,6 +306,7 @@ namespace osu.Game.Overlays
|
||||
addChannel(channels.Find(c => c.Name == @"#lobby"));
|
||||
|
||||
channelSelection.OnRequestJoin = addChannel;
|
||||
channelSelection.OnRequestLeave = removeChannel;
|
||||
channelSelection.Sections = new[]
|
||||
{
|
||||
new ChannelSection
|
||||
@ -332,7 +334,15 @@ namespace osu.Game.Overlays
|
||||
|
||||
set
|
||||
{
|
||||
if (currentChannel == value || value == null) return;
|
||||
if (currentChannel == value) return;
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
currentChannel = null;
|
||||
textbox.Current.Disabled = true;
|
||||
currentChannelContainer.Clear(false);
|
||||
return;
|
||||
}
|
||||
|
||||
currentChannel = value;
|
||||
|
||||
@ -391,6 +401,19 @@ namespace osu.Game.Overlays
|
||||
channel.Joined.Value = true;
|
||||
}
|
||||
|
||||
private void removeChannel(Channel channel)
|
||||
{
|
||||
if (channel == null) return;
|
||||
|
||||
if (channel == CurrentChannel) CurrentChannel = null;
|
||||
|
||||
careChannels.Remove(channel);
|
||||
loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel));
|
||||
channelTabs.RemoveItem(channel);
|
||||
|
||||
channel.Joined.Value = false;
|
||||
}
|
||||
|
||||
private void fetchInitialMessages(Channel channel)
|
||||
{
|
||||
var req = new GetMessagesRequest(new List<Channel> { channel }, null);
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Overlays
|
||||
dialogContainer.Add(currentDialog);
|
||||
|
||||
currentDialog.Show();
|
||||
currentDialog.StateChanged += onDialogOnStateChanged;
|
||||
currentDialog.StateChanged += state => onDialogOnStateChanged(dialog, state);
|
||||
State = Visibility.Visible;
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
private const float hidden_width = 120;
|
||||
|
||||
private void keyBindingOverlay_StateChanged(VisibilityContainer container, Visibility visibility)
|
||||
private void keyBindingOverlay_StateChanged(Visibility visibility)
|
||||
{
|
||||
switch (visibility)
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework;
|
||||
using OpenTK;
|
||||
using osu.Framework.Allocation;
|
||||
@ -19,6 +20,8 @@ namespace osu.Game.Overlays.MedalSplash
|
||||
private const float scale_when_unlocked = 0.76f;
|
||||
private const float scale_when_full = 0.6f;
|
||||
|
||||
public event Action<DisplayState> StateChanged;
|
||||
|
||||
private readonly Medal medal;
|
||||
private readonly Container medalContainer;
|
||||
private readonly Sprite medalSprite, medalGlow;
|
||||
@ -132,6 +135,8 @@ namespace osu.Game.Overlays.MedalSplash
|
||||
|
||||
state = value;
|
||||
updateState();
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
@ -16,7 +17,7 @@ using OpenTK;
|
||||
|
||||
namespace osu.Game.Overlays.Music
|
||||
{
|
||||
internal class PlaylistItem : Container, IFilterable
|
||||
internal class PlaylistItem : Container, IFilterable, IDraggable
|
||||
{
|
||||
private const float fade_duration = 100;
|
||||
|
||||
@ -33,6 +34,8 @@ namespace osu.Game.Overlays.Music
|
||||
|
||||
public Action<BeatmapSetInfo> OnSelect;
|
||||
|
||||
public bool IsDraggable => handle.IsHovered;
|
||||
|
||||
private bool selected;
|
||||
public bool Selected
|
||||
{
|
||||
@ -68,15 +71,9 @@ namespace osu.Game.Overlays.Music
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
handle = new SpriteIcon
|
||||
handle = new PlaylistItemHandle
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Size = new Vector2(12),
|
||||
Colour = colours.Gray5,
|
||||
Icon = FontAwesome.fa_bars,
|
||||
Alpha = 0f,
|
||||
Margin = new MarginPadding { Left = 5, Top = 2 },
|
||||
Colour = colours.Gray5
|
||||
},
|
||||
text = new OsuTextFlowContainer
|
||||
{
|
||||
@ -114,19 +111,19 @@ namespace osu.Game.Overlays.Music
|
||||
});
|
||||
}
|
||||
|
||||
protected override bool OnHover(Framework.Input.InputState state)
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
handle.FadeIn(fade_duration);
|
||||
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(Framework.Input.InputState state)
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
handle.FadeOut(fade_duration);
|
||||
}
|
||||
|
||||
protected override bool OnClick(Framework.Input.InputState state)
|
||||
protected override bool OnClick(InputState state)
|
||||
{
|
||||
OnSelect?.Invoke(BeatmapSetInfo);
|
||||
return true;
|
||||
@ -148,5 +145,27 @@ namespace osu.Game.Overlays.Music
|
||||
this.FadeTo(matching ? 1 : 0, 200);
|
||||
}
|
||||
}
|
||||
|
||||
private class PlaylistItemHandle : SpriteIcon
|
||||
{
|
||||
|
||||
public PlaylistItemHandle()
|
||||
{
|
||||
Anchor = Anchor.TopLeft;
|
||||
Origin = Anchor.TopLeft;
|
||||
Size = new Vector2(12);
|
||||
Icon = FontAwesome.fa_bars;
|
||||
Alpha = 0f;
|
||||
Margin = new MarginPadding { Left = 5, Top = 2 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IDraggable : IDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this <see cref="IDraggable"/> can be dragged in its current state.
|
||||
/// </summary>
|
||||
bool IsDraggable { get; }
|
||||
}
|
||||
}
|
||||
|
@ -4,105 +4,252 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Overlays.Music
|
||||
{
|
||||
internal class PlaylistList : Container
|
||||
internal class PlaylistList : CompositeDrawable
|
||||
{
|
||||
private readonly FillFlowContainer<PlaylistItem> items;
|
||||
|
||||
public IEnumerable<BeatmapSetInfo> BeatmapSets
|
||||
{
|
||||
set
|
||||
{
|
||||
items.Children = value.Select(item => new PlaylistItem(item) { OnSelect = itemSelected }).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public BeatmapSetInfo FirstVisibleSet => items.Children.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo;
|
||||
|
||||
private void itemSelected(BeatmapSetInfo b)
|
||||
{
|
||||
OnSelect?.Invoke(b);
|
||||
}
|
||||
|
||||
public Action<BeatmapSetInfo> OnSelect;
|
||||
|
||||
private readonly SearchContainer search;
|
||||
|
||||
public void Filter(string searchTerm) => search.SearchTerm = searchTerm;
|
||||
|
||||
public BeatmapSetInfo SelectedItem
|
||||
{
|
||||
get { return items.Children.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; }
|
||||
set
|
||||
{
|
||||
foreach (PlaylistItem s in items.Children)
|
||||
s.Selected = s.BeatmapSetInfo.ID == value?.ID;
|
||||
}
|
||||
}
|
||||
private readonly ItemsScrollContainer items;
|
||||
|
||||
public PlaylistList()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
InternalChild = items = new ItemsScrollContainer
|
||||
{
|
||||
new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
search = new SearchContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
items = new ItemSearchContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
OnSelect = set => OnSelect?.Invoke(set)
|
||||
};
|
||||
}
|
||||
|
||||
public void AddBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
public new MarginPadding Padding
|
||||
{
|
||||
items.Add(new PlaylistItem(beatmapSet) { OnSelect = itemSelected });
|
||||
get { return base.Padding; }
|
||||
set { base.Padding = value; }
|
||||
}
|
||||
|
||||
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
public IEnumerable<BeatmapSetInfo> BeatmapSets { set { items.Sets = value; } }
|
||||
|
||||
public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
|
||||
public BeatmapSetInfo NextSet => items.NextSet;
|
||||
public BeatmapSetInfo PreviousSet => items.PreviousSet;
|
||||
|
||||
public BeatmapSetInfo SelectedSet
|
||||
{
|
||||
PlaylistItem itemToRemove = items.Children.FirstOrDefault(item => item.BeatmapSetInfo.ID == beatmapSet.ID);
|
||||
if (itemToRemove != null) items.Remove(itemToRemove);
|
||||
get { return items.SelectedSet; }
|
||||
set { items.SelectedSet = value; }
|
||||
}
|
||||
|
||||
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
||||
public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet);
|
||||
public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet);
|
||||
|
||||
public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
|
||||
|
||||
private class ItemsScrollContainer : OsuScrollContainer
|
||||
{
|
||||
public string[] FilterTerms => new string[] { };
|
||||
public bool MatchingFilter
|
||||
public Action<BeatmapSetInfo> OnSelect;
|
||||
|
||||
private readonly SearchContainer search;
|
||||
private readonly FillFlowContainer<PlaylistItem> items;
|
||||
|
||||
public ItemsScrollContainer()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
search = new SearchContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
items = new ItemSearchContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<BeatmapSetInfo> Sets
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
InvalidateLayout();
|
||||
items.Clear();
|
||||
value.ForEach(AddBeatmapSet);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IFilterable> FilterableChildren => Children;
|
||||
|
||||
public ItemSearchContainer()
|
||||
public string SearchTerm
|
||||
{
|
||||
LayoutDuration = 200;
|
||||
LayoutEasing = Easing.OutQuint;
|
||||
get { return search.SearchTerm; }
|
||||
set { search.SearchTerm = value; }
|
||||
}
|
||||
|
||||
public void AddBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
items.Add(new PlaylistItem(beatmapSet)
|
||||
{
|
||||
OnSelect = set => OnSelect?.Invoke(set),
|
||||
Depth = items.Count
|
||||
});
|
||||
}
|
||||
|
||||
public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID);
|
||||
if (itemToRemove == null)
|
||||
return false;
|
||||
return items.Remove(itemToRemove);
|
||||
}
|
||||
|
||||
public BeatmapSetInfo SelectedSet
|
||||
{
|
||||
get { return items.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; }
|
||||
set
|
||||
{
|
||||
foreach (PlaylistItem s in items.Children)
|
||||
s.Selected = s.BeatmapSetInfo.ID == value?.ID;
|
||||
}
|
||||
}
|
||||
|
||||
public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo;
|
||||
public BeatmapSetInfo NextSet => (items.SkipWhile(i => !i.Selected).Skip(1).FirstOrDefault() ?? items.FirstOrDefault())?.BeatmapSetInfo;
|
||||
public BeatmapSetInfo PreviousSet => (items.TakeWhile(i => !i.Selected).LastOrDefault() ?? items.LastOrDefault())?.BeatmapSetInfo;
|
||||
|
||||
private Vector2 nativeDragPosition;
|
||||
private PlaylistItem draggedItem;
|
||||
|
||||
protected override bool OnDragStart(InputState state)
|
||||
{
|
||||
nativeDragPosition = state.Mouse.NativeState.Position;
|
||||
draggedItem = items.FirstOrDefault(d => d.IsDraggable);
|
||||
return draggedItem != null || base.OnDragStart(state);
|
||||
}
|
||||
|
||||
protected override bool OnDrag(InputState state)
|
||||
{
|
||||
nativeDragPosition = state.Mouse.NativeState.Position;
|
||||
if (draggedItem == null)
|
||||
return base.OnDrag(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool OnDragEnd(InputState state)
|
||||
{
|
||||
nativeDragPosition = state.Mouse.NativeState.Position;
|
||||
var handled = draggedItem != null || base.OnDragEnd(state);
|
||||
draggedItem = null;
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (draggedItem == null)
|
||||
return;
|
||||
|
||||
updateScrollPosition();
|
||||
updateDragPosition();
|
||||
}
|
||||
|
||||
private void updateScrollPosition()
|
||||
{
|
||||
const float start_offset = 10;
|
||||
const double max_power = 50;
|
||||
const double exp_base = 1.05;
|
||||
|
||||
var localPos = ToLocalSpace(nativeDragPosition);
|
||||
|
||||
if (localPos.Y < start_offset)
|
||||
{
|
||||
if (Current <= 0)
|
||||
return;
|
||||
|
||||
var power = Math.Min(max_power, Math.Abs(start_offset - localPos.Y));
|
||||
ScrollBy(-(float)Math.Pow(exp_base, power));
|
||||
}
|
||||
else if (localPos.Y > DrawHeight - start_offset)
|
||||
{
|
||||
if (IsScrolledToEnd())
|
||||
return;
|
||||
|
||||
var power = Math.Min(max_power, Math.Abs(DrawHeight - start_offset - localPos.Y));
|
||||
ScrollBy((float)Math.Pow(exp_base, power));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDragPosition()
|
||||
{
|
||||
var itemsPos = items.ToLocalSpace(nativeDragPosition);
|
||||
|
||||
int srcIndex = (int)draggedItem.Depth;
|
||||
|
||||
// Find the last item with position < mouse position. Note we can't directly use
|
||||
// the item positions as they are being transformed
|
||||
float heightAccumulator = 0;
|
||||
int dstIndex = 0;
|
||||
for (; dstIndex < items.Count; dstIndex++)
|
||||
{
|
||||
// Using BoundingBox here takes care of scale, paddings, etc...
|
||||
heightAccumulator += items[dstIndex].BoundingBox.Height;
|
||||
if (heightAccumulator > itemsPos.Y)
|
||||
break;
|
||||
}
|
||||
|
||||
dstIndex = MathHelper.Clamp(dstIndex, 0, items.Count - 1);
|
||||
|
||||
if (srcIndex == dstIndex)
|
||||
return;
|
||||
|
||||
if (srcIndex < dstIndex)
|
||||
{
|
||||
for (int i = srcIndex + 1; i <= dstIndex; i++)
|
||||
items.ChangeChildDepth(items[i], i - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = dstIndex; i < srcIndex; i++)
|
||||
items.ChangeChildDepth(items[i], i + 1);
|
||||
}
|
||||
|
||||
items.ChangeChildDepth(draggedItem, dstIndex);
|
||||
}
|
||||
|
||||
|
||||
private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
|
||||
{
|
||||
public string[] FilterTerms => new string[] { };
|
||||
public bool MatchingFilter
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
InvalidateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
// Compare with reversed ChildID and Depth
|
||||
protected override int Compare(Drawable x, Drawable y) => base.Compare(y, x);
|
||||
|
||||
public IEnumerable<IFilterable> FilterableChildren => Children;
|
||||
|
||||
public ItemSearchContainer()
|
||||
{
|
||||
LayoutDuration = 200;
|
||||
LayoutEasing = Easing.OutQuint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Music
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
beatmapBacking.ValueChanged += b => list.SelectedItem = b?.BeatmapSetInfo;
|
||||
beatmapBacking.ValueChanged += b => list.SelectedSet = b?.BeatmapSetInfo;
|
||||
beatmapBacking.TriggerChange();
|
||||
}
|
||||
|
||||
@ -126,24 +126,24 @@ namespace osu.Game.Overlays.Music
|
||||
|
||||
public void PlayPrevious()
|
||||
{
|
||||
var currentID = beatmapBacking.Value?.BeatmapSetInfo.ID ?? -1;
|
||||
var available = BeatmapSets.Reverse();
|
||||
|
||||
var playable = available.SkipWhile(b => b.ID != currentID).Skip(1).FirstOrDefault() ?? available.FirstOrDefault();
|
||||
var playable = list.PreviousSet;
|
||||
|
||||
if (playable != null)
|
||||
{
|
||||
playSpecified(playable.Beatmaps[0]);
|
||||
list.SelectedSet = playable;
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayNext()
|
||||
{
|
||||
var currentID = beatmapBacking.Value?.BeatmapSetInfo.ID ?? -1;
|
||||
var available = BeatmapSets;
|
||||
|
||||
var playable = available.SkipWhile(b => b.ID != currentID).Skip(1).FirstOrDefault() ?? available.FirstOrDefault();
|
||||
var playable = list.NextSet;
|
||||
|
||||
if (playable != null)
|
||||
{
|
||||
playSpecified(playable.Beatmaps[0]);
|
||||
list.SelectedSet = playable;
|
||||
}
|
||||
}
|
||||
|
||||
private void playSpecified(BeatmapInfo info)
|
||||
|
@ -204,7 +204,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
beatmapBacking.BindTo(game.Beatmap);
|
||||
|
||||
playlist.StateChanged += (c, s) => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
|
||||
playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -292,6 +292,8 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
Radius = 4,
|
||||
};
|
||||
|
||||
ItemsContainer.Padding = new MarginPadding();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -300,8 +302,6 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
BackgroundColour = colours.Gray3;
|
||||
}
|
||||
|
||||
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding();
|
||||
|
||||
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item);
|
||||
|
||||
private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem
|
||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
{
|
||||
private OsuButton importButton;
|
||||
private OsuButton deleteButton;
|
||||
private OsuButton restoreButton;
|
||||
|
||||
protected override string Header => "General";
|
||||
|
||||
@ -41,6 +42,20 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||
Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true));
|
||||
}
|
||||
},
|
||||
restoreButton = new OsuButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Text = "Restore all hidden difficulties",
|
||||
Action = () =>
|
||||
{
|
||||
restoreButton.Enabled.Value = false;
|
||||
Task.Run(() =>
|
||||
{
|
||||
foreach (var b in beatmaps.QueryBeatmaps(b => b.Hidden))
|
||||
beatmaps.Restore(b);
|
||||
}).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
using OpenTK;
|
||||
@ -19,6 +20,9 @@ namespace osu.Game.Overlays.Settings
|
||||
private readonly FillFlowContainer<SidebarButton> content;
|
||||
internal const float DEFAULT_WIDTH = ToolbarButton.WIDTH;
|
||||
internal const int EXPANDED_WIDTH = 200;
|
||||
|
||||
public event Action<ExpandedState> StateChanged;
|
||||
|
||||
protected override Container<SidebarButton> Content => content;
|
||||
|
||||
public Sidebar()
|
||||
@ -102,6 +106,8 @@ namespace osu.Game.Overlays.Settings
|
||||
this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint);
|
||||
break;
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
stateContainer.StateChanged -= stateChanged;
|
||||
}
|
||||
|
||||
private void stateChanged(VisibilityContainer c, Visibility state)
|
||||
private void stateChanged(Visibility state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
|
@ -167,6 +167,8 @@ namespace osu.Game.Overlays
|
||||
|
||||
private class Wave : Container, IStateful<Visibility>
|
||||
{
|
||||
public event Action<Visibility> StateChanged;
|
||||
|
||||
public float FinalPosition;
|
||||
|
||||
public Wave()
|
||||
@ -200,6 +202,7 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
|
||||
private Visibility state;
|
||||
|
||||
public Visibility State
|
||||
{
|
||||
get { return state; }
|
||||
@ -216,6 +219,8 @@ namespace osu.Game.Overlays
|
||||
this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show);
|
||||
break;
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,11 @@ namespace osu.Game.Rulesets.UI
|
||||
internal RulesetContainer(Ruleset ruleset)
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
KeyBindingInputManager = CreateInputManager();
|
||||
KeyBindingInputManager.RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Screens.Menu
|
||||
/// </summary>
|
||||
public class Button : BeatSyncedContainer, IStateful<ButtonState>
|
||||
{
|
||||
public event Action<ButtonState> StateChanged;
|
||||
|
||||
private readonly Container iconText;
|
||||
private readonly Container box;
|
||||
private readonly Box boxHoverLayer;
|
||||
@ -266,6 +268,8 @@ namespace osu.Game.Screens.Menu
|
||||
this.FadeOut(explode_duration / 4f * 3);
|
||||
break;
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
public class ButtonSystem : Container, IStateful<MenuState>
|
||||
{
|
||||
public event Action<MenuState> StateChanged;
|
||||
|
||||
public Action OnEdit;
|
||||
public Action OnExit;
|
||||
public Action OnDirect;
|
||||
@ -294,6 +296,8 @@ namespace osu.Game.Screens.Menu
|
||||
backButton.State = state == MenuState.Play ? ButtonState.Expanded : ButtonState.Contracted;
|
||||
settingsButton.State = state == MenuState.TopLevel ? ButtonState.Expanded : ButtonState.Contracted;
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +133,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private class FadeContainer : Container, IStateful<Visibility>
|
||||
{
|
||||
public event Action<Visibility> StateChanged;
|
||||
|
||||
private Visibility state;
|
||||
private ScheduledDelegate scheduledHide;
|
||||
|
||||
@ -144,8 +146,10 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
set
|
||||
{
|
||||
var lastState = state;
|
||||
if (state == value)
|
||||
return;
|
||||
|
||||
var lastState = state;
|
||||
state = value;
|
||||
|
||||
scheduledHide?.Cancel();
|
||||
@ -164,6 +168,8 @@ namespace osu.Game.Screens.Play
|
||||
this.FadeOut(1000, Easing.OutExpo);
|
||||
break;
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework;
|
||||
@ -170,6 +171,8 @@ namespace osu.Game.Screens.Play
|
||||
private const float padding = 2;
|
||||
public const float WIDTH = cube_size + padding;
|
||||
|
||||
public event Action<ColumnState> StateChanged;
|
||||
|
||||
private readonly List<Box> drawableRows = new List<Box>();
|
||||
|
||||
private float filled;
|
||||
@ -186,6 +189,7 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
|
||||
private ColumnState state;
|
||||
|
||||
public ColumnState State
|
||||
{
|
||||
get { return state; }
|
||||
@ -196,6 +200,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
if (IsLoaded)
|
||||
fillActive();
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,17 +107,44 @@ namespace osu.Game.Screens.Select
|
||||
});
|
||||
}
|
||||
|
||||
public void RemoveBeatmap(BeatmapSetInfo beatmapSet)
|
||||
public void RemoveBeatmap(BeatmapSetInfo beatmapSet) => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID));
|
||||
|
||||
internal void UpdateBeatmap(BeatmapInfo beatmap)
|
||||
{
|
||||
Schedule(delegate
|
||||
// todo: this method should not run more than once for the same BeatmapSetInfo.
|
||||
var set = manager.Refresh(beatmap.BeatmapSet);
|
||||
|
||||
// todo: this method should be smarter as to not recreate panels that haven't changed, etc.
|
||||
var group = groups.Find(b => b.BeatmapSet.ID == set.ID);
|
||||
|
||||
if (group == null)
|
||||
return;
|
||||
|
||||
var newGroup = createGroup(set);
|
||||
|
||||
int i = groups.IndexOf(group);
|
||||
groups.RemoveAt(i);
|
||||
groups.Insert(i, newGroup);
|
||||
|
||||
if (selectedGroup == group && newGroup.BeatmapPanels.Count == 0)
|
||||
selectedGroup = null;
|
||||
|
||||
Filter(null, false);
|
||||
|
||||
//check if we can/need to maintain our current selection.
|
||||
if (selectedGroup == group && newGroup.BeatmapPanels.Count > 0)
|
||||
{
|
||||
removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID));
|
||||
});
|
||||
var newSelection =
|
||||
newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID) ??
|
||||
newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, group.BeatmapPanels.IndexOf(selectedPanel))];
|
||||
|
||||
selectGroup(newGroup, newSelection);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true)
|
||||
{
|
||||
if (beatmap == null)
|
||||
if (beatmap == null || beatmap.Hidden)
|
||||
{
|
||||
SelectNext();
|
||||
return;
|
||||
@ -140,6 +167,12 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
public Action StartRequested;
|
||||
|
||||
public Action<BeatmapSetInfo> DeleteRequested;
|
||||
|
||||
public Action<BeatmapSetInfo> RestoreRequested;
|
||||
|
||||
public Action<BeatmapInfo> HideDifficultyRequested;
|
||||
|
||||
public void SelectNext(int direction = 1, bool skipDifficulties = true)
|
||||
{
|
||||
if (groups.All(g => g.State == BeatmapGroupState.Hidden))
|
||||
@ -191,7 +224,8 @@ namespace osu.Game.Screens.Select
|
||||
if (!visibleGroups.Any())
|
||||
return;
|
||||
|
||||
randomSelectedBeatmaps.Push(new KeyValuePair<BeatmapGroup, BeatmapPanel>(selectedGroup, selectedGroup.SelectedPanel));
|
||||
if (selectedGroup != null)
|
||||
randomSelectedBeatmaps.Push(new KeyValuePair<BeatmapGroup, BeatmapPanel>(selectedGroup, selectedGroup.SelectedPanel));
|
||||
|
||||
BeatmapGroup group;
|
||||
|
||||
@ -305,6 +339,9 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
SelectionChanged = (g, p) => selectGroup(g, p),
|
||||
StartRequested = b => StartRequested?.Invoke(),
|
||||
DeleteRequested = b => DeleteRequested?.Invoke(b),
|
||||
RestoreHiddenRequested = s => RestoreRequested?.Invoke(s),
|
||||
HideDifficultyRequested = b => HideDifficultyRequested?.Invoke(b),
|
||||
State = BeatmapGroupState.Collapsed
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
@ -19,23 +18,18 @@ namespace osu.Game.Screens.Select
|
||||
manager = beatmapManager;
|
||||
}
|
||||
|
||||
public BeatmapDeleteDialog(WorkingBeatmap beatmap)
|
||||
public BeatmapDeleteDialog(BeatmapSetInfo beatmap)
|
||||
{
|
||||
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
|
||||
BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
|
||||
|
||||
Icon = FontAwesome.fa_trash_o;
|
||||
HeaderText = @"Confirm deletion of";
|
||||
BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
|
||||
Buttons = new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogOkButton
|
||||
{
|
||||
Text = @"Yes. Totally. Delete it.",
|
||||
Action = () =>
|
||||
{
|
||||
beatmap.Dispose();
|
||||
manager.Delete(beatmap.BeatmapSetInfo);
|
||||
},
|
||||
Action = () => manager.Delete(beatmap),
|
||||
},
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
@ -21,6 +22,8 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
{
|
||||
public static readonly float HEIGHT = 60;
|
||||
|
||||
public event Action<Visibility> StateChanged;
|
||||
|
||||
public readonly int RankPosition;
|
||||
public readonly Score Score;
|
||||
|
||||
@ -41,11 +44,14 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
private readonly FillFlowContainer<ScoreModIcon> modsContainer;
|
||||
|
||||
private Visibility state;
|
||||
|
||||
public Visibility State
|
||||
{
|
||||
get { return state; }
|
||||
set
|
||||
{
|
||||
if (state == value)
|
||||
return;
|
||||
state = value;
|
||||
|
||||
switch (state)
|
||||
@ -88,6 +94,8 @@ namespace osu.Game.Screens.Select.Leaderboards
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,13 +54,11 @@ namespace osu.Game.Screens.Select
|
||||
ValidForResume = false;
|
||||
Push(new Editor());
|
||||
}, Key.Number3);
|
||||
|
||||
Beatmap.ValueChanged += beatmap_ValueChanged;
|
||||
}
|
||||
|
||||
private void beatmap_ValueChanged(WorkingBeatmap beatmap)
|
||||
protected override void UpdateBeatmap(WorkingBeatmap beatmap)
|
||||
{
|
||||
if (!IsCurrentScreen) return;
|
||||
base.UpdateBeatmap(beatmap);
|
||||
|
||||
beatmap.Mods.BindTo(modSelect.SelectedMods);
|
||||
|
||||
|
@ -106,6 +106,9 @@ namespace osu.Game.Screens.Select
|
||||
Origin = Anchor.CentreRight,
|
||||
SelectionChanged = carouselSelectionChanged,
|
||||
BeatmapsChanged = carouselBeatmapsLoaded,
|
||||
DeleteRequested = b => promptDelete(b),
|
||||
RestoreRequested = s => { foreach (var b in s.Beatmaps) manager.Restore(b); },
|
||||
HideDifficultyRequested = b => manager.Hide(b),
|
||||
StartRequested = () => carouselRaisedStart(),
|
||||
});
|
||||
Add(FilterControl = new FilterControl
|
||||
@ -163,7 +166,7 @@ namespace osu.Game.Screens.Select
|
||||
Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2);
|
||||
Footer.AddButton(@"options", colours.Blue, BeatmapOptions.ToggleVisibility, Key.F3);
|
||||
|
||||
BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, promptDelete, Key.Number4, float.MaxValue);
|
||||
BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => promptDelete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue);
|
||||
}
|
||||
|
||||
if (manager == null)
|
||||
@ -174,6 +177,8 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
manager.BeatmapSetAdded += onBeatmapSetAdded;
|
||||
manager.BeatmapSetRemoved += onBeatmapSetRemoved;
|
||||
manager.BeatmapHidden += onBeatmapHidden;
|
||||
manager.BeatmapRestored += onBeatmapRestored;
|
||||
|
||||
dialogOverlay = dialog;
|
||||
|
||||
@ -190,6 +195,9 @@ namespace osu.Game.Screens.Select
|
||||
carousel.AllowSelection = !Beatmap.Disabled;
|
||||
}
|
||||
|
||||
private void onBeatmapRestored(BeatmapInfo b) => carousel.UpdateBeatmap(b);
|
||||
private void onBeatmapHidden(BeatmapInfo b) => carousel.UpdateBeatmap(b);
|
||||
|
||||
private void carouselBeatmapsLoaded()
|
||||
{
|
||||
if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false)
|
||||
@ -236,7 +244,7 @@ namespace osu.Game.Screens.Select
|
||||
ensurePlayingSelected(preview);
|
||||
}
|
||||
|
||||
changeBackground(Beatmap.Value);
|
||||
UpdateBeatmap(Beatmap.Value);
|
||||
};
|
||||
|
||||
selectionChangedDebounce?.Cancel();
|
||||
@ -248,7 +256,8 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
if (beatmap == null)
|
||||
{
|
||||
performLoad();
|
||||
if (!Beatmap.IsDefault)
|
||||
performLoad();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -303,7 +312,7 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
|
||||
{
|
||||
changeBackground(Beatmap);
|
||||
UpdateBeatmap(Beatmap);
|
||||
ensurePlayingSelected();
|
||||
}
|
||||
|
||||
@ -344,12 +353,19 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
manager.BeatmapSetAdded -= onBeatmapSetAdded;
|
||||
manager.BeatmapSetRemoved -= onBeatmapSetRemoved;
|
||||
manager.BeatmapHidden -= onBeatmapHidden;
|
||||
manager.BeatmapRestored -= onBeatmapRestored;
|
||||
}
|
||||
|
||||
initialAddSetsTask?.Cancel();
|
||||
}
|
||||
|
||||
private void changeBackground(WorkingBeatmap beatmap)
|
||||
/// <summary>
|
||||
/// Allow components in SongSelect to update their loaded beatmap details.
|
||||
/// This is a debounced call (unlike directly binding to WorkingBeatmap.ValueChanged).
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The working beatmap.</param>
|
||||
protected virtual void UpdateBeatmap(WorkingBeatmap beatmap)
|
||||
{
|
||||
var backgroundModeBeatmap = Background as BackgroundScreenBeatmap;
|
||||
if (backgroundModeBeatmap != null)
|
||||
@ -377,10 +393,7 @@ namespace osu.Game.Screens.Select
|
||||
}
|
||||
}
|
||||
|
||||
private void addBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
carousel.AddBeatmap(beatmapSet);
|
||||
}
|
||||
private void addBeatmapSet(BeatmapSetInfo beatmapSet) => carousel.AddBeatmap(beatmapSet);
|
||||
|
||||
private void removeBeatmapSet(BeatmapSetInfo beatmapSet)
|
||||
{
|
||||
@ -389,10 +402,12 @@ namespace osu.Game.Screens.Select
|
||||
Beatmap.SetDefault();
|
||||
}
|
||||
|
||||
private void promptDelete()
|
||||
private void promptDelete(BeatmapSetInfo beatmap)
|
||||
{
|
||||
if (Beatmap != null && !Beatmap.IsDefault)
|
||||
dialogOverlay?.Push(new BeatmapDeleteDialog(Beatmap));
|
||||
if (beatmap == null)
|
||||
return;
|
||||
|
||||
dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap));
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
|
||||
@ -408,7 +423,8 @@ namespace osu.Game.Screens.Select
|
||||
case Key.Delete:
|
||||
if (state.Keyboard.ShiftPressed)
|
||||
{
|
||||
promptDelete();
|
||||
if (!Beatmap.IsDefault)
|
||||
promptDelete(Beatmap.Value.BeatmapSetInfo);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user