// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osuTK; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Game.Rulesets; using osu.Framework.Input.Bindings; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { public partial class Toolbar : OverlayContainer, IKeyBindingHandler { public const float HEIGHT = 40; public const float TOOLTIP_HEIGHT = 30; /// /// Whether the user hid this with . /// In this state, automatic toggles should not occur, respecting the user's preference to have no toolbar. /// private bool hiddenByUser; public Action OnHome; private ToolbarUserButton userButton; private ToolbarRulesetSelector rulesetSelector; private const double transition_time = 500; protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); // Toolbar and its components need keyboard input even when hidden. public override bool PropagateNonPositionalInputSubTree => OverlayActivationMode.Value != OverlayActivation.Disabled; public Toolbar() { RelativeSizeAxes = Axes.X; Size = new Vector2(1, HEIGHT); AlwaysPresent = true; } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); // this only needed to be set for the initial LoadComplete/Update, so layout completes and gets buttons in a state they can correctly handle keyboard input for hotkeys. AlwaysPresent = false; } [Resolved] private Bindable ruleset { get; set; } [BackgroundDependencyLoader(true)] private void load(OsuGame osuGame) { ToolbarBackground background; HoverInterceptor interceptor; Children = new Drawable[] { background = new ToolbarBackground(), new GridContainer { RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(), new Dimension(GridSizeMode.AutoSize) }, Content = new[] { new Drawable[] { new Container { Name = "Left buttons", RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Depth = float.MinValue, Children = new Drawable[] { new Box { Colour = OsuColour.Gray(0.1f), RelativeSizeAxes = Axes.Both, }, new FillFlowContainer { Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Children = new Drawable[] { new ToolbarSettingsButton(), new ToolbarHomeButton { Action = () => OnHome?.Invoke() }, }, }, } }, new Container { Name = "Ruleset selector", RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new OsuScrollContainer(Direction.Horizontal) { ScrollbarVisible = false, RelativeSizeAxes = Axes.Both, Masking = false, Children = new Drawable[] { rulesetSelector = new ToolbarRulesetSelector() } }, new Box { Colour = ColourInfo.GradientHorizontal(OsuColour.Gray(0.1f).Opacity(0), OsuColour.Gray(0.1f)), Width = 50, RelativeSizeAxes = Axes.Y, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, } }, new Container { Name = "Right buttons", RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Children = new Drawable[] { new Box { Colour = OsuColour.Gray(0.1f), RelativeSizeAxes = Axes.Both, }, new FillFlowContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Children = new Drawable[] { new ToolbarNewsButton(), new ToolbarChangelogButton(), new ToolbarWikiButton(), new ToolbarRankingsButton(), new ToolbarBeatmapListingButton(), new ToolbarChatButton(), new ToolbarSocialButton(), new ToolbarMusicButton(), //new ToolbarButton //{ // Icon = FontAwesome.Solid.search //}, userButton = new ToolbarUserButton(), new ToolbarClock(), new ToolbarNotificationButton(), } }, } }, }, } }, interceptor = new HoverInterceptor { RelativeSizeAxes = Axes.Both } }; ((IBindable)background.ShowGradient).BindTo(interceptor.ReceivedHover); if (osuGame != null) OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); } protected override void LoadComplete() { base.LoadComplete(); rulesetSelector.Current.BindTo(ruleset); } public partial class ToolbarBackground : Container { public Bindable ShowGradient { get; } = new BindableBool(); private readonly Box gradientBackground; public ToolbarBackground() { RelativeSizeAxes = Axes.Both; Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.1f), }, gradientBackground = new Box { RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomLeft, Alpha = 0, Height = 100, Colour = ColourInfo.GradientVertical( OsuColour.Gray(0).Opacity(0.9f), OsuColour.Gray(0).Opacity(0)), }, }; } protected override void LoadComplete() { base.LoadComplete(); ShowGradient.BindValueChanged(_ => updateState(), true); } private void updateState() { if (ShowGradient.Value) gradientBackground.FadeIn(transition_time, Easing.OutQuint); else gradientBackground.FadeOut(transition_time, Easing.OutQuint); } } /// /// Whenever the mouse cursor is within the bounds of the toolbar, we want the background gradient to show, for toolbar button descriptions to be legible. /// Unfortunately we also need to ensure that the toolbar buttons handle hover, to prevent the possibility of multiple descriptions being shown /// due to hover events passing through multiple buttons. /// This drawable is a workaround, that when placed front-most in the toolbar, allows to see whether hover events have been propagated through it without handling them. /// private partial class HoverInterceptor : Drawable { public IBindable ReceivedHover => receivedHover; private readonly Bindable receivedHover = new BindableBool(); protected override bool OnHover(HoverEvent e) { receivedHover.Value = true; return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { receivedHover.Value = false; base.OnHoverLost(e); } } protected override void UpdateState(ValueChangedEvent state) { bool blockShow = hiddenByUser || OverlayActivationMode.Value == OverlayActivation.Disabled; if (state.NewValue == Visibility.Visible && blockShow) { State.Value = Visibility.Hidden; return; } base.UpdateState(state); } protected override void PopIn() { this.MoveToY(0, transition_time, Easing.OutQuint); this.FadeIn(transition_time / 4, Easing.OutQuint); } protected override void PopOut() { userButton.StateContainer?.Hide(); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); this.FadeOut(transition_time, Easing.InQuint); } public bool OnPressed(KeyBindingPressEvent e) { if (OverlayActivationMode.Value == OverlayActivation.Disabled) return false; switch (e.Action) { case GlobalAction.ToggleToolbar: hiddenByUser = State.Value == Visibility.Visible; // set before toggling to allow the operation to always succeed. ToggleVisibility(); return true; } return false; } public void OnReleased(KeyBindingReleaseEvent e) { } } }