2020-09-05 02:52:07 +08:00
// 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 ;
2024-11-18 14:57:39 +08:00
using System.Collections.Generic ;
2020-09-05 02:52:07 +08:00
using osu.Framework.Allocation ;
using osu.Framework.Extensions.Color4Extensions ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Shapes ;
using osu.Framework.Graphics.Sprites ;
2022-07-27 18:35:25 +08:00
using osu.Framework.Graphics.UserInterface ;
2020-09-05 02:52:07 +08:00
using osu.Framework.Input.Events ;
2024-11-18 14:57:39 +08:00
using osu.Framework.Localisation ;
2022-07-27 14:59:36 +08:00
using osu.Game.Database ;
2020-09-05 02:52:07 +08:00
using osu.Game.Graphics ;
using osu.Game.Graphics.Containers ;
2023-03-22 13:44:52 +08:00
using osu.Game.Graphics.Sprites ;
2020-09-05 02:52:07 +08:00
using osu.Game.Graphics.UserInterface ;
using osu.Game.Overlays ;
using osuTK ;
using osuTK.Graphics ;
namespace osu.Game.Collections
{
2020-09-08 15:50:51 +08:00
/// <summary>
2022-07-27 15:46:23 +08:00
/// Visualises a <see cref="BeatmapCollection"/> inside a <see cref="DrawableCollectionList"/>.
2020-09-08 15:50:51 +08:00
/// </summary>
2024-11-18 14:57:39 +08:00
public partial class DrawableCollectionListItem : OsuRearrangeableListItem < Live < BeatmapCollection > > , IFilterable
2020-09-05 02:52:07 +08:00
{
2023-03-22 13:44:52 +08:00
private const float item_height = 45 ;
2020-09-08 12:02:58 +08:00
private const float button_width = item_height * 0.75f ;
2024-11-14 13:12:31 +08:00
protected TextBox TextBox = > content . TextBox ;
private ItemContent content = null ! ;
2020-09-08 15:50:51 +08:00
/// <summary>
/// Creates a new <see cref="DrawableCollectionListItem"/>.
/// </summary>
2022-07-27 15:46:23 +08:00
/// <param name="item">The <see cref="BeatmapCollection"/>.</param>
2022-07-27 14:59:36 +08:00
/// <param name="isCreated">Whether <paramref name="item"/> currently exists inside realm.</param>
2022-07-27 18:35:25 +08:00
public DrawableCollectionListItem ( Live < BeatmapCollection > item , bool isCreated )
2020-09-05 02:52:07 +08:00
: base ( item )
{
2023-10-16 14:51:18 +08:00
// For now we don't support rearranging and always use alphabetical sort.
// Change this to:
//
// ShowDragHandle.Value = item.IsManaged;
//
// if we want to support user sorting (but changes will need to be made to realm to persist).
ShowDragHandle . Value = false ;
2024-08-02 09:45:47 +08:00
Masking = true ;
CornerRadius = item_height / 2 ;
2020-09-05 02:52:07 +08:00
}
2024-11-14 13:12:31 +08:00
protected override Drawable CreateContent ( ) = > content = new ItemContent ( Model ) ;
2020-09-05 02:52:07 +08:00
2020-09-08 15:50:51 +08:00
/// <summary>
/// The main content of the <see cref="DrawableCollectionListItem"/>.
/// </summary>
2024-08-02 09:45:47 +08:00
private partial class ItemContent : CompositeDrawable
2020-09-05 02:52:07 +08:00
{
2022-07-27 18:35:25 +08:00
private readonly Live < BeatmapCollection > collection ;
2020-09-08 15:43:07 +08:00
2024-11-14 13:12:31 +08:00
public ItemTextBox TextBox { get ; private set ; } = null ! ;
2020-09-05 02:52:07 +08:00
2022-07-27 18:35:25 +08:00
public ItemContent ( Live < BeatmapCollection > collection )
2020-09-05 02:52:07 +08:00
{
this . collection = collection ;
RelativeSizeAxes = Axes . X ;
Height = item_height ;
}
[BackgroundDependencyLoader]
2022-01-15 08:06:39 +08:00
private void load ( )
2020-09-05 02:52:07 +08:00
{
2024-08-02 09:45:47 +08:00
InternalChildren = new [ ]
2020-09-05 02:52:07 +08:00
{
2022-07-27 18:35:25 +08:00
collection . IsManaged
? new DeleteButton ( collection )
{
Anchor = Anchor . CentreRight ,
Origin = Anchor . CentreRight ,
2024-11-14 13:12:31 +08:00
IsTextBoxHovered = v = > TextBox . ReceivePositionalInputAt ( v )
2022-07-27 18:35:25 +08:00
}
: Empty ( ) ,
new Container
2020-09-05 02:52:07 +08:00
{
RelativeSizeAxes = Axes . Both ,
2022-07-27 18:35:25 +08:00
Padding = new MarginPadding { Right = collection . IsManaged ? button_width : 0 } ,
2020-09-05 02:52:07 +08:00
Children = new Drawable [ ]
{
2025-02-11 15:54:24 +08:00
TextBox = new ItemTextBox ( collection )
2020-09-05 02:52:07 +08:00
{
2023-03-22 13:44:52 +08:00
RelativeSizeAxes = Axes . X ,
2024-02-28 23:06:47 +08:00
Height = item_height ,
2023-06-04 11:51:03 +08:00
CommitOnFocusLost = true ,
2020-09-05 02:52:07 +08:00
} ,
}
} ,
} ;
}
2020-09-08 15:43:07 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2020-09-08 16:05:31 +08:00
2021-12-23 12:21:10 +08:00
// Bind late, as the collection name may change externally while still loading.
2024-11-14 13:12:31 +08:00
TextBox . Current . Value = collection . PerformRead ( c = > c . IsValid ? c . Name : string . Empty ) ;
TextBox . OnCommit + = onCommit ;
2020-09-08 15:43:07 +08:00
}
2022-07-27 18:35:25 +08:00
private void onCommit ( TextBox sender , bool newText )
2020-09-08 15:43:07 +08:00
{
2024-11-14 13:23:43 +08:00
if ( collection . IsManaged & & collection . Value . Name ! = TextBox . Current . Value )
2024-11-14 13:12:31 +08:00
collection . PerformWrite ( c = > c . Name = TextBox . Current . Value ) ;
2020-09-08 15:43:07 +08:00
}
2020-09-05 02:52:07 +08:00
}
private partial class ItemTextBox : OsuTextBox
{
protected override float LeftRightPadding = > item_height / 2 ;
2023-03-22 13:44:52 +08:00
private const float count_text_size = 12 ;
private readonly Live < BeatmapCollection > collection ;
private OsuSpriteText countText = null ! ;
public ItemTextBox ( Live < BeatmapCollection > collection )
{
this . collection = collection ;
CornerRadius = item_height / 2 ;
}
2020-09-05 02:52:07 +08:00
[BackgroundDependencyLoader]
private void load ( OsuColour colours )
{
2021-12-10 13:15:00 +08:00
BackgroundUnfocused = colours . GreySeaFoamDarker . Darken ( 0.5f ) ;
BackgroundFocused = colours . GreySeaFoam ;
2023-03-22 13:44:52 +08:00
if ( collection . IsManaged )
{
TextContainer . Height * = ( Height - count_text_size ) / Height ;
TextContainer . Margin = new MarginPadding { Bottom = count_text_size } ;
TextContainer . Add ( countText = new OsuSpriteText
{
Anchor = Anchor . BottomLeft ,
Origin = Anchor . TopLeft ,
Depth = float . MinValue ,
Font = OsuFont . Default . With ( size : count_text_size , weight : FontWeight . SemiBold ) ,
Margin = new MarginPadding { Top = 2 , Left = 2 } ,
Colour = colours . Yellow
} ) ;
2025-02-11 16:13:08 +08:00
// interestingly, it is not required to subscribe to change notifications on this collection at all for this to work correctly.
// the reasoning for this is that `DrawableCollectionList` already takes out a subscription on the set of all `BeatmapCollection`s -
// but that subscription does not only cover *changes to the set of collections* (i.e. addition/removal/rearrangement of collections),
// but also covers *changes to the properties of collections*, which `BeatmapMD5Hashes` is one.
// when a collection item changes due to `BeatmapMD5Hashes` changing, the list item is deleted and re-inserted, thus guaranteeing this to work correctly.
int count = collection . PerformRead ( c = > c . BeatmapMD5Hashes . Count ) ;
countText . Text = count = = 1
// Intentionally not localised until we have proper support for this (see https://github.com/ppy/osu-framework/pull/4918
// but also in this case we want support for formatting a number within a string).
? $"{count:#,0} beatmap"
: $"{count:#,0} beatmaps" ;
2023-03-22 13:44:52 +08:00
}
else
{
PlaceholderText = "Create a new collection" ;
}
}
2020-09-05 02:52:07 +08:00
}
2024-08-02 09:30:52 +08:00
public partial class DeleteButton : OsuClickableContainer
2020-09-05 02:52:07 +08:00
{
2022-07-27 15:46:23 +08:00
public Func < Vector2 , bool > IsTextBoxHovered = null ! ;
2020-09-05 02:52:07 +08:00
2022-07-27 15:46:23 +08:00
[Resolved]
private IDialogOverlay ? dialogOverlay { get ; set ; }
2020-09-05 02:52:07 +08:00
2022-07-27 18:35:25 +08:00
private readonly Live < BeatmapCollection > collection ;
2020-09-05 02:52:07 +08:00
2022-07-27 15:46:23 +08:00
private Drawable fadeContainer = null ! ;
private Drawable background = null ! ;
2020-09-05 02:52:07 +08:00
2022-07-27 18:35:25 +08:00
public DeleteButton ( Live < BeatmapCollection > collection )
2020-09-05 02:52:07 +08:00
{
this . collection = collection ;
2020-09-08 12:02:58 +08:00
RelativeSizeAxes = Axes . Y ;
Width = button_width + item_height / 2 ; // add corner radius to cover with fill
2020-09-05 02:52:07 +08:00
}
[BackgroundDependencyLoader]
private void load ( OsuColour colours )
{
2024-08-02 09:30:52 +08:00
Child = fadeContainer = new Container
2020-09-05 02:52:07 +08:00
{
2020-09-08 15:43:07 +08:00
RelativeSizeAxes = Axes . Both ,
Alpha = 0.1f ,
Children = new [ ]
2020-09-05 02:52:07 +08:00
{
2020-09-08 15:43:07 +08:00
background = new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = colours . Red
} ,
new SpriteIcon
{
Anchor = Anchor . CentreRight ,
Origin = Anchor . Centre ,
X = - button_width * 0.6f ,
Size = new Vector2 ( 10 ) ,
Icon = FontAwesome . Solid . Trash
}
2020-09-05 02:52:07 +08:00
}
} ;
2024-08-02 09:30:52 +08:00
Action = ( ) = >
{
if ( collection . PerformRead ( c = > c . BeatmapMD5Hashes . Count ) = = 0 )
deleteCollection ( ) ;
else
dialogOverlay ? . Push ( new DeleteCollectionDialog ( collection , deleteCollection ) ) ;
} ;
2020-09-05 02:52:07 +08:00
}
public override bool ReceivePositionalInputAt ( Vector2 screenSpacePos ) = > base . ReceivePositionalInputAt ( screenSpacePos ) & & ! IsTextBoxHovered ( screenSpacePos ) ;
protected override bool OnHover ( HoverEvent e )
{
2020-09-08 15:43:07 +08:00
fadeContainer . FadeTo ( 1f , 100 , Easing . Out ) ;
2020-09-05 02:52:07 +08:00
return false ;
}
protected override void OnHoverLost ( HoverLostEvent e )
{
2020-09-08 15:43:07 +08:00
fadeContainer . FadeTo ( 0.1f , 100 ) ;
2020-09-05 02:52:07 +08:00
}
protected override bool OnClick ( ClickEvent e )
{
background . FlashColour ( Color4 . White , 150 ) ;
2020-09-08 13:38:25 +08:00
2024-08-02 09:30:52 +08:00
return base . OnClick ( e ) ;
2020-09-05 02:52:07 +08:00
}
2020-09-08 13:38:25 +08:00
2023-07-06 12:37:42 +08:00
private void deleteCollection ( ) = > collection . PerformWrite ( c = > c . Realm ! . Remove ( c ) ) ;
2020-09-05 02:52:07 +08:00
}
2024-11-18 14:57:39 +08:00
public IEnumerable < LocalisableString > FilterTerms = > [ ( LocalisableString ) Model . Value . Name ] ;
private bool matchingFilter = true ;
public bool MatchingFilter
{
get = > matchingFilter ;
set
{
matchingFilter = value ;
if ( matchingFilter )
this . FadeIn ( 200 ) ;
else
Hide ( ) ;
}
}
public bool FilteringActive { get ; set ; }
2020-09-05 02:52:07 +08:00
}
}