2019-10-18 16:59:54 +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.
2021-04-19 17:25:30 +08:00
using System ;
2019-10-18 16:59:54 +08:00
using System.Linq ;
using osu.Framework.Allocation ;
using osu.Framework.Bindables ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2024-06-21 16:54:40 +08:00
using osu.Framework.Graphics.Pooling ;
using osu.Framework.Graphics.Shapes ;
using osu.Framework.Graphics.UserInterface ;
using osu.Framework.Input.Events ;
using osu.Framework.Localisation ;
2019-10-18 16:59:54 +08:00
using osu.Game.Beatmaps.ControlPoints ;
2020-09-30 11:45:43 +08:00
using osu.Game.Extensions ;
2019-10-18 16:59:54 +08:00
using osu.Game.Graphics ;
2024-06-21 16:54:40 +08:00
using osu.Game.Graphics.Containers ;
2019-10-18 16:59:54 +08:00
using osu.Game.Graphics.Sprites ;
2024-06-21 16:54:40 +08:00
using osu.Game.Overlays ;
2021-04-19 14:06:07 +08:00
using osu.Game.Screens.Edit.Timing.RowAttributes ;
2019-10-18 16:59:54 +08:00
using osuTK ;
namespace osu.Game.Screens.Edit.Timing
{
2024-06-21 16:54:40 +08:00
public partial class ControlPointTable : CompositeDrawable
2019-10-18 16:59:54 +08:00
{
2024-06-21 16:54:40 +08:00
public BindableList < ControlPointGroup > Groups { get ; } = new BindableList < ControlPointGroup > ( ) ;
2019-10-18 16:59:54 +08:00
2024-06-21 16:54:40 +08:00
private const float timing_column_width = 300 ;
private const float row_height = 25 ;
private const float row_horizontal_padding = 20 ;
2021-04-13 22:26:19 +08:00
2024-06-21 16:54:40 +08:00
[BackgroundDependencyLoader]
private void load ( OverlayColourProvider colours )
2019-10-18 16:59:54 +08:00
{
2024-06-21 16:54:40 +08:00
RelativeSizeAxes = Axes . Both ;
2019-10-18 16:59:54 +08:00
2024-06-21 16:54:40 +08:00
InternalChildren = new Drawable [ ]
{
new Box
{
Colour = colours . Background4 ,
RelativeSizeAxes = Axes . Both ,
} ,
new Box
2019-10-18 16:59:54 +08:00
{
2024-06-21 16:54:40 +08:00
Colour = colours . Background3 ,
RelativeSizeAxes = Axes . Y ,
Width = timing_column_width + 10 ,
} ,
new Container
{
RelativeSizeAxes = Axes . X ,
Height = row_height ,
Padding = new MarginPadding { Horizontal = row_horizontal_padding } ,
Children = new Drawable [ ]
2021-04-13 22:26:19 +08:00
{
2024-06-21 16:54:40 +08:00
new TableHeaderText ( "Time" )
{
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
} ,
new TableHeaderText ( "Attributes" )
2021-04-13 22:26:19 +08:00
{
2024-06-21 16:54:40 +08:00
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
Margin = new MarginPadding { Left = ControlPointTable . timing_column_width }
} ,
}
} ,
new Container
{
RelativeSizeAxes = Axes . Both ,
Padding = new MarginPadding { Top = row_height } ,
Child = new ControlPointRowList
{
RelativeSizeAxes = Axes . Both ,
RowData = { BindTarget = Groups , } ,
} ,
} ,
} ;
}
2019-10-18 16:59:54 +08:00
2024-06-21 16:54:40 +08:00
private partial class ControlPointRowList : VirtualisedListContainer < ControlPointGroup , DrawableControlGroup >
{
[Resolved]
private Bindable < ControlPointGroup ? > selectedGroup { get ; set ; } = null ! ;
public ControlPointRowList ( )
: base ( row_height , 50 )
{
}
2022-06-21 11:53:06 +08:00
2024-06-21 16:54:40 +08:00
protected override ScrollContainer < Drawable > CreateScrollContainer ( ) = > new OsuScrollContainer ( ) ;
2023-12-26 20:20:18 +08:00
2024-06-21 16:54:40 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2023-12-26 20:20:18 +08:00
2024-06-21 16:54:40 +08:00
selectedGroup . BindValueChanged ( val = >
{
2024-06-27 15:46:35 +08:00
// can't use `.ScrollIntoView()` here because of the list virtualisation not giving
// child items valid coordinates from the start, so ballpark something similar
// using estimated row height.
2024-06-21 16:54:40 +08:00
var row = Items . FlowingChildren . SingleOrDefault ( item = > item . Row . Equals ( val . NewValue ) ) ;
2024-07-16 17:20:33 +08:00
2024-06-27 15:46:35 +08:00
if ( row = = null )
return ;
2024-07-16 17:20:33 +08:00
float minPos = row . Y ;
2024-06-27 15:46:35 +08:00
float maxPos = minPos + row_height ;
if ( minPos < Scroll . Current )
Scroll . ScrollTo ( minPos ) ;
else if ( maxPos > Scroll . Current + Scroll . DisplayableContent )
Scroll . ScrollTo ( maxPos - Scroll . DisplayableContent ) ;
2024-06-21 16:54:40 +08:00
} ) ;
2019-10-18 16:59:54 +08:00
}
}
2024-06-21 16:54:40 +08:00
public partial class DrawableControlGroup : PoolableDrawable , IHasCurrentValue < ControlPointGroup >
2021-04-13 22:26:19 +08:00
{
2024-06-21 16:54:40 +08:00
public Bindable < ControlPointGroup > Current
{
get = > current . Current ;
set = > current . Current = value ;
}
2021-04-13 22:26:19 +08:00
2024-06-21 16:54:40 +08:00
private readonly BindableWithCurrent < ControlPointGroup > current = new BindableWithCurrent < ControlPointGroup > ( ) ;
2023-12-26 20:20:18 +08:00
2024-06-21 16:54:40 +08:00
private Box background = null ! ;
2023-12-26 20:20:18 +08:00
2024-06-21 16:54:40 +08:00
[Resolved]
private OverlayColourProvider colourProvider { get ; set ; } = null ! ;
2021-04-13 22:26:19 +08:00
2024-06-21 16:54:40 +08:00
[Resolved]
private Bindable < ControlPointGroup ? > selectedGroup { get ; set ; } = null ! ;
[Resolved]
private EditorClock editorClock { get ; set ; } = null ! ;
[BackgroundDependencyLoader]
private void load ( )
2019-10-18 16:59:54 +08:00
{
2024-06-21 16:54:40 +08:00
RelativeSizeAxes = Axes . Both ;
2019-10-18 16:59:54 +08:00
2024-06-21 16:54:40 +08:00
InternalChildren = new Drawable [ ]
{
background = new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = colourProvider . Background1 ,
Alpha = 0 ,
} ,
new Container
{
RelativeSizeAxes = Axes . Both ,
Padding = new MarginPadding { Horizontal = row_horizontal_padding , } ,
Children = new Drawable [ ]
{
new ControlGroupTiming { Group = { BindTarget = current } , } ,
new ControlGroupAttributes ( point = > point is not TimingControlPoint )
{
Group = { BindTarget = current } ,
Margin = new MarginPadding { Left = timing_column_width }
}
}
}
} ;
}
2019-10-18 16:59:54 +08:00
2024-06-21 16:54:40 +08:00
protected override void LoadComplete ( )
2019-10-18 16:59:54 +08:00
{
2024-06-21 16:54:40 +08:00
base . LoadComplete ( ) ;
selectedGroup . BindValueChanged ( _ = > updateState ( ) , true ) ;
FinishTransforms ( true ) ;
}
protected override void PrepareForUse ( )
{
base . PrepareForUse ( ) ;
updateState ( ) ;
}
protected override bool OnHover ( HoverEvent e )
{
updateState ( ) ;
return true ;
}
protected override void OnHoverLost ( HoverLostEvent e )
{
base . OnHoverLost ( e ) ;
updateState ( ) ;
}
protected override bool OnClick ( ClickEvent e )
{
// schedule to give time for any modified focused text box to lose focus and commit changes (e.g. BPM / time signature textboxes) before switching to new point.
var currentGroup = Current . Value ;
Schedule ( ( ) = >
{
selectedGroup . Value = currentGroup ;
editorClock . SeekSmoothlyTo ( currentGroup . Time ) ;
} ) ;
return true ;
}
private void updateState ( )
{
bool isSelected = selectedGroup . Value ? . Equals ( current . Value ) = = true ;
if ( IsHovered | | isSelected )
background . FadeIn ( 100 , Easing . OutQuint ) ;
else
background . FadeOut ( 100 , Easing . OutQuint ) ;
background . Colour = isSelected ? colourProvider . Colour3 : colourProvider . Background1 ;
}
2022-11-27 10:47:02 +08:00
}
private partial class ControlGroupTiming : FillFlowContainer
{
2024-06-21 16:54:40 +08:00
public Bindable < ControlPointGroup > Group { get ; } = new Bindable < ControlPointGroup > ( ) ;
private OsuSpriteText timeText = null ! ;
[BackgroundDependencyLoader]
private void load ( )
2022-11-27 10:47:02 +08:00
{
Name = @"ControlGroupTiming" ;
RelativeSizeAxes = Axes . Y ;
2024-06-21 16:54:40 +08:00
Width = timing_column_width ;
2022-11-27 10:47:02 +08:00
Spacing = new Vector2 ( 5 ) ;
Children = new Drawable [ ]
2021-04-19 17:25:30 +08:00
{
2024-06-21 16:54:40 +08:00
timeText = new OsuSpriteText
2021-04-19 17:25:30 +08:00
{
2024-06-21 16:54:40 +08:00
Font = OsuFont . GetFont ( size : 14 , weight : FontWeight . Bold ) ,
2022-11-27 10:47:02 +08:00
Width = 70 ,
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
} ,
2024-06-21 16:54:40 +08:00
new ControlGroupAttributes ( c = > c is TimingControlPoint )
2022-11-27 10:47:02 +08:00
{
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
2024-06-21 16:54:40 +08:00
Group = { BindTarget = Group } ,
2021-04-19 17:25:30 +08:00
}
2022-11-27 10:47:02 +08:00
} ;
}
2024-06-21 16:54:40 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
Group . BindValueChanged ( _ = > timeText . Text = Group . Value ? . Time . ToEditorFormattedString ( ) ? ? default ( LocalisableString ) , true ) ;
}
2021-04-19 17:25:30 +08:00
}
2019-10-18 16:59:54 +08:00
2022-11-24 13:32:20 +08:00
private partial class ControlGroupAttributes : CompositeDrawable
2019-10-18 16:59:54 +08:00
{
2024-06-21 16:54:40 +08:00
public Bindable < ControlPointGroup > Group { get ; } = new Bindable < ControlPointGroup > ( ) ;
private BindableList < ControlPoint > controlPoints { get ; } = new BindableList < ControlPoint > ( ) ;
2021-04-19 17:25:30 +08:00
2024-06-21 16:54:40 +08:00
private readonly Func < ControlPoint , bool > matchFunction ;
2019-10-18 16:59:54 +08:00
2024-06-21 16:54:40 +08:00
private FillFlowContainer fill = null ! ;
2019-10-18 16:59:54 +08:00
2024-06-21 16:54:40 +08:00
public ControlGroupAttributes ( Func < ControlPoint , bool > matchFunction )
2019-10-27 14:19:36 +08:00
{
2021-04-19 17:25:30 +08:00
this . matchFunction = matchFunction ;
2024-06-21 16:54:40 +08:00
}
2021-04-19 17:25:30 +08:00
2024-06-21 16:54:40 +08:00
[BackgroundDependencyLoader]
private void load ( )
{
2021-04-19 17:25:30 +08:00
AutoSizeAxes = Axes . X ;
RelativeSizeAxes = Axes . Y ;
2022-11-27 10:47:02 +08:00
Name = @"ControlGroupAttributes" ;
2021-04-19 17:25:30 +08:00
2019-10-27 14:19:36 +08:00
InternalChild = fill = new FillFlowContainer
{
2021-04-19 17:25:30 +08:00
AutoSizeAxes = Axes . X ,
RelativeSizeAxes = Axes . Y ,
2019-10-27 14:19:36 +08:00
Direction = FillDirection . Horizontal ,
Spacing = new Vector2 ( 2 )
} ;
}
2019-10-18 16:59:54 +08:00
2020-10-08 04:57:20 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2024-06-21 16:54:40 +08:00
Group . BindValueChanged ( _ = >
{
controlPoints . UnbindBindings ( ) ;
controlPoints . Clear ( ) ;
if ( Group . Value ! = null )
( ( IBindableList < ControlPoint > ) controlPoints ) . BindTo ( Group . Value . ControlPoints ) ;
} , true ) ;
controlPoints . BindCollectionChanged ( ( _ , _ ) = > createChildren ( ) , true ) ;
2020-10-08 04:57:20 +08:00
}
2019-10-27 14:19:36 +08:00
private void createChildren ( )
{
2021-04-19 14:06:07 +08:00
fill . ChildrenEnumerable = controlPoints
2021-04-19 17:25:30 +08:00
. Where ( matchFunction )
2021-04-19 14:06:07 +08:00
. Select ( createAttribute )
// arbitrary ordering to make timing points first.
// probably want to explicitly define order in the future.
. OrderByDescending ( c = > c . GetType ( ) . Name ) ;
2019-10-18 16:59:54 +08:00
}
2022-11-30 13:20:54 +08:00
private Drawable createAttribute ( ControlPoint controlPoint )
2019-10-27 14:19:36 +08:00
{
switch ( controlPoint )
{
case TimingControlPoint timing :
2021-04-19 14:06:07 +08:00
return new TimingRowAttribute ( timing ) ;
2019-10-27 14:19:36 +08:00
case EffectControlPoint effect :
2021-04-19 15:28:18 +08:00
return new EffectRowAttribute ( effect ) ;
2022-11-27 10:45:56 +08:00
}
2022-11-30 13:20:54 +08:00
throw new ArgumentOutOfRangeException ( nameof ( controlPoint ) , $"Control point type {controlPoint.GetType()} is not supported" ) ;
2019-10-27 14:19:36 +08:00
}
2019-10-18 16:59:54 +08:00
}
}
}