diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs new file mode 100644 index 0000000000..f22983a6e6 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using OpenTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class ProgressBar : SliderBar + { + public Action OnSeek; + + private readonly Box fill; + private readonly Box background; + + public Color4 FillColour + { + set { fill.Colour = value; } + } + + public Color4 BackgroundColour + { + set + { + background.Alpha = 1; + background.Colour = value; + } + } + + public double EndTime + { + set { CurrentNumber.MaxValue = value; } + } + + public double CurrentTime + { + set { CurrentNumber.Value = value; } + } + + public ProgressBar() + { + CurrentNumber.MinValue = 0; + CurrentNumber.MaxValue = 1; + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + background = new Box + { + Alpha = 0, + RelativeSizeAxes = Axes.Both + }, + fill = new Box { RelativeSizeAxes = Axes.Y } + }; + } + + protected override void UpdateValue(float value) + { + fill.Width = value * UsableWidth; + } + + protected override void OnUserChange() => OnSeek?.Invoke(Current); + } +} \ No newline at end of file diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 23268a9ca9..307afb2d2b 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -11,11 +11,11 @@ namespace osu.Game.Online.API /// An API request with a well-defined response type. /// /// Type of the response (used for deserialisation). - public class APIRequest : APIRequest + public abstract class APIRequest : APIRequest { protected override WebRequest CreateWebRequest() => new JsonWebRequest(Uri); - public APIRequest() + protected APIRequest() { base.Success += onSuccess; } @@ -28,10 +28,36 @@ namespace osu.Game.Online.API public new event APISuccessHandler Success; } + public abstract class APIDownloadRequest : APIRequest + { + protected override WebRequest CreateWebRequest() + { + var request = new WebRequest(Uri); + request.DownloadProgress += request_Progress; + return request; + } + + private void request_Progress(WebRequest request, long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); }); + + protected APIDownloadRequest() + { + base.Success += onSuccess; + } + + private void onSuccess() + { + Success?.Invoke(WebRequest.ResponseData); + } + + public event APIProgressHandler Progress; + + public new event APISuccessHandler Success; + } + /// /// AN API request with no specified response type. /// - public class APIRequest + public abstract class APIRequest { /// /// The maximum amount of time before this request will fail. @@ -42,7 +68,7 @@ namespace osu.Game.Online.API protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri); - protected virtual string Uri => $@"{api.Endpoint}/api/v2/{Target}"; + protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}"; private double remainingTime => Math.Max(0, Timeout - (DateTime.Now.TotalMilliseconds() - (startTime ?? 0))); @@ -52,7 +78,7 @@ namespace osu.Game.Online.API public double StartTime => startTime ?? -1; - private APIAccess api; + protected APIAccess API; protected WebRequest WebRequest; public event APISuccessHandler Success; @@ -64,7 +90,7 @@ namespace osu.Game.Online.API public void Perform(APIAccess api) { - this.api = api; + API = api; if (checkAndProcessFailure()) return; @@ -109,9 +135,9 @@ namespace osu.Game.Online.API /// Whether we are in a failed or cancelled state. private bool checkAndProcessFailure() { - if (api == null || pendingFailure == null) return cancelled; + if (API == null || pendingFailure == null) return cancelled; - api.Scheduler.Add(pendingFailure); + API.Scheduler.Add(pendingFailure); pendingFailure = null; return true; } @@ -119,5 +145,6 @@ namespace osu.Game.Online.API public delegate void APIFailureHandler(Exception e); public delegate void APISuccessHandler(); + public delegate void APIProgressHandler(long current, long total); public delegate void APISuccessHandler(T content); } diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs index ca984d3511..e4763f73ee 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs @@ -46,6 +46,9 @@ namespace osu.Game.Online.API.Requests [JsonProperty(@"favourite_count")] private int favouriteCount { get; set; } + [JsonProperty(@"id")] + private int onlineId { get; set; } + [JsonProperty(@"beatmaps")] private IEnumerable beatmaps { get; set; } @@ -53,6 +56,7 @@ namespace osu.Game.Online.API.Requests { return new BeatmapSetInfo { + OnlineBeatmapSetID = onlineId, Metadata = this, OnlineInfo = new BeatmapSetOnlineInfo { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8c82071c23..43e3731166 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -219,6 +219,7 @@ namespace osu.Game dependencies.Cache(settings); dependencies.Cache(social); + dependencies.Cache(direct); dependencies.Cache(chat); dependencies.Cache(userProfile); dependencies.Cache(musicController); diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 98ab5e88f8..3a9e75bd38 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Framework.Input; namespace osu.Game.Overlays.Direct { @@ -25,23 +26,11 @@ namespace osu.Game.Overlays.Direct public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) { Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image) - CornerRadius = 4; - Masking = true; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 3f, - Colour = Color4.Black.Opacity(0.25f), - }; } protected override void LoadComplete() { base.LoadComplete(); - - this.FadeInFromZero(200, Easing.Out); bottomPanel.LayoutDuration = 200; bottomPanel.LayoutEasing = Easing.Out; bottomPanel.Origin = Anchor.BottomLeft; @@ -50,14 +39,10 @@ namespace osu.Game.Overlays.Direct [BackgroundDependencyLoader] private void load(OsuColour colours, LocalisationEngine localisation) { - Children = new[] + Content.CornerRadius = 4; + + AddRange(new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - CreateBackground(), new Box { RelativeSizeAxes = Axes.Both, @@ -185,7 +170,13 @@ namespace osu.Game.Overlays.Direct new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, - }; + }); + } + + protected override bool OnClick(InputState state) + { + StartDownload(); + return true; } } } diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 5b45fc7725..b3502b0827 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -28,35 +28,15 @@ namespace osu.Game.Overlays.Direct { RelativeSizeAxes = Axes.X; Height = height; - CornerRadius = 5; - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 3f, - Colour = Color4.Black.Opacity(0.25f), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - this.FadeInFromZero(200, Easing.Out); } [BackgroundDependencyLoader] private void load(LocalisationEngine localisation) { - Children = new[] + Content.CornerRadius = 5; + + AddRange(new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - CreateBackground(), new Box { RelativeSizeAxes = Axes.Both, @@ -144,10 +124,11 @@ namespace osu.Game.Overlays.Direct Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Size = new Vector2(height - vertical_padding * 2), + Action = StartDownload }, }, }, - }; + }); } private class DownloadButton : OsuClickableContainer diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 2f048b0e3d..bc30fb83f5 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -2,14 +2,28 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using System.IO; +using System.Threading.Tasks; using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transforms; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Framework.Logging; +using osu.Game.Beatmaps.IO; +using osu.Game.Overlays.Notifications; namespace osu.Game.Overlays.Direct { @@ -17,11 +31,168 @@ namespace osu.Game.Overlays.Direct { protected readonly BeatmapSetInfo SetInfo; + protected Box BlackBackground; + + private const double hover_transition_time = 400; + + private Container content; + + private APIAccess api; + private ProgressBar progressBar; + private BeatmapManager beatmaps; + private NotificationOverlay notifications; + + protected override Container Content => content; + protected DirectPanel(BeatmapSetInfo setInfo) { SetInfo = setInfo; } + [BackgroundDependencyLoader(permitNulls: true)] + private void load(APIAccess api, BeatmapManager beatmaps, OsuColour colours, NotificationOverlay notifications) + { + this.api = api; + this.beatmaps = beatmaps; + this.notifications = notifications; + + AddInternal(content = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 1f), + Radius = 2f, + Colour = Color4.Black.Opacity(0.25f), + }, + Children = new[] + { + // temporary blackness until the actual background loads. + BlackBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + CreateBackground(), + progressBar = new ProgressBar + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = 0, + Alpha = 0, + BackgroundColour = Color4.Black.Opacity(0.7f), + FillColour = colours.Blue, + Depth = -1, + }, + } + }); + } + + protected override bool OnHover(InputState state) + { + content.FadeEdgeEffectTo(1f, hover_transition_time, Easing.OutQuint); + content.TransformTo(content.PopulateTransform(new TransformEdgeEffectRadius(), 14, hover_transition_time, Easing.OutQuint)); + content.MoveToY(-4, hover_transition_time, Easing.OutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + content.FadeEdgeEffectTo(0.25f, hover_transition_time, Easing.OutQuint); + content.TransformTo(content.PopulateTransform(new TransformEdgeEffectRadius(), 2, hover_transition_time, Easing.OutQuint)); + content.MoveToY(0, hover_transition_time, Easing.OutQuint); + + base.OnHoverLost(state); + } + + protected void StartDownload() + { + if (api == null) return; + + if (!api.LocalUser.Value.IsSupporter) + { + notifications.Post(new SimpleNotification + { + Icon = FontAwesome.fa_superpowers, + Text = "You gotta be a supporter to download for now 'yo" + }); + return; + } + + progressBar.FadeIn(400, Easing.OutQuint); + progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); + + progressBar.Current.Value = 0; + + ProgressNotification downloadNotification = new ProgressNotification + { + Text = $"Downloading {SetInfo.Metadata.Artist} - {SetInfo.Metadata.Title}", + }; + + var request = new DownloadBeatmapSetRequest(SetInfo); + request.Failure += e => + { + progressBar.Current.Value = 0; + progressBar.FadeOut(500); + downloadNotification.State = ProgressNotificationState.Completed; + Logger.Error(e, "Failed to get beatmap download information"); + }; + + request.Progress += (current, total) => + { + float progress = (float)current / total; + + progressBar.Current.Value = progress; + + downloadNotification.State = ProgressNotificationState.Active; + downloadNotification.Progress = progress; + }; + + request.Success += data => + { + progressBar.Current.Value = 1; + progressBar.FadeOut(500); + + downloadNotification.State = ProgressNotificationState.Completed; + + using (var stream = new MemoryStream(data)) + using (var archive = new OszArchiveReader(stream)) + beatmaps.Import(archive); + }; + + downloadNotification.CancelRequested += () => + { + request.Cancel(); + return true; + }; + + notifications.Post(downloadNotification); + + // don't run in the main api queue as this is a long-running task. + Task.Run(() => request.Perform(api)); + } + + public class DownloadBeatmapSetRequest : APIDownloadRequest + { + private readonly BeatmapSetInfo beatmapSet; + + public DownloadBeatmapSetRequest(BeatmapSetInfo beatmapSet) + { + this.beatmapSet = beatmapSet; + } + + protected override string Target => $@"beatmapsets/{beatmapSet.OnlineBeatmapSetID}/download"; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(200, Easing.Out); + } + protected List GetDifficultyIcons() { var icons = new List(); @@ -38,7 +209,11 @@ namespace osu.Game.Overlays.Direct Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), + OnLoadComplete = d => + { + d.FadeInFromZero(400, Easing.Out); + BlackBackground.Delay(400).FadeOut(); + }, }) { RelativeSizeAxes = Axes.Both, @@ -87,5 +262,30 @@ namespace osu.Game.Overlays.Direct Value = value; } } + + private class TransformEdgeEffectRadius : Transform + { + /// + /// Current value of the transformed colour in linear colour space. + /// + private float valueAt(double time) + { + if (time < StartTime) return StartValue; + if (time >= EndTime) return EndValue; + + return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + } + + public override string TargetMember => "EdgeEffect.Colour"; + + protected override void Apply(Container c, double time) + { + EdgeEffectParameters e = c.EdgeEffect; + e.Radius = valueAt(time); + c.EdgeEffect = e; + } + + protected override void ReadIntoStartValue(Container d) => StartValue = d.EdgeEffect.Radius; + } } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d970089942..f2c90f009a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Localisation; using osu.Framework.Threading; @@ -434,49 +433,5 @@ namespace osu.Game.Overlays sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); } } - - private class ProgressBar : SliderBar - { - public Action OnSeek; - - private readonly Box fill; - - public Color4 FillColour - { - set { fill.Colour = value; } - } - - public double EndTime - { - set { CurrentNumber.MaxValue = value; } - } - - public double CurrentTime - { - set { CurrentNumber.Value = value; } - } - - public ProgressBar() - { - CurrentNumber.MinValue = 0; - CurrentNumber.MaxValue = 1; - RelativeSizeAxes = Axes.X; - - Children = new Drawable[] - { - fill = new Box - { - RelativeSizeAxes = Axes.Y - } - }; - } - - protected override void UpdateValue(float value) - { - fill.Width = value * UsableWidth; - } - - protected override void OnUserChange() => OnSeek?.Invoke(Current); - } } } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index f42b4b6cb3..a31291e1b8 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -152,11 +152,14 @@ namespace osu.Game.Overlays.Notifications break; case ProgressNotificationState.Active: case ProgressNotificationState.Queued: - State = ProgressNotificationState.Cancelled; + if (CancelRequested?.Invoke() != false) + State = ProgressNotificationState.Cancelled; break; } } + public Func CancelRequested { get; set; } + /// /// The function to post completion notifications back to. /// diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 32344ca7eb..e36db1f9da 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Toolbar AutoSizeAxes = Axes.X, Children = new Drawable[] { + new ToolbarDirectButton(), new ToolbarChatButton(), new ToolbarSocialButton(), new ToolbarMusicButton(), diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs new file mode 100644 index 0000000000..32e149b31a --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.Toolbar +{ + internal class ToolbarDirectButton : ToolbarOverlayToggleButton + { + public ToolbarDirectButton() + { + SetIcon(FontAwesome.fa_download); + } + + [BackgroundDependencyLoader] + private void load(DirectOverlay direct) + { + StateContainer = direct; + } + } +} \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4e9e769cb7..97cf1db803 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -116,6 +116,7 @@ + @@ -129,6 +130,7 @@ +