From d6d15a2ff1b02d28a2c6e9ed7177bee1969667e8 Mon Sep 17 00:00:00 2001 From: flustix Date: Thu, 25 Jul 2024 00:15:56 +0200 Subject: [PATCH] Implement club overlay --- fluXis.Game.Tests/Overlay/TestClubOverlay.cs | 18 ++ fluXis.Game/FluXisGame.cs | 2 + fluXis.Game/Graphics/Sprites/FontAwesome6.cs | 2 + .../UserInterface/Tabs/TabContainer.cs | 17 ++ .../Graphics/UserInterface/Tabs/TabControl.cs | 132 ++++++++++ .../Online/API/Requests/Clubs/ClubRequest.cs | 15 ++ fluXis.Game/Overlay/Club/ClubOverlay.cs | 228 ++++++++++++++++++ .../Club/Sidebar/ClubSidebarActivity.cs | 42 ++++ .../Overlay/Club/Sidebar/ClubSidebarStats.cs | 85 +++++++ .../Overlay/Club/Tabs/ClubMembersTab.cs | 38 +++ .../Overlay/Club/Tabs/ClubScoresTab.cs | 44 ++++ .../Club/Tabs/Members/ClubMemberEntry.cs | 137 +++++++++++ fluXis.Game/Overlay/Club/UI/ClubHeader.cs | 115 +++++++++ .../User/Sidebar/ProfileSidebarClub.cs | 9 +- 14 files changed, 881 insertions(+), 3 deletions(-) create mode 100644 fluXis.Game.Tests/Overlay/TestClubOverlay.cs create mode 100644 fluXis.Game/Graphics/UserInterface/Tabs/TabContainer.cs create mode 100644 fluXis.Game/Graphics/UserInterface/Tabs/TabControl.cs create mode 100644 fluXis.Game/Online/API/Requests/Clubs/ClubRequest.cs create mode 100644 fluXis.Game/Overlay/Club/ClubOverlay.cs create mode 100644 fluXis.Game/Overlay/Club/Sidebar/ClubSidebarActivity.cs create mode 100644 fluXis.Game/Overlay/Club/Sidebar/ClubSidebarStats.cs create mode 100644 fluXis.Game/Overlay/Club/Tabs/ClubMembersTab.cs create mode 100644 fluXis.Game/Overlay/Club/Tabs/ClubScoresTab.cs create mode 100644 fluXis.Game/Overlay/Club/Tabs/Members/ClubMemberEntry.cs create mode 100644 fluXis.Game/Overlay/Club/UI/ClubHeader.cs diff --git a/fluXis.Game.Tests/Overlay/TestClubOverlay.cs b/fluXis.Game.Tests/Overlay/TestClubOverlay.cs new file mode 100644 index 00000000..bddb7e49 --- /dev/null +++ b/fluXis.Game.Tests/Overlay/TestClubOverlay.cs @@ -0,0 +1,18 @@ +using fluXis.Game.Overlay.Club; +using osu.Framework.Allocation; + +namespace fluXis.Game.Tests.Overlay; + +public partial class TestClubOverlay : FluXisTestScene +{ + [BackgroundDependencyLoader] + private void load() + { + var overlay = new ClubOverlay(); + Add(overlay); + + AddStep("Show Club 1", () => overlay.ShowClub(1)); + AddStep("Show Club 2", () => overlay.ShowClub(2)); + AddStep("Hide", overlay.Hide); + } +} diff --git a/fluXis.Game/FluXisGame.cs b/fluXis.Game/FluXisGame.cs index a3e3e32f..fa1de387 100644 --- a/fluXis.Game/FluXisGame.cs +++ b/fluXis.Game/FluXisGame.cs @@ -14,6 +14,7 @@ using fluXis.Game.Overlay.Achievements; using fluXis.Game.Overlay.Auth; using fluXis.Game.Overlay.Chat; +using fluXis.Game.Overlay.Club; using fluXis.Game.Overlay.Exit; using fluXis.Game.Overlay.FPS; using fluXis.Game.Overlay.Music; @@ -121,6 +122,7 @@ private void load() loadComponent(dashboard = new Dashboard(), overlayContainer.Add, true); loadComponent(new ChatOverlay(), overlayContainer.Add, true); loadComponent(userProfileOverlay = new UserProfileOverlay(), overlayContainer.Add, true); + loadComponent(new ClubOverlay(), overlayContainer.Add, true); loadComponent(new MusicPlayer(), overlayContainer.Add, true); loadComponent(new SettingsMenu(), overlayContainer.Add, true); diff --git a/fluXis.Game/Graphics/Sprites/FontAwesome6.cs b/fluXis.Game/Graphics/Sprites/FontAwesome6.cs index 2af24f94..e1124d59 100644 --- a/fluXis.Game/Graphics/Sprites/FontAwesome6.cs +++ b/fluXis.Game/Graphics/Sprites/FontAwesome6.cs @@ -17,6 +17,7 @@ public static class Solid public static IconUsage ArrowPointer => getSolid(0xf245); public static IconUsage ArrowRight => getSolid(0xf061); public static IconUsage ArrowRightArrowLeft => getSolid(0xf0ec); + public static IconUsage ArrowTrendUp => getSolid(0xe098); public static IconUsage ArrowsRotate => getSolid(0xf021); public static IconUsage BackwardStep => getSolid(0xf048); public static IconUsage Bars => getSolid(0xf0c9); @@ -37,6 +38,7 @@ public static class Solid public static IconUsage Clone => getSolid(0xf24d); public static IconUsage ComputerMouse => getSolid(0xf8cc); public static IconUsage Copy => getSolid(0xf0c5); + public static IconUsage Crown => getSolid(0xf521); public static IconUsage Cube => getSolid(0xf1b2); public static IconUsage Cubes => getSolid(0xf1b3); public static IconUsage Cut => getSolid(0xf0c4); diff --git a/fluXis.Game/Graphics/UserInterface/Tabs/TabContainer.cs b/fluXis.Game/Graphics/UserInterface/Tabs/TabContainer.cs new file mode 100644 index 00000000..e9d214a9 --- /dev/null +++ b/fluXis.Game/Graphics/UserInterface/Tabs/TabContainer.cs @@ -0,0 +1,17 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; + +namespace fluXis.Game.Graphics.UserInterface.Tabs; + +public abstract partial class TabContainer : Container +{ + public abstract IconUsage Icon { get; } + public abstract string Title { get; } + + protected TabContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } +} diff --git a/fluXis.Game/Graphics/UserInterface/Tabs/TabControl.cs b/fluXis.Game/Graphics/UserInterface/Tabs/TabControl.cs new file mode 100644 index 00000000..c48f4644 --- /dev/null +++ b/fluXis.Game/Graphics/UserInterface/Tabs/TabControl.cs @@ -0,0 +1,132 @@ +using System; +using System.Linq; +using fluXis.Game.Graphics.Sprites; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osuTK; + +namespace fluXis.Game.Graphics.UserInterface.Tabs; + +public partial class TabControl : CompositeDrawable +{ + public TabContainer[] Tabs { get; init; } = Array.Empty(); + + private Bindable current; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + if (Tabs.Length <= 0) + throw new ArgumentException($"{nameof(Tabs)} must have 1 or more children."); + + current = new Bindable(Tabs.First()); + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(16), + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(24), + ChildrenEnumerable = Tabs.Select(t => new TabControlItem(t, current)) + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = Tabs + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + current.BindValueChanged(currentChanged, true); + FinishTransforms(true); + } + + private void currentChanged(ValueChangedEvent e) + { + foreach (var tab in Tabs) + { + if (tab != e.NewValue) + tab.FadeOut(150); + } + + e.NewValue.Delay(100).FadeIn(150); + } + + private partial class TabControlItem : FillFlowContainer + { + private TabContainer container { get; } + private Bindable current { get; } + + public TabControlItem(TabContainer container, Bindable current) + { + this.container = container; + this.current = current; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(8); + + InternalChildren = new Drawable[] + { + new SpriteIcon + { + Size = new Vector2(20), + Icon = container.Icon, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new FluXisSpriteText + { + Text = container.Title, + WebFontSize = 14, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + current.BindValueChanged(currentChanged, true); + FinishTransforms(); + } + + private void currentChanged(ValueChangedEvent e) + { + this.FadeTo(e.NewValue == container ? 1 : .6f, 150); + } + + protected override bool OnClick(ClickEvent e) + { + current.Value = container; + return true; + } + } +} diff --git a/fluXis.Game/Online/API/Requests/Clubs/ClubRequest.cs b/fluXis.Game/Online/API/Requests/Clubs/ClubRequest.cs new file mode 100644 index 00000000..ef14b0c9 --- /dev/null +++ b/fluXis.Game/Online/API/Requests/Clubs/ClubRequest.cs @@ -0,0 +1,15 @@ +using fluXis.Shared.Components.Clubs; + +namespace fluXis.Game.Online.API.Requests.Clubs; + +public class ClubRequest : APIRequest +{ + protected override string Path => $"/club/{id}"; + + private long id { get; } + + public ClubRequest(long id) + { + this.id = id; + } +} diff --git a/fluXis.Game/Overlay/Club/ClubOverlay.cs b/fluXis.Game/Overlay/Club/ClubOverlay.cs new file mode 100644 index 00000000..2dab1783 --- /dev/null +++ b/fluXis.Game/Overlay/Club/ClubOverlay.cs @@ -0,0 +1,228 @@ +using fluXis.Game.Graphics; +using fluXis.Game.Graphics.Containers; +using fluXis.Game.Graphics.UserInterface; +using fluXis.Game.Graphics.UserInterface.Color; +using fluXis.Game.Graphics.UserInterface.Tabs; +using fluXis.Game.Input; +using fluXis.Game.Online.API.Requests.Clubs; +using fluXis.Game.Online.Fluxel; +using fluXis.Game.Overlay.Club.Sidebar; +using fluXis.Game.Overlay.Club.Tabs; +using fluXis.Game.Overlay.Club.UI; +using fluXis.Shared.Components.Clubs; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osuTK; + +namespace fluXis.Game.Overlay.Club; + +public partial class ClubOverlay : OverlayContainer, IKeyBindingHandler +{ + [Resolved] + private IAPIClient api { get; set; } + + private APIClub club; + private Container content; + private FluXisScrollContainer scroll; + private FillFlowContainer flow; + private LoadingIcon loading; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new FullInputBlockingContainer + { + RelativeSizeAxes = Axes.Both, + Child = new ClickableContainer + { + RelativeSizeAxes = Axes.Both, + Action = Hide, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Black, + Alpha = .5f + } + } + }, + new Container + { + Width = 1320, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = content = new ClickableContainer + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Masking = true, + EdgeEffect = FluXisStyles.ShadowLargeNoOffset, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = FluXisColors.Background1 + }, + scroll = new FluXisScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AlwaysPresent = true, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 70, Bottom = 36, Horizontal = 20 }, + Spacing = new Vector2(32) + } + }, + loading = new LoadingIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(50), + Alpha = 0 + } + } + } + } + }; + } + + public void ShowClub(long id) + { + var visible = State.Value == Visibility.Visible; + Show(); + + if (club?.ID != id) + fetch(id, visible); + } + + private async void fetch(long id, bool wasVisible) + { + if (wasVisible) + { + Schedule(() => flow.FadeOut(200).OnComplete(_ => fetch(id, false))); + return; + } + + Schedule(() => + { + flow.Clear(); + loading.Show(); + }); + + var request = new ClubRequest(id); + await api.PerformRequestAsync(request); + + // we might want to show a message if the request fails + if (!request.IsSuccessful) + return; + + club = request.Response!.Data; + Schedule(() => + { + displayData(club); + loading.Hide(); + }); + } + + private void displayData(APIClub club) + { + flow.Show(); + flow.Children = new Drawable[] + { + new ClubHeader(club), + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 16 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 32), + new Dimension(GridSizeMode.Absolute, 300), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new TabControl + { + RelativeSizeAxes = Axes.X, + Tabs = new TabContainer[] + { + new ClubMembersTab(club), + new ClubScoresTab(), + } + }, + Empty(), + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(28), + Children = new Drawable[] + { + new ClubSidebarStats(club), + new ClubSidebarActivity(club), + } + } + } + } + } + } + }; + } + + protected override void PopIn() + { + content.ResizeHeightTo(0).MoveToY(1) + .ResizeHeightTo(1, 800, Easing.OutQuint) + .MoveToY(0, 800, Easing.OutQuint); + + scroll.ScrollTo(0, false); + scroll.FadeOut().Delay(400).FadeIn(200); + this.FadeIn(200); + } + + protected override void PopOut() + { + content.ResizeHeightTo(0, 800, Easing.OutQuint); + this.FadeOut(200); + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action != FluXisGlobalKeybind.Back) + return false; + + Hide(); + return true; + } + + public void OnReleased(KeyBindingReleaseEvent e) { } +} + diff --git a/fluXis.Game/Overlay/Club/Sidebar/ClubSidebarActivity.cs b/fluXis.Game/Overlay/Club/Sidebar/ClubSidebarActivity.cs new file mode 100644 index 00000000..b6a5a62b --- /dev/null +++ b/fluXis.Game/Overlay/Club/Sidebar/ClubSidebarActivity.cs @@ -0,0 +1,42 @@ +using fluXis.Game.Graphics.Sprites; +using fluXis.Shared.Components.Clubs; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; + +namespace fluXis.Game.Overlay.Club.Sidebar; + +public partial class ClubSidebarActivity : FillFlowContainer +{ + private APIClub club { get; } + + public ClubSidebarActivity(APIClub club) + { + this.club = club; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(8); + + InternalChildren = new Drawable[] + { + new FluXisSpriteText + { + Text = "Activity", + WebFontSize = 24 + }, + new FluXisSpriteText + { + Text = "Nothing here yet...", + WebFontSize = 12, + Alpha = .8f + } + }; + } +} diff --git a/fluXis.Game/Overlay/Club/Sidebar/ClubSidebarStats.cs b/fluXis.Game/Overlay/Club/Sidebar/ClubSidebarStats.cs new file mode 100644 index 00000000..74497a34 --- /dev/null +++ b/fluXis.Game/Overlay/Club/Sidebar/ClubSidebarStats.cs @@ -0,0 +1,85 @@ +using fluXis.Game.Graphics.Sprites; +using fluXis.Shared.Components.Clubs; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osuTK; + +namespace fluXis.Game.Overlay.Club.Sidebar; + +public partial class ClubSidebarStats : FillFlowContainer +{ + private APIClub club { get; } + + public ClubSidebarStats(APIClub club) + { + this.club = club; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(8); + + InternalChildren = new Drawable[] + { + new FluXisSpriteText + { + Text = "Statistics", + WebFontSize = 24 + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new StatsEntry("Rank", "#-"), + new StatsEntry("Overall Rating", $"{club.Statistics!.OverallRating}"), + new StatsEntry("Total Score", "---,---"), + new StatsEntry("Average Rank", "#-"), + } + } + }; + } + + private partial class StatsEntry : CompositeDrawable + { + private string text { get; } + private string value { get; } + + public StatsEntry(string text, string value) + { + this.text = text; + this.value = value; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Height = 20; + + InternalChildren = new Drawable[] + { + new FluXisSpriteText + { + Text = text, + WebFontSize = 12, + Alpha = .8f + }, + new FluXisSpriteText + { + Text = value, + WebFontSize = 12, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + } + }; + } + } +} diff --git a/fluXis.Game/Overlay/Club/Tabs/ClubMembersTab.cs b/fluXis.Game/Overlay/Club/Tabs/ClubMembersTab.cs new file mode 100644 index 00000000..42445793 --- /dev/null +++ b/fluXis.Game/Overlay/Club/Tabs/ClubMembersTab.cs @@ -0,0 +1,38 @@ +using System.Linq; +using fluXis.Game.Graphics.Sprites; +using fluXis.Game.Graphics.UserInterface.Tabs; +using fluXis.Game.Overlay.Club.Tabs.Members; +using fluXis.Shared.Components.Clubs; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osuTK; + +namespace fluXis.Game.Overlay.Club.Tabs; + +public partial class ClubMembersTab : TabContainer +{ + public override IconUsage Icon => FontAwesome6.Solid.UserGroup; + public override string Title => "Members"; + + private APIClub club { get; } + + public ClubMembersTab(APIClub club) + { + this.club = club; + } + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(12), + ChildrenEnumerable = club.Members.Select(m => new ClubMemberEntry(club, m)) + }; + } +} diff --git a/fluXis.Game/Overlay/Club/Tabs/ClubScoresTab.cs b/fluXis.Game/Overlay/Club/Tabs/ClubScoresTab.cs new file mode 100644 index 00000000..1be445ad --- /dev/null +++ b/fluXis.Game/Overlay/Club/Tabs/ClubScoresTab.cs @@ -0,0 +1,44 @@ +using fluXis.Game.Graphics.Sprites; +using fluXis.Game.Graphics.UserInterface.Tabs; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; + +namespace fluXis.Game.Overlay.Club.Tabs; + +public partial class ClubScoresTab : TabContainer +{ + public override IconUsage Icon => FontAwesome6.Solid.ArrowTrendUp; + public override string Title => "Scores"; + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 48 }, + Children = new Drawable[] + { + new FluXisSpriteText + { + Text = "We're still working on this!", + WebFontSize = 20, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + new FluXisSpriteText + { + Text = "Please check back later...", + WebFontSize = 16, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = .8f + } + } + }; + } +} diff --git a/fluXis.Game/Overlay/Club/Tabs/Members/ClubMemberEntry.cs b/fluXis.Game/Overlay/Club/Tabs/Members/ClubMemberEntry.cs new file mode 100644 index 00000000..894de60b --- /dev/null +++ b/fluXis.Game/Overlay/Club/Tabs/Members/ClubMemberEntry.cs @@ -0,0 +1,137 @@ +using fluXis.Game.Graphics.Containers; +using fluXis.Game.Graphics.Drawables; +using fluXis.Game.Graphics.Sprites; +using fluXis.Game.Overlay.User; +using fluXis.Game.Utils; +using fluXis.Shared.Components.Clubs; +using fluXis.Shared.Components.Users; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osuTK; + +namespace fluXis.Game.Overlay.Club.Tabs.Members; + +public partial class ClubMemberEntry : FillFlowContainer +{ + private APIClub club { get; } + private APIUser member { get; } + + [CanBeNull] + [Resolved(CanBeNull = true)] + private ClubOverlay overlay { get; set; } + + [CanBeNull] + [Resolved(CanBeNull = true)] + private UserProfileOverlay userOverlay { get; set; } + + public ClubMemberEntry(APIClub club, APIUser member) + { + this.member = member; + this.club = club; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Height = 48; + Direction = FillDirection.Horizontal; + Spacing = new Vector2(8); + + Children = new Drawable[] + { + new LoadWrapper + { + Size = new Vector2(48), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + CornerRadius = 8, + Masking = true, + LoadContent = () => new DrawableAvatar(member) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill + }, + OnComplete = a => a.FadeInFromZero(300) + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome6.Solid.Crown, + Size = new Vector2(16), + Colour = Colour4.FromHex("#FAD49E"), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Alpha = club.Owner!.ID == member.ID ? 1 : 0, + Margin = new MarginPadding { Right = 6 } + }, + new FluXisSpriteText + { + Text = $"{member.PreferredName} ", + WebFontSize = 16, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new FluXisSpriteText + { + Text = $"{member.Username}", + WebFontSize = 12, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Alpha = !string.IsNullOrEmpty(member.DisplayName) ? .8f : 0 + } + } + }, + new FluXisSpriteText + { + Text = createOnlineStatus(), + WebFontSize = 10, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + } + } + }; + } + + private string createOnlineStatus() + { + if (member.IsOnline) + return "online"; + + if (member.LastLogin is null) + return ""; + + var date = TimeUtils.GetFromSeconds(member.LastLogin.Value); + return $"last online {TimeUtils.Ago(date)}"; + } + + protected override bool OnClick(ClickEvent e) + { + userOverlay?.ShowUser(member.ID); + overlay?.Hide(); + + return true; + } +} diff --git a/fluXis.Game/Overlay/Club/UI/ClubHeader.cs b/fluXis.Game/Overlay/Club/UI/ClubHeader.cs new file mode 100644 index 00000000..70d58064 --- /dev/null +++ b/fluXis.Game/Overlay/Club/UI/ClubHeader.cs @@ -0,0 +1,115 @@ +using fluXis.Game.Graphics; +using fluXis.Game.Graphics.Containers; +using fluXis.Game.Graphics.Drawables; +using fluXis.Game.Graphics.Sprites; +using fluXis.Game.Graphics.UserInterface.Color; +using fluXis.Shared.Components.Clubs; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; + +namespace fluXis.Game.Overlay.Club.UI; + +public partial class ClubHeader : CompositeDrawable +{ + private APIClub club { get; } + + public ClubHeader(APIClub club) + { + this.club = club; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Height = 176; + CornerRadius = 16; + Masking = true; + + InternalChildren = new Drawable[] + { + new LoadWrapper + { + RelativeSizeAxes = Axes.Both, + LoadContent = () => new DrawableClubBanner(club) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + OnComplete = background => background.FadeInFromZero(400) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = FluXisColors.Background2.Opacity(.5f) + }, + new GridContainer + { + Width = 960, + Height = 128, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 128), + new Dimension(GridSizeMode.Absolute, 20), + new Dimension() + }, + Content = new[] + { + new[] + { + new LoadWrapper + { + Size = new Vector2(128), + CornerRadius = 12, + Masking = true, + EdgeEffect = FluXisStyles.ShadowMedium, + LoadContent = () => new DrawableClubIcon(club) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + OnComplete = cover => cover.FadeInFromZero(400) + }, + Empty(), + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + new FluXisSpriteText + { + Text = club.Name, + WebFontSize = 30, + Shadow = true, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new FluXisSpriteText + { + Text = $"{club.Members!.Count} members", + WebFontSize = 16, + Shadow = true, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + } + } + } + } + } + } + }; + } +} diff --git a/fluXis.Game/Overlay/User/Sidebar/ProfileSidebarClub.cs b/fluXis.Game/Overlay/User/Sidebar/ProfileSidebarClub.cs index 98f67b22..a70b13c1 100644 --- a/fluXis.Game/Overlay/User/Sidebar/ProfileSidebarClub.cs +++ b/fluXis.Game/Overlay/User/Sidebar/ProfileSidebarClub.cs @@ -2,7 +2,9 @@ using fluXis.Game.Graphics.Drawables; using fluXis.Game.Graphics.Sprites; using fluXis.Game.Graphics.UserInterface.Color; +using fluXis.Game.Overlay.Club; using fluXis.Shared.Components.Clubs; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,8 +22,8 @@ public ProfileSidebarClub(APIClub club) this.club = club; } - [BackgroundDependencyLoader] - private void load() + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] ClubOverlay clubOverlay) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -41,12 +43,13 @@ private void load() WebFontSize = 24 } }, - new Container + new ClickableContainer { RelativeSizeAxes = Axes.X, Height = 80, CornerRadius = 10, Masking = true, + Action = () => clubOverlay?.ShowClub(club.ID), Children = new Drawable[] { new LoadWrapper