1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-17 06:32:36 +08:00
Files
osu-lazer/osu.Game/Configuration/OsuConfigManager.cs
T
Susko3 9b3dba939e Add pen input settings (#37500)
- depends on https://github.com/ppy/osu-framework/pull/6737

Adds simple input settings section for pens that allows disabling the
handler and adjusting sensitivity. The section appears in-between Tablet
and Touch, and only on SDL3 (desktop and mobile). The pen sensitivity is
completely independent from mouse sensitivity.

<img width="537" height="149" alt="image"
src="https://github.com/user-attachments/assets/448eebba-84ea-4daf-8428-3bd07739bd6f"
/>

<br>

Keep in mind that the "Confine mouse cursor to window" mouse setting
also affects pens, feel free to suggest UX improvements. Also, toggling
"High precision mouse" might affect pens on certain configurations.

Edit: added image with updated header. Previously, it was "Device: Pen".

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
2026-04-28 20:18:30 +09:00

516 lines
21 KiB
C#

// 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 System.Linq;
using osu.Framework;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Input.Handlers.Mouse;
using osu.Framework.Input.Handlers.Pen;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays;
using osu.Game.Overlays.Dashboard.Friends;
using osu.Game.Overlays.Mods.Input;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
using osu.Game.Skinning;
using osu.Game.Users;
namespace osu.Game.Configuration
{
public class OsuConfigManager : IniConfigManager<OsuSetting>, IGameplaySettings
{
private readonly GameHost? host;
public OsuConfigManager(Storage storage, GameHost? host = null)
: base(storage)
{
this.host = host;
Migrate();
}
protected override void InitialiseDefaults()
{
// UI/selection defaults
SetDefault(OsuSetting.Ruleset, string.Empty);
SetDefault(OsuSetting.Skin, SkinInfo.ARGON_SKIN.ToString());
SetDefault(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Local);
SetDefault(OsuSetting.BeatmapLeaderboardSortMode, LeaderboardSortMode.Score);
SetDefault(OsuSetting.BeatmapDetailModsFilter, false);
SetDefault(OsuSetting.ShowConvertedBeatmaps, true);
SetDefault(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
SetDefault(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1);
SetDefault(OsuSetting.SongSelectGroupMode, GroupMode.None);
SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title);
SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential);
SetDefault(OsuSetting.ModSelectTextSearchStartsActive, true);
SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f, 0.01f);
SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal);
SetDefault(OsuSetting.BeatmapListingFeaturedArtistFilter, true);
SetDefault(OsuSetting.ProfileCoverExpanded, true);
SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full);
SetDefault(OsuSetting.SongSelectBackgroundBlur, false);
// Online settings
SetDefault(OsuSetting.Username, string.Empty);
SetDefault(OsuSetting.Token, string.Empty);
SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, true);
SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled =>
{
if (enabled.NewValue)
SetValue(OsuSetting.SaveUsername, true);
else
GetBindable<string>(OsuSetting.Token).SetDefault();
};
SetDefault(OsuSetting.SaveUsername, true).ValueChanged += enabled =>
{
if (!enabled.NewValue)
{
GetBindable<string>(OsuSetting.Username).SetDefault();
SetValue(OsuSetting.SavePassword, false);
}
};
SetDefault(OsuSetting.ExternalLinkWarning, true);
SetDefault(OsuSetting.PreferNoVideo, false);
SetDefault(OsuSetting.ShowOnlineExplicitContent, false);
SetDefault(OsuSetting.NotifyOnUsernameMentioned, true);
SetDefault(OsuSetting.NotifyOnPrivateMessage, true);
SetDefault(OsuSetting.NotifyOnFriendPresenceChange, true);
// Audio
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
SetDefault(OsuSetting.MenuVoice, true);
SetDefault(OsuSetting.MenuMusic, true);
SetDefault(OsuSetting.MenuTips, true);
SetDefault(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1);
SetDefault(OsuSetting.AutomaticallyAdjustBeatmapOffset, false);
// Input
SetDefault(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f);
SetDefault(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f);
SetDefault(OsuSetting.GameplayCursorDuringTouch, false);
SetDefault(OsuSetting.AutoCursorSize, false);
SetDefault(OsuSetting.MouseDisableButtons, false);
SetDefault(OsuSetting.MouseDisableWheel, false);
SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
SetDefault(OsuSetting.TouchDisableGameplayTaps, false);
// Graphics
SetDefault(OsuSetting.ShowFpsDisplay, false);
SetDefault(OsuSetting.ShowStoryboard, true);
SetDefault(OsuSetting.BeatmapSkins, true);
SetDefault(OsuSetting.BeatmapColours, true);
SetDefault(OsuSetting.BeatmapHitsounds, true);
SetDefault(OsuSetting.CursorRotation, true);
SetDefault(OsuSetting.MenuParallax, true);
// See https://stackoverflow.com/a/63307411 for default sourcing.
SetDefault(OsuSetting.Prefer24HourTime, !CultureInfoHelper.SystemCulture.DateTimeFormat.ShortTimePattern.Contains(@"tt"));
// Gameplay
SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1, 0.01f);
SetDefault(OsuSetting.DimLevel, 0.7, 0, 1, 0.01);
SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
SetDefault(OsuSetting.LightenDuringBreaks, true);
SetDefault(OsuSetting.HitLighting, true);
SetDefault(OsuSetting.StarFountains, true);
SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
SetDefault(OsuSetting.KeyOverlay, false);
SetDefault(OsuSetting.ReplaySettingsOverlay, true);
SetDefault(OsuSetting.ReplayPlaybackControlsExpanded, true);
SetDefault(OsuSetting.GameplayLeaderboard, true);
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
SetDefault(OsuSetting.FloatingComments, false);
SetDefault(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised);
SetDefault(OsuSetting.IncreaseFirstObjectVisibility, true);
SetDefault(OsuSetting.GameplayDisableWinKey, true);
// Update
SetDefault(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
SetDefault(OsuSetting.Version, string.Empty);
SetDefault(OsuSetting.ShowFirstRunSetup, true);
SetDefault(OsuSetting.ShowMobileDisclaimer, RuntimeInfo.IsMobile);
SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg);
SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false);
SetDefault(OsuSetting.Scaling, ScalingMode.Off);
SetDefault(OsuSetting.SafeAreaConsiderations, true);
SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f, 0.01f);
SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f, 0.01f);
SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f, 0.01f);
SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f, 0.01f);
SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f, 0.01f);
if (RuntimeInfo.IsMobile)
SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.1f, 0.01f);
else
SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
SetDefault(OsuSetting.UIHoldActivationDelay, 200.0, 0.0, 500.0, 50.0);
SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles);
SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 1f, 0.25f);
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f);
SetDefault(OsuSetting.EditorShowHitMarkers, true);
SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true);
SetDefault(OsuSetting.EditorLimitedDistanceSnap, false);
SetDefault(OsuSetting.EditorShowSpeedChanges, false);
SetDefault(OsuSetting.EditorScaleOrigin, EditorOrigin.GridCentre);
SetDefault(OsuSetting.EditorRotationOrigin, EditorOrigin.GridCentre);
SetDefault(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges, true);
SetDefault(OsuSetting.HideCountryFlags, false);
SetDefault(OsuSetting.MultiplayerRoomFilter, RoomPermissionsFilter.All);
SetDefault(OsuSetting.MultiplayerShowInProgressFilter, true);
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
SetDefault(OsuSetting.ComboColourNormalisationAmount, 0.2f, 0f, 1f, 0.01f);
SetDefault(OsuSetting.UserOnlineStatus, UserStatus.Online);
SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true);
SetDefault(OsuSetting.EditorTimelineShowBreaks, true);
SetDefault(OsuSetting.EditorTimelineShowTicks, true);
SetDefault(OsuSetting.EditorContractSidebars, false);
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
SetDefault(OsuSetting.AlwaysRequireHoldingForPause, false);
SetDefault(OsuSetting.EditorShowStoryboard, true);
SetDefault(OsuSetting.EditorSubmissionNotifyOnDiscussionReplies, true);
SetDefault(OsuSetting.EditorSubmissionLoadInBrowserAfterSubmission, true);
SetDefault(OsuSetting.WasSupporter, false);
// intentionally uses `DateTime?` and not `DateTimeOffset?` because the latter fails due to `DateTimeOffset` not implementing `IConvertible`
SetDefault(OsuSetting.LastOnlineTagsPopulation, (DateTime?)null);
SetDefault(OsuSetting.DashboardSortMode, UserSortCriteria.LastVisit);
SetDefault(OsuSetting.DashboardDisplayStyle, OverlayPanelDisplayStyle.Card);
}
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
{
switch (lookup)
{
case OsuSetting.Token:
return true;
}
return false;
}
public void Migrate()
{
// arrives as 2020.123.0-lazer
string rawVersion = Get<string>(OsuSetting.Version);
if (rawVersion.Length < 6)
return;
string[] pieces = rawVersion.Split('.');
// on a fresh install or when coming from a non-release build, execution will end here.
// we don't want to run migrations in such cases.
if (!int.TryParse(pieces[0], out int year)) return;
if (!int.TryParse(pieces[1], out int monthDay)) return;
int combined = year * 10000 + monthDay;
if (combined < 20250214)
{
// UI scaling on mobile platforms has been internally adjusted such that 1x UI scale looks correctly zoomed in than before.
if (RuntimeInfo.IsMobile)
GetBindable<float>(OsuSetting.UIScale).SetDefault();
}
if (combined < 20250428)
{
// Pen tablet sensitivity is now separated from cursor sensitivity.
// Most users will want the default to be what they already had set on cursor sensitivity so let's transfer it.
var mouseHandler = host?.AvailableInputHandlers.OfType<MouseHandler>().SingleOrDefault();
var penHandler = host?.AvailableInputHandlers.OfType<PenHandler>().SingleOrDefault();
if (penHandler != null && mouseHandler != null && penHandler.Sensitivity.IsDefault)
penHandler.Sensitivity.Value = mouseHandler.Sensitivity.Value;
}
}
public override TrackedSettings CreateTrackedSettings()
{
return new TrackedSettings
{
new TrackedSetting<bool>(OsuSetting.ShowFpsDisplay, state => new SettingDescription(
rawValue: state,
name: GlobalActionKeyBindingStrings.ToggleFPSCounter,
value: state ? CommonStrings.Enabled.ToLower() : CommonStrings.Disabled.ToLower(),
shortcut: LookupKeyBindings(GlobalAction.ToggleFPSDisplay))
),
new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, disabledState => new SettingDescription(
rawValue: !disabledState,
name: GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons,
value: disabledState ? CommonStrings.Disabled.ToLower() : CommonStrings.Enabled.ToLower(),
shortcut: LookupKeyBindings(GlobalAction.ToggleGameplayMouseButtons))
),
new TrackedSetting<bool>(OsuSetting.GameplayLeaderboard, state => new SettingDescription(
rawValue: state,
name: GlobalActionKeyBindingStrings.ToggleInGameLeaderboard,
value: state ? CommonStrings.Enabled.ToLower() : CommonStrings.Disabled.ToLower(),
shortcut: LookupKeyBindings(GlobalAction.ToggleInGameLeaderboard))
),
new TrackedSetting<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode, visibilityMode => new SettingDescription(
rawValue: visibilityMode,
name: GameplaySettingsStrings.HUDVisibilityMode,
value: visibilityMode.GetLocalisableDescription(),
shortcut: new TranslatableString(@"_", @"{0}: {1} {2}: {3}",
GlobalActionKeyBindingStrings.ToggleInGameInterface,
LookupKeyBindings(GlobalAction.ToggleInGameInterface),
GlobalActionKeyBindingStrings.HoldForHUD,
LookupKeyBindings(GlobalAction.HoldForHUD)))
),
new TrackedSetting<ScalingMode>(OsuSetting.Scaling, scalingMode => new SettingDescription(
rawValue: scalingMode,
name: GraphicsSettingsStrings.ScreenScaling,
value: scalingMode.GetLocalisableDescription()
)
),
new TrackedSetting<string>(OsuSetting.Skin, skin =>
{
string skinName = string.Empty;
if (Guid.TryParse(skin, out var id))
skinName = LookupSkinName(id);
return new SettingDescription(
rawValue: skinName,
name: SkinSettingsStrings.SkinSectionHeader,
value: skinName,
shortcut: new TranslatableString(@"_", @"{0}: {1}",
GlobalActionKeyBindingStrings.RandomSkin,
LookupKeyBindings(GlobalAction.RandomSkin))
);
}),
new TrackedSetting<float>(OsuSetting.UIScale, scale => new SettingDescription(
rawValue: scale,
name: GraphicsSettingsStrings.UIScaling,
value: $"{scale:N2}x"
// TODO: implement lookup for framework platform key bindings
)
),
};
}
public Func<Guid, string> LookupSkinName { private get; set; } = _ => @"unknown";
public Func<GlobalAction, LocalisableString> LookupKeyBindings { private get; set; } = _ => @"unknown";
IBindable<float> IGameplaySettings.ComboColourNormalisationAmount => GetOriginalBindable<float>(OsuSetting.ComboColourNormalisationAmount);
IBindable<float> IGameplaySettings.PositionalHitsoundsLevel => GetOriginalBindable<float>(OsuSetting.PositionalHitsoundsLevel);
}
// IMPORTANT: These are used in user configuration files.
// The naming of these keys should not be changed once they are deployed in a release, unless migration logic is also added.
public enum OsuSetting
{
Ruleset,
Token,
MenuCursorSize,
GameplayCursorSize,
AutoCursorSize,
GameplayCursorDuringTouch,
DimLevel,
BlurLevel,
EditorDim,
LightenDuringBreaks,
ShowStoryboard,
KeyOverlay,
GameplayLeaderboard,
PositionalHitsoundsLevel,
AlwaysPlayFirstComboBreak,
FloatingComments,
HUDVisibilityMode,
ShowHealthDisplayWhenCantFail,
FadePlayfieldWhenHealthLow,
/// <summary>
/// Disables mouse buttons clicks during gameplay.
/// </summary>
MouseDisableButtons,
MouseDisableWheel,
ConfineMouseMode,
/// <summary>
/// Globally applied audio offset.
/// This is added to the audio track's current time. Higher values will cause gameplay to occur earlier, relative to the audio track.
/// </summary>
AudioOffset,
VolumeInactive,
MenuMusic,
MenuVoice,
MenuTips,
CursorRotation,
MenuParallax,
Prefer24HourTime,
BeatmapDetailTab,
BeatmapLeaderboardSortMode,
BeatmapDetailModsFilter,
Username,
ReleaseStream,
SavePassword,
SaveUsername,
DisplayStarsMinimum,
DisplayStarsMaximum,
SongSelectGroupMode,
SongSelectSortingMode,
RandomSelectAlgorithm,
ModSelectHotkeyStyle,
ShowFpsDisplay,
ChatDisplayHeight,
BeatmapListingCardSize,
ToolbarClockDisplayMode,
SongSelectBackgroundBlur,
Version,
ShowFirstRunSetup,
ShowConvertedBeatmaps,
Skin,
ScreenshotFormat,
ScreenshotCaptureMenuCursor,
BeatmapSkins,
BeatmapColours,
BeatmapHitsounds,
IncreaseFirstObjectVisibility,
ScoreDisplayMode,
ExternalLinkWarning,
PreferNoVideo,
Scaling,
ScalingPositionX,
ScalingPositionY,
ScalingSizeX,
ScalingSizeY,
ScalingBackgroundDim,
UIScale,
IntroSequence,
NotifyOnUsernameMentioned,
NotifyOnPrivateMessage,
NotifyOnFriendPresenceChange,
UIHoldActivationDelay,
HitLighting,
StarFountains,
MenuBackgroundSource,
GameplayDisableWinKey,
SeasonalBackgroundMode,
EditorWaveformOpacity,
EditorShowHitMarkers,
EditorAutoSeekOnPlacement,
DiscordRichPresence,
ShowOnlineExplicitContent,
LastProcessedMetadataId,
SafeAreaConsiderations,
ComboColourNormalisationAmount,
ProfileCoverExpanded,
EditorLimitedDistanceSnap,
ReplaySettingsOverlay,
ReplayPlaybackControlsExpanded,
AutomaticallyDownloadMissingBeatmaps,
EditorShowSpeedChanges,
TouchDisableGameplayTaps,
ModSelectTextSearchStartsActive,
/// <summary>
/// The status for the current user to broadcast to other players.
/// </summary>
UserOnlineStatus,
MultiplayerRoomFilter,
HideCountryFlags,
EditorTimelineShowTimingChanges,
EditorTimelineShowTicks,
AlwaysShowHoldForMenuButton,
EditorContractSidebars,
EditorScaleOrigin,
EditorRotationOrigin,
EditorTimelineShowBreaks,
EditorAdjustExistingObjectsOnTimingChanges,
AlwaysRequireHoldingForPause,
MultiplayerShowInProgressFilter,
BeatmapListingFeaturedArtistFilter,
ShowMobileDisclaimer,
EditorShowStoryboard,
EditorSubmissionNotifyOnDiscussionReplies,
EditorSubmissionLoadInBrowserAfterSubmission,
/// <summary>
/// Cached state of whether local user is a supporter.
/// Used to allow early checks (ie for startup samples) to be in the correct state, even if the API authentication process has not completed.
/// </summary>
WasSupporter,
LastOnlineTagsPopulation,
AutomaticallyAdjustBeatmapOffset,
DashboardSortMode,
DashboardDisplayStyle,
}
}