├── .assets
└── logo.svg
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Qsor.Deploy
├── ChangelogGenerator.cs
├── Program.cs
├── Qsor.Deploy.csproj
└── tools
│ └── rcedit-x64.exe
├── Qsor.Desktop
├── Program.cs
├── Qsor.Desktop.csproj
├── QsorGameDesktop.cs
├── Updater
│ └── SquirrelUpdateManager.cs
├── icon.ico
└── qsor.nuspec
├── Qsor.Game
├── Beatmaps
│ ├── Beatmap.cs
│ ├── BeatmapContainer.cs
│ ├── BeatmapManager.cs
│ ├── BeatmapParser.cs
│ ├── TimingPoint.cs
│ └── WorkingBeatmap.cs
├── Configuration
│ └── QsorConfiguration.cs
├── Database
│ ├── DatabaseWriteUsage.cs
│ ├── Migrations
│ │ ├── 20200331222405_BeatmapModel.Designer.cs
│ │ ├── 20200331222405_BeatmapModel.cs
│ │ └── QsorDbContextModelSnapshot.cs
│ ├── Models
│ │ └── BeatmapModel.cs
│ ├── QsorDbContext.cs
│ ├── QsorDbContextFactory.cs
│ └── StorageExtension.cs
├── Graphics
│ ├── ClickableSpriteIcon.cs
│ ├── Containers
│ │ ├── BackgroundImageContainer.cs
│ │ ├── BeatSyncedContainer.cs
│ │ ├── ParallaxContainer.cs
│ │ └── QsorTooltipContainer.cs
│ ├── DragableProgressbar.cs
│ ├── Drawables
│ │ └── DrawableProgressbar.cs
│ └── UserInterface
│ │ ├── Online
│ │ └── Drawables
│ │ │ ├── DrawableAvatar.cs
│ │ │ └── DrawableLevelBar.cs
│ │ ├── Overlays
│ │ ├── MusicPlayerOverlay.cs
│ │ ├── Notification
│ │ │ ├── Drawables
│ │ │ │ ├── DrawableBigNotification.cs
│ │ │ │ └── DrawableNotification.cs
│ │ │ └── NotificationOverlay.cs
│ │ ├── Settings
│ │ │ ├── Categories
│ │ │ │ ├── SettingsAudioCategory.cs
│ │ │ │ ├── SettingsEditorCategory.cs
│ │ │ │ ├── SettingsGameplayCategory.cs
│ │ │ │ ├── SettingsGeneralCategory.cs
│ │ │ │ ├── SettingsGraphicsCategory.cs
│ │ │ │ ├── SettingsInputCategory.cs
│ │ │ │ ├── SettingsMaintenanceCategory.cs
│ │ │ │ ├── SettingsOnlineCategory.cs
│ │ │ │ └── SettingsSkinCategory.cs
│ │ │ ├── Drawables
│ │ │ │ ├── DrawableSettingsCategory.cs
│ │ │ │ ├── DrawableSettingsIconSprite.cs
│ │ │ │ ├── DrawableSettingsMenu.cs
│ │ │ │ ├── DrawableSettingsSubCategory.cs
│ │ │ │ ├── DrawableSettingsToolBar.cs
│ │ │ │ └── Objects
│ │ │ │ │ ├── DrawableSettingsCheckbox.cs
│ │ │ │ │ ├── DrawableSettingsInput.cs
│ │ │ │ │ ├── DrawableSettingsLabel.cs
│ │ │ │ │ └── DrawableSettingsObject.cs
│ │ │ ├── SettingsCategoryContainer.cs
│ │ │ └── SettingsOverlay.cs
│ │ ├── UpdaterOverlay.cs
│ │ └── UserOverlay.cs
│ │ ├── QsorColours.cs
│ │ └── Screens
│ │ ├── IntroScreen.cs
│ │ └── MainMenu
│ │ ├── Containers
│ │ ├── BottomBar.cs
│ │ └── Toolbar.cs
│ │ ├── Drawables
│ │ ├── DrawableLogoVisualisation.cs
│ │ ├── DrawableMenuSideFlashes.cs
│ │ └── DrawableQsorLogo.cs
│ │ └── MainMenuScreen.cs
├── Input
│ └── KeyBindingInputHandler.cs
├── Online
│ ├── BanchoClient.cs
│ ├── BeatmapMirrorAccess.cs
│ ├── UserManager.cs
│ └── Users
│ │ ├── User.cs
│ │ └── UserStatistics.cs
├── Qsor.Game.csproj
├── QsorBaseGame.cs
├── QsorGame.cs
├── Resources
│ └── Textures
│ │ ├── Logo-256x256.png
│ │ ├── Logo-ghost.png
│ │ ├── Logo.png
│ │ ├── approachcircle.png
│ │ ├── hitcircle.png
│ │ ├── hitcircleoverlay.png
│ │ ├── slider.png
│ │ └── sliderb.png
├── Updater
│ ├── DummyUpdateManager.cs
│ ├── UpdateManager.cs
│ └── UpdaterStatus.cs
└── Utility
│ └── SentryLogger.cs
├── Qsor.NativeLibs
├── Qsor.NativeLibs.csproj
└── _._
├── Qsor.Tests
├── Program.cs
├── Qsor.Tests.csproj
├── QsorTestGame.cs
└── Visual
│ ├── Overlays
│ ├── TestSceneMusicPlayerOverlay.cs
│ ├── TestSceneNotificationOverlay.cs
│ ├── TestSceneSettingsOverlay.cs
│ ├── TestSceneUpdaterOverlay.cs
│ └── TestSceneUserOverlay.cs
│ ├── Scenes
│ └── IntroTestScene.cs
│ └── TestSceneQsorGame.cs
├── Qsor.sln
├── Qsor.sln.DotSettings
├── Readme.md
├── appveyor.yml
├── generate_changelog.ps1
└── global.json
/.assets/logo.svg:
--------------------------------------------------------------------------------
1 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Coverlet is a free, cross platform Code Coverage Tool
141 | coverage*[.json, .xml, .info]
142 |
143 | # Visual Studio code coverage results
144 | *.coverage
145 | *.coveragexml
146 |
147 | # NCrunch
148 | _NCrunch_*
149 | .*crunch*.local.xml
150 | nCrunchTemp_*
151 |
152 | # MightyMoose
153 | *.mm.*
154 | AutoTest.Net/
155 |
156 | # Web workbench (sass)
157 | .sass-cache/
158 |
159 | # Installshield output folder
160 | [Ee]xpress/
161 |
162 | # DocProject is a documentation generator add-in
163 | DocProject/buildhelp/
164 | DocProject/Help/*.HxT
165 | DocProject/Help/*.HxC
166 | DocProject/Help/*.hhc
167 | DocProject/Help/*.hhk
168 | DocProject/Help/*.hhp
169 | DocProject/Help/Html2
170 | DocProject/Help/html
171 |
172 | # Click-Once directory
173 | publish/
174 |
175 | # Publish Web Output
176 | *.[Pp]ublish.xml
177 | *.azurePubxml
178 | # Note: Comment the next line if you want to checkin your web deploy settings,
179 | # but database connection strings (with potential passwords) will be unencrypted
180 | *.pubxml
181 | *.publishproj
182 |
183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
184 | # checkin your Azure Web App publish settings, but sensitive information contained
185 | # in these scripts will be unencrypted
186 | PublishScripts/
187 |
188 | # NuGet Packages
189 | *.nupkg
190 | # NuGet Symbol Packages
191 | *.snupkg
192 | # The packages folder can be ignored because of Package Restore
193 | **/[Pp]ackages/*
194 | # except build/, which is used as an MSBuild target.
195 | !**/[Pp]ackages/build/
196 | # Uncomment if necessary however generally it will be regenerated when needed
197 | #!**/[Pp]ackages/repositories.config
198 | # NuGet v3's project.json files produces more ignorable files
199 | *.nuget.props
200 | *.nuget.targets
201 |
202 | # Microsoft Azure Build Output
203 | csx/
204 | *.build.csdef
205 |
206 | # Microsoft Azure Emulator
207 | ecf/
208 | rcf/
209 |
210 | # Windows Store app package directories and files
211 | AppPackages/
212 | BundleArtifacts/
213 | Package.StoreAssociation.xml
214 | _pkginfo.txt
215 | *.appx
216 | *.appxbundle
217 | *.appxupload
218 |
219 | # Visual Studio cache files
220 | # files ending in .cache can be ignored
221 | *.[Cc]ache
222 | # but keep track of directories ending in .cache
223 | !?*.[Cc]ache/
224 |
225 | # Others
226 | ClientBin/
227 | ~$*
228 | *~
229 | *.dbmdl
230 | *.dbproj.schemaview
231 | *.jfm
232 | *.pfx
233 | *.publishsettings
234 | orleans.codegen.cs
235 |
236 | # Including strong name files can present a security risk
237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
238 | #*.snk
239 |
240 | # Since there are multiple workflows, uncomment next line to ignore bower_components
241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
242 | #bower_components/
243 |
244 | # RIA/Silverlight projects
245 | Generated_Code/
246 |
247 | # Backup & report files from converting an old project file
248 | # to a newer Visual Studio version. Backup files are not needed,
249 | # because we have git ;-)
250 | _UpgradeReport_Files/
251 | Backup*/
252 | UpgradeLog*.XML
253 | UpgradeLog*.htm
254 | ServiceFabricBackup/
255 | *.rptproj.bak
256 |
257 | # SQL Server files
258 | *.mdf
259 | *.ldf
260 | *.ndf
261 |
262 | # Business Intelligence projects
263 | *.rdl.data
264 | *.bim.layout
265 | *.bim_*.settings
266 | *.rptproj.rsuser
267 | *- [Bb]ackup.rdl
268 | *- [Bb]ackup ([0-9]).rdl
269 | *- [Bb]ackup ([0-9][0-9]).rdl
270 |
271 | # Microsoft Fakes
272 | FakesAssemblies/
273 |
274 | # GhostDoc plugin setting file
275 | *.GhostDoc.xml
276 |
277 | # Node.js Tools for Visual Studio
278 | .ntvs_analysis.dat
279 | node_modules/
280 |
281 | # Visual Studio 6 build log
282 | *.plg
283 |
284 | # Visual Studio 6 workspace options file
285 | *.opt
286 |
287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
288 | *.vbw
289 |
290 | # Visual Studio LightSwitch build output
291 | **/*.HTMLClient/GeneratedArtifacts
292 | **/*.DesktopClient/GeneratedArtifacts
293 | **/*.DesktopClient/ModelManifest.xml
294 | **/*.Server/GeneratedArtifacts
295 | **/*.Server/ModelManifest.xml
296 | _Pvt_Extensions
297 |
298 | # Paket dependency manager
299 | .paket/paket.exe
300 | paket-files/
301 |
302 | # FAKE - F# Make
303 | .fake/
304 |
305 | # CodeRush personal settings
306 | .cr/personal
307 |
308 | # Python Tools for Visual Studio (PTVS)
309 | __pycache__/
310 | *.pyc
311 |
312 | # Cake - Uncomment if you are using it
313 | # tools/**
314 | # !tools/packages.config
315 |
316 | # Tabs Studio
317 | *.tss
318 |
319 | # Telerik's JustMock configuration file
320 | *.jmconfig
321 |
322 | # BizTalk build output
323 | *.btp.cs
324 | *.btm.cs
325 | *.odx.cs
326 | *.xsd.cs
327 |
328 | # OpenCover UI analysis results
329 | OpenCover/
330 |
331 | # Azure Stream Analytics local run output
332 | ASALocalRun/
333 |
334 | # MSBuild Binary and Structured Log
335 | *.binlog
336 |
337 | # NVidia Nsight GPU debugger configuration file
338 | *.nvuser
339 |
340 | # MFractors (Xamarin productivity tool) working folder
341 | .mfractor/
342 |
343 | # Local History for Visual Studio
344 | .localhistory/
345 |
346 | # BeatPulse healthcheck temp database
347 | healthchecksdb
348 |
349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
350 | MigrationBackup/
351 |
352 | # Ionide (cross platform F# VS Code tools) working folder
353 | .ionide/
354 |
355 | .idea/
356 |
357 | releases/
358 |
359 | staging/
360 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at me@mempler.de. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Robin A. P.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Qsor.Deploy/ChangelogGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using LibGit2Sharp;
4 |
5 | namespace Qsor.Deploy
6 | {
7 | public static class ChangelogGenerator
8 | {
9 | public static string GenerateChangelog()
10 | {
11 | var repoPath = Repository.Discover(".");
12 | var repo = new Repository(repoPath);
13 |
14 | var changeLog = string.Empty;
15 |
16 | foreach (var commit in repo.Commits)
17 | {
18 | var lastTag = repo.Tags.Last();
19 | if (commit.Sha == lastTag.Target.Sha)
20 | break;
21 |
22 | changeLog += $"`{commit.Sha.Remove(7)}` {commit.MessageShort} - {commit.Author.Name} {Environment.NewLine}";
23 | }
24 |
25 | return changeLog;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Qsor.Deploy/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using Newtonsoft.Json;
7 | using osu.Framework.IO.Network;
8 | using osu.Framework.Platform;
9 |
10 | namespace Qsor.Deploy
11 | {
12 | internal static class Program
13 | {
14 | private static string NugetPackages => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @".nuget\packages");
15 | private static string NugetPath => Path.Combine(NugetPackages, @"nuget.commandline\5.6.0\tools\NuGet.exe");
16 | private static string SquirrelPath => Path.Combine(NugetPackages, @"ppy.squirrel.windows\1.9.0.4\tools\Squirrel.exe");
17 |
18 | private static string GithubAccessToken => Environment.GetEnvironmentVariable("GITHUB_ACCESS_TOKEN");
19 |
20 | private static void Main()
21 | {
22 | var currentDirectory = new NativeStorage(".");
23 | var solutionDirectory = new NativeStorage("..");
24 |
25 | currentDirectory.DeleteDirectory("./staging");
26 |
27 | if (!Directory.Exists("releases"))
28 | Directory.CreateDirectory("releases");
29 |
30 | var stagingDirectory = currentDirectory.GetStorageForDirectory("staging");
31 | var currentDate = DateTime.Now.ToString("yyyy.Mdd.");
32 |
33 | currentDate += currentDirectory.GetDirectories("releases").Count(s => s.Contains(currentDate));
34 |
35 | var releaseDirectory = currentDirectory.GetStorageForDirectory($"./releases/App-{currentDate}");
36 |
37 | Console.WriteLine($"Package: Qsor");
38 | Console.WriteLine($"Release Version: {currentDate}");
39 | Console.WriteLine($"Release Directory: {releaseDirectory.GetFullPath(".")}");
40 | Console.WriteLine($"Changelog: \n{ChangelogGenerator.GenerateChangelog()}");
41 |
42 | var logo = solutionDirectory.GetFullPath("Qsor.Game/Resources/Textures/Logo-256x256.png");
43 | var icon = solutionDirectory.GetFullPath("Qsor.Desktop/icon.ico");
44 |
45 | RunCommand("dotnet", $"publish -f net5.0 Qsor.Desktop --configuration Release --runtime win-x64 -p:Version={currentDate} -o {stagingDirectory.GetFullPath(".")}",
46 | solutionDirectory.GetFullPath("."));
47 |
48 | RunCommand("./tools/rcedit-x64.exe", $"\"{stagingDirectory.GetFullPath(".")}\\Qsor.exe\" --set-icon \"{icon}\"");
49 |
50 | RunCommand(NugetPath, $"pack Qsor.Desktop/qsor.nuspec -Version {currentDate} -Properties Configuration=Release -OutputDirectory {stagingDirectory.GetFullPath(".")} -BasePath {stagingDirectory.GetFullPath(".")}",
51 | solutionDirectory.GetFullPath("."));
52 |
53 | RunCommand(SquirrelPath, $"--releasify {stagingDirectory.GetFullPath($"./Qsor.{currentDate}.nupkg")} --releaseDir {releaseDirectory.GetFullPath(".")} --no-msi --icon {icon} --setupIcon {icon} --loadingGif {logo}",
54 | stagingDirectory.GetFullPath("."));
55 |
56 | RunCommand("git", $"tag {currentDate}");
57 | RunCommand("git", $"push origin {currentDate}");
58 |
59 | File.Move(releaseDirectory.GetFullPath("Setup.exe"), releaseDirectory.GetFullPath("install.exe"));
60 |
61 | stagingDirectory.DeleteDirectory(".");
62 |
63 | var req = new JsonWebRequest($"https://api.github.com/repos/mempler/Qsor/releases")
64 | {
65 | Method = HttpMethod.Post,
66 | };
67 |
68 | Console.WriteLine($"Creating release {currentDate}...");
69 |
70 | req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease
71 | {
72 | Name = currentDate,
73 | Draft = true,
74 | Body = ChangelogGenerator.GenerateChangelog()
75 | }));
76 |
77 | req.AddHeader("Authorization", $"token {GithubAccessToken}");
78 | req.Perform();
79 |
80 | var targetRelease = req.ResponseObject;
81 |
82 | var assetUploadUrl = targetRelease.UploadUrl.Replace("{?name,label}", "?name={0}");
83 | foreach (var a in Directory.GetFiles(releaseDirectory.GetFullPath(".")).Reverse())
84 | {
85 | if (Path.GetFileName(a).StartsWith('.'))
86 | continue;
87 |
88 | Console.WriteLine($"- Pushing asset {a}...");
89 | var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a))
90 | {
91 | Method = HttpMethod.Post,
92 | Timeout = 240000,
93 | ContentType = "application/octet-stream",
94 | };
95 |
96 | upload.AddRaw(File.ReadAllBytes(a));
97 | upload.AddHeader("Authorization", $"token {GithubAccessToken}");
98 | upload.Perform();
99 | }
100 | }
101 |
102 | private static void RunCommand(string command, string args, string workingDirectory = null)
103 | {
104 | Console.WriteLine($"Running {command} {args}...");
105 |
106 | var psi = new ProcessStartInfo(command, args)
107 | {
108 | WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory,
109 | CreateNoWindow = true,
110 |
111 | RedirectStandardOutput = true,
112 | RedirectStandardError = true,
113 |
114 | UseShellExecute = false,
115 | WindowStyle = ProcessWindowStyle.Hidden
116 | };
117 |
118 | var p = Process.Start(psi);
119 | if (p == null) return;
120 |
121 | var output = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd();
122 |
123 | p.WaitForExit();
124 |
125 | if (p.ExitCode == 0) return;
126 |
127 | Console.WriteLine(output);
128 | Console.WriteLine($"Command {command} {args} failed!");
129 | }
130 | }
131 |
132 | public class GitHubRelease
133 | {
134 | [JsonProperty(@"id")]
135 | public int Id;
136 |
137 | [JsonProperty(@"tag_name")]
138 | public string TagName => $"{Name}";
139 |
140 | [JsonProperty(@"name")]
141 | public string Name;
142 |
143 | [JsonProperty(@"draft")]
144 | public bool Draft;
145 |
146 | [JsonProperty(@"prerelease")]
147 | public bool PreRelease;
148 |
149 | [JsonProperty(@"upload_url")]
150 | public string UploadUrl;
151 |
152 | [JsonProperty(@"body")]
153 | public string Body;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/Qsor.Deploy/Qsor.Deploy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Qsor.Deploy/tools/rcedit-x64.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Deploy/tools/rcedit-x64.exe
--------------------------------------------------------------------------------
/Qsor.Desktop/Program.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework;
2 |
3 | namespace Qsor.Desktop
4 | {
5 | internal static class Program
6 | {
7 | public static void Main(params string[] args)
8 | {
9 | using var host = Host.GetSuitableDesktopHost("Qsor");
10 | using var game = new QsorGameDesktop(args);
11 |
12 | host.Run(game);
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/Qsor.Desktop/Qsor.Desktop.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Qsor
5 | WinExe
6 | net8.0
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Qsor.Desktop/QsorGameDesktop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using osu.Framework;
4 | using osu.Framework.Development;
5 | using Qsor.Desktop.Updater;
6 | using Qsor.Game;
7 | using Qsor.Game.Updater;
8 |
9 | namespace Qsor.Desktop;
10 |
11 | public partial class QsorGameDesktop : QsorGame
12 | {
13 | public QsorGameDesktop(string[] args = null)
14 | : base(args)
15 | {
16 | }
17 |
18 | protected override UpdateManager CreateUpdater()
19 | {
20 | if (DebugUtils.IsDebugBuild)
21 | return new DummyUpdater();
22 |
23 | switch (RuntimeInfo.OS)
24 | {
25 | case RuntimeInfo.Platform.Windows:
26 | Debug.Assert(OperatingSystem.IsWindows()); // To silence the "only supported on Windows" warning
27 |
28 | return new SquirrelUpdateManager();
29 |
30 | default:
31 | return new DummyUpdater();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Qsor.Desktop/Updater/SquirrelUpdateManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Runtime.Versioning;
6 | using System.Threading;
7 | using Newtonsoft.Json;
8 | using osu.Framework.Allocation;
9 | using osu.Framework.Platform;
10 | using Qsor.Game.Updater;
11 | using Squirrel;
12 | using Squirrel.Sources;
13 | using UpdateManager = Qsor.Game.Updater.UpdateManager;
14 |
15 | namespace Qsor.Desktop.Updater
16 | {
17 | [Cached]
18 | [SupportedOSPlatform("windows")]
19 | public partial class SquirrelUpdateManager : UpdateManager, IDisposable
20 | {
21 | [Resolved]
22 | private GameHost Host { get; set; }
23 |
24 | private Squirrel.UpdateManager _updateManager;
25 |
26 |
27 | [BackgroundDependencyLoader]
28 | private void Load()
29 | {
30 | const string GITHUB_ACCESS_TOKEN = null; // TODO: fill in your GitHub access token here
31 |
32 | _updateManager = new Squirrel.UpdateManager(new GithubSource("https://github.com/mempler/Qsor/", GITHUB_ACCESS_TOKEN, true), "qsor");
33 | }
34 |
35 | public override async void CheckAvailable()
36 | {
37 | while (_updateManager == null)
38 | Thread.Sleep(1);
39 |
40 | var updateInfo = await _updateManager.CheckForUpdate();
41 |
42 | if (updateInfo.ReleasesToApply.Count > 0)
43 | BindableStatus.Value = UpdaterStatus.Pending;
44 | }
45 |
46 | private bool _hasStarted;
47 | public override async void UpdateGame()
48 | {
49 | if (BindableStatus.Value == UpdaterStatus.Ready)
50 | {
51 | var entry = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location) ?? throw new NullReferenceException();
52 | var path = Path.Join(entry, "../");
53 |
54 | Console.WriteLine(path);
55 |
56 | Process.Start(Path.Join(path, "./Qsor.exe"), "--updated");
57 |
58 | Host.Exit();
59 | }
60 |
61 | if (_hasStarted)
62 | return;
63 |
64 | BindableStatus.Value = UpdaterStatus.Downloading;
65 |
66 | _hasStarted = true;
67 |
68 | await _updateManager.UpdateApp(progress => BindableProgress.Value = progress);
69 |
70 | BindableStatus.Value = UpdaterStatus.Ready;
71 | }
72 |
73 | protected override void Dispose(bool isDisposing)
74 | {
75 | base.Dispose(isDisposing);
76 | _updateManager?.Dispose();
77 | }
78 |
79 | public class GitHubRelease
80 | {
81 | [JsonProperty("tag_name")]
82 | public string TagName { get; set; }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Qsor.Desktop/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Desktop/icon.ico
--------------------------------------------------------------------------------
/Qsor.Desktop/qsor.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Qsor
5 | 0.0.0
6 | Qsor
7 | Mempler
8 | Robin A. Plate
9 | https://akatsuki.pw
10 | Copyright (c) 2024 Robin A. Plate
11 | An osu! like rythm game based ontop of o!F
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Qsor.Game/Beatmaps/Beatmap.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using osu.Framework.Platform;
5 | using osuTK.Graphics;
6 | using Component = osu.Framework.Graphics.Component;
7 |
8 | namespace Qsor.Game.Beatmaps
9 | {
10 | public partial class Beatmap : Component
11 | {
12 | public class GeneralSection
13 | {
14 | public string AudioFilename;
15 | public int AudioLeadIn;
16 | }
17 |
18 | public class DifficultySection
19 | {
20 | public double CircleSize = 5;
21 | public double ApproachRate = 5;
22 | public double OverallDifficulty = 5;
23 |
24 | // Sliders
25 | public double SliderMultiplier;
26 | public double TickRate;
27 | }
28 |
29 | public int BeatmapVersion = 0;
30 |
31 | public readonly GeneralSection General = new();
32 | public readonly DifficultySection Difficulty = new();
33 | public readonly List Colors = new();
34 | public List TimingPoints = new();
35 |
36 | public string BackgroundFilename;
37 |
38 | // TODO: maybe move this to WorkingBeatmap ?
39 | protected Storage BeatmapStorage { get; private set; }
40 |
41 | public TimingPoint GetTimingPointAt(double time) => TimingPoints.FirstOrDefault(t => t.Offset >= time);
42 |
43 | public static T ReadBeatmap(Storage storage, string fileName)
44 | where T : Beatmap, new()
45 | {
46 | if (!storage.Exists(fileName))
47 | throw new FileNotFoundException("Beatmap file hasn't been found!", fileName);
48 |
49 | var reader = new StreamReader(storage.GetStream(fileName));
50 | var bmContent = reader.ReadToEnd();
51 |
52 | var parser = new BeatmapParser();
53 |
54 | var bm = parser.ConstructFromString(bmContent);
55 | bm.BeatmapStorage = storage;
56 |
57 | return bm;
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/Qsor.Game/Beatmaps/BeatmapContainer.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Audio;
3 | using osu.Framework.Bindables;
4 | using osu.Framework.Graphics;
5 | using osu.Framework.Graphics.Containers;
6 | using osu.Framework.Graphics.Textures;
7 | using Qsor.Game.Graphics.Containers;
8 |
9 | namespace Qsor.Game.Beatmaps
10 | {
11 | public partial class BeatmapContainer : Container
12 | {
13 | private BackgroundImageContainer _background;
14 | public Bindable WorkingBeatmap { get; } = new();
15 |
16 | [Resolved]
17 | private AudioManager Audio { get; set; }
18 |
19 | public BeatmapContainer(Bindable beatmap)
20 | {
21 | WorkingBeatmap.BindTo(beatmap);
22 | }
23 |
24 | [BackgroundDependencyLoader]
25 | private void Load(TextureStore store)
26 | {
27 | RelativeSizeAxes = Axes.Both;
28 | Anchor = Anchor.Centre;
29 | Origin = Anchor.Centre;
30 | FillMode = FillMode.Fill;
31 |
32 | LoadComponent(WorkingBeatmap.Value);
33 |
34 | AddInternal(_background = new BackgroundImageContainer
35 | {
36 | RelativeSizeAxes = Axes.Both,
37 | Anchor = Anchor.Centre,
38 | Origin = Anchor.Centre,
39 | FillMode = FillMode.Fill,
40 | });
41 |
42 | _background.SetTexture(WorkingBeatmap.Value.Background);
43 | Audio.AddItem(WorkingBeatmap.Value.Track);
44 | }
45 |
46 | public void PlayBeatmap()
47 | {
48 | WorkingBeatmap.Value.Track.Start();
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/Qsor.Game/Beatmaps/BeatmapManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using ICSharpCode.SharpZipLib.Zip;
6 | using osu.Framework.Allocation;
7 | using osu.Framework.Audio;
8 | using osu.Framework.Bindables;
9 | using osu.Framework.Graphics.Textures;
10 | using osu.Framework.Platform;
11 | using Qsor.Game.Configuration;
12 | using Qsor.Game.Database;
13 | using Qsor.Game.Database.Models;
14 | using Qsor.Game.Graphics.Containers;
15 | using Qsor.Game.Online;
16 |
17 | namespace Qsor.Game.Beatmaps
18 | {
19 | public partial class BeatmapManager : IDependencyInjectionCandidate
20 | {
21 | public Bindable WorkingBeatmap { get; } = new();
22 |
23 | [Resolved]
24 | private BeatmapMirrorAccess MirrorAccess { get; set; }
25 |
26 | [Resolved]
27 | private Storage Storage { get; set; }
28 |
29 | [Resolved]
30 | private QsorDbContextFactory QsorDbContextFactory { get; set; }
31 |
32 | [Resolved]
33 | private AudioManager AudioManager { get; set; }
34 |
35 | [Resolved]
36 | private QsorConfigManager ConfigManager { get; set; }
37 |
38 | [Resolved]
39 | private QsorDbContextFactory DbContextFactory { get; set; }
40 |
41 | public BackgroundImageContainer Background;
42 |
43 | public BeatmapManager()
44 | {
45 | WorkingBeatmap.ValueChanged += e =>
46 | {
47 | e.OldValue?.Track.Stop();
48 | e.OldValue?.Dispose();
49 | };
50 | }
51 |
52 | [BackgroundDependencyLoader]
53 | private void Load(TextureStore store)
54 | {
55 | // TODO: Remove
56 | var setId = ConfigManager.Get(QsorSetting.BeatmapSetId);
57 | if (!Storage.ExistsDirectory($"./Songs/{setId}"))
58 | {
59 | MirrorAccess.DownloadBeatmap(setId);
60 |
61 | ImportStalled();
62 | }
63 | }
64 |
65 | ///
66 | /// Import all .osz files inside %QSOR_DIR%/Songs/
67 | ///
68 | public void ImportStalled()
69 | {
70 | foreach (var osz in Storage.GetFiles("./Songs/", "*.osz"))
71 | {
72 | using (var oszStream = Storage.GetStream(osz, FileAccess.Read, FileMode.Open))
73 | {
74 | ImportZip(oszStream);
75 | }
76 |
77 | Storage.Delete(osz); // Cleanup
78 | }
79 | }
80 |
81 | public void ImportZip(Stream beatmapFile)
82 | {
83 | using var db = QsorDbContextFactory.GetForWrite();
84 | using var s = new ZipInputStream(beatmapFile);
85 |
86 | ZipEntry theEntry;
87 | while ((theEntry = s.GetNextEntry()) != null)
88 | {
89 | var directoryStorage = Storage.GetStorageForDirectory($"./Songs/{ConfigManager.Get(QsorSetting.BeatmapSetId)}/" + Path.GetDirectoryName(theEntry.Name));
90 | var fileName = Path.GetFileName(theEntry.Name);
91 |
92 | if (fileName == string.Empty) continue;
93 |
94 | {
95 | using var streamWriter = directoryStorage.GetStream(theEntry.Name, FileAccess.Write);
96 | var data = new byte[2048];
97 |
98 | while (true)
99 | {
100 | var size = s.Read(data, 0, data.Length);
101 | if (size > 0)
102 | streamWriter.Write(data, 0, size);
103 | else
104 | break;
105 | }
106 | }
107 |
108 | if (!fileName?.EndsWith(".osu") ?? false)
109 | continue;
110 |
111 | var beatmapFilePath = directoryStorage.GetFullPath(theEntry.Name);
112 | var beatmap = Beatmap.ReadBeatmap(directoryStorage, beatmapFilePath);
113 |
114 | db.Context.Beatmaps.Add(new BeatmapModel
115 | {
116 | File = fileName,
117 | Audio = beatmap.General.AudioFilename,
118 | Path = directoryStorage.GetFullPath(string.Empty),
119 | Thumbnail = beatmap.BackgroundFilename
120 | });
121 | }
122 | }
123 |
124 | public BeatmapContainer LoadBeatmap(Storage storage, string fileName)
125 | {
126 | WorkingBeatmap.Value = Beatmap.ReadBeatmap(storage, fileName);
127 |
128 | return new BeatmapContainer(WorkingBeatmap);
129 | }
130 |
131 | private readonly List _alreadyRandomized = new();
132 |
133 | public BeatmapContainer NextRandomMap()
134 | {
135 | WorkingBeatmap.Value?.Track.Stop();
136 |
137 | var ctx = DbContextFactory.Get();
138 | while (true)
139 | {
140 | var beatmapModel = ctx.Beatmaps.Where(s => !_alreadyRandomized.Contains(s.Id))
141 | .ToList()
142 | .OrderBy(_ => Guid.NewGuid())
143 | .FirstOrDefault();
144 |
145 | // Never repeating beatmaps
146 | if (beatmapModel == null)
147 | {
148 | // We do not have a single beatmap we could use
149 | if (_alreadyRandomized.Count <= 0)
150 | {
151 | return null;
152 | }
153 |
154 | _alreadyRandomized.Clear();
155 | continue;
156 | }
157 |
158 | var beatmapStorage = Storage.GetStorageForDirectory(beatmapModel?.Path);
159 | _alreadyRandomized.Add(beatmapModel.Id);
160 | return LoadBeatmap(beatmapStorage, beatmapModel.File);
161 | }
162 | }
163 |
164 | public BeatmapContainer PreviousRandomMap()
165 | {
166 | var ctx = DbContextFactory.Get();
167 |
168 | while (!ctx.Beatmaps.Any(s => s.Id == _alreadyRandomized.LastOrDefault()))
169 | {
170 | if (_alreadyRandomized.Count <= 0)
171 | {
172 | // We don't have any beatmaps to randomize take the next one
173 | return NextRandomMap();
174 | }
175 |
176 | // Beatmap deleted, lets try again
177 | _alreadyRandomized.RemoveAt(_alreadyRandomized.Count - 1);
178 | }
179 |
180 | _alreadyRandomized.RemoveAt(_alreadyRandomized.Count - 1); // Pop back
181 |
182 | var beatmapModel = ctx.Beatmaps
183 | .FirstOrDefault(s => s.Id == _alreadyRandomized.LastOrDefault());
184 |
185 | if (beatmapModel != null)
186 | {
187 | var beatmapStorage = Storage.GetStorageForDirectory(beatmapModel.Path);
188 | return LoadBeatmap(beatmapStorage, beatmapModel.File);
189 | }
190 |
191 | // Couldn't find the last map, get the next map instead!
192 | return NextRandomMap();
193 | }
194 | }
195 | }
--------------------------------------------------------------------------------
/Qsor.Game/Beatmaps/TimingPoint.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 |
3 | namespace Qsor.Game.Beatmaps
4 | {
5 | public class TimingPoint
6 | {
7 | public double Offset;
8 | public double MsPerBeat;
9 | public int Meter;
10 | public int SampleSet;
11 | public int SampleIndex;
12 | public int Volume;
13 | public bool Inherited;
14 | public bool KiaiMode;
15 |
16 | [CanBeNull] public TimingPoint Parent = null;
17 | }
18 | }
--------------------------------------------------------------------------------
/Qsor.Game/Beatmaps/WorkingBeatmap.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Audio;
3 | using osu.Framework.Audio.Track;
4 | using osu.Framework.Graphics.Rendering;
5 | using osu.Framework.Graphics.Textures;
6 | using osu.Framework.IO.Stores;
7 |
8 | namespace Qsor.Game.Beatmaps
9 | {
10 | public partial class WorkingBeatmap : Beatmap
11 | {
12 | private StorageBackedResourceStore _beatmapStore;
13 | private ITrackStore _trackStore;
14 |
15 | public Track Track;
16 | public Texture Background;
17 |
18 | [BackgroundDependencyLoader]
19 | private void Load(IRenderer renderer, AudioManager audio, QsorBaseGame game)
20 | {
21 | _trackStore = audio.GetTrackStore(_beatmapStore = new StorageBackedResourceStore(BeatmapStorage));
22 |
23 | // TODO: maybe don't do that.
24 | Track = _trackStore.Get(General.AudioFilename);
25 | Background = Texture.FromStream(renderer, BeatmapStorage.GetStream(BackgroundFilename));
26 | }
27 |
28 | public void Play() => Track.Start();
29 |
30 | protected override void Dispose(bool isDisposing)
31 | {
32 | if (isDisposing)
33 | {
34 | base.Dispose(true);
35 | return;
36 | }
37 |
38 | _trackStore?.Dispose();
39 | _beatmapStore?.Dispose();
40 |
41 | base.Dispose(false);
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Qsor.Game/Configuration/QsorConfiguration.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Configuration;
2 | using osu.Framework.Platform;
3 |
4 | namespace Qsor.Game.Configuration
5 | {
6 | public class QsorConfigManager : IniConfigManager
7 | {
8 | protected override void InitialiseDefaults()
9 | {
10 | SetDefault(QsorSetting.BeatmapSetId, 756794);
11 | SetDefault(QsorSetting.BeatmapFile, "TheFatRat - Mayday (feat. Laura Brehm) (Voltaeyx) [[2B] Calling Out Mayday].osu");
12 | }
13 |
14 | public QsorConfigManager(Storage storage) :
15 | base(storage)
16 | {
17 | }
18 | }
19 |
20 | public enum QsorSetting
21 | {
22 | BeatmapFile,
23 | BeatmapSetId,
24 | }
25 | }
--------------------------------------------------------------------------------
/Qsor.Game/Database/DatabaseWriteUsage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Qsor.Game.Database
5 | {
6 | // Modified version of https://github.com/ppy/osu/blob/master/osu.Game/Database/DatabaseWriteUsage.cs under MIT License!
7 | public class DatabaseWriteUsage : IDisposable
8 | {
9 | public readonly QsorDbContext Context;
10 | private readonly Action _usageCompleted;
11 | public readonly List Errors = new();
12 |
13 | private bool _isDisposed;
14 |
15 | ///
16 | /// Whether this write usage will commit a transaction on completion.
17 | /// If false, there is a parent usage responsible for transaction commit.
18 | ///
19 | public bool IsTransactionLeader = false;
20 |
21 | public DatabaseWriteUsage(QsorDbContext context, Action onCompleted)
22 | {
23 | Context = context;
24 | _usageCompleted = onCompleted;
25 | }
26 |
27 | public bool PerformedWrite { get; private set; }
28 |
29 | public void Dispose()
30 | {
31 | Dispose(true);
32 | GC.SuppressFinalize(this);
33 | }
34 |
35 | protected void Dispose(bool disposing)
36 | {
37 | if (_isDisposed)
38 | return;
39 |
40 | _isDisposed = true;
41 |
42 | try
43 | {
44 | PerformedWrite |= Context.SaveChanges() > 0;
45 | } catch (Exception e)
46 | {
47 | Errors.Add(e);
48 | throw;
49 | }
50 | finally
51 | {
52 | _usageCompleted?.Invoke(this);
53 | }
54 | }
55 |
56 | ~DatabaseWriteUsage()
57 | {
58 | Dispose(false);
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/Qsor.Game/Database/Migrations/20200331222405_BeatmapModel.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 |
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 | using Microsoft.EntityFrameworkCore.Migrations;
6 |
7 | namespace Qsor.Game.Database.Migrations
8 | {
9 | [DbContext(typeof(QsorDbContext))]
10 | [Migration("20200331222405_BeatmapModel")]
11 | partial class BeatmapModel
12 | {
13 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
14 | {
15 | #pragma warning disable 612, 618
16 | modelBuilder
17 | .HasAnnotation("ProductVersion", "3.1.3");
18 |
19 | modelBuilder.Entity("Qsor.Game.Database.Models.BeatmapModel", b =>
20 | {
21 | b.Property("Id")
22 | .ValueGeneratedOnAdd()
23 | .HasColumnType("INTEGER");
24 |
25 | b.Property("Audio")
26 | .HasColumnType("TEXT");
27 |
28 | b.Property("File")
29 | .HasColumnType("TEXT");
30 |
31 | b.Property("Path")
32 | .HasColumnType("TEXT");
33 |
34 | b.Property("Thumbnail")
35 | .HasColumnType("TEXT");
36 |
37 | b.HasKey("Id");
38 |
39 | b.ToTable("beatmaps");
40 | });
41 | #pragma warning restore 612, 618
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Qsor.Game/Database/Migrations/20200331222405_BeatmapModel.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.Migrations;
2 |
3 | namespace Qsor.Game.Database.Migrations
4 | {
5 | public partial class BeatmapModel : Migration
6 | {
7 | protected override void Up(MigrationBuilder migrationBuilder)
8 | {
9 | migrationBuilder.CreateTable(
10 | name: "beatmaps",
11 | columns: table => new
12 | {
13 | Id = table.Column(nullable: false)
14 | .Annotation("Sqlite:Autoincrement", true),
15 | File = table.Column(nullable: true),
16 | Path = table.Column(nullable: true),
17 | Audio = table.Column(nullable: true),
18 | Thumbnail = table.Column(nullable: true)
19 | },
20 | constraints: table =>
21 | {
22 | table.PrimaryKey("PK_beatmaps", x => x.Id);
23 | });
24 | }
25 |
26 | protected override void Down(MigrationBuilder migrationBuilder)
27 | {
28 | migrationBuilder.DropTable(
29 | name: "beatmaps");
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Qsor.Game/Database/Migrations/QsorDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 |
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.EntityFrameworkCore.Infrastructure;
5 |
6 | namespace Qsor.Game.Database.Migrations
7 | {
8 | [DbContext(typeof(QsorDbContext))]
9 | partial class QsorDbContextModelSnapshot : ModelSnapshot
10 | {
11 | protected override void BuildModel(ModelBuilder modelBuilder)
12 | {
13 | #pragma warning disable 612, 618
14 | modelBuilder
15 | .HasAnnotation("ProductVersion", "3.1.3");
16 |
17 | modelBuilder.Entity("Qsor.Game.Database.Models.BeatmapModel", b =>
18 | {
19 | b.Property("Id")
20 | .ValueGeneratedOnAdd()
21 | .HasColumnType("INTEGER");
22 |
23 | b.Property("Audio")
24 | .HasColumnType("TEXT");
25 |
26 | b.Property("File")
27 | .HasColumnType("TEXT");
28 |
29 | b.Property("Path")
30 | .HasColumnType("TEXT");
31 |
32 | b.Property("Thumbnail")
33 | .HasColumnType("TEXT");
34 |
35 | b.HasKey("Id");
36 |
37 | b.ToTable("beatmaps");
38 | });
39 | #pragma warning restore 612, 618
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Qsor.Game/Database/Models/BeatmapModel.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations.Schema;
2 |
3 | namespace Qsor.Game.Database.Models
4 | {
5 | [Table("beatmaps")]
6 | public class BeatmapModel
7 | {
8 | public int Id { get; set; }
9 | public string File { get; set; } // this.osu
10 | public string Path { get; set; } // /Songs/.../this.osu
11 |
12 | public string Audio { get; set; }
13 | public string Thumbnail { get; set; }
14 |
15 | // TODO: Difficulty and other information pre-cached.
16 | }
17 | }
--------------------------------------------------------------------------------
/Qsor.Game/Database/QsorDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using osu.Framework.Logging;
3 | using Qsor.Game.Database.Models;
4 |
5 | namespace Qsor.Game.Database
6 | {
7 | public sealed class QsorDbContext : DbContext
8 | {
9 | private readonly string _connectionString;
10 |
11 | public QsorDbContext()
12 | : this("DataSource=:memory:")
13 | {
14 | }
15 |
16 | public QsorDbContext(string connectionString)
17 | {
18 | _connectionString = connectionString;
19 |
20 | var connection = Database.GetDbConnection();
21 |
22 | try
23 | {
24 | connection.Open();
25 |
26 | using var cmd = connection.CreateCommand();
27 |
28 | cmd.CommandText = "PRAGMA journal_mode=WAL;";
29 | cmd.ExecuteNonQuery();
30 | }
31 | catch
32 | {
33 | connection.Close();
34 | throw;
35 | }
36 | }
37 |
38 | public DbSet Beatmaps { get; set; }
39 |
40 | public void Migrate()
41 | {
42 | Logger.LogPrint("Migrating Database");
43 | Database.Migrate();
44 | }
45 |
46 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
47 | {
48 | optionsBuilder.UseSqlite(_connectionString);
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/Qsor.Game/Database/QsorDbContextFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading;
3 | using Microsoft.EntityFrameworkCore.Storage;
4 | using osu.Framework.Platform;
5 |
6 | namespace Qsor.Game.Database
7 | {
8 | // Modified version of https://github.com/ppy/osu/blob/master/osu.Game/Database/DatabaseContextFactory.cs under MIT License!
9 | public sealed class QsorDbContextFactory
10 | {
11 | private readonly Storage _storage;
12 | private readonly object _writeLock = new();
13 | private bool _currentWriteDidError;
14 |
15 | private bool _currentWriteDidWrite;
16 |
17 | private IDbContextTransaction _currentWriteTransaction;
18 |
19 | private int _currentWriteUsages;
20 | private ThreadLocal _threadContexts;
21 |
22 | public QsorDbContextFactory(Storage storage)
23 | {
24 | _storage = storage;
25 | RecycleThreadContexts();
26 | }
27 |
28 | ///
29 | /// Get a context for the current thread for read-only usage.
30 | /// If a is in progress, the existing write-safe context will be returned.
31 | ///
32 | public QsorDbContext Get() => _threadContexts.Value;
33 |
34 | ///
35 | /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying
36 | /// context).
37 | /// This method may block if a write is already active on a different thread.
38 | ///
39 | /// Whether to start a transaction for this write.
40 | /// A usage containing a usable context.
41 | public DatabaseWriteUsage GetForWrite(bool withTransaction = true)
42 | {
43 | Monitor.Enter(_writeLock);
44 | QsorDbContext context;
45 |
46 | try
47 | {
48 | if (_currentWriteTransaction == null && withTransaction)
49 | {
50 | // this mitigates the fact that changes on tracked entities will not be rolled back with the transaction by ensuring write operations are always executed in isolated contexts.
51 | // if this results in sub-optimal efficiency, we may need to look into removing Database-level transactions in favour of running SaveChanges where we currently commit the transaction.
52 | if (_threadContexts.IsValueCreated)
53 | RecycleThreadContexts();
54 |
55 | context = _threadContexts.Value;
56 | _currentWriteTransaction = context.Database.BeginTransaction();
57 | }
58 | else
59 | {
60 | // we want to try-catch the retrieval of the context because it could throw an error (in CreateContext).
61 | context = _threadContexts.Value;
62 | }
63 | } catch
64 | {
65 | // retrieval of a context could trigger a fatal error.
66 | Monitor.Exit(_writeLock);
67 | throw;
68 | }
69 |
70 | Interlocked.Increment(ref _currentWriteUsages);
71 |
72 | return new DatabaseWriteUsage(context, UsageCompleted)
73 | {
74 | IsTransactionLeader = _currentWriteTransaction != null && _currentWriteUsages == 1
75 | };
76 | }
77 |
78 | private void UsageCompleted(DatabaseWriteUsage usage)
79 | {
80 | var usages = Interlocked.Decrement(ref _currentWriteUsages);
81 |
82 | try
83 | {
84 | _currentWriteDidWrite |= usage.PerformedWrite;
85 | _currentWriteDidError |= usage.Errors.Any();
86 |
87 | if (usages == 0)
88 | {
89 | if (_currentWriteDidError)
90 | _currentWriteTransaction?.Rollback();
91 | else
92 | _currentWriteTransaction?.Commit();
93 |
94 | if (_currentWriteDidWrite || _currentWriteDidError)
95 | {
96 | // explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged).
97 | usage.Context.Dispose();
98 |
99 | // once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches.
100 | RecycleThreadContexts();
101 | }
102 |
103 | _currentWriteTransaction = null;
104 | _currentWriteDidWrite = false;
105 | _currentWriteDidError = false;
106 | }
107 | }
108 | finally
109 | {
110 | Monitor.Exit(_writeLock);
111 | }
112 | }
113 |
114 | private void RecycleThreadContexts()
115 | {
116 | // Contexts for other threads are not disposed as they may be in use elsewhere. Instead, fresh contexts are exposed
117 | // for other threads to use, and we rely on the finalizer inside SoraDbContext to handle their previous contexts
118 | _threadContexts?.Value.Dispose();
119 | _threadContexts = new ThreadLocal(CreateContext, true);
120 | }
121 |
122 | private QsorDbContext CreateContext()
123 | => new(_storage.GetDatabaseConnectionString("qsor")) {
124 | Database = {
125 | AutoTransactionBehavior = Microsoft.EntityFrameworkCore.AutoTransactionBehavior.Always
126 | }
127 | };
128 |
129 | public void ResetDatabase()
130 | {
131 | lock (_writeLock)
132 | {
133 | RecycleThreadContexts();
134 | try
135 | {
136 | _storage.DeleteDatabase("qsor");
137 | }
138 | catch
139 | {
140 | // for now we are not sure why file handles are kept open by EF, but this is generally only used in testing
141 | }
142 | }
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/Qsor.Game/Database/StorageExtension.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Platform;
2 |
3 | namespace Qsor.Game.Database
4 | {
5 | // Bring back storage stuff which was deleted from o!F some time ago
6 | internal static class StorageExtension
7 | {
8 | public static string GetDatabaseConnectionString(this Storage storage, string name)
9 | => string.Concat("Data Source=", storage.GetFullPath($@"{name}.db", true));
10 |
11 | public static void DeleteDatabase(this Storage storage, string name)
12 | => storage.Delete($@"{name}.db");
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/ClickableSpriteIcon.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Cursor;
2 | using osu.Framework.Graphics.Sprites;
3 | using osu.Framework.Input.Events;
4 | using osu.Framework.Localisation;
5 |
6 | namespace Qsor.Game.Graphics
7 | {
8 | public partial class ClickableSpriteIcon : SpriteIcon, IHasTooltip
9 | {
10 | public LocalisableString TooltipText { get; set; }
11 |
12 | public delegate bool ClickDelegate(ClickEvent e);
13 | public ClickDelegate ClickEvent;
14 |
15 | protected override bool OnClick(ClickEvent e)
16 | {
17 | return ClickEvent?.Invoke(e) ?? false;
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/Containers/BackgroundImageContainer.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Sprites;
5 | using osu.Framework.Graphics.Textures;
6 | using osuTK;
7 | using osuTK.Graphics;
8 |
9 | namespace Qsor.Game.Graphics.Containers
10 | {
11 | public partial class BackgroundImageContainer : BufferedContainer
12 | {
13 | private Sprite _backgroundImage;
14 |
15 | [BackgroundDependencyLoader]
16 | private void Load()
17 | {
18 | RelativeSizeAxes = Axes.Both;
19 |
20 | Child = _backgroundImage = new Sprite
21 | {
22 | RelativeSizeAxes = Axes.Both,
23 | Anchor = Anchor.Centre,
24 | Origin = Anchor.Centre,
25 | FillMode = FillMode.Fill
26 | };
27 |
28 | BackgroundColour = Color4.Black;
29 | Colour = new Color4(1,1,1, .5f);
30 | BlurSigma = new Vector2(5f);
31 | }
32 |
33 | public void SetTexture(Texture tex)
34 | {
35 | _backgroundImage.Texture = tex;
36 |
37 | ForceRedraw();
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/Containers/BeatSyncedContainer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
2 | // See the LICENCE file in the repository root for full licence text.
3 |
4 | using osu.Framework.Allocation;
5 | using osu.Framework.Audio.Track;
6 | using osu.Framework.Bindables;
7 | using osu.Framework.Graphics.Containers;
8 | using Qsor.Game.Beatmaps;
9 |
10 | namespace Qsor.Game.Graphics.Containers
11 | {
12 | public partial class BeatSyncedContainer : Container
13 | {
14 | protected readonly IBindable Beatmap = new Bindable();
15 |
16 | private int _lastBeat;
17 | private TimingPoint _lastTimingPoint;
18 |
19 | ///
20 | /// The amount of time before a beat we should fire .
21 | /// This allows for adding easing to animations that may be synchronised to the beat.
22 | ///
23 | protected double EarlyActivationMilliseconds;
24 |
25 | ///
26 | /// The time in milliseconds until the next beat.
27 | ///
28 | public double TimeUntilNextBeat { get; private set; }
29 |
30 | ///
31 | /// The time in milliseconds since the last beat
32 | ///
33 | public double TimeSinceLastBeat { get; private set; }
34 |
35 | ///
36 | /// How many beats per beatlength to trigger. Defaults to 1.
37 | ///
38 | public int Divisor { get; set; } = 1;
39 |
40 | ///
41 | /// An optional minimum beat length. Any beat length below this will be multiplied by two until valid.
42 | ///
43 | public double MinimumBeatLength { get; set; }
44 |
45 | ///
46 | /// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
47 | ///
48 | private const double DefaultBeatLength = 60000.0 / 60.0;
49 |
50 | private TimingPoint _defaultTiming;
51 | //private EffectControlPoint defaultEffect;
52 | private ChannelAmplitudes _defaultAmplitudes;
53 |
54 | protected bool IsBeatSyncedWithTrack { get; private set; }
55 | protected bool IsTrackPaused => Beatmap?.Value?.Track?.IsRunning ?? true;
56 |
57 | private TimingPoint _lastValidTimingPoint;
58 |
59 | protected override void Update()
60 | {
61 | Track track = null;
62 | Beatmap beatmap = null;
63 |
64 | var currentTrackTime = 0d;
65 | var timingPoint = new TimingPoint();
66 | //EffectControlPoint effectPoint = null;
67 |
68 | if (Beatmap.Value?.Track?.IsLoaded == true)
69 | {
70 | track = Beatmap.Value.Track;
71 | beatmap = Beatmap.Value;
72 | }
73 |
74 | if (track != null && beatmap != null && track.IsRunning && track.Length > 0)
75 | {
76 | currentTrackTime = track.CurrentTime + EarlyActivationMilliseconds;
77 |
78 | timingPoint = beatmap.GetTimingPointAt(currentTrackTime);
79 | }
80 |
81 | timingPoint ??= _defaultTiming;
82 |
83 | IsBeatSyncedWithTrack = timingPoint.MsPerBeat > 0;
84 |
85 | if (!IsBeatSyncedWithTrack)
86 | {
87 | // inherit kiai mode
88 | if (_lastValidTimingPoint != null && timingPoint != null && timingPoint != _defaultTiming)
89 | _lastValidTimingPoint.KiaiMode = timingPoint.KiaiMode;
90 |
91 | currentTrackTime = Clock.CurrentTime;
92 | timingPoint = _lastValidTimingPoint ?? _defaultTiming;
93 | }
94 | else
95 | {
96 | _lastValidTimingPoint = timingPoint;
97 | }
98 |
99 | if (!track?.IsRunning ?? true)
100 | {
101 | currentTrackTime = Clock.CurrentTime;
102 | timingPoint = _defaultTiming;
103 | }
104 |
105 | var beatLength = timingPoint.MsPerBeat / Divisor;
106 |
107 | while (beatLength < MinimumBeatLength)
108 | beatLength *= 2;
109 |
110 | var beatIndex = (int)((currentTrackTime - timingPoint.Offset) / beatLength);
111 |
112 | // The beats before the start of the first control point are off by 1, this should do the trick
113 | if (currentTrackTime < timingPoint.Offset)
114 | beatIndex--;
115 |
116 | TimeUntilNextBeat = (timingPoint.Offset - currentTrackTime) % beatLength;
117 | if (double.IsNaN(TimeUntilNextBeat))
118 | TimeUntilNextBeat = 0;
119 |
120 | if (TimeUntilNextBeat < 0)
121 | TimeUntilNextBeat += beatLength;
122 |
123 | TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
124 |
125 | if (timingPoint.Equals(_lastTimingPoint) && beatIndex == _lastBeat)
126 | return;
127 |
128 | using (BeginDelayedSequence(-TimeSinceLastBeat, true))
129 | OnNewBeat(beatIndex, timingPoint, track?.CurrentAmplitudes ?? _defaultAmplitudes);
130 |
131 | _lastBeat = beatIndex;
132 | _lastTimingPoint = timingPoint;
133 | }
134 |
135 | [BackgroundDependencyLoader]
136 | private void Load(BeatmapManager beatmapManager)
137 | {
138 | Beatmap.BindTo(beatmapManager.WorkingBeatmap);
139 |
140 | _defaultTiming = new TimingPoint
141 | {
142 | MsPerBeat = DefaultBeatLength,
143 | };
144 |
145 | _defaultAmplitudes = new ChannelAmplitudes(0, 0, new float[256]);
146 | }
147 |
148 | protected virtual void OnNewBeat(int beatIndex, TimingPoint timingPoint, ChannelAmplitudes amplitudes)
149 | {
150 | }
151 | }
152 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/Containers/ParallaxContainer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
2 | // See the LICENCE file in the repository root for full licence text.
3 |
4 | using System;
5 | using osu.Framework.Allocation;
6 | using osu.Framework.Graphics;
7 | using osu.Framework.Graphics.Containers;
8 | using osu.Framework.Input;
9 | using osu.Framework.Utils;
10 | using osuTK;
11 |
12 | namespace Qsor.Game.Graphics.Containers
13 | {
14 | public partial class ParallaxContainer : Container, IRequireHighFrequencyMousePosition
15 | {
16 | public const float DefaultParallaxAmount = 0.02f;
17 |
18 | ///
19 | /// The amount of parallax movement. Negative values will reverse the direction of parallax relative to user input.
20 | ///
21 | public float ParallaxAmount = DefaultParallaxAmount;
22 | public ParallaxContainer()
23 | {
24 | RelativeSizeAxes = Axes.Both;
25 |
26 | AddInternal(_content = new Container
27 | {
28 | RelativeSizeAxes = Axes.Both,
29 | Anchor = Anchor.Centre,
30 | Origin = Anchor.Centre
31 | });
32 | }
33 |
34 | public readonly Container _content;
35 | private InputManager _input;
36 |
37 | protected override Container Content => _content;
38 |
39 | [BackgroundDependencyLoader]
40 | private void Load()
41 | {
42 | _content.MoveTo(Vector2.Zero, _firstUpdate ? 0 : 1000, Easing.OutQuint);
43 | _content.Scale = new Vector2(1 + Math.Abs(ParallaxAmount));
44 | }
45 |
46 | protected override void LoadComplete()
47 | {
48 | base.LoadComplete();
49 | _input = GetContainingInputManager();
50 | }
51 |
52 | private bool _firstUpdate = true;
53 |
54 | protected override void Update()
55 | {
56 | base.Update();
57 |
58 | var offset = (_input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(_input.CurrentState.Mouse.Position) - DrawSize / 2) * ParallaxAmount;
59 |
60 | const float parallaxDuration = 100;
61 |
62 | var elapsed = Math.Clamp(Clock.ElapsedFrameTime, 0, parallaxDuration);
63 |
64 | _content.Position = Interpolation.ValueAt(elapsed, _content.Position, offset, 0, parallaxDuration, Easing.OutQuint);
65 | _content.Scale = Interpolation.ValueAt(elapsed, _content.Scale, new Vector2(1 + Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint);
66 |
67 | _firstUpdate = false;
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/Containers/QsorTooltipContainer.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Cursor;
5 | using osu.Framework.Graphics.Shapes;
6 | using osu.Framework.Localisation;
7 | using osuTK;
8 | using osuTK.Graphics;
9 |
10 | namespace Qsor.Game.Graphics.Containers
11 | {
12 | public partial class QsorTooltipContainer : TooltipContainer
13 | {
14 | protected override ITooltip CreateTooltip() => new QsorTooltip();
15 |
16 | protected override double AppearDelay => 120;
17 |
18 | public QsorTooltipContainer(CursorContainer cursor)
19 | : base(cursor)
20 | {
21 | }
22 |
23 | public partial class QsorTooltip : Tooltip
24 | {
25 | private TextFlowContainer _textFlowContainer;
26 |
27 | [BackgroundDependencyLoader]
28 | private void Load()
29 | {
30 | CornerRadius = 4f;
31 |
32 | Masking = true;
33 | BorderColour = Color4.Gray;
34 | BorderThickness = 2;
35 |
36 | Scale = new Vector2(.8f);
37 |
38 | AutoSizeAxes = Axes.Both;
39 | Alpha = 0;
40 |
41 | Children = new Drawable[]
42 | {
43 | new Box
44 | {
45 | Colour = Color4.Black,
46 | RelativeSizeAxes = Axes.Both,
47 | },
48 | _textFlowContainer = new TextFlowContainer
49 | {
50 | AutoSizeAxes = Axes.Both,
51 | MaximumSize = new Vector2(400, float.MaxValue),
52 | }
53 | };
54 | }
55 |
56 | private string _cachedString;
57 | public override void SetContent(LocalisableString content)
58 | {
59 | var contentS = content.ToString();
60 | if (_cachedString == contentS)
61 | return;
62 |
63 | _textFlowContainer.Text = contentS;
64 | _cachedString = contentS;
65 | }
66 |
67 | protected override void PopIn()
68 | {
69 | this.FadeIn(100, Easing.In);
70 | }
71 |
72 | protected override void PopOut()
73 | {
74 | this.FadeOut(100, Easing.In);
75 | }
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/DragableProgressbar.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Cursor;
2 | using osu.Framework.Input.Events;
3 | using osu.Framework.Localisation;
4 | using Qsor.Game.Graphics.Drawables;
5 |
6 | namespace Qsor.Game.Graphics
7 | {
8 | public partial class DragableProgressbar : DrawableProgressbar, IHasTooltip
9 | {
10 | public LocalisableString TooltipText { get; set; }
11 |
12 | protected override bool OnClick(ClickEvent e)
13 | {
14 | Current.Value = e.MousePosition.X / DrawWidth;
15 | return true;
16 | }
17 |
18 | protected override bool OnDragStart(DragStartEvent e) => true;
19 |
20 | protected override void OnDrag(DragEvent e)
21 | {
22 | Current.Value = e.MousePosition.X / DrawWidth;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/Drawables/DrawableProgressbar.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Bindables;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Containers;
5 | using osu.Framework.Graphics.Shapes;
6 | using osu.Framework.Graphics.UserInterface;
7 | using osuTK.Graphics;
8 |
9 | namespace Qsor.Game.Graphics.Drawables
10 | {
11 | public partial class DrawableProgressbar : Container, IHasCurrentValue
12 | {
13 | private Box _progressBox;
14 |
15 | private readonly BindableWithCurrent _current = new();
16 |
17 | public Bindable Current
18 | {
19 | get => _current.Current;
20 | set => _current.Current = value;
21 | }
22 |
23 | public DrawableProgressbar()
24 | {
25 | Height = 6;
26 | }
27 |
28 | [BackgroundDependencyLoader]
29 | private void Load()
30 | {
31 | RelativeSizeAxes = Axes.X;
32 |
33 | Masking = true;
34 |
35 | CornerRadius = 3;
36 | CornerExponent = 2f;
37 |
38 | AddInternal(new Box
39 | {
40 | Colour = new Color4(1f, 1f, 1f, 0.5f),
41 | RelativeSizeAxes = Axes.Both
42 | });
43 |
44 | AddInternal(_progressBox = new Box
45 | {
46 | Colour = Colour,
47 | RelativeSizeAxes = Axes.Y,
48 | });
49 |
50 | _current.ValueChanged += e =>
51 | {
52 | _progressBox.Width = (int) (DrawSize.X * e.NewValue);
53 | };
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Online/Drawables/DrawableAvatar.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using osu.Framework.Allocation;
3 | using osu.Framework.Bindables;
4 | using osu.Framework.Graphics;
5 | using osu.Framework.Graphics.Containers;
6 | using osu.Framework.Graphics.Sprites;
7 | using osu.Framework.Graphics.Textures;
8 | using osuTK;
9 | using Qsor.Game.Online;
10 | using Qsor.Game.Online.Users;
11 |
12 | namespace Qsor.Game.Graphics.UserInterface.Online.Drawables
13 | {
14 | public partial class DrawableAvatar : CompositeDrawable
15 | {
16 | private Sprite Avatar { get; } = new();
17 |
18 | public Bindable User = new(new User{ Id = 1 });
19 |
20 | private CancellationTokenSource _cancellationTokenSource = new();
21 |
22 | [BackgroundDependencyLoader]
23 | private void Load(TextureStore ts)
24 | {
25 | User.ValueChanged += async e
26 | => Avatar.Texture = await ts.GetAsync(BanchoClient.AvatarUrl + "/" + (e.NewValue?.Id ?? User.Default.Id), _cancellationTokenSource.Token);
27 |
28 | Avatar.FillMode = FillMode.Fit;
29 | Avatar.RelativeSizeAxes = Axes.Both;
30 | RelativeSizeAxes = Axes.Both;
31 |
32 | Margin = new MarginPadding(10);
33 | Scale = new Vector2(.8f);
34 |
35 | AddInternal(Avatar);
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Online/Drawables/DrawableLevelBar.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osuTK.Graphics;
3 | using Qsor.Game.Graphics.Drawables;
4 | using Qsor.Game.Online.Users;
5 |
6 | namespace Qsor.Game.Graphics.UserInterface.Online.Drawables
7 | {
8 | public partial class DrawableLevelBar : DrawableProgressbar
9 | {
10 | private readonly User _user;
11 |
12 | public DrawableLevelBar(User user)
13 | {
14 | _user = user;
15 | }
16 |
17 | [BackgroundDependencyLoader]
18 | private void Load()
19 | {
20 | Colour = Color4.Yellow;
21 | }
22 |
23 | protected override void LoadComplete()
24 | {
25 | Current.Value = _user.Statistics.GetProgress();
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/MusicPlayerOverlay.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Sprites;
5 | using osuTK;
6 | using Qsor.Game.Beatmaps;
7 | using Qsor.Game.Graphics.UserInterface.Overlays.Notification;
8 |
9 | namespace Qsor.Game.Graphics.UserInterface.Overlays
10 | {
11 | public partial class MusicPlayerOverlay : CompositeDrawable
12 | {
13 | private FillFlowContainer _flow;
14 | private FillFlowContainer _icons;
15 |
16 | private DragableProgressbar _progressbar;
17 |
18 | private BeatmapManager _beatmapManager;
19 |
20 | [BackgroundDependencyLoader]
21 | private void Load(NotificationOverlay notificationOverlay, BeatmapManager beatmapManager)
22 | {
23 | _beatmapManager = beatmapManager;
24 |
25 | AutoSizeAxes = Axes.Both;
26 | Padding = new MarginPadding(8);
27 |
28 | _flow = new FillFlowContainer
29 | {
30 | AutoSizeAxes = Axes.Both,
31 | Direction = FillDirection.Vertical,
32 | Spacing = new Vector2(6),
33 | };
34 |
35 | _icons = new FillFlowContainer
36 | {
37 | AutoSizeAxes = Axes.Both,
38 | Direction = FillDirection.Horizontal,
39 | Spacing = new Vector2(16, 0),
40 | };
41 |
42 | _icons.Add(new ClickableSpriteIcon
43 | {
44 | Icon = FontAwesome.Solid.StepBackward,
45 | Size = new Vector2(16),
46 |
47 | TooltipText = "Previous track",
48 |
49 | ClickEvent = e =>
50 | {
51 | notificationOverlay.AddBigNotification("<< Prev", 1100);
52 |
53 | beatmapManager?.PreviousRandomMap();
54 | beatmapManager?.WorkingBeatmap?.Value?.Play();
55 |
56 | return true;
57 | }
58 | });
59 |
60 | _icons.Add(new ClickableSpriteIcon
61 | {
62 | Icon = FontAwesome.Solid.Play,
63 | Size = new Vector2(16),
64 |
65 | TooltipText = "Play",
66 |
67 | ClickEvent = _ =>
68 | {
69 | notificationOverlay.AddBigNotification("Play", 1100);
70 |
71 | var track = beatmapManager?.WorkingBeatmap?.Value?.Track;
72 | if (track != null)
73 | {
74 | if (track.IsRunning)
75 | {
76 | track.Restart();
77 | }
78 | else
79 | {
80 | track.Start();
81 | }
82 | }
83 |
84 | return true;
85 | }
86 | });
87 |
88 | _icons.Add(new ClickableSpriteIcon
89 | {
90 | Icon = FontAwesome.Solid.Pause,
91 | Size = new Vector2(16),
92 |
93 | TooltipText = "Pause",
94 |
95 | ClickEvent = e =>
96 | {
97 | beatmapManager?.WorkingBeatmap?.Value?.Track?.Start();
98 |
99 | var track = beatmapManager?.WorkingBeatmap?.Value?.Track;
100 | if (track != null)
101 | {
102 | if (track.IsRunning)
103 | {
104 | notificationOverlay.AddBigNotification("Pause", 1100);
105 | track.Stop();
106 | }
107 | else
108 | {
109 | notificationOverlay.AddBigNotification("Unpause", 1100);
110 | track.Start();
111 | }
112 | }
113 | else
114 | {
115 | notificationOverlay.AddBigNotification("Pause", 1100);
116 | }
117 |
118 | return true;
119 | }
120 | });
121 |
122 | _icons.Add(new ClickableSpriteIcon
123 | {
124 | Icon = FontAwesome.Solid.Stop,
125 | Size = new Vector2(16),
126 |
127 | TooltipText = "Stop the music!",
128 |
129 | ClickEvent = e =>
130 | {
131 | notificationOverlay.AddBigNotification("Stop Playing", 1100);
132 |
133 | var track = beatmapManager?.WorkingBeatmap?.Value?.Track;
134 | track?.Stop();
135 | track?.Reset();
136 | return true;
137 | }
138 | });
139 |
140 | _icons.Add(new ClickableSpriteIcon
141 | {
142 | Icon = FontAwesome.Solid.StepForward,
143 | Size = new Vector2(16),
144 |
145 | TooltipText = "Next track",
146 |
147 | ClickEvent = e =>
148 | {
149 | notificationOverlay.AddBigNotification(">> Next", 1100);
150 |
151 | beatmapManager?.NextRandomMap();
152 | beatmapManager?.WorkingBeatmap?.Value?.Play();
153 |
154 | return true;
155 | }
156 | });
157 |
158 | _icons.Add(new ClickableSpriteIcon
159 | {
160 | Icon = FontAwesome.Solid.Info,
161 | Size = new Vector2(16),
162 |
163 | TooltipText = "View song info",
164 |
165 | ClickEvent = e =>
166 | {
167 | notificationOverlay.AddBigNotification("Song info will be permanently displayed.", 1100);
168 | return true;
169 | }
170 | });
171 |
172 | _icons.Add(new ClickableSpriteIcon
173 | {
174 | Icon = FontAwesome.Solid.Bars,
175 | Size = new Vector2(16),
176 |
177 | TooltipText = "Jump To window",
178 |
179 | ClickEvent = e =>
180 | {
181 | notificationOverlay.AddBigNotification("!Unimplemented!", 1100);
182 | return true;
183 | }
184 | });
185 |
186 | _flow.Add(_icons);
187 |
188 | _flow.Add(_progressbar = new DragableProgressbar
189 | {
190 | Position = new Vector2(0, 24),
191 |
192 | TooltipText = "Drag to seek to a specific point in the song.",
193 | });
194 |
195 | _progressbar.Current.ValueChanged += e =>
196 | {
197 | var track = _beatmapManager?.WorkingBeatmap?.Value?.Track;
198 | if (track == null)
199 | return;
200 |
201 | if (_progressbar.IsDragged)
202 | {
203 | track.Seek(e.NewValue * track.Length);
204 | }
205 | };
206 |
207 | AddInternal(_flow);
208 | }
209 |
210 | protected override void Update()
211 | {
212 | var track = _beatmapManager?.WorkingBeatmap?.Value?.Track;
213 | if (track == null)
214 | return;
215 |
216 | _progressbar.Current.Value = track.CurrentTime / track.Length;
217 | }
218 | }
219 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Notification/Drawables/DrawableBigNotification.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using osu.Framework.Allocation;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Containers;
5 | using osu.Framework.Graphics.Shapes;
6 | using osu.Framework.Localisation;
7 | using osu.Framework.Timing;
8 | using osuTK;
9 | using osuTK.Graphics;
10 |
11 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Notification.Drawables
12 | {
13 | public partial class DrawableBigNotification : CompositeDrawable
14 | {
15 | private readonly StopwatchClock _clock;
16 | private readonly double _duration;
17 | private readonly TextFlowContainer _textFlowContainer;
18 | private Box _background;
19 |
20 | public DrawableBigNotification(LocalisableString text, int duration = -1)
21 | {
22 | _clock = new StopwatchClock();
23 | _textFlowContainer = new TextFlowContainer
24 | {
25 | Anchor = Anchor.Centre,
26 | Origin = Anchor.Centre,
27 |
28 | Direction = FillDirection.Full,
29 | AutoSizeAxes = Axes.Both,
30 | Padding = new MarginPadding(16),
31 | TextAnchor = Anchor.Centre // Centre text
32 | };
33 |
34 | _duration = duration == -1 ? Math.Max(3000, (double) (text.ToString().Length * 100)) : duration;
35 | _textFlowContainer.AddText(text.ToString());
36 | }
37 |
38 | [BackgroundDependencyLoader]
39 | private void Load()
40 | {
41 | RelativeSizeAxes = Axes.X;
42 | AutoSizeAxes = Axes.Y;
43 |
44 | Scale = new Vector2(1.0f, 0.0f);
45 |
46 | AddInternal(_background = new Box
47 | {
48 | Colour = new Color4(0f,0f,0f,.5f),
49 | RelativeSizeAxes = Axes.Both
50 | });
51 | AddInternal(_textFlowContainer);
52 | }
53 |
54 | public override void Show()
55 | {
56 | this.FadeInFromZero(100);
57 | this.ScaleTo(new Vector2(1.0f, 1.0f), 500, Easing.OutElasticHalf);
58 | }
59 |
60 | public override void Hide()
61 | {
62 | this.FadeOut(100)
63 | .OnComplete(_ =>
64 | {
65 | if (Parent is NotificationOverlay container)
66 | container.RemoveBigNotification(this);
67 | });
68 | }
69 |
70 | protected override void Update()
71 | {
72 | if (_clock.ElapsedMilliseconds > _duration)
73 | Hide();
74 |
75 | if (!_clock.IsRunning)
76 | _clock.Start();
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Notification/Drawables/DrawableNotification.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using osu.Framework.Allocation;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Colour;
5 | using osu.Framework.Graphics.Containers;
6 | using osu.Framework.Graphics.Shapes;
7 | using osu.Framework.Input.Events;
8 | using osu.Framework.Localisation;
9 | using osu.Framework.Timing;
10 | using osuTK;
11 | using osuTK.Graphics;
12 |
13 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Notification.Drawables
14 | {
15 | public partial class DrawableNotification : CompositeDrawable
16 | {
17 | private readonly StopwatchClock _clock;
18 | private readonly Action _clickAction;
19 | private readonly ColourInfo _borderColour;
20 | private readonly TextFlowContainer _textFlowContainer;
21 | private readonly double _duration;
22 |
23 | ///
24 | /// Drawable Notification
25 | ///
26 | ///
27 | ///
28 | /// Hide after X amount of MS, -1 = PositiveInfinity
29 | ///
30 | public DrawableNotification(LocalisableString text, ColourInfo colourInfo, int duration = -1, Action clickAction = null)
31 | {
32 | Margin = new MarginPadding(6);
33 |
34 | _clock = new StopwatchClock();
35 | _clickAction = clickAction;
36 | _borderColour = colourInfo;
37 | _textFlowContainer = new TextFlowContainer
38 | {
39 | Direction = FillDirection.Full,
40 | AutoSizeAxes = Axes.Both,
41 | MaximumSize = new Vector2(290, float.MaxValue),
42 | Padding = new MarginPadding(10)
43 | };
44 | _duration = duration == -1 ? Math.Max(3000, (double) (text.ToString().Length * 100)) : duration;
45 |
46 | _textFlowContainer.AddText(text.ToString());
47 | }
48 |
49 | [BackgroundDependencyLoader]
50 | private void Load()
51 | {
52 | Masking = true;
53 | BorderThickness = 2;
54 | BorderColour = _borderColour;
55 | CornerRadius = 8;
56 | AutoSizeAxes = Axes.Y;
57 | Width = 300;
58 |
59 | AddInternal(new Box
60 | {
61 | Colour = new Color4(0f,0f,0f,.8f),
62 | RelativeSizeAxes = Axes.Both
63 | });
64 | AddInternal(_textFlowContainer);
65 | }
66 |
67 | public void FadeBorder(ColourInfo newColour, double duration = 0, Easing easing = Easing.None)
68 | => this.TransformTo(nameof(BorderColour), (ColourInfo)newColour.AverageColour, duration, easing);
69 |
70 | protected override bool OnHover(HoverEvent e)
71 | {
72 | FadeBorder(Color4.White, 100);
73 |
74 | _clock.Stop();
75 |
76 | return true;
77 | }
78 |
79 | protected override void OnHoverLost(HoverLostEvent e)
80 | {
81 | FadeBorder(_borderColour, 100);
82 |
83 | if (_clock.ElapsedMilliseconds > _duration)
84 | OnClick(null);
85 | }
86 |
87 | private bool _gotClicked;
88 |
89 | protected override bool OnClick(ClickEvent e)
90 | {
91 | if (_gotClicked)
92 | return false;
93 |
94 | if (e != null)
95 | _clickAction?.Invoke();
96 |
97 | _gotClicked = true;
98 |
99 | this.FadeOutFromOne(250).Finally(_ =>
100 | {
101 | if (Parent is FillFlowContainer container)
102 | container.Remove(this, true);
103 | });
104 |
105 | return true;
106 | }
107 |
108 | protected override void Update()
109 | {
110 | if (_clock.ElapsedMilliseconds > _duration)
111 | OnClick(null);
112 |
113 | if (!_clock.IsRunning && !IsHovered)
114 | _clock.Start();
115 |
116 | base.Update();
117 | }
118 | }
119 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Notification/NotificationOverlay.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using osu.Framework.Allocation;
4 | using osu.Framework.Graphics;
5 | using osu.Framework.Graphics.Colour;
6 | using osu.Framework.Graphics.Containers;
7 | using osu.Framework.Localisation;
8 | using Qsor.Game.Graphics.UserInterface.Overlays.Notification.Drawables;
9 |
10 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Notification
11 | {
12 | public partial class NotificationOverlay : CompositeDrawable
13 | {
14 | private FillFlowContainer _drawableNotifications;
15 |
16 | [BackgroundDependencyLoader]
17 | private void Load()
18 | {
19 | RelativeSizeAxes = Axes.Both;
20 |
21 | Anchor = Anchor.TopRight;
22 | Origin = Anchor.TopRight;
23 |
24 | _drawableNotifications = new FillFlowContainer
25 | {
26 | Anchor = Anchor.TopRight,
27 | Origin = Anchor.TopRight,
28 |
29 | Direction = FillDirection.Vertical,
30 | AutoSizeAxes = Axes.X,
31 | RelativeSizeAxes = Axes.Y,
32 | LayoutEasing = Easing.OutQuint,
33 | LayoutDuration = 400
34 | };
35 |
36 | //Padding = new MarginPadding(10);
37 |
38 | AddInternal(_drawableNotifications);
39 | }
40 |
41 | public void AddNotification(LocalisableString text, ColourInfo colourInfo, int duration = -1, Action clickAction = null)
42 | => AddNotification(new DrawableNotification(text, colourInfo, duration, clickAction));
43 |
44 | public void AddBigNotification(LocalisableString text, int duration = -1)
45 | {
46 | Scheduler.AddOnce(() =>
47 | {
48 | var notification = new DrawableBigNotification(text, duration)
49 | {
50 | Anchor = Anchor.Centre,
51 | Origin = Anchor.Centre
52 | };
53 |
54 | AddInternal(notification);
55 |
56 | notification.Show();
57 | });
58 | }
59 |
60 | public void AddNotification(DrawableNotification notification)
61 | {
62 | Scheduler.AddOnce(() =>
63 | {
64 | notification.Anchor = Anchor.BottomRight;
65 | notification.Origin = Anchor.BottomRight;
66 | notification.Alpha = 0;
67 |
68 | _drawableNotifications.Add(notification);
69 | _drawableNotifications.SetLayoutPosition(notification, -_drawableNotifications.FlowingChildren.Count());
70 |
71 | notification.FadeInFromZero(200);
72 | });
73 | }
74 |
75 | internal void RemoveBigNotification(DrawableBigNotification notification)
76 | {
77 | RemoveInternal(notification, true);
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsAudioCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Sprites;
2 |
3 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
4 | {
5 | public partial class SettingsAudioCategory : SettingsCategoryContainer
6 | {
7 | public override string Name => "Audio";
8 | public override IconUsage Icon => FontAwesome.Solid.AudioDescription;
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsEditorCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Sprites;
2 |
3 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
4 | {
5 | public partial class SettingsEditorCategory : SettingsCategoryContainer
6 | {
7 | public override string Name => "Maintenance";
8 | public override IconUsage Icon => FontAwesome.Solid.PencilAlt;
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsGameplayCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Sprites;
2 |
3 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
4 | {
5 | public partial class SettingsGameplayCategory : SettingsCategoryContainer
6 | {
7 | public override string Name => "Gameplay";
8 | public override IconUsage Icon => FontAwesome.Regular.Circle;
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsGeneralCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics.Sprites;
3 | using Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables;
4 | using Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables.Objects;
5 |
6 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
7 | {
8 | public partial class SettingsGeneralCategory : SettingsCategoryContainer
9 | {
10 | public override string Name => "General";
11 | public override IconUsage Icon => FontAwesome.Solid.Cog;
12 |
13 | [BackgroundDependencyLoader]
14 | private void Load()
15 | {
16 | var signIn = new DrawableSettingsSubCategory("SIGN IN");
17 | signIn.Content.Add(new DrawableSettingsInput("", "Username", "Enter your username"));
18 | signIn.Content.Add(new DrawableSettingsInput("false", "Password", "Enter your password", true));
19 | signIn.Content.Add(new DrawableSettingsCheckbox(true, "Remember Username", "Remember the username next time Qsor starts."));
20 | signIn.Content.Add(new DrawableSettingsCheckbox(false, "Remember Password", "Remember the password next time Qsor starts."));
21 |
22 | AddInternal(signIn);
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsGraphicsCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics.Sprites;
3 |
4 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
5 | {
6 | public partial class SettingsGraphicsCategory : SettingsCategoryContainer
7 | {
8 | public override string Name => "Graphics";
9 | public override IconUsage Icon => FontAwesome.Solid.Desktop;
10 |
11 | [BackgroundDependencyLoader]
12 | private void Load()
13 | {
14 |
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsInputCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Sprites;
2 |
3 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
4 | {
5 | public partial class SettingsInputCategory : SettingsCategoryContainer
6 | {
7 | public override string Name => "Input";
8 | public override IconUsage Icon => FontAwesome.Solid.Gamepad;
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsMaintenanceCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Sprites;
2 |
3 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
4 | {
5 | public partial class SettingsMaintenanceCategory : SettingsCategoryContainer
6 | {
7 | public override string Name => "Maintenance";
8 | public override IconUsage Icon => FontAwesome.Solid.Wrench;
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsOnlineCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Sprites;
2 |
3 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
4 | {
5 | public partial class SettingsOnlineCategory : SettingsCategoryContainer
6 | {
7 | public override string Name => "Online";
8 | public override IconUsage Icon => FontAwesome.Solid.GlobeAmericas;
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Categories/SettingsSkinCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics.Sprites;
2 |
3 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories
4 | {
5 | public partial class SettingsSkinCategory : SettingsCategoryContainer
6 | {
7 | public override string Name => "Skin";
8 | public override IconUsage Icon => FontAwesome.Solid.PaintBrush;
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/DrawableSettingsCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Sprites;
5 | using osuTK.Graphics;
6 |
7 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables
8 | {
9 | public partial class DrawableSettingsCategory : FillFlowContainer
10 | {
11 | private SpriteText _text;
12 |
13 | private readonly SettingsCategoryContainer _categoryContainer;
14 |
15 | public DrawableSettingsCategory(SettingsCategoryContainer category)
16 | {
17 | _categoryContainer = category;
18 | }
19 |
20 | [BackgroundDependencyLoader]
21 | private void Load()
22 | {
23 | _text = new SpriteText
24 | {
25 | Text = Name.ToUpper(),
26 | Colour = Color4.SkyBlue,
27 | Font = new FontUsage(size: 24, weight: "Bold"),
28 | Anchor = Anchor.TopRight,
29 | Origin = Anchor.TopRight
30 | };
31 |
32 | Direction = FillDirection.Vertical;
33 |
34 | Add(_text);
35 |
36 | Add(_categoryContainer);
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/DrawableSettingsIconSprite.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Bindables;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Containers;
5 | using osu.Framework.Graphics.Shapes;
6 | using osu.Framework.Graphics.Sprites;
7 | using osu.Framework.Input.Events;
8 | using osuTK.Graphics;
9 |
10 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables
11 | {
12 | public partial class DrawableSettingsIconSprite : CompositeDrawable
13 | {
14 |
15 | [Resolved]
16 | private DrawableSettingsToolBar ToolBar { get; set; }
17 |
18 | private readonly Bindable _selectedCategory = new();
19 |
20 | private readonly SpriteIcon _icon = new();
21 | private Box _selector;
22 |
23 |
24 | public IconUsage Icon
25 | {
26 | get => _icon.Icon;
27 | set => _icon.Icon = value;
28 | }
29 |
30 | public SettingsCategoryContainer Category { get; }
31 |
32 | public DrawableSettingsIconSprite(SettingsCategoryContainer category)
33 | {
34 | Category = category;
35 | }
36 |
37 | [BackgroundDependencyLoader]
38 | private void Load(SettingsOverlay settingsOverlay)
39 | {
40 | _selectedCategory.BindTo(settingsOverlay.SelectedCategory);
41 | CornerRadius = 0;
42 |
43 | Width = 48;
44 | Height = 48;
45 |
46 | AddInternal(_icon);
47 | _icon.Width = 24;
48 | _icon.Height = 24;
49 | _icon.Origin = Anchor.Centre;
50 | _icon.Anchor = Anchor.Centre;
51 | _icon.Colour = Color4.Gray;
52 |
53 | AddInternal(_selector = new Box
54 | {
55 | Colour = Color4.HotPink,
56 | Anchor = Anchor.CentreRight,
57 | Origin = Anchor.Centre,
58 | Width = 4,
59 | Height = 48,
60 | Margin = new MarginPadding { Right = 2.5f },
61 | Alpha = 0,
62 | });
63 |
64 | _selectedCategory.ValueChanged += e =>
65 | {
66 | _icon.FadeColour(e.NewValue == Category ? Color4.White : Color4.Gray, 100);
67 | _selector.FadeTo(e.NewValue == Category ? 1f : 0f, 250);
68 | };
69 | }
70 |
71 | protected override bool OnHover(HoverEvent e)
72 | {
73 | if (_selectedCategory.Value == Category)
74 | return false;
75 |
76 | _icon.FadeColour(Color4.White, 100);
77 | return true;
78 | }
79 |
80 | protected override void OnHoverLost(HoverLostEvent e)
81 | {
82 | if (_selectedCategory.Value != Category)
83 | _icon.FadeColour(Color4.Gray, 100);
84 | }
85 |
86 | protected override bool OnClick(ClickEvent e)
87 | {
88 | _selectedCategory.Value = Category;
89 | return true;
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/DrawableSettingsMenu.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Specialized;
2 | using System.Linq;
3 | using osu.Framework.Allocation;
4 | using osu.Framework.Bindables;
5 | using osu.Framework.Graphics;
6 | using osu.Framework.Graphics.Containers;
7 | using osu.Framework.Graphics.Shapes;
8 | using osu.Framework.Graphics.Sprites;
9 | using osuTK.Graphics;
10 |
11 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables
12 | {
13 | [Cached]
14 | public partial class DrawableSettingsMenu : CompositeDrawable
15 | {
16 | private readonly BindableList _categories = new();
17 | private SearchContainer _searchContainer;
18 | private BasicScrollContainer _scrollContainer;
19 |
20 | public DrawableSettingsMenu(BindableList categories)
21 | {
22 | _categories.BindTo(categories);
23 | }
24 |
25 | [BackgroundDependencyLoader]
26 | private void Load(SettingsOverlay settingsOverlay)
27 | {
28 | Masking = true;
29 | RelativeSizeAxes = Axes.Y;
30 |
31 | Margin = new MarginPadding{ Left = 48 };
32 | Width = 400;
33 |
34 | AddInternal(new Box
35 | {
36 | RelativeSizeAxes = Axes.Both,
37 | Colour = Color4.Black,
38 | Alpha = .6f
39 | });
40 |
41 | AddInternal(_scrollContainer = new BasicScrollContainer
42 | {
43 | RelativeSizeAxes = Axes.Both,
44 | ScrollbarVisible = true
45 | });
46 |
47 | var headerContainer = new CustomizableTextContainer
48 | {
49 | Anchor = Anchor.TopCentre,
50 | Origin = Anchor.TopCentre,
51 | TextAnchor = Anchor.TopCentre,
52 | //RelativeSizeAxes = Axes.X,
53 | Width = 400,
54 | AutoSizeAxes = Axes.Y,
55 |
56 | Margin = new MarginPadding{ Top = 50 }
57 | };
58 |
59 | headerContainer.AddPlaceholder(new SpriteIcon
60 | {
61 | Width = 24,
62 | Height = 24,
63 | Icon = FontAwesome.Solid.Search
64 | });
65 |
66 | headerContainer.AddText("Options\n", e => e.Font = new FontUsage(size: 24, weight: "Bold"));
67 | headerContainer.AddText("Change the way Qsor behaves\n\n\n\n", e => { e.Colour = Color4.PaleVioletRed; e.Font = new FontUsage(size: 18);});
68 | headerContainer.AddText("[0] Type to search!", e => e.Font = new FontUsage(size: 24));
69 |
70 | _scrollContainer.ScrollContent.Add(headerContainer);
71 |
72 | _scrollContainer.ScrollbarVisible = false;
73 | _scrollContainer.ScrollContent.AutoSizeAxes = Axes.Y;
74 | _scrollContainer.ScrollContent.RelativeSizeAxes = Axes.X;
75 |
76 | _scrollContainer.ScrollContent.Add(_searchContainer = new SearchContainer
77 | {
78 | RelativeSizeAxes = Axes.X,
79 | AutoSizeAxes = Axes.Y,
80 | Margin = new MarginPadding{ Top = 150 },
81 | });
82 |
83 | settingsOverlay.SelectedCategory.ValueChanged += e =>
84 | {
85 | // Little hack to make it feel smoother
86 | if (_categories.FirstOrDefault() == e.NewValue)
87 | {
88 | _scrollContainer.ScrollToStart();
89 | }
90 | else if (_categories.LastOrDefault() == e.NewValue)
91 | {
92 | _scrollContainer.ScrollToEnd();
93 | }
94 | else
95 | {
96 | _scrollContainer.ScrollTo(e.NewValue);
97 | }
98 | };
99 |
100 | _categories.CollectionChanged += (_, e) =>
101 | {
102 | if (e.Action == NotifyCollectionChangedAction.Add)
103 | {
104 | foreach (var i in e.NewItems)
105 | {
106 | var item = (SettingsCategoryContainer) i;
107 |
108 | var settingsCategory = new DrawableSettingsCategory(item)
109 | {
110 | Name = item.Name,
111 | Anchor = Anchor.TopLeft,
112 | Padding = new MarginPadding(20),
113 | RelativeSizeAxes = Axes.X,
114 | AutoSizeAxes = Axes.Y
115 | };
116 |
117 | _searchContainer.Add(settingsCategory);
118 | }
119 | }
120 | };
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/DrawableSettingsSubCategory.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Bindables;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Containers;
5 | using osu.Framework.Graphics.Shapes;
6 | using osu.Framework.Graphics.Sprites;
7 | using osu.Framework.Localisation;
8 | using osuTK;
9 |
10 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables
11 | {
12 | public partial class DrawableSettingsSubCategory : CompositeDrawable
13 | {
14 | public Bindable Label = new(string.Empty);
15 | private SpriteText _label;
16 |
17 | public FillFlowContainer Content { get; } = new FillFlowContainer
18 | {
19 | RelativeSizeAxes = Axes.X,
20 | AutoSizeAxes = Axes.Y,
21 | Spacing = new Vector2(0, 2.5f),
22 | Margin = new MarginPadding { Left = 10, Top = 50 }
23 | };
24 |
25 | [BackgroundDependencyLoader]
26 | private void Load()
27 | {
28 | RelativeSizeAxes = Axes.X;
29 | AutoSizeAxes = Axes.Y;
30 |
31 | AddInternal(new Box
32 | {
33 | RelativeSizeAxes = Axes.Y,
34 | Width = 2,
35 | Origin = Anchor.Centre,
36 | Anchor = Anchor.CentreLeft,
37 | Alpha = .2f
38 | });
39 | AddInternal(_label = new SpriteText
40 | {
41 | Text = Label.Value,
42 | Font = new FontUsage(size: 22, weight: "bold"),
43 | Margin = new MarginPadding{ Left = 10, Top = 5 }
44 | });
45 |
46 | Label.ValueChanged += e => _label.Text = e.NewValue;
47 | AddInternal(Content);
48 | }
49 |
50 | public DrawableSettingsSubCategory(LocalisableString label)
51 | {
52 | Label.Default = label;
53 | Label.SetDefault();
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/DrawableSettingsToolBar.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Specialized;
2 | using System.Linq;
3 | using osu.Framework.Allocation;
4 | using osu.Framework.Bindables;
5 | using osu.Framework.Graphics;
6 | using osu.Framework.Graphics.Containers;
7 | using osu.Framework.Graphics.Shapes;
8 | using osuTK.Graphics;
9 |
10 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables
11 | {
12 | [Cached]
13 | public partial class DrawableSettingsToolBar : CompositeDrawable
14 | {
15 | private readonly BindableList _categories = new();
16 | private BasicScrollContainer _scrollContainer;
17 | private SearchContainer _iconFlowContainer;
18 |
19 | private readonly Bindable _selectedCategory = new();
20 |
21 | public DrawableSettingsToolBar(BindableList categories)
22 | {
23 | _categories.BindTo(categories);
24 | }
25 |
26 | [BackgroundDependencyLoader]
27 | private void Load(SettingsOverlay settingsOverlay)
28 | {
29 | _selectedCategory.BindTo(settingsOverlay.SelectedCategory);
30 |
31 | RelativeSizeAxes = Axes.Y;
32 | Width = 48;
33 |
34 | AddInternal(new Box
35 | {
36 | RelativeSizeAxes = Axes.Both,
37 | Colour = Color4.Black
38 | });
39 |
40 | AddInternal(_scrollContainer = new BasicScrollContainer
41 | {
42 | RelativeSizeAxes = Axes.Both,
43 | });
44 |
45 | _scrollContainer.ScrollbarVisible = false;
46 | _scrollContainer.ScrollContent.AutoSizeAxes = Axes.Y;
47 | _scrollContainer.ScrollContent.RelativeSizeAxes = Axes.X;
48 | _scrollContainer.ScrollContent.Origin = Anchor.Centre;
49 | _scrollContainer.ScrollContent.Anchor = Anchor.Centre;
50 |
51 | _scrollContainer.ScrollContent.Add(_iconFlowContainer = new SearchContainer
52 | {
53 | RelativeSizeAxes = Axes.X,
54 | AutoSizeAxes = Axes.Y,
55 | Direction = FillDirection.Vertical
56 | });
57 |
58 | _categories.CollectionChanged += (_, e) =>
59 | {
60 | if (e.Action == NotifyCollectionChangedAction.Add)
61 | {
62 | foreach (var i in e.NewItems)
63 | {
64 | var item = (SettingsCategoryContainer) i;
65 |
66 | var settingsIconSprite = new DrawableSettingsIconSprite(item)
67 | {
68 | Name = item.Name,
69 | Icon = item.Icon,
70 | Origin = Anchor.Centre,
71 | Anchor = Anchor.Centre,
72 | Colour = Color4.LightGray,
73 | };
74 |
75 | _iconFlowContainer.Add(settingsIconSprite);
76 | }
77 | }
78 | };
79 | }
80 |
81 | public void Default()
82 | {
83 | var sprite = _iconFlowContainer.FirstOrDefault(s => s.Name == "General");
84 | if (sprite == null)
85 | return;
86 |
87 | _selectedCategory.Value = sprite.Category;
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/Objects/DrawableSettingsCheckbox.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Bindables;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Containers;
5 | using osu.Framework.Graphics.Shapes;
6 | using osu.Framework.Graphics.Sprites;
7 | using osu.Framework.Input.Events;
8 | using osu.Framework.Localisation;
9 | using osuTK.Graphics;
10 |
11 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables.Objects
12 | {
13 | public partial class DrawableSettingsCheckbox : DrawableSettingsObject
14 | {
15 | private SpriteText _label;
16 |
17 | [BackgroundDependencyLoader]
18 | private void Load()
19 | {
20 | Origin = Anchor.Centre;
21 | Anchor = Anchor.Centre;
22 |
23 | AddInternal(new DrawableSettingsCheckboxNode(Value));
24 | AddInternal(_label = new SpriteText
25 | {
26 | Origin = Anchor.CentreLeft,
27 |
28 | Margin = new MarginPadding{ Left = 18f },
29 |
30 | Font = new FontUsage(size: 18),
31 | Text = Label.Value
32 | });
33 |
34 | Label.ValueChanged += e => _label.Text = e.NewValue;
35 | }
36 |
37 | public DrawableSettingsCheckbox(bool defaultValue, LocalisableString label, LocalisableString toolTip) : base(defaultValue, label, toolTip)
38 | {
39 | }
40 |
41 | protected override bool OnClick(ClickEvent e)
42 | {
43 | if (Value.Disabled)
44 | return false;
45 |
46 | Value.Value = !Value.Value;
47 | return true;
48 | }
49 |
50 | private partial class DrawableSettingsCheckboxNode : CompositeDrawable
51 | {
52 | public readonly Bindable Checked = new();
53 |
54 | public DrawableSettingsCheckboxNode(Bindable checkedBindable)
55 | {
56 | Checked.BindTo(checkedBindable);
57 | }
58 |
59 | private Box _box;
60 |
61 | [BackgroundDependencyLoader]
62 | private void Load()
63 | {
64 | Width = 16;
65 | Height = 16;
66 |
67 | Origin = Anchor.Centre;
68 |
69 | Masking = true;
70 | BorderColour = Color4.PaleVioletRed;
71 | BorderThickness = 2.5f;
72 |
73 | CornerRadius = Width / 2f;
74 |
75 | AddInternal(_box = new Box
76 | {
77 | RelativeSizeAxes = Axes.Both,
78 | Colour = Color4.PaleVioletRed,
79 |
80 | Alpha = Checked.Value ? 1 : 0,
81 | AlwaysPresent = true
82 | });
83 |
84 | Checked.ValueChanged += e => _box.FadeTo(e.NewValue ? 1 : 0, 100, Easing.In);
85 | Checked.DisabledChanged += e =>
86 | {
87 | _box.Colour = e ? Color4.DimGray : Color4.PaleVioletRed;
88 | BorderColour = e ? Color4.Gray : Color4.PaleVioletRed;
89 | };
90 | }
91 | }
92 | }
93 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/Objects/DrawableSettingsInput.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Bindables;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Colour;
5 | using osu.Framework.Graphics.Shapes;
6 | using osu.Framework.Graphics.Sprites;
7 | using osu.Framework.Graphics.UserInterface;
8 | using osu.Framework.Input.Events;
9 | using osu.Framework.Localisation;
10 | using osu.Framework.Logging;
11 | using osuTK;
12 | using osuTK.Graphics;
13 |
14 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables.Objects
15 | {
16 | public partial class DrawableSettingsInput : DrawableSettingsObject
17 | {
18 | private bool _isPassword;
19 | private DrawableSettingsInputNode _node;
20 | private SpriteText _label;
21 |
22 | [BackgroundDependencyLoader]
23 | private void Load()
24 | {
25 | Origin = Anchor.Centre;
26 | Anchor = Anchor.Centre;
27 |
28 | Height = 64;
29 |
30 | AddInternal(_label = new SpriteText
31 | {
32 | Origin = Anchor.CentreLeft,
33 |
34 | Font = new FontUsage(size: 18),
35 | Text = Label.Value
36 | });
37 |
38 | AddInternal(_node = new DrawableSettingsInputNode(Value, _isPassword)
39 | {
40 | RelativeSizeAxes = Axes.X,
41 | Position = new Vector2(-2, 16),
42 | Height = 22
43 | });
44 |
45 | Label.ValueChanged += e => _label.Text = e.NewValue;
46 | }
47 |
48 |
49 | public override bool AcceptsFocus => true;
50 | protected override void OnFocus(FocusEvent e)
51 | {
52 | Logger.LogPrint("Focus!!");
53 | base.OnFocus(e);
54 | GetContainingInputManager().ChangeFocus(null);
55 | GetContainingInputManager().ChangeFocus(_node);
56 | }
57 |
58 | public DrawableSettingsInput(string defaultValue, LocalisableString label, LocalisableString toolTip,
59 | bool isPassword = false)
60 | : base(defaultValue, label, toolTip)
61 | {
62 | _isPassword = isPassword;
63 | }
64 |
65 | private partial class DrawableSettingsInputNode : TextBox
66 | {
67 | private readonly bool _isPassword;
68 |
69 | protected override bool AllowClipboardExport => !_isPassword;
70 | protected override bool AllowWordNavigation => !_isPassword;
71 | protected override Drawable AddCharacterToFlow(char c) =>
72 | base.AddCharacterToFlow(_isPassword ? '*' : c);
73 |
74 | public DrawableSettingsInputNode(Bindable inputBox, bool isPassword)
75 | {
76 | _isPassword = isPassword;
77 | }
78 |
79 | [BackgroundDependencyLoader]
80 | private void Load()
81 | {
82 | Masking = true;
83 |
84 | //CornerRadius = 3;
85 | //CornerExponent = 2f;
86 |
87 | BorderColour = Color4.DarkGray;
88 | BorderThickness = 3f;
89 |
90 | AddInternal(new Box
91 | {
92 | Colour = Color4.Transparent,
93 | RelativeSizeAxes = Axes.Both,
94 | });
95 | }
96 |
97 | protected override void NotifyInputError()
98 | {
99 | }
100 |
101 | protected override SpriteText CreatePlaceholder()
102 | {
103 | return new SpriteText();
104 | }
105 |
106 | protected override Caret CreateCaret()
107 | {
108 | return new BasicTextBox.BasicCaret();
109 | }
110 |
111 | protected override void OnFocus(FocusEvent e)
112 | {
113 | this.TransformTo(nameof(BorderColour), (ColourInfo)Color4.White, 100);
114 | base.OnFocus(e);
115 | }
116 |
117 | protected override void OnFocusLost(FocusLostEvent e)
118 | {
119 | this.TransformTo(nameof(BorderColour), (ColourInfo)Color4.DarkGray, 100);
120 | base.OnFocusLost(e);
121 | }
122 | }
123 | }
124 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/Objects/DrawableSettingsLabel.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Sprites;
4 | using osu.Framework.Localisation;
5 |
6 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables.Objects
7 | {
8 | public partial class DrawableSettingsLabel : DrawableSettingsObject
9 | {
10 | private SpriteText _label;
11 |
12 | [BackgroundDependencyLoader]
13 | private void Load()
14 | {
15 | Label.BindTo(Value);
16 |
17 | AddInternal(_label = new SpriteText
18 | {
19 | Origin = Anchor.CentreLeft,
20 |
21 | Font = new FontUsage(size: 18),
22 |
23 | Text = Label.Value
24 | });
25 |
26 | Label.ValueChanged += e => _label.Text = e.NewValue;
27 | }
28 |
29 | public DrawableSettingsLabel(LocalisableString defaultValue, LocalisableString toolTip) : base(defaultValue, string.Empty, toolTip)
30 | {
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/Drawables/Objects/DrawableSettingsObject.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Bindables;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Containers;
5 | using osu.Framework.Graphics.Cursor;
6 | using osu.Framework.Input;
7 | using osu.Framework.Input.Events;
8 | using osu.Framework.Localisation;
9 |
10 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables.Objects
11 | {
12 | public abstract partial class DrawableSettingsObject : CompositeDrawable, IRequireHighFrequencyMousePosition, IHasTooltip
13 | {
14 | public readonly Bindable Label = new(string.Empty);
15 | public readonly Bindable ToolTip = new(string.Empty);
16 | public readonly Bindable Value;
17 |
18 | public LocalisableString TooltipText => ToolTip.Value;
19 |
20 | [Resolved]
21 | private SettingsOverlay SettingsOverlay { get; set; }
22 |
23 | public DrawableSettingsObject(T defaultValue, LocalisableString label, LocalisableString toolTip)
24 | {
25 | Label.Value = label;
26 | ToolTip.Value = toolTip;
27 |
28 | Value = new Bindable(defaultValue);
29 |
30 | Value.SetDefault();
31 | }
32 |
33 | [BackgroundDependencyLoader]
34 | private void Load()
35 | {
36 | RelativeSizeAxes = Axes.X;
37 |
38 | Height = 32;
39 |
40 | Padding = new MarginPadding(10);
41 | }
42 |
43 | protected override bool OnMouseMove(MouseMoveEvent e)
44 | {
45 | if (!IsHovered)
46 | return base.OnMouseMove(e);
47 |
48 | SettingsOverlay.MoveIndexTo(this);
49 | return true;
50 | }
51 |
52 | protected override bool OnHover(HoverEvent e)
53 | {
54 | SettingsOverlay.ObjectHovering();
55 | return true;
56 | }
57 |
58 | protected override void OnHoverLost(HoverLostEvent e)
59 | {
60 | SettingsOverlay.ObjectHoverLost();
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/SettingsCategoryContainer.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Graphics;
2 | using osu.Framework.Graphics.Containers;
3 | using osu.Framework.Graphics.Sprites;
4 |
5 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings
6 | {
7 | public abstract partial class SettingsCategoryContainer : Container
8 | {
9 | public new abstract string Name { get; }
10 | public abstract IconUsage Icon { get; }
11 |
12 | public SettingsCategoryContainer()
13 | {
14 | RelativeSizeAxes = Axes.X;
15 | AutoSizeAxes = Axes.Y;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/Settings/SettingsOverlay.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Bindables;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Graphics.Containers;
5 | using osu.Framework.Graphics.Shapes;
6 | using osuTK.Graphics;
7 | using Qsor.Game.Graphics.UserInterface.Overlays.Settings.Categories;
8 | using Qsor.Game.Graphics.UserInterface.Overlays.Settings.Drawables;
9 |
10 | namespace Qsor.Game.Graphics.UserInterface.Overlays.Settings
11 | {
12 | [Cached]
13 | public partial class SettingsOverlay : CompositeDrawable
14 | {
15 | private readonly BindableList _categories = new();
16 |
17 | private DrawableSettingsToolBar _toolBar;
18 | private DrawableSettingsMenu _menu;
19 |
20 | public readonly Bindable SelectedCategory = new();
21 |
22 | private Box _settingsIndex;
23 |
24 | [BackgroundDependencyLoader]
25 | private void Load()
26 | {
27 | RelativeSizeAxes = Axes.Y;
28 | AutoSizeAxes = Axes.X;
29 |
30 | AddInternal(_settingsIndex = new Box
31 | {
32 | Anchor = Anchor.TopCentre,
33 | Origin = Anchor.Centre,
34 |
35 | Colour = Color4.Black,
36 | RelativeSizeAxes = Axes.X,
37 | Alpha = 0
38 | });
39 |
40 | AddInternal(_menu = new DrawableSettingsMenu(_categories));
41 | AddInternal(_toolBar = new DrawableSettingsToolBar(_categories));
42 |
43 | AddCategory(new SettingsGeneralCategory());
44 | AddCategory(new SettingsGraphicsCategory());
45 | AddCategory(new SettingsGameplayCategory());
46 | AddCategory(new SettingsSkinCategory());
47 | AddCategory(new SettingsInputCategory());
48 | AddCategory(new SettingsEditorCategory());
49 | AddCategory(new SettingsOnlineCategory());
50 | AddCategory(new SettingsMaintenanceCategory());
51 |
52 | _menu.Width = 0;
53 | Alpha = 0;
54 | }
55 |
56 | protected override void LoadComplete()
57 | {
58 | _toolBar.Default();
59 | }
60 |
61 | public void AddCategory(SettingsCategoryContainer category)
62 | {
63 | _categories.Add(category);
64 | }
65 |
66 | public bool IsShown { get; private set; }
67 | public override void Show()
68 | {
69 | IsShown = true;
70 |
71 | this.FadeIn(200);
72 | _menu.ResizeWidthTo(400, 400, Easing.InOutCubic);
73 | }
74 |
75 | public override void Hide()
76 | {
77 | IsShown = false;
78 |
79 | this.FadeOut(800);
80 | _menu.ResizeWidthTo(0, 1000, Easing.InOutCubic);
81 | }
82 |
83 | private int _childHoverLength;
84 |
85 | internal void ObjectHovering()
86 | {
87 | if (_childHoverLength <= 0)
88 | {
89 | _settingsIndex.FadeIn(100);
90 | }
91 |
92 | _childHoverLength++;
93 | }
94 |
95 | internal void ObjectHoverLost()
96 | {
97 | _childHoverLength--;
98 | if (_childHoverLength > 0)
99 | return;
100 |
101 | _childHoverLength = 0;
102 | _settingsIndex.FadeOut(500);
103 | }
104 |
105 | private object _movingTo;
106 | public void MoveIndexTo(Drawable settingsObject)
107 | {
108 | if (_movingTo == settingsObject)
109 | return;
110 |
111 | _movingTo = settingsObject;
112 |
113 | var pos = settingsObject.ToSpaceOfOtherDrawable(settingsObject.OriginPosition, _menu);
114 |
115 | // -5f because of 10 padding around the settings object.
116 | _settingsIndex.MoveToY(pos.Y - 5f, 100, Easing.OutBack);
117 | _settingsIndex.ResizeHeightTo(settingsObject.DrawSize.Y, 140, Easing.OutBack);
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/UpdaterOverlay.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Sprites;
5 | using osu.Framework.Input.Events;
6 | using osu.Framework.Localisation;
7 | using osuTK;
8 | using Qsor.Game.Updater;
9 |
10 | namespace Qsor.Game.Graphics.UserInterface.Overlays
11 | {
12 | public partial class UpdaterOverlay : CompositeDrawable
13 | {
14 | private SpriteText _statusText;
15 | private SpriteIcon _spinningIcon;
16 |
17 | public LocalisableString Text { get => _statusText.Text; set => _statusText.Text = value; }
18 |
19 | [Resolved]
20 | private UpdateManager UpdateManager { get; set; }
21 |
22 | [BackgroundDependencyLoader]
23 | private void Load()
24 | {
25 | Anchor = Origin = Anchor.BottomCentre;
26 | AutoSizeAxes = Axes.Both;
27 |
28 | Padding = new MarginPadding(25);
29 | Alpha = 0;
30 |
31 | AddInternal(_spinningIcon = new SpriteIcon
32 | {
33 | Icon = FontAwesome.Solid.SyncAlt,
34 | Size = new Vector2(32),
35 | Origin = Anchor.Centre,
36 | Anchor = Anchor.TopCentre,
37 | Rotation = 0
38 | });
39 |
40 | _spinningIcon
41 | .RotateTo(360, 2500, Easing.InOutQuint)
42 | .Then()
43 | .RotateTo(0, 1) // 0 duration is not working ?
44 | .Loop();
45 |
46 | AddInternal(_statusText = new SpriteText
47 | {
48 | Text = "Update available, click here to update!",
49 | Origin = Anchor.TopCentre,
50 | Anchor = Anchor.Centre,
51 | });
52 | }
53 |
54 | protected override bool OnHover(HoverEvent e)
55 | {
56 | this.ScaleTo(1.25f, 500, Easing.OutBack);
57 |
58 | return true;
59 | }
60 |
61 | protected override void OnHoverLost(HoverLostEvent e)
62 | {
63 | this.ScaleTo(1f, 500, Easing.OutBack);
64 | }
65 |
66 | protected override bool OnClick(ClickEvent e)
67 | {
68 | UpdateManager.UpdateGame();
69 |
70 | return true;
71 | }
72 |
73 | public override void Show()
74 | {
75 | this.FadeIn(2500, Easing.InOutQuint);
76 | }
77 |
78 | public override void Hide()
79 | {
80 | this.FadeOut(2500, Easing.InOutQuint);
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Overlays/UserOverlay.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Shapes;
5 | using osu.Framework.Graphics.Sprites;
6 | using osu.Framework.Input.Events;
7 | using osu.Framework.Localisation;
8 | using osuTK;
9 | using osuTK.Graphics;
10 | using Qsor.Game.Graphics.UserInterface.Online.Drawables;
11 | using Qsor.Game.Online;
12 | using Qsor.Game.Online.Users;
13 |
14 | namespace Qsor.Game.Graphics.UserInterface.Overlays
15 | {
16 | public partial class UserOverlay : CompositeDrawable
17 | {
18 | private TextFlowContainer _textFlowContainer;
19 | private DrawableAvatar _avatar;
20 | private DrawableLevelBar _levelBar;
21 | private User _activeUser;
22 | private Box _background;
23 |
24 | [BackgroundDependencyLoader]
25 | private void Load(UserManager userManager)
26 | {
27 | _activeUser = userManager.User;
28 |
29 | Width = 256;
30 | Height = 80;
31 |
32 | _textFlowContainer = new TextFlowContainer
33 | {
34 | AutoSizeAxes = Axes.Both,
35 | Padding = new MarginPadding(4),
36 | OriginPosition = new Vector2(-75,0)
37 | };
38 |
39 | _textFlowContainer.AddParagraph($"{_activeUser.Username}", text => text.Font = new FontUsage(size: 24, weight: "Bold"));
40 | _textFlowContainer.AddParagraph($"Performance: {_activeUser.Statistics.PerformancePoints}pp", text => text.Font = new FontUsage(size: 14));
41 | _textFlowContainer.AddParagraph($"Accuracy: {_activeUser.Statistics.Accuracy:0.00}%", text => text.Font = new FontUsage(size: 14));
42 | _textFlowContainer.AddParagraph($"Level: {_activeUser.Statistics.GetLevel()}", text => text.Font = new FontUsage(size: 14));
43 |
44 | AddRangeInternal(new Drawable[]
45 | {
46 | _background = new Box
47 | {
48 | RelativeSizeAxes = Axes.Both,
49 | Colour = Color4.White,
50 | Alpha = 0f
51 | },
52 | _avatar = new DrawableAvatar(),
53 | new SpriteText
54 | {
55 | Font = new FontUsage(size: 32, weight: "Bold"),
56 | Colour = Color4.DarkGray,
57 | Text = new LocalisableString("#1"),
58 |
59 | Anchor = Anchor.BottomRight,
60 | Origin = Anchor.BottomRight,
61 | Padding = new MarginPadding(0),
62 | Position = new Vector2(0, -5)
63 | },
64 | _textFlowContainer,
65 | _levelBar = new DrawableLevelBar(_activeUser)
66 | {
67 | Anchor = Anchor.BottomRight,
68 | Origin = Anchor.BottomRight,
69 | }
70 | });
71 |
72 | _avatar.User.Value = _activeUser;
73 | }
74 |
75 | protected override bool OnHover(HoverEvent e)
76 | {
77 | _background.FadeTo(.3f, 50);
78 | return true;
79 | }
80 |
81 | protected override void OnHoverLost(HoverLostEvent e)
82 | {
83 | _background.FadeTo(0f, 50);
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/QsorColours.cs:
--------------------------------------------------------------------------------
1 | namespace Qsor.Game.Graphics.UserInterface
2 | {
3 | ///
4 | /// Commonly used colours across the whole project
5 | ///
6 | public static class QsorColours
7 | {
8 | // TODO: Add commonly used colours here.
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Screens/IntroScreen.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Sprites;
5 | using osu.Framework.Screens;
6 | using osuTK;
7 | using osuTK.Graphics;
8 |
9 | namespace Qsor.Game.Graphics.UserInterface.Screens
10 | {
11 | public partial class IntroScreen : Screen
12 | {
13 | private SpriteIcon _spriteIcon;
14 | private CustomizableTextContainer _textFlowContainer;
15 |
16 | [BackgroundDependencyLoader]
17 | private void Load()
18 | {
19 | _textFlowContainer = new CustomizableTextContainer
20 | {
21 | Anchor = Anchor.Centre,
22 | Origin = Anchor.Centre,
23 |
24 | RelativeSizeAxes = Axes.Both,
25 | TextAnchor = Anchor.Centre,
26 | };
27 |
28 | var warningIcon = _textFlowContainer.AddPlaceholder(_spriteIcon = new SpriteIcon
29 | {
30 | Icon = FontAwesome.Solid.ExclamationTriangle,
31 | Size = new Vector2(48),
32 | Colour = Color4.Yellow,
33 | });
34 |
35 | _textFlowContainer.AddParagraph($"[{warningIcon}]");
36 | _textFlowContainer.AddParagraph("Warning!", text =>
37 | {
38 | text.Colour = Color4.Yellow;
39 | text.Font = new FontUsage(size: 32, weight: "Bold");
40 | });
41 | _textFlowContainer.AddParagraph("This game is currently in really early alpha! Bugs to be expected.");
42 | _textFlowContainer.AddParagraph("Please consider reporting them all, no matter how small they are.");
43 |
44 | AddInternal(_textFlowContainer);
45 | }
46 |
47 | public override void OnEntering(ScreenTransitionEvent e)
48 | {
49 | for (var i = 0; i < _textFlowContainer.Count; i++)
50 | _textFlowContainer[i].FadeInFromZero(250 + 100 * i);
51 |
52 | Scheduler.AddDelayed(() => _spriteIcon.FlashColour(Color4.White, 1000), 1000, true);
53 | }
54 |
55 | public override bool OnExiting(ScreenExitEvent e)
56 | {
57 | _textFlowContainer.FadeOutFromOne(500);
58 |
59 | return true;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Screens/MainMenu/Containers/BottomBar.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Shapes;
5 | using osuTK.Graphics;
6 |
7 | namespace Qsor.Game.Graphics.UserInterface.Screens.MainMenu.Containers
8 | {
9 | public partial class BottomBar : CompositeDrawable
10 | {
11 | [BackgroundDependencyLoader]
12 | private void Load()
13 | {
14 | RelativeSizeAxes = Axes.X;
15 | FillMode = FillMode.Fit;
16 | Anchor = Anchor.BottomLeft;
17 | Origin = Anchor.BottomLeft;
18 | Height = 80;
19 |
20 | InternalChildren = new Drawable[]
21 | {
22 | new Box
23 | {
24 | Name = "Background",
25 | RelativeSizeAxes = Axes.Both,
26 | Colour = Color4.Black,
27 | Alpha = .4f
28 | }
29 | };
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Screens/MainMenu/Containers/Toolbar.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Containers;
4 | using osu.Framework.Graphics.Shapes;
5 | using osuTK.Graphics;
6 | using Qsor.Game.Graphics.UserInterface.Overlays;
7 |
8 | namespace Qsor.Game.Graphics.UserInterface.Screens.MainMenu.Containers
9 | {
10 | public partial class Toolbar : CompositeDrawable
11 | {
12 | [BackgroundDependencyLoader]
13 | private void Load()
14 | {
15 | RelativeSizeAxes = Axes.X;
16 | FillMode = FillMode.Fit;
17 | Height = 80;
18 |
19 | InternalChildren = new Drawable[]
20 | {
21 | new Box
22 | {
23 | Name = "Background",
24 | RelativeSizeAxes = Axes.Both,
25 | Colour = Color4.Black,
26 | Alpha = .4f
27 | },
28 |
29 | new UserOverlay(),
30 |
31 | new MusicPlayerOverlay
32 | {
33 | Anchor = Anchor.TopRight,
34 | Origin = Anchor.TopRight,
35 | }
36 | };
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Screens/MainMenu/Drawables/DrawableLogoVisualisation.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
2 | // See the LICENCE file in the repository root for full licence text.
3 |
4 | using System;
5 | using osu.Framework.Allocation;
6 | using osu.Framework.Bindables;
7 | using osu.Framework.Extensions.Color4Extensions;
8 | using osu.Framework.Graphics;
9 | using osu.Framework.Graphics.Primitives;
10 | using osu.Framework.Graphics.Rendering;
11 | using osu.Framework.Graphics.Rendering.Vertices;
12 | using osu.Framework.Graphics.Shaders;
13 | using osu.Framework.Graphics.Textures;
14 | using osuTK;
15 | using osuTK.Graphics;
16 | using Qsor.Game.Beatmaps;
17 |
18 | namespace Qsor.Game.Graphics.UserInterface.Screens.MainMenu.Drawables
19 | {
20 | public partial class DrawableLogoVisualisation : Drawable
21 | {
22 | private readonly IBindable _beatmap = new Bindable();
23 |
24 | ///
25 | /// The number of bars to jump each update iteration.
26 | ///
27 | private const int IndexChange = 5;
28 |
29 | ///
30 | /// The maximum length of each bar in the visualiser. Will be reduced when kiai is not activated.
31 | ///
32 | private const float BarLength = 600;
33 |
34 | ///
35 | /// The number of bars in one rotation of the visualiser.
36 | ///
37 | private const int BarsPerVisualiser = 200;
38 |
39 | ///
40 | /// How many times we should stretch around the circumference (overlapping overselves).
41 | ///
42 | private const float VisualiserRounds = 5;
43 |
44 | ///
45 | /// How much should each bar go down each millisecond (based on a full bar).
46 | ///
47 | private const float DecayPerMillisecond = 0.0024f;
48 |
49 | ///
50 | /// Number of milliseconds between each amplitude update.
51 | ///
52 | private const float TimeBetweenUpdates = 50;
53 |
54 | ///
55 | /// The minimum amplitude to show a bar.
56 | ///
57 | private const float AmplitudeDeadZone = 1f / BarLength;
58 |
59 | private int _indexOffset;
60 |
61 | public Color4 AccentColour { get; set; }
62 |
63 | private readonly float[] _frequencyAmplitudes = new float[256];
64 |
65 | private IShader _shader;
66 | private Texture _texture;
67 |
68 | public DrawableLogoVisualisation()
69 | {
70 | Blending = BlendingParameters.Additive;
71 | }
72 |
73 | [BackgroundDependencyLoader]
74 | private void Load(IRenderer renderer, ShaderManager shaders, BeatmapManager beatmapManager)
75 | {
76 | _texture = renderer.WhitePixel;
77 | _beatmap.BindTo(beatmapManager.WorkingBeatmap);
78 | _shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
79 |
80 | UpdateColour();
81 | }
82 |
83 | private void UpdateAmplitudes()
84 | {
85 | var track = _beatmap.Value?.Track?.IsLoaded == true ? _beatmap.Value.Track : null;
86 | var timingPoint = _beatmap.Value?.GetTimingPointAt(Time.Current);
87 |
88 | var temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes;
89 |
90 | for (var i = 0; i < BarsPerVisualiser; i++)
91 | {
92 | if (track?.IsRunning ?? false)
93 | {
94 | var targetAmplitude = (temporalAmplitudes?.Span[(i + _indexOffset) % BarsPerVisualiser] ?? 0) * (timingPoint?.KiaiMode == true ? 1 : 0.5f);
95 | if (targetAmplitude > _frequencyAmplitudes[i])
96 | _frequencyAmplitudes[i] = targetAmplitude;
97 | }
98 | else
99 | {
100 | var index = (i + IndexChange) % BarsPerVisualiser;
101 | if (_frequencyAmplitudes[index] > _frequencyAmplitudes[i])
102 | _frequencyAmplitudes[i] = _frequencyAmplitudes[index];
103 | }
104 | }
105 |
106 | _indexOffset = (_indexOffset + IndexChange) % BarsPerVisualiser;
107 | }
108 |
109 | private void UpdateColour()
110 | {
111 | var defaultColour = Color4.White.Opacity(0.2f);
112 |
113 | AccentColour = defaultColour;
114 | }
115 |
116 | protected override void LoadComplete()
117 | {
118 | base.LoadComplete();
119 |
120 | var delayed = Scheduler.AddDelayed(UpdateAmplitudes, TimeBetweenUpdates, true);
121 | delayed.PerformRepeatCatchUpExecutions = false;
122 | }
123 |
124 | protected override void Update()
125 | {
126 | base.Update();
127 |
128 | var decayFactor = (float)Time.Elapsed * DecayPerMillisecond;
129 |
130 | for (var i = 0; i < BarsPerVisualiser; i++)
131 | {
132 | //3% of extra bar length to make it a little faster when bar is almost at it's minimum
133 | _frequencyAmplitudes[i] -= decayFactor * (_frequencyAmplitudes[i] + 0.03f);
134 | if (_frequencyAmplitudes[i] < 0)
135 | _frequencyAmplitudes[i] = 0;
136 | }
137 |
138 | Invalidate(Invalidation.DrawNode);
139 | }
140 |
141 | protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this);
142 |
143 | private class VisualisationDrawNode : DrawNode
144 | {
145 | protected new DrawableLogoVisualisation Source => (DrawableLogoVisualisation)base.Source;
146 |
147 | private IShader _shader;
148 | private Texture _texture;
149 |
150 | //Assuming the logo is a circle, we don't need a second dimension.
151 | private float _size;
152 |
153 | private Color4 _colour;
154 | private float[] _audioData;
155 |
156 | private IVertexBatch _vertexBatch;
157 |
158 | public VisualisationDrawNode(DrawableLogoVisualisation source)
159 | : base(source)
160 | {
161 | }
162 |
163 | public override void ApplyState()
164 | {
165 | base.ApplyState();
166 |
167 | _shader = Source._shader;
168 | _texture = Source._texture;
169 | _size = Source.DrawSize.X;
170 | _colour = Source.AccentColour;
171 | _audioData = Source._frequencyAmplitudes;
172 | }
173 |
174 | protected override void Draw(IRenderer renderer)
175 | {
176 | base.Draw(renderer);
177 |
178 | _vertexBatch ??= renderer.CreateQuadBatch(100, 10);
179 |
180 | _shader.Bind();
181 |
182 | var inflation = DrawInfo.MatrixInverse.ExtractScale().Xy;
183 |
184 | var colourInfo = DrawColourInfo.Colour;
185 | colourInfo.ApplyChild(_colour);
186 |
187 | if (_audioData != null)
188 | {
189 | for (var j = 0; j < VisualiserRounds; j++)
190 | {
191 | for (var i = 0; i < BarsPerVisualiser; i++)
192 | {
193 | if (_audioData[i] < AmplitudeDeadZone)
194 | continue;
195 |
196 | var rotation = float.DegreesToRadians(i / (float)BarsPerVisualiser * 360 + j * 360 / VisualiserRounds);
197 | var rotationCos = MathF.Cos(rotation);
198 | var rotationSin = MathF.Sin(rotation);
199 | //taking the cos and sin to the 0..1 range
200 | var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * _size;
201 |
202 | var barSize = new Vector2(_size * MathF.Sqrt(2 * (1 - MathF.Cos(float.DegreesToRadians(360f / BarsPerVisualiser)))) / 2f, BarLength * _audioData[i]);
203 | //The distance between the position and the sides of the bar.
204 | var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2);
205 | //The distance between the bottom side of the bar and the top side.
206 | var amplitudeOffset = new Vector2(rotationCos * barSize.Y, rotationSin * barSize.Y);
207 |
208 | var rectangle = new Quad(
209 | Vector2Extensions.Transform(barPosition - bottomOffset, DrawInfo.Matrix),
210 | Vector2Extensions.Transform(barPosition - bottomOffset + amplitudeOffset, DrawInfo.Matrix),
211 | Vector2Extensions.Transform(barPosition + bottomOffset, DrawInfo.Matrix),
212 | Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix)
213 | );
214 |
215 | renderer.DrawQuad(
216 | _texture,
217 | rectangle,
218 | colourInfo,
219 | null,
220 | _vertexBatch.AddAction,
221 | //barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that.
222 | Vector2.Divide(inflation, barSize.Yx));
223 | }
224 | }
225 | }
226 |
227 | _shader.Unbind();
228 | }
229 |
230 | protected override void Dispose(bool isDisposing)
231 | {
232 | base.Dispose(isDisposing);
233 |
234 | _vertexBatch.Dispose();
235 | }
236 | }
237 | }
238 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Screens/MainMenu/Drawables/DrawableMenuSideFlashes.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
2 | // See the LICENCE file in the repository root for full licence text.
3 |
4 | using osu.Framework.Allocation;
5 | using osu.Framework.Audio.Track;
6 | using osu.Framework.Bindables;
7 | using osu.Framework.Extensions.Color4Extensions;
8 | using osu.Framework.Graphics;
9 | using osu.Framework.Graphics.Colour;
10 | using osu.Framework.Graphics.Shapes;
11 | using osuTK.Graphics;
12 | using Qsor.Game.Beatmaps;
13 | using Qsor.Game.Graphics.Containers;
14 |
15 | namespace Qsor.Game.Graphics.UserInterface.Screens.MainMenu.Drawables
16 | {
17 | public partial class DrawableMenuSideFlashes : BeatSyncedContainer
18 | {
19 | private readonly IBindable _beatmap = new Bindable();
20 |
21 | private Box _leftBox;
22 | private Box _rightBox;
23 |
24 | private const float AmplitudeDeadZone = 0.25f;
25 | private const float AlphaMultiplier = 1.0f;
26 | private const float KiaiMultiplier = 1.0f;
27 |
28 | private const int BoxMaxAlpha = 200;
29 | private const double BoxFadeInTime = 65;
30 | private const int BoxWidth = 200;
31 |
32 | public DrawableMenuSideFlashes()
33 | {
34 | EarlyActivationMilliseconds = BoxFadeInTime;
35 |
36 | RelativeSizeAxes = Axes.Both;
37 | Anchor = Anchor.Centre;
38 | Origin = Anchor.Centre;
39 | }
40 |
41 | [BackgroundDependencyLoader]
42 | private void Load(BeatmapManager beatmapManager)
43 | {
44 | _beatmap.BindTo(beatmapManager.WorkingBeatmap);
45 |
46 | Children = new Drawable[]
47 | {
48 | _leftBox = new Box
49 | {
50 | Anchor = Anchor.CentreLeft,
51 | Origin = Anchor.CentreLeft,
52 | RelativeSizeAxes = Axes.Y,
53 | Width = BoxWidth * 2,
54 | Height = 1.5f,
55 | // align off-screen to make sure our edges don't become visible during parallax.
56 | X = -BoxWidth,
57 | Alpha = 0,
58 | Blending = BlendingParameters.Additive
59 | },
60 | _rightBox = new Box
61 | {
62 | Anchor = Anchor.CentreRight,
63 | Origin = Anchor.CentreRight,
64 | RelativeSizeAxes = Axes.Y,
65 | Width = BoxWidth * 2,
66 | Height = 1.5f,
67 | X = BoxWidth,
68 | Alpha = 0,
69 | Blending = BlendingParameters.Additive
70 | }
71 | };
72 |
73 | var baseColour = Color4.White;
74 | var gradientDark = baseColour.Opacity(0).ToLinear();
75 | var gradientLight = baseColour.Opacity(0.6f).ToLinear();
76 |
77 | _leftBox.Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark);
78 | _rightBox.Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight);
79 | }
80 |
81 | protected override void OnNewBeat(int beatIndex, TimingPoint timingPoint, ChannelAmplitudes amplitudes)
82 | {
83 | var meter = timingPoint.Parent?.Meter ?? timingPoint.Meter;
84 | if (beatIndex < 0 || meter <= 0)
85 | return;
86 |
87 | if (timingPoint.KiaiMode ? beatIndex % 2 == 0 : beatIndex % meter == 0)
88 | Flash(_leftBox, timingPoint.MsPerBeat, timingPoint.KiaiMode, amplitudes);
89 | if (timingPoint.KiaiMode ? beatIndex % 2 == 1 : beatIndex % meter == 0)
90 | Flash(_rightBox, timingPoint.MsPerBeat, timingPoint.KiaiMode, amplitudes);
91 | }
92 |
93 | private void Flash(Drawable d, double beatLength, bool kiai, ChannelAmplitudes amplitudes)
94 | {
95 | d.FadeTo(kiai ? KiaiMultiplier : AlphaMultiplier, BoxFadeInTime)
96 | .Then()
97 | .FadeOut(beatLength, Easing.In);
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Screens/MainMenu/Drawables/DrawableQsorLogo.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
2 | // See the LICENCE file in the repository root for full licence text.
3 |
4 | using System;
5 | using osu.Framework.Allocation;
6 | using osu.Framework.Audio.Track;
7 | using osu.Framework.Graphics;
8 | using osu.Framework.Graphics.Containers;
9 | using osu.Framework.Graphics.Sprites;
10 | using osu.Framework.Graphics.Textures;
11 | using osu.Framework.Input.Events;
12 | using Qsor.Game.Beatmaps;
13 | using Qsor.Game.Graphics.Containers;
14 |
15 | namespace Qsor.Game.Graphics.UserInterface.Screens.MainMenu.Drawables
16 | {
17 | public partial class DrawableQsorLogo : BeatSyncedContainer
18 | {
19 | private const double EarlyActivation = 60;
20 |
21 | private Container _logoHoverContainer;
22 | private Container _logoBeatContainer;
23 | private Sprite _qsorLogo;
24 | private Sprite _ripple;
25 | private Sprite _ghostingLogo;
26 |
27 | private DrawableLogoVisualisation _visualisation;
28 | private int _lastBeatIndex;
29 |
30 | [BackgroundDependencyLoader]
31 | private void Load(TextureStore ts)
32 | {
33 | AddInternal(new Container
34 | {
35 | Anchor = Anchor.Centre,
36 | Origin = Anchor.Centre,
37 | AutoSizeAxes = Axes.Both,
38 |
39 | Children = new []
40 | {
41 | _logoHoverContainer = new CircularContainer
42 | {
43 | Anchor = Anchor.Centre,
44 | Origin = Anchor.Centre,
45 | AutoSizeAxes = Axes.Both,
46 |
47 | Children = new Drawable[]
48 | {
49 | _logoBeatContainer = new Container
50 | {
51 | Anchor = Anchor.Centre,
52 | Origin = Anchor.Centre,
53 | AutoSizeAxes = Axes.Both,
54 |
55 | Children = new Drawable[]
56 | {
57 | _ripple = new Sprite
58 | {
59 | Anchor = Anchor.Centre,
60 | Origin = Anchor.Centre,
61 | Texture = ts.Get("Logo-ghost"),
62 | },
63 | _qsorLogo = new Sprite
64 | {
65 | Anchor = Anchor.Centre,
66 | Origin = Anchor.Centre,
67 | Texture = ts.Get("Logo"),
68 | },
69 | _ghostingLogo = new Sprite
70 | {
71 | Anchor = Anchor.Centre,
72 | Origin = Anchor.Centre,
73 | Texture = ts.Get("Logo-ghost"),
74 | Alpha = .2f
75 | },
76 | _visualisation = new DrawableLogoVisualisation
77 | {
78 | Anchor = Anchor.Centre,
79 | Origin = Anchor.Centre
80 | },
81 | }
82 | }
83 | },
84 | }
85 | }
86 | });
87 |
88 | CornerRadius = Math.Min(_qsorLogo.Width, _qsorLogo.Height) / 2f;
89 | }
90 |
91 | protected override void Update()
92 | {
93 | base.Update();
94 |
95 | const float scaleAdjustCutoff = 0.4f;
96 |
97 | if (!IsTrackPaused)
98 | {
99 | var maxAmplitude = _lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0;
100 |
101 | _qsorLogo.ScaleTo(1 - Math.Max(0, maxAmplitude - scaleAdjustCutoff) * 0.04f, 75, Easing.OutQuint);
102 | _ghostingLogo.ScaleTo(1 + Math.Max(0, maxAmplitude - scaleAdjustCutoff) * 0.04f, 75, Easing.OutQuint);
103 | }
104 |
105 | _visualisation.Scale = _qsorLogo.Scale;
106 | _visualisation.Size = _qsorLogo.Size;
107 | }
108 |
109 | protected override void OnNewBeat(int beatIndex, TimingPoint timingPoint, ChannelAmplitudes amplitudes)
110 | {
111 | _lastBeatIndex = beatIndex;
112 |
113 | var amplitudeAdjust = Math.Min(1, 0.4f + amplitudes.Maximum);
114 |
115 | _logoBeatContainer
116 | .ScaleTo(1 - 0.05f * amplitudeAdjust, EarlyActivation, Easing.Out)
117 | .Then()
118 | .ScaleTo(1, timingPoint.MsPerBeat * 2, Easing.OutQuint);
119 |
120 | _ghostingLogo
121 | .ScaleTo(1 + 0.05f * amplitudeAdjust, EarlyActivation, Easing.Out)
122 | .Then()
123 | .ScaleTo(1, timingPoint.MsPerBeat * 2, Easing.OutQuint);
124 |
125 | _ripple.ClearTransforms();
126 | _ripple
127 | .ScaleTo(_qsorLogo.Scale)
128 | .ScaleTo(_qsorLogo.Scale * (1 + 0.16f * amplitudeAdjust), timingPoint.MsPerBeat, Easing.OutQuint)
129 | .FadeTo(0.3f * amplitudeAdjust)
130 | .FadeOut(timingPoint.MsPerBeat, Easing.OutQuint);
131 |
132 | if (timingPoint.KiaiMode)
133 | {
134 | _visualisation.ClearTransforms();
135 | _visualisation
136 | .FadeTo(0.9f * amplitudeAdjust, EarlyActivation, Easing.Out)
137 | .Then()
138 | .FadeTo(0.5f, timingPoint.MsPerBeat);
139 | }
140 | }
141 |
142 | protected override bool OnHover(HoverEvent e)
143 | {
144 | _logoHoverContainer.ScaleTo(1.05f, 100, Easing.In);
145 | return true;
146 | }
147 |
148 | protected override void OnHoverLost(HoverLostEvent e)
149 | {
150 | _logoHoverContainer.ScaleTo(1, 100, Easing.Out);
151 | }
152 | }
153 | }
--------------------------------------------------------------------------------
/Qsor.Game/Graphics/UserInterface/Screens/MainMenu/MainMenuScreen.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Audio;
3 | using osu.Framework.Bindables;
4 | using osu.Framework.Development;
5 | using osu.Framework.Graphics;
6 | using osu.Framework.Graphics.Containers;
7 | using osu.Framework.Input.Events;
8 | using osu.Framework.Localisation;
9 | using osu.Framework.Platform;
10 | using osu.Framework.Screens;
11 | using osu.Framework.Timing;
12 | using osuTK;
13 | using osuTK.Graphics;
14 | using Qsor.Game.Beatmaps;
15 | using Qsor.Game.Database;
16 | using Qsor.Game.Graphics.Containers;
17 | using Qsor.Game.Graphics.UserInterface.Overlays;
18 | using Qsor.Game.Graphics.UserInterface.Overlays.Notification;
19 | using Qsor.Game.Graphics.UserInterface.Screens.MainMenu.Containers;
20 | using Qsor.Game.Graphics.UserInterface.Screens.MainMenu.Drawables;
21 | using Qsor.Game.Updater;
22 |
23 | namespace Qsor.Game.Graphics.UserInterface.Screens.MainMenu
24 | {
25 | public partial class MainMenuScreen : Screen
26 | {
27 | private BackgroundImageContainer _background;
28 | private DrawableQsorLogo _drawableQsorLogo;
29 | private Toolbar _toolbar;
30 | private BottomBar _bottomBar;
31 | private DrawableMenuSideFlashes _sideFlashes;
32 |
33 | private Bindable _workingBeatmap = new();
34 |
35 | [Resolved]
36 | private NotificationOverlay NotificationOverlay { get; set; }
37 |
38 | [Resolved]
39 | private Storage Storage { get; set; }
40 |
41 | [Resolved]
42 | private UpdateManager UpdateManager { get; set; }
43 |
44 | [Resolved]
45 | private GameHost Host { get; set; }
46 |
47 | [BackgroundDependencyLoader]
48 | private void Load(UpdaterOverlay updaterOverlay, AudioManager audioManager, QsorDbContextFactory ctxFactory, BeatmapManager beatmapManager)
49 | {
50 | var parallaxBack = new ParallaxContainer
51 | {
52 | ParallaxAmount = -0.005f
53 | };
54 | parallaxBack._content.Add(_background = new BackgroundImageContainer
55 | {
56 | Anchor = Anchor.Centre,
57 | Origin = Anchor.Centre,
58 | FillMode = FillMode.Fill,
59 | });
60 |
61 | _workingBeatmap.BindTo(beatmapManager.WorkingBeatmap);
62 | _workingBeatmap.ValueChanged += e =>
63 | {
64 | if (e.NewValue == null)
65 | return;
66 |
67 | LoadComponent(e.NewValue);
68 |
69 | _background.SetTexture(e.NewValue.Background);
70 | audioManager.AddItem(e.NewValue.Track);
71 | };
72 |
73 | var parallaxFront = new ParallaxContainer
74 | {
75 | ParallaxAmount = -0.015f,
76 | RelativeSizeAxes = Axes.Both,
77 | };
78 | parallaxFront._content.Add(new DrawSizePreservingFillContainer
79 | {
80 | Child = _drawableQsorLogo = new DrawableQsorLogo
81 | {
82 | Anchor = Anchor.Centre,
83 | Origin = Anchor.Centre,
84 | AutoSizeAxes = Axes.Both,
85 | Scale = new Vector2(1f) * (DrawSize.X / DrawSize.Y)
86 | }
87 | });
88 |
89 | InternalChildren = new Drawable[]
90 | {
91 | parallaxBack,
92 | parallaxFront,
93 |
94 | _sideFlashes = new DrawableMenuSideFlashes(),
95 | _toolbar = new Toolbar(),
96 | _bottomBar = new BottomBar(),
97 |
98 | updaterOverlay
99 | };
100 |
101 | // Start a random map, TODO: remove this from here
102 | beatmapManager.NextRandomMap();
103 | beatmapManager.WorkingBeatmap.Value?.Play();
104 | }
105 |
106 | public override void OnEntering(ScreenTransitionEvent e)
107 | {
108 | _clock.Start();
109 |
110 | this.FadeInFromZero(2500, Easing.InExpo).Finally(e =>
111 | {
112 | NotificationOverlay.AddNotification(new LocalisableString(
113 | "Please note that this game is in very early development and is not representative of the final product. " +
114 | "If you encounter any issues, please report them on our GitHub repository!"),
115 | Color4.Orange, 10000);
116 |
117 | NotificationOverlay.AddNotification(new LocalisableString(
118 | "You can play different beatmaps by editing \"game.ini\" config file. " +
119 | "To open the Qsor configuration directory, click this notification!"),
120 | Color4.Orange, 10000, () => { Storage.OpenFileExternally(string.Empty); });
121 |
122 | NotificationOverlay.AddNotification(new LocalisableString(
123 | $"You're currently running {QsorBaseGame.Version}! " +
124 | "Click here to view the changelog."),
125 | Color4.Gray, 10000, () => Host.OpenUrlExternally($"https://github.com/osuAkatsuki/Qsor/releases/tag/{QsorBaseGame.Version}"));
126 |
127 | if (!DebugUtils.IsDebugBuild)
128 | UpdateManager.CheckAvailable();
129 | });
130 | }
131 |
132 | public override bool OnExiting(ScreenExitEvent e)
133 | {
134 | this.FadeOutFromOne(2500, Easing.OutExpo);
135 | return true;
136 | }
137 |
138 | // Fade clock
139 | private StopwatchClock _clock = new();
140 | private bool _isFading;
141 |
142 | protected override void Update()
143 | {
144 | if (_isFading || _clock.ElapsedMilliseconds <= 5000)
145 | return;
146 |
147 | _toolbar.FadeOut(8000);
148 | _bottomBar.FadeOut(8000);
149 |
150 | _isFading = true;
151 | }
152 | protected override bool OnMouseMove(MouseMoveEvent e)
153 | {
154 | if (_clock.ElapsedMilliseconds >= 5000)
155 | {
156 | _toolbar.ClearTransforms();
157 | _bottomBar.ClearTransforms();
158 |
159 | _toolbar.FadeIn(250);
160 | _bottomBar.FadeIn(250);
161 |
162 | _isFading = false;
163 | _clock.Restart();
164 | }
165 |
166 | return true;
167 | }
168 |
169 | protected override bool OnClick(ClickEvent e)
170 | {
171 | if (_drawableQsorLogo.IsHovered)
172 | {
173 | NotificationOverlay.AddBigNotification("Gameplay as it stands right now is not implemented.\n" +
174 | "We want to implement the UI First before we do any gameplay features.\n" +
175 | "That way we can guarantee that it feels just right!", 10000);
176 | }
177 |
178 | return base.OnClick(e);
179 | }
180 | }
181 | }
--------------------------------------------------------------------------------
/Qsor.Game/Input/KeyBindingInputHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using osu.Framework.Graphics;
4 | using osu.Framework.Input;
5 | using osu.Framework.Input.Bindings;
6 |
7 | namespace Qsor.Game.Input
8 | {
9 | public partial class GlobalKeyBindingInputHandler : KeyBindingContainer, IHandleGlobalKeyboardInput
10 | {
11 | private readonly Drawable _handler;
12 |
13 | public GlobalKeyBindingInputHandler(QsorBaseGame game)
14 | : base(matchingMode: KeyCombinationMatchingMode.Modifiers)
15 | {
16 | _handler = game;
17 | }
18 |
19 | public override IEnumerable DefaultKeyBindings => new[]
20 | {
21 | new KeyBinding(new [] { InputKey.Control, InputKey.O }, GlobalAction.ToggleOptions),
22 | new KeyBinding(InputKey.Escape, GlobalAction.ExitOverlay),
23 | };
24 |
25 | protected override IEnumerable KeyBindingInputQueue =>
26 | _handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(_handler);
27 | }
28 |
29 | public enum GlobalAction
30 | {
31 | ToggleOptions,
32 | ExitOverlay
33 | }
34 | }
--------------------------------------------------------------------------------
/Qsor.Game/Online/BanchoClient.cs:
--------------------------------------------------------------------------------
1 | namespace Qsor.Game.Online
2 | {
3 | public class BanchoClient
4 | {
5 | public const string WebUrl = "https://ripple.moe";
6 | public const string AvatarUrl = "https://a.ripple.moe";
7 | }
8 | }
--------------------------------------------------------------------------------
/Qsor.Game/Online/BeatmapMirrorAccess.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using osu.Framework.Allocation;
3 | using osu.Framework.IO.Network;
4 | using osu.Framework.Platform;
5 |
6 | namespace Qsor.Game.Online
7 | {
8 | public partial class BeatmapMirrorAccess : IDependencyInjectionCandidate
9 | {
10 | private const string BeatmapMirror = "https://api.nerinyan.moe";
11 |
12 | [Resolved]
13 | private Storage Storage { get; set; }
14 |
15 | ///
16 | /// Download a beatmap from a given
17 | ///
18 | /// Set ID
19 | public void DownloadBeatmap(int setId)
20 | {
21 | using var fileReq = new FileWebRequest(Storage.GetFullPath($"Songs/{setId}.osz"), $"{BeatmapMirror}/d/{setId}");
22 |
23 | fileReq.Perform(); // Works
24 | }
25 |
26 | ///
27 | /// Download a beatmap from a given asynchronously
28 | ///
29 | /// Set ID
30 | ///
31 | /// NOTE: this seems broken right now.
32 | ///
33 | public async Task DownloadBeatmapAsync(int setId)
34 | {
35 | using var fileReq = new FileWebRequest(Storage.GetFullPath($"Songs/{setId}.osz"), $"{BeatmapMirror}/d/{setId}");
36 |
37 | await fileReq.PerformAsync(); // Gets stuck
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/Qsor.Game/Online/UserManager.cs:
--------------------------------------------------------------------------------
1 | using Qsor.Game.Online.Users;
2 |
3 | namespace Qsor.Game.Online
4 | {
5 | public class UserManager
6 | {
7 | public User User { get; } = new()
8 | {
9 | Id = 0,
10 | Username = "Guest"
11 | };
12 | }
13 | }
--------------------------------------------------------------------------------
/Qsor.Game/Online/Users/User.cs:
--------------------------------------------------------------------------------
1 | namespace Qsor.Game.Online.Users
2 | {
3 | public class User
4 | {
5 | public int Id = 1;
6 | public string Username = "Guest";
7 |
8 | public UserStatistics Statistics { get; } = new();
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Online/Users/UserStatistics.cs:
--------------------------------------------------------------------------------
1 | namespace Qsor.Game.Online.Users
2 | {
3 | public class UserStatistics
4 | {
5 | public int PerformancePoints = 0;
6 | public double Accuracy = 0;
7 |
8 | public int Rank = 0;
9 |
10 | public int GetLevel() => 0;
11 | public double GetProgress() => .5d;
12 | }
13 | }
--------------------------------------------------------------------------------
/Qsor.Game/Qsor.Game.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Qsor.Game
5 | Qsor.Game
6 | 9
7 | net8.0
8 |
9 |
10 |
11 |
12 |
13 | all
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Qsor.Game/QsorBaseGame.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using osu.Framework.Allocation;
4 | using osu.Framework.Development;
5 | using osu.Framework.Graphics;
6 | using osu.Framework.Graphics.Cursor;
7 | using osu.Framework.IO.Stores;
8 | using osu.Framework.Platform;
9 | using osu.Framework.Screens;
10 | using Qsor.Game.Beatmaps;
11 | using Qsor.Game.Configuration;
12 | using Qsor.Game.Database;
13 | using Qsor.Game.Graphics.Containers;
14 | using Qsor.Game.Graphics.UserInterface.Overlays;
15 | using Qsor.Game.Graphics.UserInterface.Overlays.Notification;
16 | using Qsor.Game.Graphics.UserInterface.Overlays.Settings;
17 | using Qsor.Game.Online;
18 | using Qsor.Game.Updater;
19 | using Qsor.Game.Utility;
20 |
21 | namespace Qsor.Game
22 | {
23 | public partial class QsorBaseGame : osu.Framework.Game
24 | {
25 | protected readonly string[] Args;
26 | protected BeatmapManager BeatmapManager;
27 | protected UserManager UserManager;
28 |
29 | protected BeatmapMirrorAccess BeatmapMirrorAccess;
30 |
31 | protected QsorDbContextFactory QsorDbContextFactory;
32 | protected QsorConfigManager ConfigManager;
33 |
34 | protected NotificationOverlay NotificationOverlay;
35 | protected UpdaterOverlay UpdaterOverlay;
36 | protected SentryLogger SentryLogger;
37 | protected SettingsOverlay SettingsOverlay;
38 | protected TooltipContainer TooltipContainer;
39 |
40 | public UpdateManager UpdateManager;
41 |
42 | private DependencyContainer _dependencies;
43 |
44 | private ScreenStack _stack;
45 |
46 | public static string Version => !DebugUtils.IsDebugBuild
47 | ? Assembly.GetEntryAssembly()?.GetName().Version?.ToString(3)
48 | : DateTime.Now.ToString("yyyy.Mdd.0") + (DebugUtils.IsDebugBuild ? " Debug Build" : string.Empty);
49 |
50 | protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
51 | _dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
52 |
53 | public QsorBaseGame(string[] args)
54 | {
55 | Args = args;
56 | }
57 |
58 | [BackgroundDependencyLoader]
59 | private void Load(Storage storage)
60 | {
61 | // Load important dependencies
62 | {
63 | _dependencies.Cache(UserManager = new UserManager());
64 |
65 | _dependencies.Cache(BeatmapMirrorAccess = new BeatmapMirrorAccess());
66 | Dependencies.Inject(BeatmapMirrorAccess);
67 |
68 | _dependencies.Cache(QsorDbContextFactory = new QsorDbContextFactory(storage));
69 | _dependencies.Cache(ConfigManager = new QsorConfigManager(storage));
70 |
71 | _dependencies.Cache(NotificationOverlay = new NotificationOverlay());
72 |
73 | _dependencies.Cache(SentryLogger = new SentryLogger(this));
74 |
75 | _dependencies.CacheAs(this);
76 | _dependencies.CacheAs(Host);
77 |
78 | _dependencies.Cache(BeatmapManager = new BeatmapManager());
79 | Dependencies.Inject(BeatmapManager);
80 |
81 | UpdateManager ??= CreateUpdater();
82 | UpdaterOverlay = new UpdaterOverlay();
83 |
84 | _dependencies.Cache(UpdaterOverlay);
85 | _dependencies.CacheAs(UpdateManager);
86 |
87 | LoadComponent(UpdateManager);
88 | }
89 |
90 | // Add our Resources/ directory
91 | Resources.AddStore(new NamespacedResourceStore(new DllResourceStore(typeof(QsorGame).Assembly), @"Resources"));
92 |
93 | // Migrate the given database
94 | QsorDbContextFactory.Get().Migrate();
95 |
96 | // Root container for everything and anything, we have QsorToolTipContainer as root
97 | // so we can display our tooltips cleanly.
98 | Add(TooltipContainer = new QsorTooltipContainer(null)
99 | {
100 | RelativeSizeAxes = Axes.Both,
101 | });
102 |
103 | // Update config (if necessary)
104 | ConfigManager.Save();
105 |
106 | _stack = new ScreenStack();
107 |
108 | TooltipContainer.Add(_stack);
109 | TooltipContainer.Add(SettingsOverlay = new SettingsOverlay());
110 | TooltipContainer.Add(NotificationOverlay);
111 | }
112 |
113 | protected virtual UpdateManager CreateUpdater() => new DummyUpdater();
114 |
115 | protected override void Dispose(bool isDisposing)
116 | {
117 | if (isDisposing) {
118 | ConfigManager?.Dispose();
119 | NotificationOverlay?.Dispose();
120 |
121 | SentryLogger?.Dispose();
122 | }
123 |
124 | base.Dispose(isDisposing);
125 | }
126 |
127 | public void PushScreen(Screen screen)
128 | {
129 | _stack.Push(screen);
130 | }
131 |
132 | public void ExitScreen()
133 | {
134 | _stack.Exit();
135 | }
136 | }
137 | }
--------------------------------------------------------------------------------
/Qsor.Game/QsorGame.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using osu.Framework.Allocation;
3 | using osu.Framework.Development;
4 | using osu.Framework.Input.Bindings;
5 | using osu.Framework.Input.Events;
6 | using osuTK.Input;
7 | using Qsor.Game.Graphics.UserInterface.Screens;
8 | using Qsor.Game.Graphics.UserInterface.Screens.MainMenu;
9 | using Qsor.Game.Input;
10 |
11 | namespace Qsor.Game
12 | {
13 | [Cached]
14 | public partial class QsorGame : QsorBaseGame, IKeyBindingHandler
15 | {
16 | private GlobalKeyBindingInputHandler _keyBindingInputHandler;
17 |
18 | [BackgroundDependencyLoader]
19 | private void Load()
20 | {
21 | Audio.Frequency.Set(1);
22 | Audio.Volume.Set(.35);
23 |
24 | Window.Title = $"Qsor - {Version}";
25 |
26 | Add(_keyBindingInputHandler = new GlobalKeyBindingInputHandler(this));
27 |
28 | if (!DebugUtils.IsDebugBuild)
29 | {
30 | PushScreen(new IntroScreen());
31 |
32 | Scheduler.AddDelayed(() =>
33 | {
34 | ExitScreen();
35 |
36 | Scheduler.AddDelayed(() => PushScreen(new MainMenuScreen()), 2000);
37 | }, 6000);
38 | }
39 | else
40 | {
41 | PushScreen(new MainMenuScreen());
42 | }
43 | }
44 |
45 | protected override bool OnKeyDown(KeyDownEvent e)
46 | {
47 | switch (e.Key)
48 | {
49 | case Key.Down:
50 | Audio.Frequency.Value -= .1;
51 | return true;
52 | case Key.Up:
53 | Audio.Frequency.Value += .1;
54 | return true;
55 | case Key.Space:
56 | if (!BeatmapManager.WorkingBeatmap.Value.Track.IsRunning)
57 | BeatmapManager.WorkingBeatmap.Value.Track.Start();
58 | else
59 | BeatmapManager.WorkingBeatmap.Value.Track.Stop();
60 | return true;
61 | default:
62 | return false;
63 | }
64 | }
65 |
66 | public QsorGame(string[] args) : base(args)
67 | {
68 | }
69 |
70 | public bool OnPressed(KeyBindingPressEvent e)
71 | {
72 | switch (e.Action)
73 | {
74 | case GlobalAction.ToggleOptions:
75 | if (SettingsOverlay.IsShown)
76 | SettingsOverlay.Hide();
77 | else
78 | SettingsOverlay.Show();
79 | break;
80 |
81 | case GlobalAction.ExitOverlay:
82 | if (SettingsOverlay.IsShown)
83 | SettingsOverlay.Hide();
84 | break;
85 |
86 | default:
87 | throw new ArgumentOutOfRangeException(nameof(e.Action), e.Action, null);
88 | }
89 |
90 | return true;
91 | }
92 |
93 | public void OnReleased(KeyBindingReleaseEvent e)
94 | {
95 | }
96 |
97 | protected override bool OnClick(ClickEvent e)
98 | {
99 | if (SettingsOverlay.IsShown && !SettingsOverlay.IsHovered)
100 | SettingsOverlay.Hide();
101 |
102 | return base.OnClick(e);
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/Qsor.Game/Resources/Textures/Logo-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Game/Resources/Textures/Logo-256x256.png
--------------------------------------------------------------------------------
/Qsor.Game/Resources/Textures/Logo-ghost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Game/Resources/Textures/Logo-ghost.png
--------------------------------------------------------------------------------
/Qsor.Game/Resources/Textures/Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Game/Resources/Textures/Logo.png
--------------------------------------------------------------------------------
/Qsor.Game/Resources/Textures/approachcircle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Game/Resources/Textures/approachcircle.png
--------------------------------------------------------------------------------
/Qsor.Game/Resources/Textures/hitcircle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Game/Resources/Textures/hitcircle.png
--------------------------------------------------------------------------------
/Qsor.Game/Resources/Textures/hitcircleoverlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Game/Resources/Textures/hitcircleoverlay.png
--------------------------------------------------------------------------------
/Qsor.Game/Resources/Textures/slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Game/Resources/Textures/slider.png
--------------------------------------------------------------------------------
/Qsor.Game/Resources/Textures/sliderb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mempler/Qsor/c1124f16faca2d46c642be57651690558be1e599/Qsor.Game/Resources/Textures/sliderb.png
--------------------------------------------------------------------------------
/Qsor.Game/Updater/DummyUpdateManager.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 |
3 | namespace Qsor.Game.Updater
4 | {
5 | [Cached]
6 | public partial class DummyUpdater : UpdateManager
7 | {
8 | public override void CheckAvailable()
9 | {
10 | BindableStatus.Value = UpdaterStatus.Latest;
11 | }
12 |
13 | public override void UpdateGame()
14 | {
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Qsor.Game/Updater/UpdateManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using osu.Framework.Allocation;
3 | using osu.Framework.Bindables;
4 | using osu.Framework.Graphics;
5 | using osu.Framework.Localisation;
6 | using Qsor.Game.Graphics.UserInterface.Overlays;
7 |
8 | namespace Qsor.Game.Updater
9 | {
10 | [Cached]
11 | public abstract partial class UpdateManager : Component
12 | {
13 | public readonly Bindable BindableStatus = new();
14 | public readonly BindableFloat BindableProgress = new();
15 |
16 | public abstract void CheckAvailable();
17 | public abstract void UpdateGame();
18 |
19 | [Resolved]
20 | private UpdaterOverlay UpdaterOverlay { get; set; }
21 |
22 | [BackgroundDependencyLoader]
23 | private void Load()
24 | {
25 | BindableStatus.ValueChanged += e =>
26 | {
27 | switch (e.NewValue)
28 | {
29 | case UpdaterStatus.Latest:
30 | break; // Ignore
31 |
32 | case UpdaterStatus.Pending:
33 | case UpdaterStatus.Downloading:
34 | UpdaterOverlay.Show();
35 | break;
36 |
37 | case UpdaterStatus.Ready:
38 | UpdaterOverlay.Text = new LocalisableString("Click here to restart!");
39 | UpdaterOverlay.Show();
40 | break;
41 | default:
42 | throw new ArgumentOutOfRangeException();
43 | }
44 | };
45 |
46 | BindableProgress.ValueChanged += e =>
47 | UpdaterOverlay.Text = new LocalisableString($"Updating Qsor... {e.NewValue}%");
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/Qsor.Game/Updater/UpdaterStatus.cs:
--------------------------------------------------------------------------------
1 | namespace Qsor.Game.Updater
2 | {
3 | public enum UpdaterStatus
4 | {
5 | Latest,
6 | Pending,
7 | Downloading,
8 | Ready
9 | }
10 | }
--------------------------------------------------------------------------------
/Qsor.Game/Utility/SentryLogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using osu.Framework.Development;
3 | using osu.Framework.Logging;
4 | using Sentry;
5 |
6 | namespace Qsor.Game.Utility
7 | {
8 | public class SentryLogger : IDisposable
9 | {
10 | private IDisposable _sentry;
11 |
12 | public SentryLogger(QsorBaseGame game)
13 | {
14 | if (DebugUtils.IsDebugBuild)
15 | return;
16 |
17 | _sentry = SentrySdk.Init(o =>
18 | {
19 | o.Dsn = "https://ddefbeb05e074f9c993bf1a72eb2a602@o169266.ingest.sentry.io/5193034";
20 | o.Release = QsorBaseGame.Version;
21 | });
22 |
23 | Exception lastException = null;
24 | Logger.NewEntry += entry =>
25 | {
26 | if (entry.Level < LogLevel.Verbose)
27 | return;
28 |
29 | var exception = entry.Exception;
30 |
31 | if (exception != null)
32 | {
33 | if (lastException != null && // We shouldn't resubmit the same exception
34 | lastException.Message == exception.Message &&
35 | exception.StackTrace?.StartsWith(lastException.StackTrace ?? string.Empty) == true)
36 | return;
37 |
38 | SentrySdk.CaptureEvent(new SentryEvent(exception) { Message = entry.Message });
39 | lastException = exception;
40 | }
41 | else
42 | {
43 | SentrySdk.AddBreadcrumb(entry.Message, entry.Target.ToString(), "qsor-logger");
44 | }
45 | };
46 | }
47 |
48 | public void Dispose()
49 | {
50 | _sentry?.Dispose();
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/Qsor.NativeLibs/Qsor.NativeLibs.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Library
4 | net8.0
5 | true
6 |
7 |
8 |
9 | true
10 | Qsor Native Libraries
11 | Native libraries for Qsor
12 | Qsor.NativeLibs
13 | qsor libraries
14 | false
15 |
16 |
17 |
18 |
19 | true
20 | runtimes
21 |
22 |
23 |
24 |
25 | true
26 | lib\$(TargetFramework)
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Qsor.NativeLibs/_._:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Qsor.Tests/Program.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework;
2 |
3 | namespace Qsor.Tests
4 | {
5 | internal static class Program
6 | {
7 | private static void Main(string[] args)
8 | {
9 | using var host = Host.GetSuitableDesktopHost(@"Qsor");
10 | host.Run(new QsorTestGame(args));
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/Qsor.Tests/Qsor.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 9
5 | Exe
6 | net8.0
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Qsor.Tests/QsorTestGame.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.IO.Stores;
3 | using osu.Framework.Testing;
4 | using Qsor.Game;
5 |
6 | namespace Qsor.Tests
7 | {
8 | public partial class QsorTestGame : QsorBaseGame
9 | {
10 | [BackgroundDependencyLoader]
11 | private void Load()
12 | {
13 | Resources.AddStore(new NamespacedResourceStore(new DllResourceStore(typeof(QsorTestGame).Assembly), @"Resources"));
14 |
15 | Add(new TestBrowser("Qsor.Tests"));
16 | }
17 |
18 | public QsorTestGame(string[] args) : base(args)
19 | {
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Qsor.Tests/Visual/Overlays/TestSceneMusicPlayerOverlay.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics;
3 | using osu.Framework.Graphics.Shapes;
4 | using osu.Framework.Testing;
5 | using osuTK.Graphics;
6 | using Qsor.Game.Graphics.UserInterface.Overlays;
7 |
8 | namespace Qsor.Tests.Visual.Overlays
9 | {
10 | public partial class TestSceneMusicPlayerOverlay : TestScene
11 | {
12 | private MusicPlayerOverlay _musicPlayer;
13 |
14 | [SetUpSteps]
15 | public void Setup()
16 | {
17 | // Background
18 | Add(new Box
19 | {
20 | Colour = new Color4(0.2f, 0.2f, 0.2f, 1.0f),
21 | RelativeSizeAxes = Axes.Both,
22 | });
23 |
24 | Add(_musicPlayer = new MusicPlayerOverlay());
25 | }
26 |
27 | [BackgroundDependencyLoader]
28 | private void Load()
29 | {
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/Qsor.Tests/Visual/Overlays/TestSceneNotificationOverlay.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NLipsum.Core;
3 | using osu.Framework.Allocation;
4 | using osu.Framework.Graphics;
5 | using osu.Framework.Graphics.Shapes;
6 | using osu.Framework.Localisation;
7 | using osu.Framework.Testing;
8 | using osuTK.Graphics;
9 | using Qsor.Game.Graphics.UserInterface.Overlays.Notification;
10 |
11 | namespace Qsor.Tests.Visual.Overlays
12 | {
13 | public partial class TestSceneNotificationOverlay : TestScene
14 | {
15 | private LipsumGenerator _lipsumGenerator;
16 | private NotificationOverlay _notificationOverlay;
17 |
18 | [SetUpSteps]
19 | public void Setup()
20 | {
21 | // Background
22 | Add(new Box
23 | {
24 | Colour = new Color4(0.2f, 0.2f, 0.2f, 1.0f),
25 | RelativeSizeAxes = Axes.Both,
26 | });
27 |
28 | _lipsumGenerator = new LipsumGenerator();
29 | _notificationOverlay = new NotificationOverlay();
30 |
31 | Add(_notificationOverlay = new NotificationOverlay());
32 | }
33 |
34 | [BackgroundDependencyLoader]
35 | private void Load()
36 | {
37 | AddStep("Create random Lorem Ipsum", () =>
38 | {
39 | var random = new Random();
40 | var randomColour = new Color4((float) random.NextDouble(), (float) random.NextDouble(), (float) random.NextDouble(), 1f);
41 |
42 | _notificationOverlay.AddNotification(
43 | new LocalisableString(_lipsumGenerator.GenerateLipsum(4, Features.Sentences, FormatStrings.Paragraph.LineBreaks)
44 | .Trim()),
45 | randomColour, 5000);
46 | });
47 |
48 | AddStep("Create Big Notification", () =>
49 | {
50 | _notificationOverlay.AddBigNotification(
51 | new LocalisableString(_lipsumGenerator.GenerateLipsum(4, Features.Sentences, FormatStrings.Paragraph.LineBreaks)
52 | .Trim()),
53 | 5000);
54 | });
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/Qsor.Tests/Visual/Overlays/TestSceneSettingsOverlay.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Graphics.Textures;
3 | using osu.Framework.Testing;
4 | using Qsor.Game.Graphics.Containers;
5 | using Qsor.Game.Graphics.UserInterface.Overlays.Settings;
6 |
7 | namespace Qsor.Tests.Visual.Overlays
8 | {
9 | public partial class TestSceneSettingsOverlay : TestScene
10 | {
11 | [BackgroundDependencyLoader]
12 | private void Load(TextureStore ts)
13 | {
14 | var bg = new BackgroundImageContainer();
15 | Add(bg);
16 | bg.SetTexture(ts.Get("https://3.bp.blogspot.com/-906HDJiF4Nk/UbAN4_DrK_I/AAAAAAAAAvE/pZQwo-u2RbQ/s1600/Background+images.jpg"));
17 |
18 | SettingsOverlay settingsOverlay;
19 | Add(settingsOverlay = new SettingsOverlay());
20 |
21 | AddStep("Show Overlay", settingsOverlay.Show);
22 | AddStep("Hide Overlay", settingsOverlay.Hide);
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Qsor.Tests/Visual/Overlays/TestSceneUpdaterOverlay.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using osu.Framework.Allocation;
3 | using osu.Framework.Testing;
4 | using Qsor.Game.Graphics.UserInterface.Overlays;
5 |
6 | namespace Qsor.Tests.Visual.Overlays
7 | {
8 | public partial class TestSceneUpdaterOverlay : TestScene
9 | {
10 | [BackgroundDependencyLoader]
11 | private void Load()
12 | {
13 | Console.WriteLine("Test");
14 |
15 | var updaterOverlay = new UpdaterOverlay();
16 |
17 | Add(updaterOverlay);
18 |
19 | AddStep("Show", updaterOverlay.Show);
20 | AddStep("Hide", updaterOverlay.Hide);
21 | }
22 |
23 | }
24 | }
--------------------------------------------------------------------------------
/Qsor.Tests/Visual/Overlays/TestSceneUserOverlay.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Testing;
3 | using Qsor.Game.Graphics.UserInterface.Overlays;
4 |
5 | namespace Qsor.Tests.Visual.Overlays
6 | {
7 | public partial class TestSceneUserOverlay : TestScene
8 | {
9 | [BackgroundDependencyLoader]
10 | private void Load()
11 | {
12 | AddStep("Setup", () => Add(new UserOverlay()));
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/Qsor.Tests/Visual/Scenes/IntroTestScene.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using osu.Framework.Allocation;
3 | using osu.Framework.Screens;
4 | using osu.Framework.Testing;
5 | using Qsor.Game.Graphics.UserInterface.Screens;
6 |
7 | namespace Qsor.Tests.Visual.Scenes
8 | {
9 | [TestFixture]
10 | public partial class IntroTestScene : TestScene
11 | {
12 | [BackgroundDependencyLoader]
13 | private void Load()
14 | {
15 | var stack = new ScreenStack();
16 |
17 | Add(stack);
18 |
19 | AddStep("Start sequence", () =>
20 | {
21 | stack.Push(new IntroScreen());
22 | });
23 |
24 | AddStep("Exit sequence", () =>
25 | {
26 | stack.Exit();
27 | });
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Qsor.Tests/Visual/TestSceneQsorGame.cs:
--------------------------------------------------------------------------------
1 | using osu.Framework.Allocation;
2 | using osu.Framework.Testing;
3 | using Qsor.Game;
4 |
5 | namespace Qsor.Tests.Visual
6 | {
7 | public partial class TestSceneQsorGame : TestScene
8 | {
9 | [BackgroundDependencyLoader]
10 | private void Load()
11 | {
12 | AddGame(new QsorGame(new []{ string.Empty }));
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/Qsor.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qsor.Game", "Qsor.Game\Qsor.Game.csproj", "{80063128-CB8C-4F95-9EE3-C4539D01D580}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qsor.Tests", "Qsor.Tests\Qsor.Tests.csproj", "{2DC454D9-325D-4FA4-BEE2-D4FC3A1EAD48}"
6 | EndProject
7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qsor.Desktop", "Qsor.Desktop\Qsor.Desktop.csproj", "{74EC9492-F46A-48A7-858F-170090C7BF35}"
8 | EndProject
9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qsor.Deploy", "Qsor.Deploy\Qsor.Deploy.csproj", "{2D2B2A6E-B641-46E5-97EE-02B9172C9494}"
10 | EndProject
11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qsor.NativeLibs", "Qsor.NativeLibs\Qsor.NativeLibs.csproj", "{BB29328A-2A1A-440A-8B50-AA0C7E2E2BC2}"
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|Any CPU = Debug|Any CPU
16 | Release|Any CPU = Release|Any CPU
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {80063128-CB8C-4F95-9EE3-C4539D01D580}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {80063128-CB8C-4F95-9EE3-C4539D01D580}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {80063128-CB8C-4F95-9EE3-C4539D01D580}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {80063128-CB8C-4F95-9EE3-C4539D01D580}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {2DC454D9-325D-4FA4-BEE2-D4FC3A1EAD48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {2DC454D9-325D-4FA4-BEE2-D4FC3A1EAD48}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {2DC454D9-325D-4FA4-BEE2-D4FC3A1EAD48}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {2DC454D9-325D-4FA4-BEE2-D4FC3A1EAD48}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {74EC9492-F46A-48A7-858F-170090C7BF35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {74EC9492-F46A-48A7-858F-170090C7BF35}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {74EC9492-F46A-48A7-858F-170090C7BF35}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {74EC9492-F46A-48A7-858F-170090C7BF35}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {2D2B2A6E-B641-46E5-97EE-02B9172C9494}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {2D2B2A6E-B641-46E5-97EE-02B9172C9494}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {2D2B2A6E-B641-46E5-97EE-02B9172C9494}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {2D2B2A6E-B641-46E5-97EE-02B9172C9494}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {BB29328A-2A1A-440A-8B50-AA0C7E2E2BC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {BB29328A-2A1A-440A-8B50-AA0C7E2E2BC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {BB29328A-2A1A-440A-8B50-AA0C7E2E2BC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {BB29328A-2A1A-440A-8B50-AA0C7E2E2BC2}.Release|Any CPU.Build.0 = Release|Any CPU
39 | EndGlobalSection
40 | EndGlobal
41 |
--------------------------------------------------------------------------------
/Qsor.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
3 | True
4 | True
5 | True
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Qsor
6 |
7 | [](https://discord.gg/ujNk7sVQCt) [](https://www.codefactor.io/repository/github/mempler/qsor) [](https://ci.appveyor.com/project/mempler/qsor)
8 |
9 | for people who wan't the nostalgic feeling of osu!Stable once osu!Lazer gets released.
10 |
11 | ## Requirements
12 |
13 | #### Knowledge
14 |
15 | you'll need prior knowledge in C\# and .NET 5 and osu!Framework if you want to develop on Qsor!
16 |
17 | #### Dependencies
18 |
19 | * [.NET 5](https://dotnet.microsoft.com)
20 |
21 | ## Setup Qsor
22 |
23 | If you're a not a developer, you can simply go over to the [releases](https://github.com/mempler/qsor/releases) and grab the latest release.
24 |
25 | but if you're one, there you go:
26 |
27 | ```shell
28 | :~$ git clone https://github.com/mempler/qsor.git
29 | :~$ cd qsor
30 | :~$ dotnet run Qsor.Desktop -c Release
31 | ```
32 |
33 | ## License
34 |
35 | Qsor's code is licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](./LICENSE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source.
36 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: '{build}'
2 | image: Visual Studio 2022
3 | configuration: Release
4 | init:
5 | - ps: Update-AppveyorBuild -Version "$(Get-Date -format yyyy.Mdd).$env:appveyor_build_number"
6 | dotnet_csproj:
7 | patch: true
8 | file: '**\*.csproj'
9 | version: '{version}'
10 | version_prefix: '{version}'
11 | package_version: '{version}'
12 | assembly_version: '{version}'
13 | file_version: '{version}'
14 | informational_version: '{version}'
15 | before_build:
16 | - ps: dotnet restore
17 | build:
18 | project: Qsor.Desktop
19 | parallel: true
20 | verbosity: minimal
21 | deploy: off
22 | on_success:
23 | - ps: >-
24 | Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
25 |
26 | ./send.ps1 success $env:WEBHOOK_URL
27 | on_failure:
28 | - sh: >-
29 | Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1
30 |
31 | ./send.ps1 failure $env:WEBHOOK_URL
32 |
--------------------------------------------------------------------------------
/generate_changelog.ps1:
--------------------------------------------------------------------------------
1 | $TYPE = "DEV"
2 | $LAST_TAG = git describe --tags --abbrev=0
3 | $COMMIT_COUNT = git rev-list "$LAST_TAG..HEAD" --count
4 | $DATE = Get-Date -Format "yyyy.ddMM"
5 | $CHANGE_LOG = git log "$LAST_TAG..HEAD" --pretty=format:"%h %s"
6 |
7 | Write-Output "Create Tag $TYPE-$DATE.$COMMIT_COUNT"
8 | Write-Output "Changelog:"
9 | Write-Output $CHANGE_LOG
10 |
11 | git tag "$TYPE-$DATE.$COMMIT_COUNT"
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "5.0",
4 | "rollForward": "latestMajor",
5 | "allowPrerelease": false
6 | }
7 | }
--------------------------------------------------------------------------------