diff --git a/osu-framework b/osu-framework index 2e74df0a1b..16a01c7381 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 2e74df0a1bca294cad2094c86bf25d1833df7a14 +Subproject commit 16a01c7381e3ee8ef6b4fe863ba9232deba04c29 diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index d51f898870..ef2a4b8ba1 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Linq; +using System.Threading; using osu.Desktop.Beatmaps.IO; using osu.Framework; using osu.Framework.Desktop; @@ -26,16 +27,22 @@ namespace osu.Desktop { LegacyFilesystemReader.Register(); + // Back up the cwd before DesktopGameHost changes it + var cwd = Environment.CurrentDirectory; + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) { if (!host.IsPrimaryInstance) { var importer = new BeatmapImporter(host); - + // Restore the cwd so relative paths given at the command line work correctly + Directory.SetCurrentDirectory(cwd); foreach (var file in args) - if (!importer.Import(file).Wait(1000)) + { + Console.WriteLine(@"Importing {0}", file); + if (!importer.Import(Path.GetFullPath(file)).Wait(3000)) throw new TimeoutException(@"IPC took too long to send"); - Console.WriteLine(@"Sent import requests to running instance"); + } } else { diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 9efefaf8c3..8e4e37db12 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -31,31 +31,32 @@ namespace osu.Game.Beatmaps.Drawables public List BeatmapPanels; + public BeatmapSetInfo BeatmapSet; + public BeatmapGroupState State { get { return state; } set { - state = value; - switch (state) + switch (value) { case BeatmapGroupState.Expanded: - foreach (BeatmapPanel panel in BeatmapPanels) - panel.FadeIn(250); - Header.State = PanelSelectedState.Selected; - if (SelectedPanel != null) - SelectedPanel.State = PanelSelectedState.Selected; + foreach (BeatmapPanel panel in BeatmapPanels) + panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : PanelSelectedState.NotSelected; break; case BeatmapGroupState.Collapsed: Header.State = PanelSelectedState.NotSelected; - if (SelectedPanel != null) - SelectedPanel.State = PanelSelectedState.NotSelected; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.FadeOut(300, EasingTypes.OutQuint); + panel.State = PanelSelectedState.Hidden; + break; + case BeatmapGroupState.Hidden: + Header.State = PanelSelectedState.Hidden; + foreach (BeatmapPanel panel in BeatmapPanels) + panel.State = PanelSelectedState.Hidden; break; } + state = value; } } @@ -75,6 +76,7 @@ namespace osu.Game.Beatmaps.Drawables RelativeSizeAxes = Axes.X, }).ToList(); + BeatmapSet = set; Header.AddDifficultyIcons(BeatmapPanels); } @@ -111,5 +113,6 @@ namespace osu.Game.Beatmaps.Drawables { Collapsed, Expanded, + Hidden, } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index fdbf7430fb..9edfd9783a 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -2,14 +2,11 @@ //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.MathUtils; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; @@ -17,7 +14,6 @@ using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; using osu.Framework.Input; -using osu.Game.Modes; namespace osu.Game.Beatmaps.Drawables { @@ -33,6 +29,7 @@ namespace osu.Game.Beatmaps.Drawables protected override void Selected() { base.Selected(); + GainedSelection?.Invoke(this); background.ColourInfo = ColourInfo.GradientVertical( diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index b15942c5bb..d961cd067c 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -68,12 +68,6 @@ namespace osu.Game.Beatmaps.Drawables }; } - protected override void LoadComplete() - { - base.LoadComplete(); - FadeInFromZero(250); - } - protected override void Selected() { base.Selected(); diff --git a/osu.Game/Beatmaps/Drawables/Panel.cs b/osu.Game/Beatmaps/Drawables/Panel.cs index ff89e5882d..22261dd49c 100644 --- a/osu.Game/Beatmaps/Drawables/Panel.cs +++ b/osu.Game/Beatmaps/Drawables/Panel.cs @@ -55,6 +55,7 @@ namespace osu.Game.Beatmaps.Drawables { switch (state) { + case PanelSelectedState.Hidden: case PanelSelectedState.NotSelected: Deselected(); break; @@ -62,6 +63,11 @@ namespace osu.Game.Beatmaps.Drawables Selected(); break; } + + if (state == PanelSelectedState.Hidden) + FadeOut(300, EasingTypes.OutQuint); + else + FadeIn(250); } private PanelSelectedState state = PanelSelectedState.NotSelected; @@ -112,6 +118,7 @@ namespace osu.Game.Beatmaps.Drawables enum PanelSelectedState { + Hidden, NotSelected, Selected } diff --git a/osu.Game/Overlays/Pause/PauseOverlay.cs b/osu.Game/Overlays/Pause/PauseOverlay.cs index a45c1ec2c0..3cf514b7c8 100644 --- a/osu.Game/Overlays/Pause/PauseOverlay.cs +++ b/osu.Game/Overlays/Pause/PauseOverlay.cs @@ -1,212 +1,212 @@ -using System; -using OpenTK; -using OpenTK.Input; -using OpenTK.Graphics; -using osu.Game.Graphics; -using osu.Framework.Input; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Containers; +using System; +using OpenTK; +using OpenTK.Input; +using OpenTK.Graphics; +using osu.Game.Graphics; +using osu.Framework.Input; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transformations; using System.Threading.Tasks; -namespace osu.Game.Overlays.Pause -{ - public class PauseOverlay : OverlayContainer - { - private const int transition_duration = 200; - private const int button_height = 70; - private const float background_alpha = 0.75f; - - public Action OnResume; - public Action OnRetry; - public Action OnQuit; - - public int Retries - { - set - { +namespace osu.Game.Overlays.Pause +{ + public class PauseOverlay : OverlayContainer + { + private const int transition_duration = 200; + private const int button_height = 70; + private const float background_alpha = 0.75f; + + public Action OnResume; + public Action OnRetry; + public Action OnQuit; + + public int Retries + { + set + { if (retryCounterContainer != null) { // "You've retried 1,065 times in this session" - // "You've retried 1 time in this session" + // "You've retried 1 time in this session" retryCounterContainer.Children = new Drawable[] - { - new SpriteText - { - Text = "You've retried ", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new SpriteText - { - Text = String.Format("{0:n0}", value), - Font = @"Exo2.0-Bold", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new SpriteText - { - Text = $" time{((value == 1) ? "" : "s")} in this session", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 + { + new SpriteText + { + Text = "You've retried ", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new SpriteText + { + Text = String.Format("{0:n0}", value), + Font = @"Exo2.0-Bold", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new SpriteText + { + Text = $" time{((value == 1) ? "" : "s")} in this session", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 } }; - } - } - } - - private FlowContainer retryCounterContainer; - - public override bool Contains(Vector2 screenSpacePos) => true; - public override bool HandleInput => State == Visibility.Visible; - - protected override void PopIn() => FadeIn(transition_duration, EasingTypes.In); - protected override void PopOut() => FadeOut(transition_duration, EasingTypes.In); - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (args.Key == Key.Escape) - { + } + } + } + + private FlowContainer retryCounterContainer; + + public override bool Contains(Vector2 screenSpacePos) => true; + public override bool HandleInput => State == Visibility.Visible; + + protected override void PopIn() => FadeIn(transition_duration, EasingTypes.In); + protected override void PopOut() => FadeOut(transition_duration, EasingTypes.In); + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Key == Key.Escape) + { if (State == Visibility.Hidden) return false; resume(); - return true; - } - return base.OnKeyDown(state, args); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) + return true; + } + return base.OnKeyDown(state, args); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = background_alpha, - }, - new FlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FlowDirection.VerticalOnly, - Spacing = new Vector2(0f, 50f), - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Children = new Drawable[] - { - new FlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FlowDirection.VerticalOnly, - Spacing = new Vector2(0f, 20f), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Children = new Drawable[] - { - new SpriteText - { - Text = @"paused", - Font = @"Exo2.0-Medium", - Spacing = new Vector2(5, 0), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - TextSize = 30, - Colour = colours.Yellow, - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f) - }, - new SpriteText - { - Text = @"you're not going to do what i think you're going to do, are ya?", - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f) - } - } - }, - new FlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Masking = true, - EdgeEffect = new EdgeEffect - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.6f), - Radius = 50 - }, - Children = new Drawable[] - { - new ResumeButton - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Height = button_height, - Action = resume - }, - new RetryButton - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Height = button_height, - Action = delegate - { - OnRetry?.Invoke(); - Hide(); - } - }, - new QuitButton - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Height = button_height, - Action = delegate - { - OnQuit?.Invoke(); - Hide(); - } - } - } - }, - retryCounterContainer = new FlowContainer - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre - } - } - }, - new PauseProgressBar - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Width = 1f + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = background_alpha, + }, + new FlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FlowDirection.VerticalOnly, + Spacing = new Vector2(0f, 50f), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + new FlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FlowDirection.VerticalOnly, + Spacing = new Vector2(0f, 20f), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + new SpriteText + { + Text = @"paused", + Font = @"Exo2.0-Medium", + Spacing = new Vector2(5, 0), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + TextSize = 30, + Colour = colours.Yellow, + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f) + }, + new SpriteText + { + Text = @"you're not going to do what i think you're going to do, are ya?", + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f) + } + } + }, + new FlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + EdgeEffect = new EdgeEffect + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.6f), + Radius = 50 + }, + Children = new Drawable[] + { + new ResumeButton + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Height = button_height, + Action = resume + }, + new RetryButton + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Height = button_height, + Action = delegate + { + OnRetry?.Invoke(); + Hide(); + } + }, + new QuitButton + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Height = button_height, + Action = delegate + { + OnQuit?.Invoke(); + Hide(); + } + } + } + }, + retryCounterContainer = new FlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre + } + } + }, + new PauseProgressBar + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Width = 1f } - }; - + }; + Retries = 0; - } - - private void resume() + } + + private void resume() { - OnResume?.Invoke(); - Hide(); - } + OnResume?.Invoke(); + Hide(); + } public PauseOverlay() { RelativeSizeAxes = Axes.Both; } - } -} + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs index 1c25fcfb0e..250b789511 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundModeBeatmap.cs @@ -22,17 +22,19 @@ namespace osu.Game.Screens.Backgrounds { return beatmap; } - set { - if (beatmap == value) + if (beatmap == value && beatmap != null) return; - beatmap = value; Schedule(() => { - Background newBackground = new BeatmapBackground(beatmap); + Background newBackground; + if (beatmap == null) + newBackground = new Background(@"Backgrounds/bg1"); + else + newBackground = new BeatmapBackground(beatmap); newBackground.Preload(Game, delegate { @@ -84,7 +86,6 @@ namespace osu.Game.Screens.Backgrounds { Sprite.Texture = beatmap.Background; } - } } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 8825f9474b..6fbbafff07 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Menu { Action = onOsuLogo, Origin = Anchor.Centre, - Anchor = Anchor.Centre + Anchor = Anchor.Centre, } }; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e76d9d5214..fb55c0d87b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -252,7 +252,7 @@ namespace osu.Game.Screens.Play protected override void OnEntering(GameMode last) { base.OnEntering(last); - + (Background as BackgroundModeBeatmap)?.BlurTo(Vector2.Zero, 1000); Background?.FadeTo((100f- dimLevel)/100, 1000); diff --git a/osu.Game/Screens/Select/CarouselContainer.cs b/osu.Game/Screens/Select/CarouselContainer.cs index f1e30693ef..017bc6512e 100644 --- a/osu.Game/Screens/Select/CarouselContainer.cs +++ b/osu.Game/Screens/Select/CarouselContainer.cs @@ -1,26 +1,27 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using osu.Framework.Caching; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transformations; -using osu.Game.Database; -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Lists; -using osu.Game.Beatmaps.Drawables; +using OpenTK; +using osu.Framework.Caching; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transformations; +using osu.Game.Database; +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Lists; +using osu.Game.Beatmaps.Drawables; using osu.Framework.Timing; using osu.Framework.Input; using OpenTK.Input; -using osu.Framework.MathUtils; +using System.Collections; +using osu.Framework.MathUtils; namespace osu.Game.Screens.Select { - class CarouselContainer : ScrollContainer + class CarouselContainer : ScrollContainer, IEnumerable { private Container scrollableContent; private List groups = new List(); @@ -28,29 +29,29 @@ namespace osu.Game.Screens.Select public BeatmapGroup SelectedGroup { get; private set; } public BeatmapPanel SelectedPanel { get; private set; } - private List yPositions = new List(); - private CarouselLifetimeList Lifetime; - - public CarouselContainer() - { - DistanceDecayJump = 0.01; - - Add(scrollableContent = new Container(Lifetime = new CarouselLifetimeList(DepthComparer)) - { - RelativeSizeAxes = Axes.X, - }); - } - - internal class CarouselLifetimeList : LifetimeList - { - public CarouselLifetimeList(IComparer comparer) - : base(comparer) - { - } - - public int StartIndex; + private List yPositions = new List(); + private CarouselLifetimeList Lifetime; + + public CarouselContainer() + { + DistanceDecayJump = 0.01; + + Add(scrollableContent = new Container(Lifetime = new CarouselLifetimeList(DepthComparer)) + { + RelativeSizeAxes = Axes.X, + }); + } + + internal class CarouselLifetimeList : LifetimeList + { + public CarouselLifetimeList(IComparer comparer) + : base(comparer) + { + } + + public int StartIndex; public int EndIndex; - + public override bool Update(FrameTimeInfo time) { bool anyAliveChanged = false; @@ -76,9 +77,9 @@ namespace osu.Game.Screens.Select } return anyAliveChanged; - } - } - + } + } + public void AddGroup(BeatmapGroup group) { group.State = BeatmapGroupState.Collapsed; @@ -96,10 +97,10 @@ namespace osu.Game.Screens.Select computeYPositions(); } - private void movePanel(Panel panel, bool advance, ref float currentY) + private void movePanel(Panel panel, bool advance, bool animated, ref float currentY) { yPositions.Add(currentY); - panel.MoveToY(currentY, 750, EasingTypes.OutExpo); + panel.MoveToY(currentY, animated && (panel.IsOnScreen || panel.State != PanelSelectedState.Hidden) ? 750 : 0, EasingTypes.OutExpo); if (advance) currentY += panel.DrawHeight + 5; @@ -109,7 +110,7 @@ namespace osu.Game.Screens.Select /// Computes the target Y positions for every panel in the carousel. /// /// The Y position of the currently selected panel. - private float computeYPositions() + private float computeYPositions(bool animated = true) { yPositions.Clear(); @@ -118,7 +119,7 @@ namespace osu.Game.Screens.Select foreach (BeatmapGroup group in groups) { - movePanel(group.Header, true, ref currentY); + movePanel(group.Header, group.State != BeatmapGroupState.Hidden, animated, ref currentY); if (group.State == BeatmapGroupState.Expanded) { @@ -136,7 +137,7 @@ namespace osu.Game.Screens.Select if (panel.Alpha == 0) panel.MoveToY(headerY); - movePanel(panel, true, ref currentY); + movePanel(panel, true, animated, ref currentY); } } else @@ -146,7 +147,7 @@ namespace osu.Game.Screens.Select foreach (BeatmapPanel panel in group.BeatmapPanels) { panel.MoveToX(0, 500, EasingTypes.OutExpo); - movePanel(panel, false, ref currentY); + movePanel(panel, false, animated, ref currentY); } } } @@ -157,30 +158,30 @@ namespace osu.Game.Screens.Select return selectedY; } - public void SelectBeatmap(BeatmapInfo beatmap) + public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true) { foreach (BeatmapGroup group in groups) { var panel = group.BeatmapPanels.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); if (panel != null) { - SelectGroup(group, panel); + SelectGroup(group, panel, animated); return; } } } - public void SelectGroup(BeatmapGroup group, BeatmapPanel panel) + public void SelectGroup(BeatmapGroup group, BeatmapPanel panel, bool animated = true) { - if (SelectedGroup != null && SelectedGroup != group) + if (SelectedGroup != null && SelectedGroup != group && SelectedGroup.State != BeatmapGroupState.Hidden) SelectedGroup.State = BeatmapGroupState.Collapsed; SelectedGroup = group; panel.State = PanelSelectedState.Selected; SelectedPanel = panel; - float selectedY = computeYPositions(); - ScrollTo(selectedY); + float selectedY = computeYPositions(animated); + ScrollTo(selectedY, animated); } private static float offsetX(float dist, float halfHeight) @@ -201,7 +202,9 @@ namespace osu.Game.Screens.Select /// Half the draw height of the carousel container. private void updatePanel(Panel p, float halfHeight) { - float panelDrawY = p.Position.Y - Current + p.DrawHeight / 2; + var height = p.IsVisible ? p.DrawHeight : 0; + + float panelDrawY = p.Position.Y - Current + height / 2; float dist = Math.Abs(1f - panelDrawY / halfHeight); // Setting the origin position serves as an additive position on top of potential @@ -223,11 +226,11 @@ namespace osu.Game.Screens.Select float drawHeight = DrawHeight; float halfHeight = drawHeight / 2; - foreach (Panel p in Lifetime.AliveItems) - { - float panelPosY = p.Position.Y; - p.IsOnScreen = panelPosY >= Current - p.DrawHeight && panelPosY <= Current + drawHeight; - updatePanel(p, halfHeight); + foreach (Panel p in Lifetime.AliveItems) + { + float panelPosY = p.Position.Y; + p.IsOnScreen = panelPosY >= Current - p.DrawHeight && panelPosY <= Current + drawHeight; + updatePanel(p, halfHeight); } // Determine range of indices for items that are now definitely on screen to be added @@ -243,7 +246,8 @@ namespace osu.Game.Screens.Select for (int i = firstIndex; i < lastIndex; ++i) { Panel p = Lifetime[i]; - p.IsOnScreen = true; + if (p.State != PanelSelectedState.Hidden) + p.IsOnScreen = true; //we don't want to update the on-screen state of hidden pannels as they have incorrect (stacked) y values. updatePanel(p, halfHeight); } } @@ -271,34 +275,50 @@ namespace osu.Game.Screens.Select break; } - if (direction != 0) + if (direction == 0) + return base.OnKeyDown(state, args); + + if (!skipDifficulties) { - int index = SelectedGroup.BeatmapPanels.IndexOf(SelectedPanel) + direction; + int i = SelectedGroup.BeatmapPanels.IndexOf(SelectedPanel) + direction; - if (!skipDifficulties && index >= 0 && index < SelectedGroup.BeatmapPanels.Count) - //changing difficulty panel, not set. - SelectGroup(SelectedGroup, SelectedGroup.BeatmapPanels[index]); - else + if (i >= 0 && i < SelectedGroup.BeatmapPanels.Count) { - index = (groups.IndexOf(SelectedGroup) + direction + groups.Count) % groups.Count; - SelectBeatmap(groups[index].BeatmapPanels.First().Beatmap); + //changing difficulty panel, not set. + SelectGroup(SelectedGroup, SelectedGroup.BeatmapPanels[i]); + return true; } - - return true; } - return base.OnKeyDown(state, args); - } - - public void SelectRandom() - { - if (groups.Count < 1) - return; - BeatmapGroup group = groups[RNG.Next(groups.Count)]; - BeatmapPanel panel = group?.BeatmapPanels.First(); - if (panel == null) - return; - SelectGroup(group, panel); + int startIndex = groups.IndexOf(SelectedGroup); + int index = startIndex; + + do + { + index = (index + direction + groups.Count) % groups.Count; + if (groups[index].State != BeatmapGroupState.Hidden) + { + SelectBeatmap(groups[index].BeatmapPanels.First().Beatmap); + return true; + } + } while (index != startIndex); + + return true; } + + public void SelectRandom() + { + if (groups.Count < 1) + return; + BeatmapGroup group = groups[RNG.Next(groups.Count)]; + BeatmapPanel panel = group?.BeatmapPanels.First(); + if (panel == null) + return; + SelectGroup(group, panel); + } + + public IEnumerator GetEnumerator() => groups.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs new file mode 100644 index 0000000000..9050b9b4cb --- /dev/null +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -0,0 +1,279 @@ +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Select +{ + public class FilterControl : Container + { + public Action FilterChanged; + + public string Search => searchTextBox.Text; + public SortMode Sort { get; private set; } = SortMode.Title; + public Action Exit; + + private SearchTextBox searchTextBox; + + public FilterControl() + { + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + Alpha = 0.6f, + RelativeSizeAxes = Axes.Both, + }, + new FlowContainer + { + Padding = new MarginPadding(20), + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 0.4f, // TODO: InnerWidth property or something + Direction = FlowDirection.VerticalOnly, + Children = new Drawable[] + { + searchTextBox = new SearchTextBox { RelativeSizeAxes = Axes.X }, + new GroupSortTabs() + } + } + }; + + searchTextBox.OnChange += (TextBox sender, bool newText) => + { + if (newText) + FilterChanged?.Invoke(); + }; + searchTextBox.Exit = () => Exit?.Invoke(); + } + + public void Deactivate() + { + searchTextBox.GrabFocus = false; + searchTextBox.TriggerFocusLost(); + } + + public void Activate() + { + searchTextBox.GrabFocus = true; + } + + private class TabItem : ClickableContainer + { + public string Text + { + get { return text.Text; } + set { text.Text = value; } + } + + private void FadeActive() + { + box.FadeIn(300); + text.FadeColour(Color4.White, 300); + } + + private void FadeInactive() + { + box.FadeOut(300); + text.FadeColour(fadeColour, 300); + } + + private bool active; + public bool Active + { + get { return active; } + set + { + active = value; + if (active) + FadeActive(); + else + FadeInactive(); + } + } + + private SpriteText text; + private Box box; + private Color4 fadeColour; + + protected override bool OnHover(InputState state) + { + if (!active) + FadeActive(); + return true; + } + + protected override void OnHoverLost(InputState state) + { + if (!active) + FadeInactive(); + } + + public TabItem() + { + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + text = new SpriteText + { + Margin = new MarginPadding(5), + TextSize = 14, + Font = @"Exo2.0-Bold", + }, + box = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Alpha = 0, + Colour = Color4.White, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.Blue; + fadeColour = colours.Blue; + } + } + + private class GroupSortTabs : Container + { + private TextAwesome groupsEllipsis, sortEllipsis; + private SpriteText sortLabel; + + public GroupSortTabs() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = OsuColour.Gray(80), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + }, + new FlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FlowDirection.HorizontalOnly, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new TabItem + { + Text = "All", + Active = true, + }, + new TabItem + { + Text = "Recently Played", + Active = false, + }, + new TabItem + { + Text = "Collections", + Active = false, + }, + groupsEllipsis = new TextAwesome + { + Icon = FontAwesome.fa_ellipsis_h, + TextSize = 14, + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + } + } + }, + new FlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FlowDirection.HorizontalOnly, + Spacing = new Vector2(10, 0), + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Children = new Drawable[] + { + sortLabel = new SpriteText + { + Font = @"Exo2.0-Bold", + Text = "Sort results by", + TextSize = 14, + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + }, + new TabItem + { + Text = "Artist", + Active = true, + }, + sortEllipsis = new TextAwesome + { + Icon = FontAwesome.fa_ellipsis_h, + TextSize = 14, + Margin = new MarginPadding { Top = 5, Bottom = 5 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + groupsEllipsis.Colour = colours.Blue; + sortLabel.Colour = colours.GreenLight; + sortEllipsis.Colour = colours.GreenLight; + } + } + + public enum SortMode + { + Arist, + BPM, + Creator, + DateAdded, + Difficulty, + Length, + RankAchieved, + Title + } + + public enum GroupMode + { + NoGrouping, + Arist, + BPM, + Creator, + DateAdded, + Difficulty, + Length, + RankAchieved, + Title, + Collections, + Favorites, + MyMaps, + RankedStatus, + RecentlyPlayed + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 69336dc013..1fa6d9d3a6 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -29,6 +29,9 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics; using osu.Framework.Input; using OpenTK.Input; +using osu.Game.Graphics; +using System.Collections.Generic; +using osu.Framework.Threading; namespace osu.Game.Screens.Select { @@ -50,6 +53,8 @@ namespace osu.Game.Screens.Select private AudioSample sampleChangeDifficulty; private AudioSample sampleChangeBeatmap; + + private List beatmapGroups; private Footer footer; @@ -82,10 +87,11 @@ namespace osu.Game.Screens.Select } Player player; + FilterControl filter; private void start() { - if (player != null) + if (player != null || Beatmap == null) return; //in the future we may want to move this logic to a PlayerLoader gamemode or similar, so we can rely on the SongSelect transition @@ -111,7 +117,8 @@ namespace osu.Game.Screens.Select OsuGame osuGame, OsuColour colours) { const float carouselWidth = 640; - + const float bottomToolHeight = 50; + beatmapGroups = new List(); Children = new Drawable[] { new ParallaxContainer @@ -134,6 +141,13 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }, + filter = new FilterControl + { + Position = wedged_container_start_position, + RelativeSizeAxes = Axes.X, + FilterChanged = filterChanged, + Exit = Exit, + }, beatmapInfoWedge = new BeatmapInfoWedge { Alpha = 0, @@ -174,9 +188,43 @@ namespace osu.Game.Screens.Select Task.Factory.StartNew(() => addBeatmapSets(game, initialAddSetsTask.Token), initialAddSetsTask.Token); } + private ScheduledDelegate filterTask; + + private void filterChanged() + { + filterTask?.Cancel(); + filterTask = Scheduler.AddDelayed(() => + { + filterTask = null; + var search = filter.Search; + BeatmapGroup newSelection = null; + foreach (var beatmapGroup in carousel) + { + var set = beatmapGroup.BeatmapSet; + bool match = string.IsNullOrEmpty(search) + || (set.Metadata.Artist ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.ArtistUnicode ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.Title ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.TitleUnicode ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1; + if (match) + { + beatmapGroup.State = BeatmapGroupState.Collapsed; + if (newSelection == null || beatmapGroup.BeatmapSet.OnlineBeatmapSetID == Beatmap.BeatmapSetInfo.OnlineBeatmapSetID) + newSelection = beatmapGroup; + } + else + { + beatmapGroup.State = BeatmapGroupState.Hidden; + } + } + if (newSelection != null) + carousel.SelectBeatmap(newSelection.BeatmapSet.Beatmaps[0], false); + }, 250); + } + private void onDatabaseOnBeatmapSetAdded(BeatmapSetInfo s) { - Schedule(() => addBeatmapSet(s, Game)); + Schedule(() => addBeatmapSet(s, Game, true)); } protected override void OnEntering(GameMode last) @@ -190,6 +238,8 @@ namespace osu.Game.Screens.Select beatmapInfoWedge.MoveToX(wedged_container_start_position.X - 50); beatmapInfoWedge.MoveToX(wedged_container_start_position.X, 800, EasingTypes.OutQuint); + + filter.Activate(); } protected override void OnResuming(GameMode last) @@ -203,6 +253,8 @@ namespace osu.Game.Screens.Select Content.FadeIn(250); Content.ScaleTo(1, 250, EasingTypes.OutSine); + + filter.Activate(); } protected override void OnSuspending(GameMode next) @@ -210,6 +262,8 @@ namespace osu.Game.Screens.Select Content.ScaleTo(1.1f, 250, EasingTypes.InSine); Content.FadeOut(250); + + filter.Deactivate(); base.OnSuspending(next); } @@ -219,6 +273,8 @@ namespace osu.Game.Screens.Select beatmapInfoWedge.RotateTo(10, 800, EasingTypes.InQuint); Content.FadeOut(100); + + filter.Deactivate(); return base.OnExiting(next); } @@ -239,18 +295,15 @@ namespace osu.Game.Screens.Select private void changeBackground(WorkingBeatmap beatmap) { - if (beatmap == null) - return; - var backgroundModeBeatmap = Background as BackgroundModeBeatmap; if (backgroundModeBeatmap != null) { backgroundModeBeatmap.Beatmap = beatmap; - // TODO: Remove this once we have non-nullable Beatmap - (Background as BackgroundModeBeatmap)?.BlurTo(BACKGROUND_BLUR, 1000); + backgroundModeBeatmap.BlurTo(BACKGROUND_BLUR, 1000); } - beatmapInfoWedge.UpdateBeatmap(beatmap); + if (beatmap != null) + beatmapInfoWedge.UpdateBeatmap(beatmap); } /// @@ -262,11 +315,9 @@ namespace osu.Game.Screens.Select //todo: change background in selectionChanged instead; support per-difficulty backgrounds. changeBackground(beatmap); - selectBeatmap(beatmap.BeatmapInfo); + carousel.SelectBeatmap(beatmap.BeatmapInfo); } - private void selectBeatmap(BeatmapInfo beatmap) => carousel.SelectBeatmap(beatmap); - /// /// selection has been changed as the result of interaction with the carousel. /// @@ -301,7 +352,7 @@ namespace osu.Game.Screens.Select } } - private void addBeatmapSet(BeatmapSetInfo beatmapSet, BaseGame game) + private void addBeatmapSet(BeatmapSetInfo beatmapSet, BaseGame game, bool select = false) { beatmapSet = database.GetWithChildren(beatmapSet.ID); beatmapSet.Beatmaps.ForEach(b => @@ -314,7 +365,7 @@ namespace osu.Game.Screens.Select var beatmap = new WorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault(), beatmapSet, database); - var group = new BeatmapGroup(beatmap) + var group = new BeatmapGroup(beatmap, beatmapSet) { SelectionChanged = selectionChanged, StartRequested = b => start() @@ -324,9 +375,11 @@ namespace osu.Game.Screens.Select //this likely won't scale so well, but allows us to completely async the loading flow. Task.WhenAll(group.BeatmapPanels.Select(panel => panel.Preload(game))).ContinueWith(task => Schedule(delegate { + beatmapGroups.Add(group); + carousel.AddGroup(group); - if (Beatmap == null) + if (Beatmap == null || select) carousel.SelectBeatmap(beatmapSet.Beatmaps.First()); else { diff --git a/osu.Game/Screens/Select/SearchTextBox.cs b/osu.Game/Screens/Select/SearchTextBox.cs new file mode 100644 index 0000000000..9428fe7f32 --- /dev/null +++ b/osu.Game/Screens/Select/SearchTextBox.cs @@ -0,0 +1,86 @@ +using System; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Select +{ + public class SearchTextBox : TextBox + { + protected override Color4 BackgroundUnfocused => new Color4(10, 10, 10, 255); + protected override Color4 BackgroundFocused => new Color4(10, 10, 10, 255); + public Action Exit; + public bool GrabFocus = false; + + private SpriteText placeholder; + + protected override string InternalText + { + get { return base.InternalText; } + set + { + base.InternalText = value; + if (placeholder != null) + { + if (string.IsNullOrEmpty(value)) + placeholder.Text = "type to search"; + else + placeholder.Text = string.Empty; + } + } + } + + public SearchTextBox() + { + Height = 35; + TextContainer.Padding = new MarginPadding(5); + Add(new Drawable[] + { + placeholder = new SpriteText + { + Font = @"Exo2.0-MediumItalic", + Text = "type to search", + Colour = new Color4(180, 180, 180, 255), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 10 }, + }, + new TextAwesome + { + Icon = FontAwesome.fa_search, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 }, + } + }); + } + + protected override void Update() + { + if (GrabFocus && !HasFocus && IsVisible) + TriggerFocus(); + base.Update(); + } + + protected override void OnFocusLost(InputState state) + { + if (state.Keyboard.Keys.Any(key => key == Key.Escape)) + Exit?.Invoke(); + base.OnFocusLost(state); + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Key == Key.Left || args.Key == Key.Right || args.Key == Key.Enter) + return false; + return base.OnKeyDown(state, args); + } + } +} \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7356889b18..bcf1427ef9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -235,6 +235,8 @@ + + @@ -272,4 +274,4 @@ --> - \ No newline at end of file +