This commit rearranges the contents of `ShearedButtons` to be more
independent of each other in regards to sizing. Thanks to that, the
custom logic related to enabling autosizing is no longer necessary.
Witdh and height are no longer set via the constructor, and can be
freely configured using the initializer syntax.
Additionally, this allows the button to use relative sizing without
having to resort to any hackery with `Size` (this will come in handy for
me when implementing the new footer on multiplayer screens).
Given that most of the `ShearedButton`s currently in use set their width
explicitly, I did not set `AutoSizeAxes = Axes.X` like it would be by
default previously. Instead it is set on the only two such buttons (show
converts/selected mods on ssv2). I suppose it might be a good idea to
have it set that by default if no `Width` is specified, as right now
it'll just not show anything.
Also I've set the margin on the text field by default in all cases
instead of only when autosizing like how it was previously, since
otherwise it would be a pain to set that on each button instance when
needed. I've checked all affected components and could not find any text
overflowing issues that this could cause.
---------
Co-authored-by: Dean Herbert <pe@ppy.sh>
The reproduction scenario for the subscription leak is as follows:
1. Switch to a scrolling ruleset (anything but osu! from the standard
ones).
2. Select a beatmap to edit.
3. Load the composer.
4. Go to timing tab.
5. Change a timing point.
6. Go back to the composer.
At this point, `EditorChangeHandler.OnStateChange` will have multiple of
the same delegate in the invocation list.
<img width="691" height="311" alt="Screenshot 2026-03-05 at 11 15 55"
src="https://github.com/user-attachments/assets/57788341-9573-48f1-b360-f21036891081"
/>
That in turn is caused by the fact that changing a timing point *does*
incur a full reload of the composer via the following flow:
https://github.com/ppy/osu/blob/15b6e28ebe888b1a87574891be1a0db3b04093b7/osu.Game/Rulesets/Edit/ScrollingHitObjectComposer.cs#L145https://github.com/ppy/osu/blob/64a29313a852d50095ae4b7ea8f22fde23aa634f/osu.Game/Screens/Edit/Editor.cs#L1137-L1145
This flow is my "fault"; see https://github.com/ppy/osu/pull/28444. The
reason why a full composer reload is used is not clear to my
recollection at this time, but it is likely because it's just the least
likely to fail. A smarter solution that wouldn't require a full reload
would also entail checking that there exists a safe insertion point that
allows replacing timing points in a way that will reflect everywhere it
must. Including all of the `IScrollingAlgorithm` machinery and such.
In general it is not uncommon in the codebase to not bother to clean up
some event callbacks if it is implicitly or explicitly guaranteed that
both objects bound by the callback will always get disposed in tandem at
the same time. This *was* true with this particular flow to a point,
which was until that full composer reload was implemented.
<details>
<summary>To address the elephant in the room</summary>
Someone will inevitably notice https://github.com/ppy/osu/pull/36824
which was a clanked pull request pointing out this leak. And then
someone will inevitably call this "AI discrimination"! *Gasp!*
So first of all, let me stop you right there. Yes, as far as I am
_personally_ concerned, it is "AI discrimination". I invoke the full
force of the Butlerian Jihad.
The clank army's goal is to eradicate my job and make me work in an
Amazon warehouse instead. Or, if not that, at least my job is to be rid
of all remnants of fun I still get from it and for me to be reduced to
that one guy from the meme "i guess we're doin circles now". You know
the one.
I resent this. You attack me directly. I do not perceive the need to
meet you halfway or be civil.
That said, I have too much respect for the users of this software to
leave reports of potentially real issues unchecked. So I did check, and
it was real. And you know what? Good job to the clanker. It did what it
was designed to do: it parsed a code file, recognised a hole in a
pattern it was designed to recognise, and invoked forms of language
given to it to communicate this to the meatbag that opened that PR.
And here's the thing: my primary issue is with that meatbag that opened
that PR. That meatbag served no functional purpose in any of this. The
meatbag took a hose that spews 90% water and 10% raw sewage at random
intervals and pointed it at my house directly, claiming that they just
want to clean it. At no point did the meatbag appear to have the common
decency to pull out a container, pour some magic liquid out, check if
there's sewage in it, and filter it out if there is any. But no, that
would take *effort* and *thought*, would it not? The *effort* and
*thought* that is required of *me* to *review* the clanker's work?
The PR had no reproduction scenario, and had testing checkboxes that
were presumably meant for *me* to check off. Why is it *my* job to
figure all of this out rather than the submitter meatbag's?
I do *not* have obligations towards spew-hose-pointing meatbags. Point
that hose at your own backyard at your peril.
If you *actually manage* to get the clanker to filter out *all* of the
spew without fail itself, my only win condition is gone. But it is not
yet that time. So at least have the decency to check for the spew
yourself, rather than telling the clanker to put checkboxes in the PR
descriptions telling *me* to check for it.
</details>
Closes https://github.com/ppy/osu/issues/36453.
My omission was in assuming that web was going to start filtering out
the tags below the threshold from API responses, which is not the case.
Whether or not the consumers want or not to display tags below threshold
is subjective. I figured that the matchmaking card tooltip might want to
display below threshold but I dunno.
Regressed in 60d9c358b8.
In general an `APIBeatmap`'s `BeatmapSet` need not be present. In the
usage site of `osu.Game.Overlays.BeatmapSet.Info`, `Beatmap` and
`BeatmapSet` were actually two separate bindables. Moving the logic to a
helper, and therefore implicitly moving `BeatmapSet` from tracking said
separate bindable to instead refer to `Beatmap.BeatmapSet` broke this.
Resolves#36099
This PR fixes keyboard navigation in the beatmap select carousel for
lazer by implementing page-wise traversal with the Page Up and Page Down
keys and changing it from only scrolling to actually selecting items.
**Changes:**
- Added handling for `TraversalType.Page` in the keyboard traversal
switch.
- Implemented `traverseKeyboardPage(int direction)` method to move the
selection by approximately one "page" of visible items, accounting for
partially obscured items like the search bar. Also it does not wrap
around (like the current PageUp/Down functionality).
- Added new key bindings:
- `PageUp` → SelectPreviousPage
- `PageDown` → SelectNextPage
The code may be very explicit for the scroll logic with the page keys,
so I would appreciate some feedback when the PR is reviewed.
The naming of the keybinds may need to be adjusted. `Next page` and
`previous page` may be somewhat misleading.
**Behavior after the change:**
- Pressing Page Up/Down now moves the selection by a page of items.
- After navigating, pressing Left/Right selects the navigated song
instead of moving relative to the previous position.
**See:**
https://www.youtube.com/watch?v=JXmKAhhKiCc
---------
Signed-off-by: Linus Genz <linuslinuxgenz@gmail.com>
Co-authored-by: Dean Herbert <pe@ppy.sh>
Part of the `ScreenFooter` refactor, which intends to move the footer
content handling to `OsuScreen`. This commit updates the `ScreenFooter`
test to operate on entire `OsuScreen`s, in order to better test the
entire flow of pushing a screen, and having it create and add its own
content to the footer.
This should be 80-90% identical to the original test case structure
wise, just that instead of manually manipulating the footer with
`SetButtons()`, various screens with the appropriate buttons are being
moved around the screen stack.
Additionally this adds some more tests handling common use cases, as
well as removes `TestLoadOverlayAfterFooterIsDisplayed()`, since as far
as I understand the behaviour described in it doesn't actually happen in
production code. From what I can see, Screens instantiate their overlays
in `load()`, and then register them in `LoadComplete()`. There seems to
be no case where a `ShearedOverlayContainer` is created in the middle of
a screen's lifecycle.
Part of the screen footer refactor.
Once footer content is being managed by `OsuScreen`, the current tests
which simply create the tested overlay and `ScreenFooter` in a container
will no longer work.
This PR refactors them to use `ScreenTestScene` with the setup being
creating a dedicated testing `OsuScreen` which does the bare minimum to
create the tested overlay and necessary components (eg.
`FooterButtonFreeModsV2` for `TestSceneFreeModsOverlay`).
Most of the changes here can be described as
`%s/<...>Overlay/screen.Overlay/g`, with some minor touchups as
necessary, given that we're now testing a more complete flow which
checks more things that were previously not handled by the tests.
## [Move footer to front in
ScreenTestScene](https://github.com/ppy/osu/commit/f8740e0403b3c0badd60d394c737f2aa912a9ed6)
Self-explanatory. Without it the footer would show below the actual
overlay, breaking tests depending on manual input. For the sake of tests
not breaking in CI, both #36718 and this have this included - would
prefer the former to be merged first since it was already reviewed
there.
## `TestSceneModSelectOverlay`
There were a few tests (`TestColumnHidingOnIsValidChange`,
`TestColumnHidingOnTextFilterChange`, and
`TestHidingOverlayClearsTextSearch`) that would create a custom overlay
instance instead of the globally provided one. I've tested both and the
tests run fine with the default overlay, so they're now using that
instead.
## `TestSceneFreeModSelectOverlay`
Updated to use footer v2.
---------
Co-authored-by: Dean Herbert <pe@ppy.sh>
Rebase of https://github.com/smoogipoo/osu/pull/193
Going forward, the client will have to know the type of pool being
invited to so that it can enter the appropriate screen when clicking the
notification.
Unfortunately, SignalR does not support overloading methods, or even
adding parameters to them, so this PR deprecates the
`MatchmakingRoomInvited` event and adds its replacement
`MatchmakingRoomInvitedWithParams` with a complex `invitation` parameter
that we _can_ extend in the future if required (such as potentially
adding the name of the pool).
This also prepares the notification by extracting some code to a
`Complete` method receiving said `invitation` parameter. This part of
code will be further modified to enter the correct screen:
https://github.com/smoogipoo/osu/blob/0a4018045b9d908f66c63dee65d0059d05b26e43/osu.Game/Screens/OnlinePlay/Matchmaking/Queue/QueueController.cs#L200
In particular, I have tested that new clients continue to work with the
old server (dev.ppy.sh) in quick play
| | Old Server | New Server |
| ------------- |:-------------:| :-----:|
| Old Client | 🟢 | 🟢 |
| New Client | 🟢 | 🟢 |