1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 20:22:55 +08:00

Merge branch 'master' into skin-components-list

This commit is contained in:
Dan Balasescu 2021-05-11 17:27:48 +09:00 committed by GitHub
commit 7d9cdd3bc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1310 additions and 165 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.507.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.510.0" />
</ItemGroup>
</Project>

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public class BeatmapDifficultyManagerTest
public class BeatmapDifficultyCacheTest
{
[Test]
public void TestKeyEqualsWithDifferentModInstances()

View File

@ -1,22 +1,26 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneComposeSelectBox : OsuTestScene
public class TestSceneComposeSelectBox : OsuManualInputManagerTestScene
{
private Container selectionArea;
private SelectionBox selectionBox;
public TestSceneComposeSelectBox()
[SetUp]
public void SetUp() => Schedule(() =>
{
SelectionBox selectionBox = null;
AddStep("create box", () =>
Child = selectionArea = new Container
{
Size = new Vector2(400),
@ -26,6 +30,8 @@ namespace osu.Game.Tests.Visual.Editing
{
selectionBox = new SelectionBox
{
RelativeSizeAxes = Axes.Both,
CanRotate = true,
CanScaleX = true,
CanScaleY = true,
@ -34,12 +40,11 @@ namespace osu.Game.Tests.Visual.Editing
OnScale = handleScale
}
}
});
};
AddToggleStep("toggle rotation", state => selectionBox.CanRotate = state);
AddToggleStep("toggle x", state => selectionBox.CanScaleX = state);
AddToggleStep("toggle y", state => selectionBox.CanScaleY = state);
}
InputManager.MoveMouseTo(selectionBox);
InputManager.ReleaseButton(MouseButton.Left);
});
private bool handleScale(Vector2 amount, Anchor reference)
{
@ -68,5 +73,99 @@ namespace osu.Game.Tests.Visual.Editing
selectionArea.Rotation += angle;
return true;
}
[Test]
public void TestRotationHandleShownOnHover()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over handle", () => InputManager.MoveMouseTo(rotationHandle));
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestRotationHandleShownOnHoveringClosestScaleHandler()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestHoverRotationHandleFromScaleHandle()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddAssert("rotation handle not hovered", () => !rotationHandle.IsHovered);
AddStep("hover over rotation handle", () => InputManager.MoveMouseTo(rotationHandle));
AddAssert("rotation handle still shown", () => rotationHandle.Alpha == 1);
AddAssert("rotation handle hovered", () => rotationHandle.IsHovered);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox));
AddUntilStep("handle hidden", () => rotationHandle.Alpha == 0);
}
[Test]
public void TestHoldingScaleHandleHidesCorrespondingRotationHandle()
{
SelectionBoxRotationHandle rotationHandle = null;
AddStep("retrieve rotation handle", () => rotationHandle = this.ChildrenOfType<SelectionBoxRotationHandle>().First());
AddAssert("rotation handle hidden", () => rotationHandle.Alpha == 0);
AddStep("hover over closest scale handle", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectionBoxScaleHandle>().Single(s => s.Anchor == rotationHandle.Anchor));
});
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("hold scale handle", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
int i;
ScheduledDelegate mouseMove = null;
AddStep("start dragging", () =>
{
i = 0;
mouseMove = Scheduler.AddDelayed(() =>
{
InputManager.MoveMouseTo(selectionBox.ScreenSpaceDrawQuad.TopLeft + Vector2.One * (5 * ++i));
}, 100, true);
});
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
AddStep("end dragging", () => mouseMove.Cancel());
AddAssert("rotation handle still hidden", () => rotationHandle.Alpha == 0);
AddStep("unhold left", () => InputManager.ReleaseButton(MouseButton.Left));
AddUntilStep("rotation handle shown", () => rotationHandle.Alpha == 1);
AddStep("move mouse away", () => InputManager.MoveMouseTo(selectionBox, new Vector2(20)));
AddUntilStep("rotation handle hidden", () => rotationHandle.Alpha == 0);
}
}
}

View File

@ -50,9 +50,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) },
new HitCircle { StartTime = 300, Position = new Vector2(100) },
new HitCircle { StartTime = 400, Position = new Vector2(150) },
new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(300) },
}));
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
@ -95,9 +95,9 @@ namespace osu.Game.Tests.Visual.Editing
var addedObjects = new[]
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) },
new HitCircle { StartTime = 300, Position = new Vector2(100) },
new HitCircle { StartTime = 400, Position = new Vector2(150) },
new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(300) },
};
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects));
@ -131,9 +131,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(50) },
new HitCircle { StartTime = 300, Position = new Vector2(100) },
new HitCircle { StartTime = 400, Position = new Vector2(150) },
new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(300) },
}));
moveMouseToObject(() => addedObjects[0]);

View File

@ -0,0 +1,240 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers.Markdown;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneOsuMarkdownContainer : OsuTestScene
{
private OsuMarkdownContainer markdownContainer;
[Cached]
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
[SetUp]
public void Setup() => Schedule(() =>
{
Children = new Drawable[]
{
new Box
{
Colour = overlayColour.Background5,
RelativeSizeAxes = Axes.Both,
},
new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(20),
Child = markdownContainer = new OsuMarkdownContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}
}
};
});
[Test]
public void TestEmphases()
{
AddStep("Emphases", () =>
{
markdownContainer.Text = @"_italic with underscore_
*italic with asterisk*
__bold with underscore__
**bold with asterisk**
*__italic with asterisk, bold with underscore__*
_**italic with underscore, bold with asterisk**_";
});
}
[Test]
public void TestHeading()
{
AddStep("Add Heading", () =>
{
markdownContainer.Text = @"# Header 1
## Header 2
### Header 3
#### Header 4
##### Header 5";
});
}
[Test]
public void TestLink()
{
AddStep("Add Link", () =>
{
markdownContainer.Text = "[Welcome to osu!](https://osu.ppy.sh)";
});
}
[Test]
public void TestLinkWithInlineText()
{
AddStep("Add Link with inline text", () =>
{
markdownContainer.Text = "Hey, [welcome to osu!](https://osu.ppy.sh) Please enjoy the game.";
});
}
[Test]
public void TestInlineCode()
{
AddStep("Add inline code", () =>
{
markdownContainer.Text = "This is `inline code` text";
});
}
[Test]
public void TestParagraph()
{
AddStep("Add paragraph", () =>
{
markdownContainer.Text = @"first paragraph
second paragraph
third paragraph";
});
}
[Test]
public void TestFencedCodeBlock()
{
AddStep("Add Code Block", () =>
{
markdownContainer.Text = @"```markdown
# Markdown code block
This is markdown code block.
```";
});
}
[Test]
public void TestSeparator()
{
AddStep("Add Seperator", () =>
{
markdownContainer.Text = @"Line above
---
Line below";
});
}
[Test]
public void TestQuote()
{
AddStep("Add quote", () =>
{
markdownContainer.Text =
@"> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.";
});
}
[Test]
public void TestTable()
{
AddStep("Add Table", () =>
{
markdownContainer.Text =
@"| Left Aligned | Center Aligned | Right Aligned |
| :------------------- | :--------------------: | ---------------------:|
| Long Align Left Text | Long Align Center Text | Long Align Right Text |
| Align Left | Align Center | Align Right |
| Left | Center | Right |";
});
}
[Test]
public void TestUnorderedList()
{
AddStep("Add Unordered List", () =>
{
markdownContainer.Text = @"- First item level 1
- Second item level 1
- First item level 2
- First item level 3
- Second item level 3
- Third item level 3
- First item level 4
- Second item level 4
- Third item level 4
- Second item level 2
- Third item level 2
- Third item level 1";
});
}
[Test]
public void TestOrderedList()
{
AddStep("Add Ordered List", () =>
{
markdownContainer.Text = @"1. First item level 1
2. Second item level 1
1. First item level 2
1. First item level 3
2. Second item level 3
3. Third item level 3
1. First item level 4
2. Second item level 4
3. Third item level 4
2. Second item level 2
3. Third item level 2
3. Third item level 1";
});
}
[Test]
public void TestLongMixedList()
{
AddStep("Add long mixed list", () =>
{
markdownContainer.Text = @"1. The osu! World Cup is a country-based team tournament played on the osu! game mode.
- While this competition is planned as a 4 versus 4 setup, this may change depending on the number of incoming registrations.
2. Beatmap scoring is based on Score V2.
3. The beatmaps for each round will be announced by the map selectors in advance on the Sunday before the actual matches take place. Only these beatmaps will be used during the respective matches.
- One beatmap will be a tiebreaker beatmap. This beatmap will only be played in case of a tie. **The only exception to this is the Qualifiers pool.**
4. The match schedule will be settled by the Tournament Management (see the [scheduling instructions](#scheduling-instructions)).
5. If no staff or referee is available, the match will be postponed.
6. Use of the Visual Settings to alter background dim or disable beatmap elements like storyboards and skins are allowed.
7. If the beatmap ends in a draw, the map will be nullified and replayed.
8. If a player disconnects, their scores will not be counted towards their team's total.
- Disconnects within 30 seconds or 25% of the beatmap length (whichever happens first) after beatmap begin can be aborted and/or rematched. This is up to the referee's discretion.
9. Beatmaps cannot be reused in the same match unless the map was nullified.
10. If less than the minimum required players attend, the maximum time the match can be postponed is 10 minutes.
11. Exchanging players during a match is allowed without limitations.
- **If a map rematch is required, exchanging players is not allowed. With the referee's discretion, an exception can be made if the previous roster is unavailable to play.**
12. Lag is not a valid reason to nullify a beatmap.
13. All players are supposed to keep the match running fluently and without delays. Penalties can be issued to the players if they cause excessive match delays.
14. If a player disconnects between maps and the team cannot provide a replacement, the match can be delayed 10 minutes at maximum.
15. All players and referees must be treated with respect. Instructions of the referees and tournament Management are to be followed. Decisions labeled as final are not to be objected.
16. Disrupting the match by foul play, insulting and provoking other players or referees, delaying the match or other deliberate inappropriate misbehavior is strictly prohibited.
17. The multiplayer chatrooms are subject to the [osu! community rules](/wiki/Rules).
- Breaking the chat rules will result in a silence. Silenced players can not participate in multiplayer matches and must be exchanged for the time being.
18. **The seeding method will be revealed after all the teams have played their Qualifier rounds.**
19. Unexpected incidents are handled by the tournament management. Referees may allow higher tolerance depending on the circumstances. This is up to their discretion.
20. Penalties for violating the tournament rules may include:
- Exclusion of specific players for one beatmap
- Exclusion of specific players for an entire match
- Declaring the match as Lost by Default
- Disqualification from the entire tournament
- Disqualification from the current and future official tournaments until appealed
- Any modification of these rules will be announced.";
});
}
}
}

View File

@ -0,0 +1,82 @@
// 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 Markdig;
using Markdig.Extensions.AutoIdentifiers;
using Markdig.Extensions.Tables;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownContainer : MarkdownContainer
{
public OsuMarkdownContainer()
{
LineSpacing = 21;
}
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
{
switch (markdownObject)
{
case YamlFrontMatterBlock _:
// Don't parse YAML Frontmatter
break;
case ListItemBlock listItemBlock:
var isOrdered = ((ListBlock)listItemBlock.Parent).IsOrdered;
var childContainer = CreateListItem(listItemBlock, level, isOrdered);
container.Add(childContainer);
foreach (var single in listItemBlock)
base.AddMarkdownComponent(single, childContainer.Content, level);
break;
default:
base.AddMarkdownComponent(markdownObject, container, level);
break;
}
}
public override SpriteText CreateSpriteText() => new OsuSpriteText
{
Font = OsuFont.GetFont(size: 14),
};
public override MarkdownTextFlowContainer CreateTextFlow() => new OsuMarkdownTextFlowContainer();
protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new OsuMarkdownHeading(headingBlock);
protected override MarkdownFencedCodeBlock CreateFencedCodeBlock(FencedCodeBlock fencedCodeBlock) => new OsuMarkdownFencedCodeBlock(fencedCodeBlock);
protected override MarkdownSeparator CreateSeparator(ThematicBreakBlock thematicBlock) => new OsuMarkdownSeparator();
protected override MarkdownQuoteBlock CreateQuoteBlock(QuoteBlock quoteBlock) => new OsuMarkdownQuoteBlock(quoteBlock);
protected override MarkdownTable CreateTable(Table table) => new OsuMarkdownTable(table);
protected override MarkdownList CreateList(ListBlock listBlock) => new MarkdownList
{
Padding = new MarginPadding(0)
};
protected virtual OsuMarkdownListItem CreateListItem(ListItemBlock listItemBlock, int level, bool isOrdered)
{
if (isOrdered)
return new OsuMarkdownOrderedListItem(listItemBlock.Order);
return new OsuMarkdownUnorderedListItem(level);
}
protected override MarkdownPipeline CreateBuilder()
=> new MarkdownPipelineBuilder().UseAutoIdentifiers(AutoIdentifierOptions.GitHub)
.UseEmojiAndSmiley()
.UseYamlFrontMatter()
.UseAdvancedExtensions().Build();
}
}

View File

@ -0,0 +1,45 @@
// 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 Markdig.Syntax;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownFencedCodeBlock : MarkdownFencedCodeBlock
{
// TODO : change to monospace font for this component
public OsuMarkdownFencedCodeBlock(FencedCodeBlock fencedCodeBlock)
: base(fencedCodeBlock)
{
}
protected override Drawable CreateBackground() => new CodeBlockBackground();
public override MarkdownTextFlowContainer CreateTextFlow() => new CodeBlockTextFlowContainer();
private class CodeBlockBackground : Box
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.Both;
Colour = colourProvider.Background6;
}
}
private class CodeBlockTextFlowContainer : OsuMarkdownTextFlowContainer
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Colour = colourProvider.Light1;
Margin = new MarginPadding(10);
}
}
}
}

View File

@ -0,0 +1,75 @@
// 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 Markdig.Syntax;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownHeading : MarkdownHeading
{
private readonly int level;
public OsuMarkdownHeading(HeadingBlock headingBlock)
: base(headingBlock)
{
level = headingBlock.Level;
}
public override MarkdownTextFlowContainer CreateTextFlow() => new HeadingTextFlowContainer
{
Weight = GetFontWeightByLevel(level),
};
protected override float GetFontSizeByLevel(int level)
{
// Reference for this font size
// https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/bem/osu-md.less#L9
// https://github.com/ppy/osu-web/blob/376cac43a051b9c85ce95e2c446099be187b3e45/resources/assets/less/variables.less#L161
const float base_font_size = 14;
switch (level)
{
case 1:
return 30 / base_font_size;
case 2:
return 26 / base_font_size;
case 3:
return 20 / base_font_size;
case 4:
return 18 / base_font_size;
case 5:
return 16 / base_font_size;
default:
return 1;
}
}
protected virtual FontWeight GetFontWeightByLevel(int level)
{
switch (level)
{
case 1:
case 2:
return FontWeight.SemiBold;
default:
return FontWeight.Bold;
}
}
private class HeadingTextFlowContainer : OsuMarkdownTextFlowContainer
{
public FontWeight Weight { get; set; }
protected override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: Weight));
}
}
}

View File

@ -0,0 +1,48 @@
// 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 Markdig.Syntax.Inlines;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownLinkText : MarkdownLinkText
{
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
private SpriteText spriteText;
public OsuMarkdownLinkText(string text, LinkInline linkInline)
: base(text, linkInline)
{
}
[BackgroundDependencyLoader]
private void load()
{
spriteText.Colour = colourProvider.Light2;
}
public override SpriteText CreateSpriteText()
{
return spriteText = base.CreateSpriteText();
}
protected override bool OnHover(HoverEvent e)
{
spriteText.Colour = colourProvider.Light1;
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
spriteText.Colour = colourProvider.Light2;
base.OnHoverLost(e);
}
}
}

View File

@ -0,0 +1,44 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Sprites;
using osuTK;
namespace osu.Game.Graphics.Containers.Markdown
{
public abstract class OsuMarkdownListItem : CompositeDrawable
{
[Resolved]
private IMarkdownTextComponent parentTextComponent { get; set; }
public FillFlowContainer Content { get; private set; }
protected OsuMarkdownListItem()
{
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
CreateMarker(),
Content = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(10, 10),
}
};
}
protected virtual SpriteText CreateMarker() => parentTextComponent.CreateSpriteText();
}
}

View File

@ -0,0 +1,27 @@
// 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 osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownOrderedListItem : OsuMarkdownListItem
{
private const float left_padding = 30;
private readonly int order;
public OsuMarkdownOrderedListItem(int order)
{
this.order = order;
Padding = new MarginPadding { Left = left_padding };
}
protected override SpriteText CreateMarker() => base.CreateMarker().With(t =>
{
t.X = -left_padding;
t.Text = $"{order}.";
});
}
}

View File

@ -0,0 +1,44 @@
// 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 Markdig.Syntax;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownQuoteBlock : MarkdownQuoteBlock
{
public OsuMarkdownQuoteBlock(QuoteBlock quoteBlock)
: base(quoteBlock)
{
}
protected override Drawable CreateBackground() => new QuoteBackground();
public override MarkdownTextFlowContainer CreateTextFlow()
{
return base.CreateTextFlow().With(f => f.Margin = new MarginPadding
{
Vertical = 10,
Horizontal = 20,
});
}
private class QuoteBackground : Box
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
RelativeSizeAxes = Axes.Y;
Width = 2;
Colour = colourProvider.Content2;
}
}
}
}

View File

@ -0,0 +1,27 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownSeparator : MarkdownSeparator
{
protected override Drawable CreateSeparator() => new Separator();
private class Separator : Box
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.X;
Height = 1;
Colour = colourProvider.Background3;
}
}
}
}

View File

@ -0,0 +1,18 @@
// 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 Markdig.Extensions.Tables;
using osu.Framework.Graphics.Containers.Markdown;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownTable : MarkdownTable
{
public OsuMarkdownTable(Table table)
: base(table)
{
}
protected override MarkdownTableCell CreateTableCell(TableCell cell, TableColumnDefinition definition, bool isHeading) => new OsuMarkdownTableCell(cell, definition, isHeading);
}
}

View File

@ -0,0 +1,77 @@
// 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 Markdig.Extensions.Tables;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownTableCell : MarkdownTableCell
{
private readonly bool isHeading;
public OsuMarkdownTableCell(TableCell cell, TableColumnDefinition definition, bool isHeading)
: base(cell, definition)
{
this.isHeading = isHeading;
Masking = false;
BorderThickness = 0;
}
[BackgroundDependencyLoader]
private void load()
{
AddInternal(CreateBorder(isHeading));
}
public override MarkdownTextFlowContainer CreateTextFlow() => new TableCellTextFlowContainer
{
Weight = isHeading ? FontWeight.Bold : FontWeight.Regular,
Padding = new MarginPadding(10),
};
protected virtual Box CreateBorder(bool isHeading)
{
if (isHeading)
return new TableHeadBorder();
return new TableBodyBorder();
}
private class TableHeadBorder : Box
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Colour = colourProvider.Background3;
RelativeSizeAxes = Axes.X;
Height = 2;
Anchor = Anchor.BottomLeft;
Origin = Anchor.BottomLeft;
}
}
private class TableBodyBorder : Box
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Colour = colourProvider.Background4;
RelativeSizeAxes = Axes.X;
Height = 1;
}
}
private class TableCellTextFlowContainer : OsuMarkdownTextFlowContainer
{
public FontWeight Weight { get; set; }
protected override SpriteText CreateSpriteText() => base.CreateSpriteText().With(t => t.Font = t.Font.With(weight: Weight));
}
}
}

View File

@ -0,0 +1,28 @@
// 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 Markdig.Syntax.Inlines;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownTextFlowContainer : MarkdownTextFlowContainer
{
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
protected override void AddLinkText(string text, LinkInline linkInline)
=> AddDrawable(new OsuMarkdownLinkText(text, linkInline));
// TODO : Add background (colour B6) and change font to monospace
protected override void AddCodeInLine(CodeInline codeInline)
=> AddText(codeInline.Content, t => { t.Colour = colourProvider.Light1; });
protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic)
=> CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic));
}
}

View File

@ -0,0 +1,51 @@
// 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 osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Graphics.Containers.Markdown
{
public class OsuMarkdownUnorderedListItem : OsuMarkdownListItem
{
private const float left_padding = 20;
private readonly int level;
public OsuMarkdownUnorderedListItem(int level)
{
this.level = level;
Padding = new MarginPadding { Left = left_padding };
}
protected override SpriteText CreateMarker() => base.CreateMarker().With(t =>
{
t.Text = GetTextMarker(level);
t.Font = t.Font.With(size: t.Font.Size / 2);
t.Origin = Anchor.Centre;
t.X = -left_padding / 2;
t.Y = t.Font.Size;
});
/// <summary>
/// Get text marker based on <paramref name="level"/>
/// </summary>
/// <param name="level">The markdown level of current list item.</param>
/// <returns>The marker string of this list item</returns>
protected virtual string GetTextMarker(int level)
{
switch (level)
{
case 1:
return "●";
case 2:
return "○";
default:
return "■";
}
}
}
}

View File

@ -6,13 +6,14 @@ using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Input.Bindings;
namespace osu.Game.Input
{
/// <summary>
/// Track whether the end-user is in an idle state, based on their last interaction with the game.
/// </summary>
public class IdleTracker : Component, IKeyBindingHandler<PlatformAction>, IHandleGlobalKeyboardInput
public class IdleTracker : Component, IKeyBindingHandler<PlatformAction>, IKeyBindingHandler<GlobalAction>, IHandleGlobalKeyboardInput
{
private readonly double timeToIdle;
@ -58,6 +59,10 @@ namespace osu.Game.Input
public void OnReleased(PlatformAction action) => updateLastInteractionTime();
public bool OnPressed(GlobalAction action) => updateLastInteractionTime();
public void OnReleased(GlobalAction action) => updateLastInteractionTime();
protected override bool Handle(UIEvent e)
{
switch (e)

View File

@ -69,13 +69,15 @@ namespace osu.Game.Overlays.Toolbar
base.LoadComplete();
Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true);
Current.BindValueChanged(_ => moveLineToCurrent(), true);
Current.BindValueChanged(_ => moveLineToCurrent());
// Scheduled to allow the button flow layout to be computed before the line position is updated
ScheduleAfterChildren(moveLineToCurrent);
}
private bool hasInitialPosition;
// Scheduled to allow the flow layout to be computed before the line position is updated
private void moveLineToCurrent() => ScheduleAfterChildren(() =>
private void moveLineToCurrent()
{
if (SelectedTab != null)
{
@ -86,7 +88,7 @@ namespace osu.Game.Overlays.Toolbar
hasInitialPosition = true;
}
});
}
public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput;

View File

@ -111,7 +111,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
}
}
private Container dragHandles;
private SelectionBoxDragHandleContainer dragHandles;
private FillFlowContainer buttons;
private OsuSpriteText selectionDetailsText;
@ -195,7 +195,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
},
}
},
dragHandles = new Container
dragHandles = new SelectionBoxDragHandleContainer
{
RelativeSizeAxes = Axes.Both,
// ensures that the centres of all drag handles line up with the middle of the selection box border.
@ -220,75 +220,76 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void addRotationComponents()
{
const float separation = 40;
addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90));
addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90));
AddRangeInternal(new Drawable[]
{
new Box
{
Depth = float.MaxValue,
Colour = colours.YellowLight,
Blending = BlendingParameters.Additive,
Alpha = 0.3f,
Size = new Vector2(BORDER_RADIUS, separation),
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
},
new SelectionBoxDragHandleButton(FontAwesome.Solid.Redo, "Free rotate")
{
Anchor = Anchor.TopCentre,
Y = -separation,
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e)),
OperationStarted = operationStarted,
OperationEnded = operationEnded
}
});
addRotateHandle(Anchor.TopLeft);
addRotateHandle(Anchor.TopRight);
addRotateHandle(Anchor.BottomLeft);
addRotateHandle(Anchor.BottomRight);
}
private void addYScaleComponents()
{
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical));
addDragHandle(Anchor.TopCentre);
addDragHandle(Anchor.BottomCentre);
addScaleHandle(Anchor.TopCentre);
addScaleHandle(Anchor.BottomCentre);
}
private void addFullScaleComponents()
{
addDragHandle(Anchor.TopLeft);
addDragHandle(Anchor.TopRight);
addDragHandle(Anchor.BottomLeft);
addDragHandle(Anchor.BottomRight);
addScaleHandle(Anchor.TopLeft);
addScaleHandle(Anchor.TopRight);
addScaleHandle(Anchor.BottomLeft);
addScaleHandle(Anchor.BottomRight);
}
private void addXScaleComponents()
{
addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal));
addDragHandle(Anchor.CentreLeft);
addDragHandle(Anchor.CentreRight);
addScaleHandle(Anchor.CentreLeft);
addScaleHandle(Anchor.CentreRight);
}
private void addButton(IconUsage icon, string tooltip, Action action)
{
buttons.Add(new SelectionBoxDragHandleButton(icon, tooltip)
var button = new SelectionBoxButton(icon, tooltip)
{
OperationStarted = operationStarted,
OperationEnded = operationEnded,
Action = action
});
};
button.OperationStarted += operationStarted;
button.OperationEnded += operationEnded;
buttons.Add(button);
}
private void addDragHandle(Anchor anchor) => dragHandles.Add(new SelectionBoxDragHandle
private void addScaleHandle(Anchor anchor)
{
var handle = new SelectionBoxScaleHandle
{
Anchor = anchor,
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor),
OperationStarted = operationStarted,
OperationEnded = operationEnded
});
HandleDrag = e => OnScale?.Invoke(e.Delta, anchor)
};
handle.OperationStarted += operationStarted;
handle.OperationEnded += operationEnded;
dragHandles.AddScaleHandle(handle);
}
private void addRotateHandle(Anchor anchor)
{
var handle = new SelectionBoxRotationHandle
{
Anchor = anchor,
HandleDrag = e => OnRotation?.Invoke(convertDragEventToAngleOfRotation(e))
};
handle.OperationStarted += operationStarted;
handle.OperationEnded += operationEnded;
dragHandles.AddRotationHandle(handle);
}
private int activeOperations;

View File

@ -12,10 +12,7 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// A drag "handle" which shares the visual appearance but behaves more like a clickable button.
/// </summary>
public sealed class SelectionBoxDragHandleButton : SelectionBoxDragHandle, IHasTooltip
public sealed class SelectionBoxButton : SelectionBoxControl, IHasTooltip
{
private SpriteIcon icon;
@ -23,7 +20,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
public Action Action;
public SelectionBoxDragHandleButton(IconUsage iconUsage, string tooltip)
public SelectionBoxButton(IconUsage iconUsage, string tooltip)
{
this.iconUsage = iconUsage;
@ -36,7 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
[BackgroundDependencyLoader]
private void load()
{
Size *= 2;
Size = new Vector2(20);
AddInternal(icon = new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
@ -49,16 +46,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnClick(ClickEvent e)
{
OperationStarted?.Invoke();
TriggerOperationStarted();
Action?.Invoke();
OperationEnded?.Invoke();
TriggerOperatoinEnded();
return true;
}
protected override void UpdateHoverState()
{
base.UpdateHoverState();
icon.Colour = !HandlingMouse && IsHovered ? Color4.White : Color4.Black;
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
}
public string TooltipText { get; }

View File

@ -0,0 +1,97 @@
// 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;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// Represents the base appearance for UI controls of the <see cref="SelectionBox"/>,
/// such as scale handles, rotation handles, buttons, etc...
/// </summary>
public abstract class SelectionBoxControl : CompositeDrawable
{
public const double TRANSFORM_DURATION = 100;
public event Action OperationStarted;
public event Action OperationEnded;
private Circle circle;
/// <summary>
/// Whether the user is currently holding the control with mouse.
/// </summary>
public bool IsHeld { get; private set; }
[Resolved]
protected OsuColour Colours { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
circle = new Circle
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
UpdateHoverState();
FinishTransforms(true);
}
protected override bool OnHover(HoverEvent e)
{
UpdateHoverState();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
UpdateHoverState();
}
protected override bool OnMouseDown(MouseDownEvent e)
{
IsHeld = true;
UpdateHoverState();
return true;
}
protected override void OnMouseUp(MouseUpEvent e)
{
IsHeld = false;
UpdateHoverState();
}
protected virtual void UpdateHoverState()
{
if (IsHeld)
circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint);
else
circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint);
this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint);
}
protected void TriggerOperationStarted() => OperationStarted?.Invoke();
protected void TriggerOperatoinEnded() => OperationEnded?.Invoke();
}
}

View File

@ -2,75 +2,17 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
public class SelectionBoxDragHandle : Container
public abstract class SelectionBoxDragHandle : SelectionBoxControl
{
public Action OperationStarted;
public Action OperationEnded;
public Action<DragEvent> HandleDrag { get; set; }
private Circle circle;
[Resolved]
private OsuColour colours { get; set; }
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(10);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
circle = new Circle
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
UpdateHoverState();
}
protected override bool OnHover(HoverEvent e)
{
UpdateHoverState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
UpdateHoverState();
}
protected bool HandlingMouse;
protected override bool OnMouseDown(MouseDownEvent e)
{
HandlingMouse = true;
UpdateHoverState();
return true;
}
protected override bool OnDragStart(DragStartEvent e)
{
OperationStarted?.Invoke();
TriggerOperationStarted();
return true;
}
@ -82,24 +24,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override void OnDragEnd(DragEndEvent e)
{
HandlingMouse = false;
OperationEnded?.Invoke();
TriggerOperatoinEnded();
UpdateHoverState();
base.OnDragEnd(e);
}
protected override void OnMouseUp(MouseUpEvent e)
#region Internal events for SelectionBoxDragHandleContainer
internal event Action HoverGained;
internal event Action HoverLost;
internal event Action MouseDown;
internal event Action MouseUp;
protected override bool OnHover(HoverEvent e)
{
HandlingMouse = false;
UpdateHoverState();
base.OnMouseUp(e);
bool result = base.OnHover(e);
HoverGained?.Invoke();
return result;
}
protected virtual void UpdateHoverState()
protected override void OnHoverLost(HoverLostEvent e)
{
circle.Colour = HandlingMouse ? colours.GrayF : (IsHovered ? colours.Red : colours.YellowDark);
this.ScaleTo(HandlingMouse || IsHovered ? 1.5f : 1, 100, Easing.OutQuint);
base.OnHoverLost(e);
HoverLost?.Invoke();
}
protected override bool OnMouseDown(MouseDownEvent e)
{
bool result = base.OnMouseDown(e);
MouseDown?.Invoke();
return result;
}
protected override void OnMouseUp(MouseUpEvent e)
{
base.OnMouseUp(e);
MouseUp?.Invoke();
}
#endregion
}
}

View File

@ -0,0 +1,109 @@
// 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.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// Represents a display composite containing and managing the visibility state of the selection box's drag handles.
/// </summary>
public class SelectionBoxDragHandleContainer : CompositeDrawable
{
private Container<SelectionBoxScaleHandle> scaleHandles;
private Container<SelectionBoxRotationHandle> rotationHandles;
private readonly List<SelectionBoxDragHandle> allDragHandles = new List<SelectionBoxDragHandle>();
public new MarginPadding Padding
{
get => base.Padding;
set => base.Padding = value;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
scaleHandles = new Container<SelectionBoxScaleHandle>
{
RelativeSizeAxes = Axes.Both,
},
rotationHandles = new Container<SelectionBoxRotationHandle>
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-12.5f),
},
};
}
public void AddScaleHandle(SelectionBoxScaleHandle handle)
{
bindDragHandle(handle);
scaleHandles.Add(handle);
}
public void AddRotationHandle(SelectionBoxRotationHandle handle)
{
handle.Alpha = 0;
handle.AlwaysPresent = true;
bindDragHandle(handle);
rotationHandles.Add(handle);
}
private void bindDragHandle(SelectionBoxDragHandle handle)
{
handle.HoverGained += updateRotationHandlesVisibility;
handle.HoverLost += updateRotationHandlesVisibility;
handle.MouseDown += updateRotationHandlesVisibility;
handle.MouseUp += updateRotationHandlesVisibility;
allDragHandles.Add(handle);
}
private SelectionBoxRotationHandle displayedRotationHandle;
private SelectionBoxDragHandle activeHandle;
private void updateRotationHandlesVisibility()
{
// if the active handle is a rotation handle and is held or hovered,
// then no need to perform any updates to the rotation handles visibility.
if (activeHandle is SelectionBoxRotationHandle && (activeHandle?.IsHeld == true || activeHandle?.IsHovered == true))
return;
displayedRotationHandle?.FadeOut(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
displayedRotationHandle = null;
// if the active handle is not a rotation handle but is held, then keep the rotation handle hidden.
if (activeHandle?.IsHeld == true)
return;
activeHandle = rotationHandles.SingleOrDefault(h => h.IsHeld || h.IsHovered);
activeHandle ??= allDragHandles.SingleOrDefault(h => h.IsHovered);
if (activeHandle != null)
{
displayedRotationHandle = getCorrespondingRotationHandle(activeHandle, rotationHandles);
displayedRotationHandle?.FadeIn(SelectionBoxControl.TRANSFORM_DURATION, Easing.OutQuint);
}
}
/// <summary>
/// Gets the rotation handle corresponding to the given handle.
/// </summary>
[CanBeNull]
private static SelectionBoxRotationHandle getCorrespondingRotationHandle(SelectionBoxDragHandle handle, IEnumerable<SelectionBoxRotationHandle> rotationHandles)
{
if (handle is SelectionBoxRotationHandle rotationHandle)
return rotationHandle;
return rotationHandles.SingleOrDefault(r => r.Anchor == handle.Anchor);
}
}
}

View File

@ -0,0 +1,42 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components
{
public class SelectionBoxRotationHandle : SelectionBoxDragHandle
{
private SpriteIcon icon;
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(15f);
AddInternal(icon = new SpriteIcon
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Redo,
Scale = new Vector2
{
X = Anchor.HasFlagFast(Anchor.x0) ? 1f : -1f,
Y = Anchor.HasFlagFast(Anchor.y0) ? 1f : -1f
}
});
}
protected override void UpdateHoverState()
{
base.UpdateHoverState();
icon.FadeColour(!IsHeld && IsHovered ? Color4.White : Color4.Black, TRANSFORM_DURATION, Easing.OutQuint);
}
}
}

View File

@ -0,0 +1,17 @@
// 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 osu.Framework.Allocation;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
public class SelectionBoxScaleHandle : SelectionBoxDragHandle
{
[BackgroundDependencyLoader]
private void load()
{
Size = new Vector2(10);
}
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@ -9,6 +10,7 @@ using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
namespace osu.Game.Screens.Play
@ -33,9 +35,8 @@ namespace osu.Game.Screens.Play
protected override void LoadAsyncComplete()
{
if (!handleTokenRetrieval()) return;
base.LoadAsyncComplete();
handleTokenRetrieval();
}
private bool handleTokenRetrieval()
@ -43,6 +44,12 @@ namespace osu.Game.Screens.Play
// Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request.
var tcs = new TaskCompletionSource<bool>();
if (Mods.Value.Any(m => m is ModAutoplay))
{
handleTokenFailure(new InvalidOperationException("Autoplay loaded."));
return false;
}
if (!api.IsLoggedIn)
{
handleTokenFailure(new InvalidOperationException("API is not online."));

View File

@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.507.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.510.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
<PackageReference Include="Sentry" Version="3.3.4" />
<PackageReference Include="SharpCompress" Version="0.28.2" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.507.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.510.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2021.507.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.510.0" />
<PackageReference Include="SharpCompress" Version="0.28.2" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />