Merge remote-tracking branch 'origin/master' into audio-refactor-update

This commit is contained in:
smoogipoo
2019-06-04 10:50:33 +09:00
25 changed files with 1396 additions and 22 deletions

View File

@ -0,0 +1,167 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using osuTK.Graphics;
namespace osu.Game.Overlays.Changelog
{
public class ChangelogBuild : FillFlowContainer
{
public const float HORIZONTAL_PADDING = 70;
public Action<APIChangelogBuild> SelectBuild;
protected readonly APIChangelogBuild Build;
public readonly FillFlowContainer ChangelogEntries;
public ChangelogBuild(APIChangelogBuild build)
{
Build = build;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Direction = FillDirection.Vertical;
Padding = new MarginPadding { Horizontal = HORIZONTAL_PADDING };
Children = new Drawable[]
{
CreateHeader(),
ChangelogEntries = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
},
};
foreach (var categoryEntries in build.ChangelogEntries.GroupBy(b => b.Category).OrderBy(c => c.Key))
{
ChangelogEntries.Add(new OsuSpriteText
{
Text = categoryEntries.Key,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 24),
Margin = new MarginPadding { Top = 35, Bottom = 15 },
});
var fontLarge = OsuFont.GetFont(size: 18);
var fontMedium = OsuFont.GetFont(size: 14);
var fontSmall = OsuFont.GetFont(size: 12);
foreach (APIChangelogEntry entry in categoryEntries)
{
LinkFlowContainer title = new LinkFlowContainer
{
Direction = FillDirection.Full,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Vertical = 5 },
};
title.AddIcon(FontAwesome.Solid.Check, t =>
{
t.Font = fontSmall;
t.Padding = new MarginPadding { Left = -17, Right = 5 };
});
title.AddText(entry.Title, t => { t.Font = fontLarge; });
if (!string.IsNullOrEmpty(entry.Repository))
{
title.AddText(" (", t => t.Font = fontLarge);
title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External,
creationParameters: t => { t.Font = fontLarge; });
title.AddText(")", t => t.Font = fontLarge);
}
title.AddText(" by ", t => t.Font = fontMedium);
if (entry.GithubUser.UserId != null)
title.AddUserLink(new User
{
Username = entry.GithubUser.OsuUsername,
Id = entry.GithubUser.UserId.Value
}, t => t.Font = fontMedium);
else if (entry.GithubUser.GithubUrl != null)
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t => t.Font = fontMedium);
else
title.AddText(entry.GithubUser.DisplayName, t => t.Font = fontSmall);
ChangelogEntries.Add(title);
if (!string.IsNullOrEmpty(entry.MessageHtml))
{
TextFlowContainer message = new TextFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
};
// todo: use markdown parsing once API returns markdown
message.AddText(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty), t =>
{
t.Font = fontSmall;
t.Colour = new Color4(235, 184, 254, 255);
});
ChangelogEntries.Add(message);
}
}
}
}
protected virtual FillFlowContainer CreateHeader() => new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Top = 20 },
Children = new Drawable[]
{
new OsuHoverContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Action = () => SelectBuild?.Invoke(Build),
Child = new FillFlowContainer<SpriteText>
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Horizontal = 40 },
Children = new[]
{
new OsuSpriteText
{
Text = Build.UpdateStream.DisplayName,
Font = OsuFont.GetFont(weight: FontWeight.Medium, size: 19),
},
new OsuSpriteText
{
Text = " ",
Font = OsuFont.GetFont(weight: FontWeight.Medium, size: 19),
},
new OsuSpriteText
{
Text = Build.DisplayVersion,
Font = OsuFont.GetFont(weight: FontWeight.Light, size: 19),
Colour = Build.UpdateStream.Colour,
},
}
}
},
}
};
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using System;
namespace osu.Game.Overlays.Changelog
{
public class ChangelogContent : FillFlowContainer
{
public Action<APIChangelogBuild> BuildSelected;
public void SelectBuild(APIChangelogBuild build) => BuildSelected?.Invoke(build);
public ChangelogContent()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Direction = FillDirection.Vertical;
Padding = new MarginPadding { Bottom = 100 };
}
}
}

View File

@ -0,0 +1,170 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
namespace osu.Game.Overlays.Changelog
{
public class ChangelogHeader : OverlayHeader
{
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
public Action ListingSelected;
public UpdateStreamBadgeArea Streams;
private const string listing_string = "Listing";
public ChangelogHeader()
{
TabControl.AddItem(listing_string);
TabControl.Current.ValueChanged += e =>
{
if (e.NewValue == listing_string)
ListingSelected?.Invoke();
};
Current.ValueChanged += showBuild;
Streams.Current.ValueChanged += e =>
{
if (e.NewValue?.LatestBuild != null && e.NewValue != Current.Value?.UpdateStream)
Current.Value = e.NewValue.LatestBuild;
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
TabControl.AccentColour = colours.Violet;
}
private ChangelogHeaderTitle title;
private void showBuild(ValueChangedEvent<APIChangelogBuild> e)
{
if (e.OldValue != null)
TabControl.RemoveItem(e.OldValue.ToString());
if (e.NewValue != null)
{
TabControl.AddItem(e.NewValue.ToString());
TabControl.Current.Value = e.NewValue.ToString();
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == e.NewValue.UpdateStream.Name);
title.Version = e.NewValue.UpdateStream.DisplayName;
}
else
{
TabControl.Current.Value = listing_string;
Streams.Current.Value = null;
title.Version = null;
}
}
protected override Drawable CreateBackground() => new HeaderBackground();
protected override Drawable CreateContent() => new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
Streams = new UpdateStreamBadgeArea(),
}
};
protected override ScreenTitle CreateTitle() => title = new ChangelogHeaderTitle();
public class HeaderBackground : Sprite
{
public HeaderBackground()
{
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fill;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get(@"Headers/changelog");
}
}
private class ChangelogHeaderTitle : ScreenTitle
{
public string Version
{
set => Section = value ?? listing_string;
}
public ChangelogHeaderTitle()
{
Title = "Changelog";
Version = null;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Violet;
}
protected override Drawable CreateIcon() => new ChangelogIcon();
internal class ChangelogIcon : CompositeDrawable
{
private const float circle_allowance = 0.8f;
[BackgroundDependencyLoader]
private void load(TextureStore textures, OsuColour colours)
{
Size = new Vector2(ICON_SIZE / circle_allowance);
InternalChildren = new Drawable[]
{
new CircularContainer
{
Masking = true,
BorderColour = colours.Violet,
BorderThickness = 3,
MaskingSmoothness = 1,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Sprite
{
RelativeSizeAxes = Axes.Both,
Texture = textures.Get(@"Icons/changelog"),
Size = new Vector2(circle_allowance),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Violet,
Alpha = 0,
AlwaysPresent = true,
},
}
},
};
}
}
}
}
}

View File

@ -0,0 +1,80 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osuTK.Graphics;
namespace osu.Game.Overlays.Changelog
{
public class ChangelogListing : ChangelogContent
{
private readonly List<APIChangelogBuild> entries;
public ChangelogListing(List<APIChangelogBuild> entries)
{
this.entries = entries;
}
[BackgroundDependencyLoader]
private void load()
{
DateTime currentDate = DateTime.MinValue;
if (entries == null) return;
foreach (APIChangelogBuild build in entries)
{
if (build.CreatedAt.Date != currentDate)
{
if (Children.Count != 0)
{
Add(new Box
{
RelativeSizeAxes = Axes.X,
Height = 2,
Colour = new Color4(17, 17, 17, 255),
Margin = new MarginPadding { Top = 30 },
});
}
Add(new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Top = 15 },
Text = build.CreatedAt.Date.ToString("dd MMM yyyy"),
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 24),
Colour = OsuColour.FromHex(@"FD5"),
});
currentDate = build.CreatedAt.Date;
}
else
{
Add(new Container
{
RelativeSizeAxes = Axes.X,
Height = 1,
Padding = new MarginPadding { Horizontal = ChangelogBuild.HORIZONTAL_PADDING },
Margin = new MarginPadding { Top = 30 },
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = new Color4(32, 24, 35, 255),
}
});
}
Add(new ChangelogBuild(build) { SelectBuild = SelectBuild });
}
}
}
}

View File

@ -0,0 +1,142 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
namespace osu.Game.Overlays.Changelog
{
public class ChangelogSingleBuild : ChangelogContent
{
private APIChangelogBuild build;
public ChangelogSingleBuild(APIChangelogBuild build)
{
this.build = build;
}
[BackgroundDependencyLoader]
private void load(CancellationToken? cancellation, IAPIProvider api)
{
bool complete = false;
var req = new GetChangelogBuildRequest(build.UpdateStream.Name, build.Version);
req.Success += res =>
{
build = res;
complete = true;
};
req.Failure += _ => complete = true;
// This is done on a separate thread to support cancellation below
Task.Run(() => req.Perform(api));
while (!complete)
{
if (cancellation?.IsCancellationRequested == true)
{
req.Cancel();
return;
}
Thread.Sleep(10);
}
if (build != null)
Child = new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild };
}
public class ChangelogBuildWithNavigation : ChangelogBuild
{
public ChangelogBuildWithNavigation(APIChangelogBuild build)
: base(build)
{
}
protected override FillFlowContainer CreateHeader()
{
var fill = base.CreateHeader();
foreach (var existing in fill.Children.OfType<OsuHoverContainer>())
{
existing.Scale = new Vector2(1.25f);
existing.Action = null;
existing.Add(new OsuSpriteText
{
Text = Build.CreatedAt.Date.ToString("dd MMM yyyy"),
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14),
Colour = OsuColour.FromHex(@"FD5"),
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Top = 5 },
});
}
NavigationIconButton left, right;
fill.AddRange(new[]
{
left = new NavigationIconButton(Build.Versions?.Previous)
{
Icon = FontAwesome.Solid.ChevronLeft,
SelectBuild = b => SelectBuild(b)
},
right = new NavigationIconButton(Build.Versions?.Next)
{
Icon = FontAwesome.Solid.ChevronRight,
SelectBuild = b => SelectBuild(b)
},
});
fill.SetLayoutPosition(left, -1);
fill.SetLayoutPosition(right, 1);
return fill;
}
}
private class NavigationIconButton : IconButton
{
public Action<APIChangelogBuild> SelectBuild;
public NavigationIconButton(APIChangelogBuild build)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
if (build == null) return;
TooltipText = build.DisplayVersion;
Action = () =>
{
SelectBuild?.Invoke(build);
Enabled.Value = false;
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
HoverColour = colours.GreyVioletLight.Opacity(0.6f);
FlashColour = colours.GreyVioletLighter;
}
}
}
}

View File

@ -0,0 +1,157 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Changelog
{
public class UpdateStreamBadge : TabItem<APIUpdateStream>
{
private const float badge_height = 66.5f;
private const float badge_width = 100;
private const float transition_duration = 100;
private readonly ExpandingBar expandingBar;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private readonly FillFlowContainer<SpriteText> text;
public readonly Bindable<APIUpdateStream> SelectedTab = new Bindable<APIUpdateStream>();
private readonly Container fadeContainer;
public UpdateStreamBadge(APIUpdateStream stream)
: base(stream)
{
Size = new Vector2(stream.IsFeatured ? badge_width * 2 : badge_width, badge_height);
Padding = new MarginPadding(5);
Child = fadeContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
text = new FillFlowContainer<SpriteText>
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new[]
{
new OsuSpriteText
{
Text = stream.DisplayName,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12),
Margin = new MarginPadding { Top = 6 },
},
new OsuSpriteText
{
Text = stream.LatestBuild.DisplayVersion,
Font = OsuFont.GetFont(weight: FontWeight.Light, size: 16),
},
new OsuSpriteText
{
Text = stream.LatestBuild.Users > 0 ? $"{stream.LatestBuild.Users:N0} {"user".Pluralize(stream.LatestBuild.Users == 1)} online" : null,
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 10),
Colour = new Color4(203, 164, 218, 255),
},
}
},
expandingBar = new ExpandingBar
{
Anchor = Anchor.TopCentre,
Colour = stream.Colour,
ExpandedSize = 4,
CollapsedSize = 2,
IsCollapsed = true
},
}
};
SelectedTab.BindValueChanged(_ => updateState(), true);
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleClick = audio.Sample.Get(@"UI/generic-select-soft");
sampleHover = audio.Sample.Get(@"UI/generic-hover-soft");
}
protected override void OnActivated() => updateState();
protected override void OnDeactivated() => updateState();
protected override bool OnClick(ClickEvent e)
{
sampleClick?.Play();
return base.OnClick(e);
}
protected override bool OnHover(HoverEvent e)
{
sampleHover?.Play();
updateState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateState();
base.OnHoverLost(e);
}
private void updateState()
{
// Expand based on the local state
bool shouldExpand = Active.Value || IsHovered;
// Expand based on whether no build is selected and the badge area is hovered
shouldExpand |= SelectedTab.Value == null && !externalDimRequested;
if (shouldExpand)
{
expandingBar.Expand();
fadeContainer.FadeTo(1, transition_duration);
}
else
{
expandingBar.Collapse();
fadeContainer.FadeTo(0.5f, transition_duration);
}
text.FadeTo(externalDimRequested && !IsHovered ? 0.5f : 1, transition_duration);
}
private bool externalDimRequested;
public void EnableDim()
{
externalDimRequested = true;
updateState();
}
public void DisableDim()
{
externalDimRequested = false;
updateState();
}
}
}

View File

@ -0,0 +1,75 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Online.API.Requests.Responses;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osuTK.Graphics;
namespace osu.Game.Overlays.Changelog
{
public class UpdateStreamBadgeArea : TabControl<APIUpdateStream>
{
public UpdateStreamBadgeArea()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
AddInternal(new Box
{
Colour = Color4.Black,
Alpha = 0.12f,
RelativeSizeAxes = Axes.Both,
});
}
public void Populate(List<APIUpdateStream> streams)
{
Current.Value = null;
foreach (APIUpdateStream updateStream in streams)
AddItem(updateStream);
}
protected override bool OnHover(HoverEvent e)
{
foreach (UpdateStreamBadge streamBadge in TabContainer.Children.OfType<UpdateStreamBadge>())
streamBadge.EnableDim();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
foreach (UpdateStreamBadge streamBadge in TabContainer.Children.OfType<UpdateStreamBadge>())
streamBadge.DisableDim();
base.OnHoverLost(e);
}
protected override TabFillFlowContainer CreateTabFlow()
{
var flow = base.CreateTabFlow();
flow.RelativeSizeAxes = Axes.X;
flow.AutoSizeAxes = Axes.Y;
flow.AllowMultiline = true;
flow.Padding = new MarginPadding
{
Vertical = 20,
Horizontal = 85,
};
return flow;
}
protected override Dropdown<APIUpdateStream> CreateDropdown() => null;
protected override TabItem<APIUpdateStream> CreateTabItem(APIUpdateStream value) =>
new UpdateStreamBadge(value) { SelectedTab = { BindTarget = Current } };
}
}

View File

@ -0,0 +1,210 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Changelog;
namespace osu.Game.Overlays
{
public class ChangelogOverlay : FullscreenOverlay
{
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
private ChangelogHeader header;
private Container<ChangelogContent> content;
private SampleChannel sampleBack;
private List<APIChangelogBuild> builds;
private List<APIUpdateStream> streams;
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuColour colour)
{
Waves.FirstWaveColour = colour.GreyVioletLight;
Waves.SecondWaveColour = colour.GreyViolet;
Waves.ThirdWaveColour = colour.GreyVioletDark;
Waves.FourthWaveColour = colour.GreyVioletDarker;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour.PurpleDarkAlternative,
},
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
Child = new ReverseChildIDFillFlowContainer<Drawable>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
header = new ChangelogHeader
{
ListingSelected = ShowListing,
},
content = new Container<ChangelogContent>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
},
},
},
};
sampleBack = audio.Sample.Get(@"UI/generic-select-soft");
header.Current.BindTo(Current);
Current.BindValueChanged(e =>
{
if (e.NewValue != null)
loadContent(new ChangelogSingleBuild(e.NewValue));
else
loadContent(new ChangelogListing(builds));
});
}
public void ShowListing()
{
Current.Value = null;
State = Visibility.Visible;
}
/// <summary>
/// Fetches and shows a specific build from a specific update stream.
/// </summary>
/// <param name="build">Must contain at least <see cref="APIUpdateStream.Name"/> and
/// <see cref="APIChangelogBuild.Version"/>. If <see cref="APIUpdateStream.DisplayName"/> and
/// <see cref="APIChangelogBuild.DisplayVersion"/> are specified, the header will instantly display them.</param>
public void ShowBuild([NotNull] APIChangelogBuild build)
{
if (build == null) throw new ArgumentNullException(nameof(build));
Current.Value = build;
State = Visibility.Visible;
}
public void ShowBuild([NotNull] string updateStream, [NotNull] string version)
{
if (updateStream == null) throw new ArgumentNullException(nameof(updateStream));
if (version == null) throw new ArgumentNullException(nameof(version));
performAfterFetch(() =>
{
var build = builds.Find(b => b.Version == version && b.UpdateStream.Name == updateStream)
?? streams.Find(s => s.Name == updateStream)?.LatestBuild;
if (build != null)
ShowBuild(build);
});
State = Visibility.Visible;
}
public override bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.Back:
if (Current.Value == null)
{
State = Visibility.Hidden;
}
else
{
Current.Value = null;
sampleBack?.Play();
}
return true;
}
return false;
}
protected override void PopIn()
{
base.PopIn();
if (initialFetchTask == null)
// fetch and refresh to show listing, if no other request was made via Show methods
performAfterFetch(() => Current.TriggerChange());
}
private Task initialFetchTask;
private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ => Schedule(action));
private Task fetchListing()
{
if (initialFetchTask != null)
return initialFetchTask;
return initialFetchTask = Task.Run(async () =>
{
var tcs = new TaskCompletionSource<bool>();
var req = new GetChangelogRequest();
req.Success += res =>
{
// remap streams to builds to ensure model equality
res.Builds.ForEach(b => b.UpdateStream = res.Streams.Find(s => s.Id == b.UpdateStream.Id));
res.Streams.ForEach(s => s.LatestBuild.UpdateStream = res.Streams.Find(s2 => s2.Id == s.LatestBuild.UpdateStream.Id));
builds = res.Builds;
streams = res.Streams;
header.Streams.Populate(res.Streams);
tcs.SetResult(true);
};
req.Failure += _ => initialFetchTask = null;
req.Perform(API);
await tcs.Task;
});
}
private CancellationTokenSource loadContentCancellation;
private void loadContent(ChangelogContent newContent)
{
content.FadeTo(0.2f, 300, Easing.OutQuint);
loadContentCancellation?.Cancel();
LoadComponentAsync(newContent, c =>
{
content.FadeIn(300, Easing.OutQuint);
c.BuildSelected = ShowBuild;
content.Child = c;
}, (loadContentCancellation = new CancellationTokenSource()).Token);
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Linq;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -14,6 +15,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Chat;
using osu.Game.Users;
using osuTK;
@ -201,6 +203,9 @@ namespace osu.Game.Overlays.Chat
private Action startChatAction;
[Resolved]
private IAPIProvider api { get; set; }
public MessageSender(User sender)
{
this.sender = sender;
@ -213,11 +218,21 @@ namespace osu.Game.Overlays.Chat
startChatAction = () => chatManager?.OpenPrivateChannel(sender);
}
public MenuItem[] ContextMenuItems => new MenuItem[]
public MenuItem[] ContextMenuItems
{
new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action),
new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction),
};
get
{
List<MenuItem> items = new List<MenuItem>
{
new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action)
};
if (sender.Id != api.LocalUser.Value.Id)
items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction));
return items.ToArray();
}
}
}
private static readonly Color4[] username_colours =

View File

@ -69,6 +69,7 @@ namespace osu.Game.Overlays.Toolbar
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new ToolbarChangelogButton(),
new ToolbarDirectButton(),
new ToolbarChatButton(),
new ToolbarSocialButton(),

View File

@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarChangelogButton : ToolbarOverlayToggleButton
{
public ToolbarChangelogButton()
{
SetIcon(FontAwesome.Solid.Bullhorn);
}
[BackgroundDependencyLoader(true)]
private void load(ChangelogOverlay changelog)
{
StateContainer = changelog;
}
}
}