diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 983b302de8..4bd50f29f6 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable private void load() { // todo: this should come from the skin. - AccentColour = colourForRrepesentation(HitObject.VisualRepresentation); + AccentColour = colourForRepresentation(HitObject.VisualRepresentation); InternalChildren = new[] { @@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); } - private Color4 colourForRrepesentation(FruitVisualRepresentation representation) + private Color4 colourForRepresentation(FruitVisualRepresentation representation) { switch (representation) { diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs new file mode 100644 index 0000000000..8c5299e1a2 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Timing; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public abstract class ManiaSelectionBlueprintTestCase : SelectionBlueprintTestCase + { + [Cached(Type = typeof(IAdjustableClock))] + private readonly IAdjustableClock clock = new StopwatchClock(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs index 993f7520e8..756031a463 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs @@ -15,7 +15,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseHoldNoteSelectionBlueprint : SelectionBlueprintTestCase + public class TestCaseHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase { private readonly DrawableHoldNote drawableObject; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs index 67370be66c..4023f97eb3 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseNoteSelectionBlueprint : SelectionBlueprintTestCase + public class TestCaseNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase { private readonly DrawableNote drawableObject; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index afeb0a585e..4b78dd68cb 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints base.Update(); Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight); - Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft); // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do // When scrolling upwards our origin is already at the top of the head note (which is the intended location), diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 53f9dd8752..97817d5414 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -1,23 +1,79 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public class ManiaSelectionBlueprint : SelectionBlueprint { + protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; + + protected IClock EditorClock { get; private set; } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + public ManiaSelectionBlueprint(DrawableHitObject hitObject) : base(hitObject) { RelativeSizeAxes = Axes.None; } + [BackgroundDependencyLoader] + private void load(IAdjustableClock clock) + { + EditorClock = clock; + } + + protected override void Update() + { + base.Update(); + + Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero)); + } + public override void AdjustPosition(DragEvent dragEvent) { + var objectParent = HitObject.Parent; + + // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame + // without the position having been updated by the parenting ScrollingHitObjectContainer + HitObject.Y += dragEvent.Delta.Y; + + float targetPosition; + + // If we're scrolling downwards, a position of 0 is actually further away from the hit target + // so we need to flip the vertical coordinate in the hitobject container's space + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + targetPosition = -HitObject.Position.Y; + else + targetPosition = HitObject.Position.Y; + + HitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition, + EditorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + objectParent.DrawHeight); + } + + public override void Show() + { + HitObject.AlwaysAlive = true; + base.Show(); + } + + public override void Hide() + { + HitObject.AlwaysAlive = false; + base.Hide(); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index 7df7924c51..440a539412 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints base.Update(); Size = HitObject.DrawSize; - Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft); } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 8c96c6dfe7..70ff7b4124 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -12,6 +12,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public abstract class DrawableManiaHitObject : DrawableHitObject { + /// + /// Whether this should always remain alive. + /// + internal bool AlwaysAlive; + /// /// The which causes this to be hit. /// @@ -34,6 +39,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Direction.BindValueChanged(OnDirectionChanged, true); } + protected override bool ShouldBeAlive => AlwaysAlive || base.ShouldBeAlive; + protected virtual void OnDirectionChanged(ScrollingDirection direction) { Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 4dac46831c..eddcd16b93 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -23,6 +23,7 @@ namespace osu.Game.Tests.Visual public class TestCaseChatLink : OsuTestCase { private readonly TestChatLineContainer textContainer; + private readonly DialogOverlay dialogOverlay; private Color4 linkColour; public override IReadOnlyList RequiredTypes => new[] @@ -37,6 +38,7 @@ namespace osu.Game.Tests.Visual public TestCaseChatLink() { + Add(dialogOverlay = new DialogOverlay { Depth = float.MinValue }); Add(textContainer = new TestChatLineContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, @@ -57,6 +59,7 @@ namespace osu.Game.Tests.Visual Dependencies.Cache(chatManager); Dependencies.Cache(new ChatOverlay()); + Dependencies.Cache(dialogOverlay); testLinksGeneral(); testEcho(); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9ac2cabe9f..8975ab8a0e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -42,6 +42,8 @@ namespace osu.Game.Configuration if (!val) Set(OsuSetting.SavePassword, false); }; + Set(OsuSetting.ExternalLinkWarning, true); + // Audio Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); @@ -148,6 +150,7 @@ namespace osu.Game.Configuration BeatmapSkins, BeatmapHitsounds, IncreaseFirstObjectVisibility, - ScoreDisplayMode + ScoreDisplayMode, + ExternalLinkWarning } } diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 5c8fddc342..54a2ea47f9 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Logging; -using osu.Framework.Platform; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -24,14 +23,12 @@ namespace osu.Game.Graphics.Containers private OsuGame game; private ChannelManager channelManager; private Action showNotImplementedError; - private GameHost host; [BackgroundDependencyLoader(true)] - private void load(OsuGame game, NotificationOverlay notifications, GameHost host, ChannelManager channelManager) + private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager) { // will be null in tests this.game = game; - this.host = host; this.channelManager = channelManager; showNotImplementedError = () => notifications?.Post(new SimpleNotification @@ -98,7 +95,7 @@ namespace osu.Game.Graphics.Containers showNotImplementedError?.Invoke(); break; case LinkAction.External: - host.OpenUrlExternally(url); + game?.OpenUrlExternally(url); break; case LinkAction.OpenUserProfile: if (long.TryParse(linkArgument, out long userId)) diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs new file mode 100644 index 0000000000..d8b8adbbad --- /dev/null +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Game.Configuration; +using osu.Game.Overlays; +using osu.Game.Overlays.Chat; + +namespace osu.Game.Online.Chat +{ + public class ExternalLinkOpener : Component + { + private GameHost host; + private DialogOverlay dialogOverlay; + private Bindable externalLinkWarning; + + [BackgroundDependencyLoader(true)] + private void load(GameHost host, DialogOverlay dialogOverlay, OsuConfigManager config) + { + this.host = host; + this.dialogOverlay = dialogOverlay; + externalLinkWarning = config.GetBindable(OsuSetting.ExternalLinkWarning); + } + + public void OpenUrlExternally(string url) + { + if (externalLinkWarning) + dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url))); + else + host.OpenUrlExternally(url); + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2894e096fb..1b0a0b2210 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -181,6 +181,9 @@ namespace osu.Game LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); } + private ExternalLinkOpener externalLinkOpener; + public void OpenUrlExternally(string url) => externalLinkOpener.OpenUrlExternally(url); + private ScheduledDelegate scoreLoad; /// @@ -382,6 +385,8 @@ namespace osu.Game dependencies.Cache(notifications); dependencies.Cache(dialogOverlay); + Add(externalLinkOpener = new ExternalLinkOpener()); + var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications }; overlays.AddRange(singleDisplaySideOverlays); diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 5aff50a68a..6294851415 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -139,7 +139,7 @@ namespace osu.Game.Overlays var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); req.Success += res => { - ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + BeatmapSet = res.ToBeatmapSet(rulesets); header.Picker.Beatmap.Value = header.BeatmapSet.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); }; api.Queue(req); @@ -150,7 +150,7 @@ namespace osu.Game.Overlays { BeatmapSet = null; var req = new GetBeatmapSetRequest(beatmapSetId); - req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + req.Success += res => BeatmapSet = res.ToBeatmapSet(rulesets); api.Queue(req); Show(); } diff --git a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs new file mode 100644 index 0000000000..b3f7b282a9 --- /dev/null +++ b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Overlays.Chat +{ + public class ExternalLinkDialog : PopupDialog + { + public ExternalLinkDialog(string url, Action openExternalLinkAction) + { + HeaderText = "Just checking..."; + BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}"; + + Icon = FontAwesome.fa_warning; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Go for it.", + Action = openExternalLinkAction + }, + new PopupDialogCancelButton + { + Text = @"No! Abort mission!" + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 3100266c6f..ca921b7882 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -7,12 +7,10 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; @@ -35,7 +33,7 @@ namespace osu.Game.Overlays.Dialog private readonly Container ring; private readonly FillFlowContainer buttonsContainer; private readonly SpriteIcon icon; - private readonly SpriteText header; + private readonly TextFlowContainer header; private readonly TextFlowContainer body; private bool actionInvoked; @@ -46,10 +44,19 @@ namespace osu.Game.Overlays.Dialog set => icon.Icon = value; } + private string text; + public string HeaderText { - get => header.Text; - set => header.Text = value; + get => text; + set + { + if (text == value) + return; + text = value; + + header.Text = value; + } } public string BodyText @@ -164,18 +171,20 @@ namespace osu.Game.Overlays.Dialog }, }, }, - header = new OsuSpriteText + header = new OsuTextFlowContainer(t => t.TextSize = 25) { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - TextSize = 25, - Shadow = true, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15), + TextAnchor = Anchor.TopCentre, }, body = new OsuTextFlowContainer(t => t.TextSize = 18) { - Padding = new MarginPadding(15), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15), TextAnchor = Anchor.TopCentre, }, }, diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs new file mode 100644 index 0000000000..62e307f323 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Online +{ + public class WebSettings : SettingsSubsection + { + protected override string Header => "Web"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Warn about opening external links", + Bindable = config.GetBindable(OsuSetting.ExternalLinkWarning) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 28cb503288..3a2e52d2eb 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Overlays.Settings.Sections.Online; namespace osu.Game.Overlays.Settings.Sections { @@ -15,6 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections { Children = new Drawable[] { + new WebSettings() }; } }