Merge remote-tracking branch 'origin/master' into pr/n1202_2yangk23

This commit is contained in:
smoogipooo 2017-09-04 09:42:24 +09:00
commit c72a9b1301
25 changed files with 766 additions and 406 deletions

@ -1 +1 @@
Subproject commit 3db7e231653ec6ffe28b5dcd1a86230ec754cc1c Subproject commit 2bd341b29d6a7ed864aa9c1c5fad4668dafe03a4

View File

@ -69,28 +69,28 @@ namespace osu.Desktop.Tests.Visual
private class MyContextMenuContainer : Container, IHasContextMenu private class MyContextMenuContainer : Container, IHasContextMenu
{ {
public ContextMenuItem[] ContextMenuItems => new ContextMenuItem[] public MenuItem[] ContextMenuItems => new MenuItem[]
{ {
new OsuContextMenuItem(@"Some option"), new OsuMenuItem(@"Some option"),
new OsuContextMenuItem(@"Highlighted option", MenuItemType.Highlighted), new OsuMenuItem(@"Highlighted option", MenuItemType.Highlighted),
new OsuContextMenuItem(@"Another option"), new OsuMenuItem(@"Another option"),
new OsuContextMenuItem(@"Choose me please"), new OsuMenuItem(@"Choose me please"),
new OsuContextMenuItem(@"And me too"), new OsuMenuItem(@"And me too"),
new OsuContextMenuItem(@"Trying to fill"), new OsuMenuItem(@"Trying to fill"),
new OsuContextMenuItem(@"Destructive option", MenuItemType.Destructive), new OsuMenuItem(@"Destructive option", MenuItemType.Destructive),
}; };
} }
private class AnotherContextMenuContainer : Container, IHasContextMenu private class AnotherContextMenuContainer : Container, IHasContextMenu
{ {
public ContextMenuItem[] ContextMenuItems => new ContextMenuItem[] public MenuItem[] ContextMenuItems => new MenuItem[]
{ {
new OsuContextMenuItem(@"Simple option"), new OsuMenuItem(@"Simple option"),
new OsuContextMenuItem(@"Simple very very long option"), new OsuMenuItem(@"Simple very very long option"),
new OsuContextMenuItem(@"Change width", MenuItemType.Highlighted) { Action = () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint) }, new OsuMenuItem(@"Change width", MenuItemType.Highlighted, () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)),
new OsuContextMenuItem(@"Change height", MenuItemType.Highlighted) { Action = () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint) }, new OsuMenuItem(@"Change height", MenuItemType.Highlighted, () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)),
new OsuContextMenuItem(@"Change width back", MenuItemType.Destructive) { Action = () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint) }, new OsuMenuItem(@"Change width back", MenuItemType.Destructive, () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)),
new OsuContextMenuItem(@"Change height back", MenuItemType.Destructive) { Action = () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint) }, new OsuMenuItem(@"Change height back", MenuItemType.Destructive, () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)),
}; };
} }
} }

View File

@ -52,6 +52,8 @@ namespace osu.Game.Beatmaps
[JsonProperty("file_sha2")] [JsonProperty("file_sha2")]
public string Hash { get; set; } public string Hash { get; set; }
public bool Hidden { get; set; }
/// <summary> /// <summary>
/// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.).
/// </summary> /// </summary>

View File

@ -33,11 +33,21 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public event Action<BeatmapSetInfo> BeatmapSetAdded; public event Action<BeatmapSetInfo> BeatmapSetAdded;
/// <summary>
/// Fired when a single difficulty has been hidden.
/// </summary>
public event Action<BeatmapInfo> BeatmapHidden;
/// <summary> /// <summary>
/// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database. /// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database.
/// </summary> /// </summary>
public event Action<BeatmapSetInfo> BeatmapSetRemoved; public event Action<BeatmapSetInfo> BeatmapSetRemoved;
/// <summary>
/// Fired when a single difficulty has been restored.
/// </summary>
public event Action<BeatmapInfo> BeatmapRestored;
/// <summary> /// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available. /// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary> /// </summary>
@ -71,6 +81,8 @@ namespace osu.Game.Beatmaps
beatmaps = new BeatmapStore(connection); beatmaps = new BeatmapStore(connection);
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
this.storage = storage; this.storage = storage;
this.files = files; this.files = files;
@ -162,7 +174,6 @@ namespace osu.Game.Beatmaps
// If we have an ID then we already exist in the database. // If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return; if (beatmapSetInfo.ID != 0) return;
lock (beatmaps)
beatmaps.Add(beatmapSetInfo); beatmaps.Add(beatmapSetInfo);
} }
@ -170,16 +181,27 @@ namespace osu.Game.Beatmaps
/// Delete a beatmap from the manager. /// Delete a beatmap from the manager.
/// Is a no-op for already deleted beatmaps. /// Is a no-op for already deleted beatmaps.
/// </summary> /// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param> /// <param name="beatmapSet">The beatmap set to delete.</param>
public void Delete(BeatmapSetInfo beatmapSet) public void Delete(BeatmapSetInfo beatmapSet)
{ {
lock (beatmaps)
if (!beatmaps.Delete(beatmapSet)) return; if (!beatmaps.Delete(beatmapSet)) return;
if (!beatmapSet.Protected) if (!beatmapSet.Protected)
files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); 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> /// <summary>
/// Returns a <see cref="BeatmapSetInfo"/> to a usable state if it has previously been deleted but not yet purged. /// 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. /// Is a no-op for already usable beatmaps.
@ -187,7 +209,6 @@ namespace osu.Game.Beatmaps
/// <param name="beatmapSet">The beatmap to restore.</param> /// <param name="beatmapSet">The beatmap to restore.</param>
public void Undelete(BeatmapSetInfo beatmapSet) public void Undelete(BeatmapSetInfo beatmapSet)
{ {
lock (beatmaps)
if (!beatmaps.Undelete(beatmapSet)) return; if (!beatmaps.Undelete(beatmapSet)) return;
if (!beatmapSet.Protected) if (!beatmapSet.Protected)
@ -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> /// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s. /// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary> /// </summary>
@ -255,7 +283,7 @@ namespace osu.Game.Beatmaps
/// <returns>Results from the provided query.</returns> /// <returns>Results from the provided query.</returns>
public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query) public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query)
{ {
lock (beatmaps) return beatmaps.QueryAndPopulate(query); return beatmaps.QueryAndPopulate(query);
} }
/// <summary> /// <summary>
@ -264,8 +292,6 @@ namespace osu.Game.Beatmaps
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns> /// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query) public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query)
{
lock (beatmaps)
{ {
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query); BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
@ -274,7 +300,6 @@ namespace osu.Game.Beatmaps
return set; return set;
} }
}
/// <summary> /// <summary>
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s. /// Perform a lookup query on available <see cref="BeatmapInfo"/>s.

View File

@ -16,11 +16,14 @@ namespace osu.Game.Beatmaps
public event Action<BeatmapSetInfo> BeatmapSetAdded; public event Action<BeatmapSetInfo> BeatmapSetAdded;
public event Action<BeatmapSetInfo> BeatmapSetRemoved; public event Action<BeatmapSetInfo> BeatmapSetRemoved;
public event Action<BeatmapInfo> BeatmapHidden;
public event Action<BeatmapInfo> BeatmapRestored;
/// <summary> /// <summary>
/// The current version of this store. Used for migrations (see <see cref="PerformMigration(int, int)"/>). /// The current version of this store. Used for migrations (see <see cref="PerformMigration(int, int)"/>).
/// The initial version is 1. /// The initial version is 1.
/// </summary> /// </summary>
protected override int StoreVersion => 3; protected override int StoreVersion => 4;
public BeatmapStore(SQLiteConnection connection) public BeatmapStore(SQLiteConnection connection)
: base(connection) : base(connection)
@ -81,6 +84,10 @@ namespace osu.Game.Beatmaps
// Added MD5Hash column to BeatmapInfo // Added MD5Hash column to BeatmapInfo
Connection.MigrateTable<BeatmapInfo>(); Connection.MigrateTable<BeatmapInfo>();
break; break;
case 4:
// Added Hidden column to BeatmapInfo
Connection.MigrateTable<BeatmapInfo>();
break;
} }
} }
} }
@ -100,7 +107,7 @@ namespace osu.Game.Beatmaps
} }
/// <summary> /// <summary>
/// Delete a <see cref="BeatmapSetInfo"/> to the database. /// Delete a <see cref="BeatmapSetInfo"/> from the database.
/// </summary> /// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param> /// <param name="beatmapSet">The beatmap to delete.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns> /// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
@ -131,6 +138,38 @@ namespace osu.Game.Beatmaps
return true; 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() private void cleanupPendingDeletions()
{ {
Connection.RunInTransaction(() => Connection.RunInTransaction(() =>

View File

@ -23,6 +23,12 @@ namespace osu.Game.Beatmaps.Drawables
/// </summary> /// </summary>
public Action<BeatmapInfo> StartRequested; public Action<BeatmapInfo> StartRequested;
public Action<BeatmapSetInfo> DeleteRequested;
public Action<BeatmapSetInfo> RestoreHiddenRequested;
public Action<BeatmapInfo> HideDifficultyRequested;
public BeatmapSetHeader Header; public BeatmapSetHeader Header;
private BeatmapGroupState state; private BeatmapGroupState state;
@ -66,14 +72,17 @@ namespace osu.Game.Beatmaps.Drawables
Header = new BeatmapSetHeader(beatmap) Header = new BeatmapSetHeader(beatmap)
{ {
GainedSelection = headerGainedSelection, GainedSelection = headerGainedSelection,
DeleteRequested = b => DeleteRequested(b),
RestoreHiddenRequested = b => RestoreHiddenRequested(b),
RelativeSizeAxes = Axes.X, 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) BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b)
{ {
Alpha = 0, Alpha = 0,
GainedSelection = panelGainedSelection, GainedSelection = panelGainedSelection,
HideRequested = p => HideDifficultyRequested?.Invoke(p),
StartRequested = p => { StartRequested?.Invoke(p.Beatmap); }, StartRequested = p => { StartRequested?.Invoke(p.Beatmap); },
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}).ToList(); }).ToList();

View File

@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
@ -14,16 +15,20 @@ using OpenTK.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
public class BeatmapPanel : Panel public class BeatmapPanel : Panel, IHasContextMenu
{ {
public BeatmapInfo Beatmap; public BeatmapInfo Beatmap;
private readonly Sprite background; private readonly Sprite background;
public Action<BeatmapPanel> GainedSelection; public Action<BeatmapPanel> GainedSelection;
public Action<BeatmapPanel> StartRequested; public Action<BeatmapPanel> StartRequested;
public Action<BeatmapPanel> EditRequested;
public Action<BeatmapInfo> HideRequested;
private readonly Triangles triangles; private readonly Triangles triangles;
private readonly StarCounter starCounter; 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)),
};
} }
} }

View File

@ -3,22 +3,31 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
public class BeatmapSetHeader : Panel public class BeatmapSetHeader : Panel, IHasContextMenu
{ {
public Action<BeatmapSetHeader> GainedSelection; public Action<BeatmapSetHeader> GainedSelection;
public Action<BeatmapSetInfo> DeleteRequested;
public Action<BeatmapSetInfo> RestoreHiddenRequested;
private readonly SpriteText title; private readonly SpriteText title;
private readonly SpriteText artist; private readonly SpriteText artist;
@ -148,5 +157,23 @@ namespace osu.Game.Beatmaps.Drawables
foreach (var p in panels) foreach (var p in panels)
difficultyIcons.Add(new DifficultyIcon(p.Beatmap)); 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();
}
}
} }
} }

View File

@ -9,6 +9,6 @@ namespace osu.Game.Graphics.Cursor
{ {
public class OsuContextMenuContainer : ContextMenuContainer public class OsuContextMenuContainer : ContextMenuContainer
{ {
protected override ContextMenu<ContextMenuItem> CreateContextMenu() => new OsuContextMenu<ContextMenuItem>(); protected override Menu CreateMenu() => new OsuContextMenu();
} }
} }

View File

@ -1,30 +1,21 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuContextMenu<TItem> : ContextMenu<TItem> public class OsuContextMenu : OsuMenu
where TItem : ContextMenuItem
{
protected override Menu<TItem> CreateMenu() => new CustomMenu();
public class CustomMenu : Menu<TItem>
{ {
private const int fade_duration = 250; private const int fade_duration = 250;
public CustomMenu() public OsuContextMenu()
{ {
CornerRadius = 5; CornerRadius = 5;
ItemsContainer.Padding = new MarginPadding { Vertical = OsuContextMenuItem.MARGIN_VERTICAL };
Masking = true;
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
{ {
Type = EdgeEffectType.Shadow, Type = EdgeEffectType.Shadow,
@ -36,17 +27,12 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Background.Colour = colours.ContextMenuGray; BackgroundColour = colours.ContextMenuGray;
} }
protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint); protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint); protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint);
protected override void UpdateContentHeight() protected override MarginPadding ItemFlowContainerPadding => new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL };
{
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
this.ResizeTo(new Vector2(1, State == MenuState.Opened ? actualHeight : 0), 300, Easing.OutQuint);
}
}
} }
} }

View File

@ -1,114 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
public class OsuContextMenuItem : ContextMenuItem
{
private const int transition_length = 80;
private const int margin_horizontal = 17;
public const int MARGIN_VERTICAL = 4;
private const int text_size = 17;
private OsuSpriteText text;
private OsuSpriteText textBold;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private readonly MenuItemType type;
protected override Container CreateTextContainer(string title) => new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Text = title,
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
},
textBold = new OsuSpriteText
{
AlwaysPresent = true,
Alpha = 0,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Text = title,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
}
}
};
public OsuContextMenuItem(string title, MenuItemType type = MenuItemType.Standard) : base(title)
{
this.type = type;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Sample.Get(@"UI/generic-hover");
sampleClick = audio.Sample.Get(@"UI/generic-click");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
updateTextColour();
}
private void updateTextColour()
{
switch (type)
{
case MenuItemType.Standard:
textBold.Colour = text.Colour = Color4.White;
break;
case MenuItemType.Destructive:
textBold.Colour = text.Colour = Color4.Red;
break;
case MenuItemType.Highlighted:
textBold.Colour = text.Colour = OsuColour.FromHex(@"ffcc22");
break;
}
}
protected override bool OnHover(InputState state)
{
sampleHover.Play();
textBold.FadeIn(transition_length, Easing.OutQuint);
text.FadeOut(transition_length, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
textBold.FadeOut(transition_length, Easing.OutQuint);
text.FadeIn(transition_length, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
sampleClick.Play();
return base.OnClick(state);
}
}
}

View File

@ -14,51 +14,150 @@ using OpenTK;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuDropdown<T> : Dropdown<T> public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour
{ {
protected override DropdownHeader CreateHeader() => new OsuDropdownHeader { AccentColour = AccentColour }; private Color4 accentColour;
public Color4 AccentColour
protected override Menu CreateMenu() => new OsuMenu();
private Color4? accentColour;
public virtual Color4 AccentColour
{ {
get { return accentColour.GetValueOrDefault(); } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
if (Header != null) updateAccentColour();
((OsuDropdownHeader)Header).AccentColour = value;
foreach (var item in MenuItems.OfType<OsuDropdownMenuItem>())
item.AccentColour = value;
} }
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == null) if (accentColour == default(Color4))
AccentColour = colours.PinkDarker; accentColour = colours.PinkDarker;
updateAccentColour();
} }
protected override DropdownMenuItem<T> CreateMenuItem(string text, T value) => new OsuDropdownMenuItem(text, value) { AccentColour = AccentColour }; private void updateAccentColour()
public class OsuDropdownMenuItem : DropdownMenuItem<T>
{ {
public OsuDropdownMenuItem(string text, T current) : base(text, current) var header = Header as IHasAccentColour;
if (header != null) header.AccentColour = accentColour;
var menu = Menu as IHasAccentColour;
if (menu != null) menu.AccentColour = accentColour;
}
protected override DropdownHeader CreateHeader() => new OsuDropdownHeader();
protected override DropdownMenu CreateMenu() => new OsuDropdownMenu();
#region OsuDropdownMenu
protected class OsuDropdownMenu : DropdownMenu, IHasAccentColour
{
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
public OsuDropdownMenu()
{
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
protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint);
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()
{
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
this.ResizeHeightTo(State == MenuState.Opened ? actualHeight : 0, 300, Easing.OutQuint);
}
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
foreach (var c in Children.OfType<IHasAccentColour>())
c.AccentColour = value;
}
}
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuDropdownMenuItem(item) { AccentColour = accentColour };
#region DrawableOsuDropdownMenuItem
protected class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour
{
private Color4? accentColour;
public Color4 AccentColour
{
get { return accentColour ?? nonAccentSelectedColour; }
set
{
accentColour = value;
updateColours();
}
}
private void updateColours()
{
BackgroundColourHover = accentColour ?? nonAccentHoverColour;
BackgroundColourSelected = accentColour ?? nonAccentSelectedColour;
UpdateBackgroundColour();
UpdateForegroundColour();
}
private Color4 nonAccentHoverColour;
private Color4 nonAccentSelectedColour;
public DrawableOsuDropdownMenuItem(MenuItem item)
: base(item)
{ {
Foreground.Padding = new MarginPadding(2); Foreground.Padding = new MarginPadding(2);
Masking = true; Masking = true;
CornerRadius = 6; CornerRadius = 6;
}
Children = new[] [BackgroundDependencyLoader]
private void load(OsuColour colours)
{ {
new FillFlowContainer BackgroundColour = Color4.Transparent;
nonAccentHoverColour = colours.PinkDarker;
nonAccentSelectedColour = Color4.Black.Opacity(0.5f);
updateColours();
}
protected override void UpdateForegroundColour()
{ {
Direction = FillDirection.Horizontal, base.UpdateForegroundColour();
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, var content = Foreground.Children.FirstOrDefault() as Content;
if (content != null) content.Chevron.Alpha = IsHovered ? 1 : 0;
}
protected override Drawable CreateContent() => new Content();
protected class Content : FillFlowContainer, IHasText
{
public string Text
{
get { return Label.Text; }
set { Label.Text = value; }
}
public readonly OsuSpriteText Label;
public readonly SpriteIcon Chevron;
public Content()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Direction = FillDirection.Horizontal;
Children = new Drawable[] Children = new Drawable[]
{ {
Chevron = new SpriteIcon Chevron = new SpriteIcon
@ -72,49 +171,20 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
}, },
Label = new OsuSpriteText { Label = new OsuSpriteText
Text = text, {
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
} }
}
}
}; };
} }
private Color4? accentColour;
protected readonly SpriteIcon Chevron;
protected readonly OsuSpriteText Label;
protected override void FormatForeground(bool hover = false)
{
base.FormatForeground(hover);
Chevron.Alpha = hover ? 1 : 0;
}
public Color4 AccentColour
{
get { return accentColour.GetValueOrDefault(); }
set
{
accentColour = value;
BackgroundColourHover = BackgroundColourSelected = value;
FormatBackground();
FormatForeground();
} }
} }
#endregion
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = Color4.Transparent;
BackgroundColourHover = accentColour ?? colours.PinkDarker;
BackgroundColourSelected = Color4.Black.Opacity(0.5f);
}
} }
#endregion
public class OsuDropdownHeader : DropdownHeader public class OsuDropdownHeader : DropdownHeader, IHasAccentColour
{ {
protected readonly SpriteText Text; protected readonly SpriteText Text;
protected override string Label protected override string Label
@ -125,14 +195,14 @@ namespace osu.Game.Graphics.UserInterface
protected readonly SpriteIcon Icon; protected readonly SpriteIcon Icon;
private Color4? accentColour; private Color4 accentColour;
public virtual Color4 AccentColour public virtual Color4 AccentColour
{ {
get { return accentColour.GetValueOrDefault(); } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
BackgroundColourHover = value; BackgroundColourHover = accentColour;
} }
} }
@ -167,7 +237,7 @@ namespace osu.Game.Graphics.UserInterface
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
BackgroundColour = Color4.Black.Opacity(0.5f); BackgroundColour = Color4.Black.Opacity(0.5f);
BackgroundColourHover = accentColour ?? colours.PinkDarker; BackgroundColourHover = colours.PinkDarker;
} }
} }
} }

View File

@ -1,11 +1,17 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -14,19 +20,136 @@ namespace osu.Game.Graphics.UserInterface
public OsuMenu() public OsuMenu()
{ {
CornerRadius = 4; CornerRadius = 4;
Background.Colour = Color4.Black.Opacity(0.5f); BackgroundColour = Color4.Black.Opacity(0.5f);
ItemsContainer.Padding = new MarginPadding(5);
} }
protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint); protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint);
protected override void UpdateContentHeight() protected override void UpdateMenuHeight()
{ {
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight; var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
this.ResizeTo(new Vector2(1, State == MenuState.Opened ? actualHeight : 0), 300, Easing.OutQuint); this.ResizeHeightTo(State == MenuState.Opened ? actualHeight : 0, 300, Easing.OutQuint);
}
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding(5);
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item);
protected class DrawableOsuMenuItem : DrawableMenuItem
{
private const int margin_horizontal = 17;
private const int text_size = 17;
private const int transition_length = 80;
public const int MARGIN_VERTICAL = 4;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private TextContainer text;
public DrawableOsuMenuItem(MenuItem item)
: base(item)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Sample.Get(@"UI/generic-hover");
sampleClick = audio.Sample.Get(@"UI/generic-click");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
updateTextColour();
}
private void updateTextColour()
{
switch ((Item as OsuMenuItem)?.Type)
{
default:
case MenuItemType.Standard:
text.Colour = Color4.White;
break;
case MenuItemType.Destructive:
text.Colour = Color4.Red;
break;
case MenuItemType.Highlighted:
text.Colour = OsuColour.FromHex(@"ffcc22");
break;
}
}
protected override bool OnHover(InputState state)
{
sampleHover.Play();
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
text.BoldText.FadeOut(transition_length, Easing.OutQuint);
text.NormalText.FadeIn(transition_length, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
sampleClick.Play();
return base.OnClick(state);
}
protected override Drawable CreateContent() => text = new TextContainer();
private class TextContainer : Container, IHasText
{
public string Text
{
get { return NormalText.Text; }
set
{
NormalText.Text = value;
BoldText.Text = value;
}
}
public readonly SpriteText NormalText;
public readonly SpriteText BoldText;
public TextContainer()
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
NormalText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
},
BoldText = new OsuSpriteText
{
AlwaysPresent = true,
Alpha = 0,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
}
};
}
}
} }
} }
} }

View File

@ -0,0 +1,25 @@
// 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.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterface
{
public class OsuMenuItem : MenuItem
{
public readonly MenuItemType Type;
public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard)
: base(text)
{
Type = type;
}
public OsuMenuItem(string text, MenuItemType type, Action action)
: base(text, action)
{
Type = type;
}
}
}

View File

@ -1,11 +1,16 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Platform;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -15,6 +20,49 @@ namespace osu.Game.Graphics.UserInterface
public override bool AllowClipboardExport => false; public override bool AllowClipboardExport => false;
private readonly CapsWarning warning;
private GameHost host;
public OsuPasswordTextBox()
{
Add(warning = new CapsWarning
{
Size = new Vector2(20),
Origin = Anchor.CentreRight,
Anchor = Anchor.CentreRight,
Margin = new MarginPadding { Right = 10 },
Alpha = 0,
});
}
[BackgroundDependencyLoader]
private void load(GameHost host)
{
this.host = host;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Key == Key.CapsLock)
updateCapsWarning(host.CapsLockEnabled);
return base.OnKeyDown(state, args);
}
protected override void OnFocus(InputState state)
{
updateCapsWarning(host.CapsLockEnabled);
base.OnFocus(state);
}
protected override void OnFocusLost(InputState state)
{
updateCapsWarning(false);
base.OnFocusLost(state);
}
private void updateCapsWarning(bool visible) => warning.FadeTo(visible ? 1 : 0, 250, Easing.OutQuint);
public class PasswordMaskChar : Container public class PasswordMaskChar : Container
{ {
private readonly CircularContainer circle; private readonly CircularContainer circle;
@ -51,5 +99,21 @@ namespace osu.Game.Graphics.UserInterface
circle.ResizeTo(new Vector2(0.8f), 500, Easing.OutQuint); circle.ResizeTo(new Vector2(0.8f), 500, Easing.OutQuint);
} }
} }
private class CapsWarning : SpriteIcon, IHasTooltip
{
public string TooltipText => @"Caps lock is active";
public CapsWarning()
{
Icon = FontAwesome.fa_warning;
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
Colour = colour.YellowLight;
}
}
} }
} }

View File

@ -37,34 +37,34 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == null) if (accentColour == default(Color4))
AccentColour = colours.Blue; AccentColour = colours.Blue;
} }
private Color4? accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour.GetValueOrDefault(); } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
var dropDown = Dropdown as OsuTabDropdown; var dropdown = Dropdown as IHasAccentColour;
if (dropDown != null) if (dropdown != null)
dropDown.AccentColour = value; dropdown.AccentColour = value;
foreach (var item in TabContainer.Children.OfType<OsuTabItem>()) foreach (var i in TabContainer.Children.OfType<IHasAccentColour>())
item.AccentColour = value; i.AccentColour = value;
} }
} }
public class OsuTabItem : TabItem<T> public class OsuTabItem : TabItem<T>, IHasAccentColour
{ {
protected readonly SpriteText Text; protected readonly SpriteText Text;
private readonly Box box; private readonly Box box;
private Color4? accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour.GetValueOrDefault(); } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
@ -103,7 +103,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == null) if (accentColour == default(Color4))
AccentColour = colours.Blue; AccentColour = colours.Blue;
} }
@ -140,38 +140,55 @@ namespace osu.Game.Graphics.UserInterface
protected override void OnDeactivated() => fadeInactive(); protected override void OnDeactivated() => fadeInactive();
} }
// todo: this needs to go
private class OsuTabDropdown : OsuDropdown<T> private class OsuTabDropdown : OsuDropdown<T>
{ {
protected override DropdownHeader CreateHeader() => new OsuTabDropdownHeader
{
AccentColour = AccentColour,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
};
protected override DropdownMenuItem<T> CreateMenuItem(string text, T value)
{
var item = base.CreateMenuItem(text, value);
item.ForegroundColourHover = Color4.Black;
return item;
}
public OsuTabDropdown() public OsuTabDropdown()
{ {
DropdownMenu.Anchor = Anchor.TopRight;
DropdownMenu.Origin = Anchor.TopRight;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
DropdownMenu.Background.Colour = Color4.Black.Opacity(0.7f);
DropdownMenu.MaxHeight = 400;
} }
protected override DropdownMenu CreateMenu() => new OsuTabDropdownMenu();
protected override DropdownHeader CreateHeader() => new OsuTabDropdownHeader
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight
};
private class OsuTabDropdownMenu : OsuDropdownMenu
{
public OsuTabDropdownMenu()
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
BackgroundColour = Color4.Black.Opacity(0.7f);
MaxHeight = 400;
}
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item) { AccentColour = AccentColour };
private class DrawableOsuTabDropdownMenuItem : DrawableOsuDropdownMenuItem
{
public DrawableOsuTabDropdownMenuItem(MenuItem item)
: base(item)
{
ForegroundColourHover = Color4.Black;
}
}
}
protected class OsuTabDropdownHeader : OsuDropdownHeader protected class OsuTabDropdownHeader : OsuDropdownHeader
{ {
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get
{
return base.AccentColour;
}
set set
{ {
base.AccentColour = value; base.AccentColour = value;
@ -179,18 +196,6 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
protected override bool OnHover(InputState state)
{
Foreground.Colour = BackgroundColour;
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
Foreground.Colour = BackgroundColourHover;
base.OnHoverLost(state);
}
public OsuTabDropdownHeader() public OsuTabDropdownHeader()
{ {
RelativeSizeAxes = Axes.None; RelativeSizeAxes = Axes.None;
@ -220,6 +225,18 @@ namespace osu.Game.Graphics.UserInterface
Padding = new MarginPadding { Left = 5, Right = 5 }; Padding = new MarginPadding { Left = 5, Right = 5 };
} }
protected override bool OnHover(InputState state)
{
Foreground.Colour = BackgroundColour;
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
Foreground.Colour = BackgroundColourHover;
base.OnHoverLost(state);
}
} }
} }
} }

View File

@ -15,15 +15,37 @@ namespace osu.Game.Overlays.Music
{ {
public class CollectionsDropdown<T> : OsuDropdown<T> public class CollectionsDropdown<T> : OsuDropdown<T>
{ {
protected override DropdownHeader CreateHeader() => new CollectionsHeader { AccentColour = AccentColour };
protected override Menu CreateMenu() => new CollectionsMenu();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
AccentColour = colours.Gray6; AccentColour = colours.Gray6;
} }
protected override DropdownHeader CreateHeader() => new CollectionsHeader();
protected override DropdownMenu CreateMenu() => new CollectionsMenu();
private class CollectionsMenu : OsuDropdownMenu
{
public CollectionsMenu()
{
CornerRadius = 5;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Gray4;
}
}
private class CollectionsHeader : OsuDropdownHeader private class CollectionsHeader : OsuDropdownHeader
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -48,26 +70,5 @@ namespace osu.Game.Overlays.Music
}; };
} }
} }
private class CollectionsMenu : OsuMenu
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Background.Colour = colours.Gray4;
}
public CollectionsMenu()
{
CornerRadius = 5;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
}
} }
} }

View File

@ -12,8 +12,9 @@ namespace osu.Game.Overlays.SearchableList
{ {
public class SlimEnumDropdown<T> : OsuEnumDropdown<T> public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
{ {
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour }; protected override DropdownHeader CreateHeader() => new SlimDropdownHeader();
protected override Menu CreateMenu() => new SlimMenu();
protected override DropdownMenu CreateMenu() => new SlimMenu();
private class SlimDropdownHeader : OsuDropdownHeader private class SlimDropdownHeader : OsuDropdownHeader
{ {
@ -31,11 +32,11 @@ namespace osu.Game.Overlays.SearchableList
} }
} }
private class SlimMenu : OsuMenu private class SlimMenu : OsuDropdownMenu
{ {
public SlimMenu() public SlimMenu()
{ {
Background.Colour = Color4.Black.Opacity(0.7f); BackgroundColour = Color4.Black.Opacity(0.7f);
} }
} }
} }

View File

@ -257,9 +257,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
private class UserDropdown : OsuEnumDropdown<UserAction> private class UserDropdown : OsuEnumDropdown<UserAction>
{ {
protected override DropdownHeader CreateHeader() => new UserDropdownHeader { AccentColour = AccentColour }; protected override DropdownHeader CreateHeader() => new UserDropdownHeader();
protected override Menu CreateMenu() => new UserDropdownMenu();
protected override DropdownMenuItem<UserAction> CreateMenuItem(string text, UserAction value) => new UserDropdownMenuItem(text, value) { AccentColour = AccentColour }; protected override DropdownMenu CreateMenu() => new UserDropdownMenu();
public Color4 StatusColour public Color4 StatusColour
{ {
@ -277,6 +277,49 @@ namespace osu.Game.Overlays.Settings.Sections.General
AccentColour = colours.Gray5; AccentColour = colours.Gray5;
} }
private class UserDropdownMenu : OsuDropdownMenu
{
public UserDropdownMenu()
{
Masking = true;
CornerRadius = 5;
Margin = new MarginPadding { Bottom = 5 };
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Gray3;
}
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding();
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item);
private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem
{
public DrawableUserDropdownMenuItem(MenuItem item)
: base(item)
{
Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 };
CornerRadius = 5;
}
protected override Drawable CreateContent() => new Content
{
Label = { Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 } }
};
}
}
private class UserDropdownHeader : OsuDropdownHeader private class UserDropdownHeader : OsuDropdownHeader
{ {
public const float LABEL_LEFT_MARGIN = 20; public const float LABEL_LEFT_MARGIN = 20;
@ -324,38 +367,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
} }
} }
private class UserDropdownMenu : OsuMenu
{
public UserDropdownMenu()
{
Margin = new MarginPadding { Bottom = 5 };
CornerRadius = 5;
ItemsContainer.Padding = new MarginPadding(0);
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Background.Colour = colours.Gray3;
}
}
private class UserDropdownMenuItem : OsuDropdownMenuItem
{
public UserDropdownMenuItem(string text, UserAction current) : base(text, current)
{
Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 };
Label.Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 };
CornerRadius = 5;
}
}
} }
private enum UserAction private enum UserAction

View File

@ -13,6 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
private OsuButton importButton; private OsuButton importButton;
private OsuButton deleteButton; private OsuButton deleteButton;
private OsuButton restoreButton;
protected override string Header => "General"; 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)); 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));
}
},
}; };
} }
} }

View File

@ -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) public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true)
{ {
if (beatmap == null) if (beatmap == null || beatmap.Hidden)
{ {
SelectNext(); SelectNext();
return; return;
@ -140,6 +167,12 @@ namespace osu.Game.Screens.Select
public Action StartRequested; public Action StartRequested;
public Action<BeatmapSetInfo> DeleteRequested;
public Action<BeatmapSetInfo> RestoreRequested;
public Action<BeatmapInfo> HideDifficultyRequested;
public void SelectNext(int direction = 1, bool skipDifficulties = true) public void SelectNext(int direction = 1, bool skipDifficulties = true)
{ {
if (groups.All(g => g.State == BeatmapGroupState.Hidden)) if (groups.All(g => g.State == BeatmapGroupState.Hidden))
@ -191,6 +224,7 @@ namespace osu.Game.Screens.Select
if (!visibleGroups.Any()) if (!visibleGroups.Any())
return; return;
if (selectedGroup != null)
randomSelectedBeatmaps.Push(new KeyValuePair<BeatmapGroup, BeatmapPanel>(selectedGroup, selectedGroup.SelectedPanel)); randomSelectedBeatmaps.Push(new KeyValuePair<BeatmapGroup, BeatmapPanel>(selectedGroup, selectedGroup.SelectedPanel));
BeatmapGroup group; BeatmapGroup group;
@ -305,6 +339,9 @@ namespace osu.Game.Screens.Select
{ {
SelectionChanged = (g, p) => selectGroup(g, p), SelectionChanged = (g, p) => selectGroup(g, p),
StartRequested = b => StartRequested?.Invoke(), StartRequested = b => StartRequested?.Invoke(),
DeleteRequested = b => DeleteRequested?.Invoke(b),
RestoreHiddenRequested = s => RestoreRequested?.Invoke(s),
HideDifficultyRequested = b => HideDifficultyRequested?.Invoke(b),
State = BeatmapGroupState.Collapsed State = BeatmapGroupState.Collapsed
}; };
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -19,23 +18,18 @@ namespace osu.Game.Screens.Select
manager = beatmapManager; 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; Icon = FontAwesome.fa_trash_o;
HeaderText = @"Confirm deletion of"; HeaderText = @"Confirm deletion of";
BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
Buttons = new PopupDialogButton[] Buttons = new PopupDialogButton[]
{ {
new PopupDialogOkButton new PopupDialogOkButton
{ {
Text = @"Yes. Totally. Delete it.", Text = @"Yes. Totally. Delete it.",
Action = () => Action = () => manager.Delete(beatmap),
{
beatmap.Dispose();
manager.Delete(beatmap.BeatmapSetInfo);
},
}, },
new PopupDialogCancelButton new PopupDialogCancelButton
{ {

View File

@ -54,13 +54,11 @@ namespace osu.Game.Screens.Select
ValidForResume = false; ValidForResume = false;
Push(new Editor()); Push(new Editor());
}, Key.Number3); }, 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); beatmap.Mods.BindTo(modSelect.SelectedMods);

View File

@ -106,6 +106,9 @@ namespace osu.Game.Screens.Select
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
SelectionChanged = carouselSelectionChanged, SelectionChanged = carouselSelectionChanged,
BeatmapsChanged = carouselBeatmapsLoaded, BeatmapsChanged = carouselBeatmapsLoaded,
DeleteRequested = b => promptDelete(b),
RestoreRequested = s => { foreach (var b in s.Beatmaps) manager.Restore(b); },
HideDifficultyRequested = b => manager.Hide(b),
StartRequested = () => carouselRaisedStart(), StartRequested = () => carouselRaisedStart(),
}); });
Add(FilterControl = new FilterControl Add(FilterControl = new FilterControl
@ -163,7 +166,7 @@ namespace osu.Game.Screens.Select
Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2);
Footer.AddButton(@"options", colours.Blue, BeatmapOptions.ToggleVisibility, Key.F3); 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) if (manager == null)
@ -174,6 +177,8 @@ namespace osu.Game.Screens.Select
manager.BeatmapSetAdded += onBeatmapSetAdded; manager.BeatmapSetAdded += onBeatmapSetAdded;
manager.BeatmapSetRemoved += onBeatmapSetRemoved; manager.BeatmapSetRemoved += onBeatmapSetRemoved;
manager.BeatmapHidden += onBeatmapHidden;
manager.BeatmapRestored += onBeatmapRestored;
dialogOverlay = dialog; dialogOverlay = dialog;
@ -190,6 +195,9 @@ namespace osu.Game.Screens.Select
carousel.AllowSelection = !Beatmap.Disabled; carousel.AllowSelection = !Beatmap.Disabled;
} }
private void onBeatmapRestored(BeatmapInfo b) => carousel.UpdateBeatmap(b);
private void onBeatmapHidden(BeatmapInfo b) => carousel.UpdateBeatmap(b);
private void carouselBeatmapsLoaded() private void carouselBeatmapsLoaded()
{ {
if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false) if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false)
@ -236,7 +244,7 @@ namespace osu.Game.Screens.Select
ensurePlayingSelected(preview); ensurePlayingSelected(preview);
} }
changeBackground(Beatmap.Value); UpdateBeatmap(Beatmap.Value);
}; };
selectionChangedDebounce?.Cancel(); selectionChangedDebounce?.Cancel();
@ -304,7 +312,7 @@ namespace osu.Game.Screens.Select
{ {
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
{ {
changeBackground(Beatmap); UpdateBeatmap(Beatmap);
ensurePlayingSelected(); ensurePlayingSelected();
} }
@ -345,12 +353,19 @@ namespace osu.Game.Screens.Select
{ {
manager.BeatmapSetAdded -= onBeatmapSetAdded; manager.BeatmapSetAdded -= onBeatmapSetAdded;
manager.BeatmapSetRemoved -= onBeatmapSetRemoved; manager.BeatmapSetRemoved -= onBeatmapSetRemoved;
manager.BeatmapHidden -= onBeatmapHidden;
manager.BeatmapRestored -= onBeatmapRestored;
} }
initialAddSetsTask?.Cancel(); 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; var backgroundModeBeatmap = Background as BackgroundScreenBeatmap;
if (backgroundModeBeatmap != null) if (backgroundModeBeatmap != null)
@ -378,10 +393,7 @@ namespace osu.Game.Screens.Select
} }
} }
private void addBeatmapSet(BeatmapSetInfo beatmapSet) private void addBeatmapSet(BeatmapSetInfo beatmapSet) => carousel.AddBeatmap(beatmapSet);
{
carousel.AddBeatmap(beatmapSet);
}
private void removeBeatmapSet(BeatmapSetInfo beatmapSet) private void removeBeatmapSet(BeatmapSetInfo beatmapSet)
{ {
@ -390,10 +402,12 @@ namespace osu.Game.Screens.Select
Beatmap.SetDefault(); Beatmap.SetDefault();
} }
private void promptDelete() private void promptDelete(BeatmapSetInfo beatmap)
{ {
if (Beatmap != null && !Beatmap.IsDefault) if (beatmap == null)
dialogOverlay?.Push(new BeatmapDeleteDialog(Beatmap)); return;
dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap));
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@ -409,7 +423,8 @@ namespace osu.Game.Screens.Select
case Key.Delete: case Key.Delete:
if (state.Keyboard.ShiftPressed) if (state.Keyboard.ShiftPressed)
{ {
promptDelete(); if (!Beatmap.IsDefault)
promptDelete(Beatmap.Value.BeatmapSetInfo);
return true; return true;
} }
break; break;

View File

@ -91,7 +91,7 @@
<Compile Include="Graphics\UserInterface\LineGraph.cs" /> <Compile Include="Graphics\UserInterface\LineGraph.cs" />
<Compile Include="Graphics\UserInterface\MenuItemType.cs" /> <Compile Include="Graphics\UserInterface\MenuItemType.cs" />
<Compile Include="Graphics\UserInterface\OsuContextMenu.cs" /> <Compile Include="Graphics\UserInterface\OsuContextMenu.cs" />
<Compile Include="Graphics\UserInterface\OsuContextMenuItem.cs" /> <Compile Include="Graphics\UserInterface\OsuMenuItem.cs" />
<Compile Include="Input\Bindings\DatabasedKeyBinding.cs" /> <Compile Include="Input\Bindings\DatabasedKeyBinding.cs" />
<Compile Include="Input\Bindings\DatabasedKeyBindingInputManager.cs" /> <Compile Include="Input\Bindings\DatabasedKeyBindingInputManager.cs" />
<Compile Include="Input\KeyBindingStore.cs" /> <Compile Include="Input\KeyBindingStore.cs" />