Initial commit.

This commit is contained in:
Dean Herbert
2016-08-26 13:14:57 +09:00
commit f3d00712fd
180 changed files with 18178 additions and 0 deletions

19
.gitattributes vendored Normal file
View File

@@ -0,0 +1,19 @@
# This won't normalise line endings, but it will ensure that merge drivers use CRLF
* -text eol=crlf
# Currently in-use binary file extensions
*.blend binary
*.bmp binary
*.dll binary
*.exe binary
*.icns binary
*.ico binary
*.jpg binary
*.osz2 binary
*.pdn binary
*.psd binary
*.PSD binary
*.tga binary
*.ttf binary
*.wav binary
*.xnb binary

258
.gitignore vendored Normal file
View File

@@ -0,0 +1,258 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

2
.travis.yml Normal file
View File

@@ -0,0 +1,2 @@
language: csharp
solution: osu-framework.sln

21
LICENCE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2007-2016 ppy Pty Ltd https://osu.ppy.sh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

19
README.md Normal file
View File

@@ -0,0 +1,19 @@
# osu-framework
[dev chat](https://discord.gg/ppy)
A game framework written with osu! in mind.
# Requirements
- A desktop platform which can compile .NET 4.5.
- Visual Studio or MonoDevelop is recommended.
# Contributing
Contributions can be made via pull requests to this repository. We hope to credit and reward larger contributions via a [bounty system](https://goo.gl/nFdoyI). If you're unsure of what you can help with, check out the [list](https://github.com/ppy/osu-framework/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Abounty) of available issues with bounty.
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible.
# Licence
This framework is licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) 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.

View File

@@ -0,0 +1,9 @@
extensions: .cs
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
extensions: .xml .config .xsd
<!--
Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
-->

33
osu-framework.sln Normal file
View File

@@ -0,0 +1,33 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework", "osu.Framework\osu.Framework.csproj", "{C76BF5B3-985E-4D39-95FE-97C9C879B83A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework.Desktop", "osu.Framework.Desktop\osu.Framework.Desktop.csproj", "{65DC628F-A640-4111-AB35-3A5652BC1E17}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Deploy|Any CPU = Deploy|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Deploy|Any CPU.ActiveCfg = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Deploy|Any CPU.Build.0 = Debug|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.Build.0 = Release|Any CPU
{65DC628F-A640-4111-AB35-3A5652BC1E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65DC628F-A640-4111-AB35-3A5652BC1E17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65DC628F-A640-4111-AB35-3A5652BC1E17}.Deploy|Any CPU.ActiveCfg = Debug|Any CPU
{65DC628F-A640-4111-AB35-3A5652BC1E17}.Deploy|Any CPU.Build.0 = Debug|Any CPU
{65DC628F-A640-4111-AB35-3A5652BC1E17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65DC628F-A640-4111-AB35-3A5652BC1E17}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,26 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Desktop.OS.Linux;
using osu.Framework.Desktop.OS.Windows;
using osu.Framework.Framework;
using OpenTK.Graphics;
namespace osu.Framework.Desktop
{
public static class Host
{
public static BasicGameHost GetSuitableHost()
{
BasicGameHost host = null;
GraphicsContextFlags flags = GraphicsContextFlags.Default;
if (RuntimeInfo.IsLinux)
host = new LinuxGameHost(flags);
else
host = new WindowsGameHost(flags);
return host;
}
}
}

View File

@@ -0,0 +1,11 @@
<!--
Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
-->
<configuration>
<dllmap os="linux" dll="bass" target="libbass.x86.so" wordsize="32"/>
<dllmap os="linux" dll="bass_fx" target="libbass_fx.x86.so" wordsize="32"/>
<dllmap os="linux" dll="bass" target="libbass.x64.so" wordsize="64"/>
<dllmap os="linux" dll="bass_fx" target="libbass_fx.x64.so" wordsize="64"/>
</configuration>

View File

@@ -0,0 +1,43 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Drawing;
using osu.Framework.Desktop.OS.Windows;
using osu.Framework.Framework;
using OpenTK.Graphics;
namespace osu.Framework.Desktop.OS
{
public abstract class DesktopGameWindow : BasicGameWindow
{
public override BasicGameForm Form { get; }
public override Rectangle ClientBounds => Form.ClientBounds;
public override bool IsMinimized => Form.IsMinimized;
public override IntPtr Handle => Form.Handle;
internal DesktopGameWindow(GraphicsContextFlags flags)
{
Form = CreateGameForm(flags);
Form.ScreenChanged += delegate { OnScreenDeviceNameChanged(); };
Form.ApplicationActivated += delegate { OnActivated(); };
Form.ApplicationDeactivated += delegate { OnDeactivated(); };
Form.SizeChanged += delegate { OnClientSizeChanged(); };
Form.Closing += delegate { OnDeactivated(); };
Form.Paint += delegate { OnPaint(); };
}
protected abstract BasicGameForm CreateGameForm(GraphicsContextFlags flags);
public override void Close()
{
Form.Close();
}
protected override void SetTitle(string title)
{
Form.Text = title;
}
}
}

View File

@@ -0,0 +1,65 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Drawing;
using System.Windows.Forms;
using osu.Framework.Framework;
using OpenTK.Graphics;
namespace osu.Framework.Desktop.OS.Linux
{
public class LinuxGameForm : BasicGameForm
{
public override bool IsMinimized => ClientSize.Width != 0 || ClientSize.Height == 0;
public override event EventHandler ScreenChanged;
private Screen screen;
internal LinuxGameForm(GraphicsContextFlags flags) : base(flags)
{
SuspendLayout();
CausesValidation = false;
ClientSize = new Size(1, 1);
BackColor = Color.Black;
ResumeLayout(false);
LocationChanged += delegate { updateScreen(); };
ClientSizeChanged += delegate { updateScreen(); };
}
protected override bool ProcessDialogKey(Keys keyData)
{
//stop alt/f10 from freezing form rendering.
if (((keyData & Keys.Alt) == Keys.Alt && (keyData & Keys.F4) != Keys.F4) || (keyData & Keys.F10) == Keys.F10)
return true;
return base.ProcessDialogKey(keyData);
}
public override Rectangle ClientBounds
{
get
{
Point point = PointToScreen(Point.Empty);
return new Rectangle(point.X, point.Y, ClientSize.Width, ClientSize.Height);
}
}
public override void CentreToScreen()
{
CenterToScreen();
}
private void updateScreen()
{
var screen = Screen.FromHandle(Handle);
if ((this.screen == null) || !this.screen.Equals(screen))
{
this.screen = screen;
if (this.screen != null)
ScreenChanged?.Invoke(this, EventArgs.Empty);
}
}
}
}

View File

@@ -0,0 +1,39 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Windows.Forms;
using osu.Framework.Desktop.OS.Windows.Native;
using osu.Framework.Framework;
using OpenTK.Graphics;
namespace osu.Framework.Desktop.OS.Linux
{
public class LinuxGameHost : BasicGameHost
{
public override BasicGameWindow Window => window;
public override GLControl GLControl => window.Form;
public override bool IsActive => true; // TODO LINUX
private LinuxGameWindow window;
internal LinuxGameHost(GraphicsContextFlags flags)
{
window = new LinuxGameWindow(flags);
Window.Activated += OnActivated;
Window.Deactivated += OnDeactivated;
}
protected override void OnActivated(object sender, EventArgs args)
{
Execution.SetThreadExecutionState(Execution.ExecutionState.Continuous | Execution.ExecutionState.SystemRequired | Execution.ExecutionState.DisplayRequired);
base.OnActivated(sender, args);
}
protected override void OnDeactivated(object sender, EventArgs args)
{
base.OnDeactivated(sender, args);
}
}
}

View File

@@ -0,0 +1,21 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Framework;
using OpenTK.Graphics;
namespace osu.Framework.Desktop.OS.Linux
{
public class LinuxGameWindow : DesktopGameWindow
{
public LinuxGameWindow(GraphicsContextFlags flags)
: base(flags)
{
}
protected override BasicGameForm CreateGameForm(GraphicsContextFlags flags)
{
return new LinuxGameForm(flags);
}
}
}

View File

@@ -0,0 +1,25 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Runtime.InteropServices;
namespace osu.Framework.Desktop.OS.Windows.Native
{
internal static class Architecture
{
private static string nativeIncludePath => $@"{Environment.CurrentDirectory}/{arch}/";
private static string arch => Is64Bit ? @"x64" : @"x86";
internal static bool Is64Bit => IntPtr.Size == 8;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetDllDirectory(string lpPathName);
internal static void SetIncludePath()
{
SetDllDirectory(nativeIncludePath);
}
}
}

View File

@@ -0,0 +1,317 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using osu.Framework.Framework;
namespace osu.Framework.Desktop.OS.Windows.Native
{
static class Desktop
{
[DllImport("user32.dll")]
private static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DeviceMode devMode);
[DllImport("user32.dll")]
internal static extern int ChangeDisplaySettingsEx(string deviceName, ref DeviceMode devMode, IntPtr hWnd, int flags, IntPtr lParam);
/// <summary>
/// Prototype required for providing the null pointer as devmode for resolution reset.
/// </summary>
[DllImport("user32.dll")]
internal static extern int ChangeDisplaySettingsEx(string deviceName, IntPtr devMode, IntPtr hWnd, int flags, IntPtr lParam);
[DllImport("user32.dll")]
static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfo monitorInfo);
[DllImport("user32.dll")]
static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
const int MONITOR_DEFAULTTONULL = 0;
const int MONITOR_DEFAULTTOPRIMARY = 1;
const int MONITOR_DEFAULTTONEAREST = 2;
const int ENUM_REGISTRY_SETTINGS = -2;
internal const int CDS_FULLSCREEN = 0x04;
internal const int CDS_TEST = 0x02;
internal const int DISP_CHANGE_FAILED = -1;
internal const int DISP_CHANGE_RESTART = 1;
internal const int DISP_CHANGE_SUCCESSFUL = 0;
internal const int ENUM_CURRENT_SETTINGS = -1;
internal const int DM_BITSPERPEL = 0x00040000;
internal const int DM_PELSWIDTH = 0x00080000;
internal const int DM_PELSHEIGHT = 0x00100000;
internal const int DM_DISPLAYFREQUENCY = 0x00400000;
private static Window window;
internal static void InitializeWindow(Window window)
{
Desktop.window = window;
}
internal static bool ChangeResolution(int width, int height, int? refreshRate = null, bool testOnly = false)
{
MonitorInfo monitorInfo = new MonitorInfo() { Size = 72 };
IntPtr monitor = MonitorFromWindow(Game.Window.Handle, MONITOR_DEFAULTTONEAREST);
GetMonitorInfo(monitor, ref monitorInfo);
DeviceMode dm = new DeviceMode();
dm.dmSize = (short)Marshal.SizeOf(dm);
bool success = false;
if (!EnumDisplaySettings(monitorInfo.DeviceName, ENUM_CURRENT_SETTINGS, ref dm))
return false;
// Are we already on the desired resolution? If yes we don't need to do anything
if (dm.dmPelsWidth == width && dm.dmPelsHeight == height && (!refreshRate.HasValue || refreshRate.Value == dm.dmDisplayFrequency))
return true;
// At this point we are sure we need a custom resolution change.
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
dm.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
if (refreshRate.HasValue)
{
dm.dmDisplayFrequency = refreshRate.Value;
dm.dmFields |= DM_DISPLAYFREQUENCY;
}
success = ChangeDisplaySettingsEx(monitorInfo.DeviceName, ref dm, IntPtr.Zero, CDS_TEST, IntPtr.Zero) == DISP_CHANGE_SUCCESSFUL;
if (testOnly)
return success;
success &= ChangeDisplaySettingsEx(monitorInfo.DeviceName, ref dm, IntPtr.Zero, CDS_FULLSCREEN, IntPtr.Zero) == DISP_CHANGE_SUCCESSFUL;
return success;
}
internal static bool ResetResolution()
{
MonitorInfo monitorInfo = new MonitorInfo() { Size = 72 };
IntPtr monitor = MonitorFromWindow(window.Handle, MONITOR_DEFAULTTONEAREST);
GetMonitorInfo(monitor, ref monitorInfo);
DeviceMode dmCurrent = new DeviceMode();
dmCurrent.dmSize = (short)Marshal.SizeOf(dmCurrent);
DeviceMode dmRegistry = new DeviceMode();
dmRegistry.dmSize = (short)Marshal.SizeOf(dmRegistry);
if (!EnumDisplaySettings(monitorInfo.DeviceName, ENUM_CURRENT_SETTINGS, ref dmCurrent) || !EnumDisplaySettings(monitorInfo.DeviceName, ENUM_REGISTRY_SETTINGS, ref dmRegistry))
return false;
// No need to reset if we already have the settings that we want.
if (dmCurrent.dmPelsWidth == dmRegistry.dmPelsWidth && dmCurrent.dmPelsHeight == dmRegistry.dmPelsHeight && dmCurrent.dmDisplayFrequency == dmRegistry.dmDisplayFrequency)
return true;
return ChangeDisplaySettingsEx(monitorInfo.DeviceName, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero) == DISP_CHANGE_SUCCESSFUL;
}
internal static List<Size> GetResolutions()
{
MonitorInfo monitorInfo = new MonitorInfo() { Size = 72 };
IntPtr monitor = MonitorFromWindow(window.Handle, MONITOR_DEFAULTTONEAREST);
GetMonitorInfo(monitor, ref monitorInfo);
List<Size> results = new List<Size>();
DeviceMode vDevMode = new DeviceMode();
int i = 0;
while (EnumDisplaySettings(monitorInfo.DeviceName, i++, ref vDevMode))
results.Add(new Size(vDevMode.dmPelsWidth, vDevMode.dmPelsHeight));
return results;
}
internal static Size FindNativeResolution()
{
List<Size> resolutions = GetResolutions();
Size native_res = new Size(640, 480);
foreach (Size res in resolutions)
if ((res.Width > native_res.Width) || ((res.Width == native_res.Width) && (res.Height > native_res.Height))) native_res = res;
return native_res;
}
// http://www.dotnetspark.com/kb/1948-change-display-settings-programmatically.aspx
private static DeviceMode? GetCurrentSettings()
{
MonitorInfo monitorInfo = new MonitorInfo() { Size = 72 };
IntPtr monitor = MonitorFromWindow(window.Handle, MONITOR_DEFAULTTONEAREST);
GetMonitorInfo(monitor, ref monitorInfo);
DeviceMode mode = new DeviceMode();
mode.dmSize = (short)Marshal.SizeOf(mode);
if (EnumDisplaySettings(monitorInfo.DeviceName, ENUM_CURRENT_SETTINGS, ref mode) != true)
return null;
return mode;
}
internal static int GetCurrentRefreshRate()
{
DeviceMode? mode = GetCurrentSettings();
if (mode == null) return 0;
return ((DeviceMode)mode).dmDisplayFrequency;
}
internal static Size GetDesktopResolution()
{
MonitorInfo monitorInfo = new MonitorInfo() { Size = 40 };
IntPtr monitor = MonitorFromWindow(window.Handle, MONITOR_DEFAULTTONEAREST);
GetMonitorInfo(monitor, ref monitorInfo);
return new Size(monitorInfo.Monitor.Right - monitorInfo.Monitor.Left, monitorInfo.Monitor.Bottom - monitorInfo.Monitor.Top);
}
internal static Point GetDesktopPosition()
{
MonitorInfo monitorInfo = new MonitorInfo() { Size = 40 };
IntPtr monitor = MonitorFromWindow(window.Handle, MONITOR_DEFAULTTONEAREST);
GetMonitorInfo(monitor, ref monitorInfo);
return new Point(monitorInfo.Monitor.Left, monitorInfo.Monitor.Top);
}
[StructLayout(LayoutKind.Sequential)]
struct MonitorInfo
{
/// <summary>
/// The size, in bytes, of the structure. Set this member to sizeof(MONITORINFO) (40) before calling the GetMonitorInfo function.
/// Doing so lets the function determine the type of structure you are passing to it.
/// </summary>
internal int Size;
/// <summary>
/// A RECT structure that specifies the display monitor rectangle, expressed in virtual-screen coordinates.
/// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
/// </summary>
internal RectStruct Monitor;
/// <summary>
/// A RECT structure that specifies the work area rectangle of the display monitor that can be used by applications,
/// expressed in virtual-screen coordinates. Windows uses this rectangle to maximize an application on the monitor.
/// The rest of the area in rcMonitor contains system windows such as the task bar and side bars.
/// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
/// </summary>
internal RectStruct WorkArea;
/// <summary>
/// The attributes of the display monitor.
///
/// This member can be the following value:
/// 1 : MONITORINFOF_PRIMARY
/// </summary>
internal uint Flags;
/// <summary>
/// The monitor device name.
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal String DeviceName;
internal void Init()
{
this.Size = 72;
}
}
/// <summary>
/// The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle.
/// </summary>
/// <see cref="http://msdn.microsoft.com/en-us/library/dd162897%28VS.85%29.aspx"/>
/// <remarks>
/// By convention, the right and bottom edges of the rectangle are normally considered exclusive.
/// In other words, the pixel whose coordinates are ( right, bottom ) lies immediately outside of the the rectangle.
/// For example, when RECT is passed to the FillRect function, the rectangle is filled up to, but not including,
/// the right column and bottom row of pixels. This structure is identical to the RECTL structure.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct RectStruct
{
/// <summary>
/// The x-coordinate of the upper-left corner of the rectangle.
/// </summary>
internal int Left;
/// <summary>
/// The y-coordinate of the upper-left corner of the rectangle.
/// </summary>
internal int Top;
/// <summary>
/// The x-coordinate of the lower-right corner of the rectangle.
/// </summary>
internal int Right;
/// <summary>
/// The y-coordinate of the lower-right corner of the rectangle.
/// </summary>
internal int Bottom;
}
[StructLayout(LayoutKind.Sequential)]
internal struct DeviceMode
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string dmDeviceName;
internal short dmSpecVersion;
internal short dmDriverVersion;
internal short dmSize;
internal short dmDriverExtra;
internal int dmFields;
internal short dmOrientation;
internal short dmPaperSize;
internal short dmPaperLength;
internal short dmPaperWidth;
internal short dmScale;
internal short dmCopies;
internal short dmDefaultSource;
internal short dmPrintQuality;
internal short dmColor;
internal short dmDuplex;
internal short dmYResolution;
internal short dmTTOption;
internal short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string dmFormName;
internal short dmLogPixels;
internal short dmBitsPerPel;
internal int dmPelsWidth;
internal int dmPelsHeight;
internal int dmDisplayFlags;
internal int dmDisplayFrequency;
internal int dmICMMethod;
internal int dmICMIntent;
internal int dmMediaType;
internal int dmDitherType;
internal int dmReserved1;
internal int dmReserved2;
internal int dmPanningWidth;
internal int dmPanningHeight;
}
}
}

View File

@@ -0,0 +1,24 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Runtime.InteropServices;
namespace osu.Framework.Desktop.OS.Windows.Native
{
static class Execution
{
[DllImport("kernel32.dll")]
internal static extern uint SetThreadExecutionState(ExecutionState state);
[Flags]
internal enum ExecutionState : uint
{
AwaymodeRequired = 0x00000040,
Continuous = 0x80000000,
DisplayRequired = 0x00000002,
SystemRequired = 0x00000001,
UserPresent = 0x00000004,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,131 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.IO;
using System.Security.AccessControl;
using Microsoft.Win32;
namespace osu.Framework.Desktop.OS.Windows.Native
{
public static class Registry
{
public static bool Check(string progId, string executable)
{
try
{
using (RegistryKey key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(progId))
{
if (key == null || key.OpenSubKey(@"shell\open\command").GetValue(string.Empty).ToString() != string.Format("\"{0}\" \"%1\"", executable))
return false;
return true;
}
}
catch
{
}
return false;
}
public static bool Add(string extension, string progId, string description, string executable, bool urlHandler = false)
{
try
{
if (!string.IsNullOrEmpty(extension))
{
if (extension[0] != '.')
extension = "." + extension;
// register the extension, if necessary
using (RegistryKey key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(extension, true))
{
if (key == null)
{
using (RegistryKey extKey = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(extension))
{
extKey.SetValue(string.Empty, progId);
}
}
else
{
key.SetValue(string.Empty, progId);
}
}
}
// register the progId, if necessary
using (RegistryKey key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(progId))
{
if (key == null || key.OpenSubKey("shell\\open\\command").GetValue(string.Empty).ToString() != string.Format("\"{0}\" \"%1\"", executable))
{
using (RegistryKey progIdKey = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(progId))
{
progIdKey.SetValue(string.Empty, description);
if (urlHandler) progIdKey.SetValue("URL Protocol", string.Empty);
using (RegistryKey command = progIdKey.CreateSubKey("shell\\open\\command"))
{
command.SetValue(string.Empty, string.Format("\"{0}\" \"%1\"", executable));
}
using (RegistryKey command = progIdKey.CreateSubKey("DefaultIcon"))
{
command.SetValue(string.Empty, string.Format("\"{0}\",1", executable));
}
}
}
// Add new icon for existing osu! users anyway
else if (key.OpenSubKey("DefaultIcon").GetValue(string.Empty) == null)
{
using (RegistryKey progIdKey = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(progId))
{
using (RegistryKey command = progIdKey.CreateSubKey("DefaultIcon"))
{
command.SetValue(string.Empty, string.Format("\"{0}\",1", executable));
}
}
}
}
}
catch
{
return false;
}
return true;
}
public static void Delete(string progId)
{
try
{
if (null != Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(progId))
Microsoft.Win32.Registry.ClassesRoot.DeleteSubKeyTree(progId);
}
catch
{
}
}
// Adds an ACL entry on the specified directory for the specified account.
public static void AddDirectorySecurity(string FileName, string Account, FileSystemRights Rights,
InheritanceFlags Inheritance, PropagationFlags Propogation,
AccessControlType ControlType)
{
// Create a new DirectoryInfo object.
DirectoryInfo dInfo = new DirectoryInfo(FileName);
// Get a DirectorySecurity object that represents the
// current security settings.
DirectorySecurity dSecurity = dInfo.GetAccessControl();
// Add the FileSystemAccessRule to the security settings.
dSecurity.AddAccessRule(new FileSystemAccessRule(Account,
Rights,
Inheritance,
Propogation,
ControlType));
// Set the new access settings.
dInfo.SetAccessControl(dSecurity);
}
}
}

View File

@@ -0,0 +1,88 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Drawing;
using System.Windows.Forms;
using osu.Framework.Framework;
using OpenTK.Graphics;
namespace osu.Framework.Desktop.OS.Windows
{
public class WindowsGameForm : BasicGameForm
{
public delegate void WndProcDelegate(ref Message m);
public override event EventHandler ScreenChanged;
public event WndProcDelegate OnWndProc;
public override bool IsMinimized => ClientSize.Width != 0 || ClientSize.Height == 0;
private Screen screen;
internal WindowsGameForm(GraphicsContextFlags flags) : base(flags)
{
SuspendLayout();
CausesValidation = false;
ClientSize = new Size(1, 1);
BackColor = Color.Black;
ResumeLayout(false);
LocationChanged += delegate { updateScreen(); };
ClientSizeChanged += delegate { updateScreen(); };
}
protected override bool ProcessDialogKey(Keys keyData)
{
//stop alt/f10 from freezing form rendering.
if (((keyData & Keys.Alt) == Keys.Alt && (keyData & Keys.F4) != Keys.F4) || (keyData & Keys.F10) == Keys.F10)
return true;
return base.ProcessDialogKey(keyData);
}
public override Rectangle ClientBounds
{
get
{
Point point = PointToScreen(Point.Empty);
return new Rectangle(point.X, point.Y, ClientSize.Width, ClientSize.Height);
}
}
public override void CentreToScreen()
{
CenterToScreen();
}
private void updateScreen()
{
Screen screen = Screen.FromHandle(Handle);
if ((this.screen == null) || !this.screen.Equals(screen))
{
this.screen = screen;
if (this.screen != null)
ScreenChanged?.Invoke(this, EventArgs.Empty);
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0x1c)
{
bool active = m.WParam != IntPtr.Zero;
OnActivateApp(active);
}
OnWndProc?.Invoke(ref m);
if (m.Result.ToInt32() < 0)
{
m.Result = new IntPtr(-m.Result.ToInt32() - 1);
return;
}
base.WndProc(ref m);
}
}
}

View File

@@ -0,0 +1,119 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows.Forms;
using osu.Framework.Desktop.OS.Windows.Native;
using osu.Framework.Framework;
using OpenTK.Graphics;
namespace osu.Framework.Desktop.OS.Windows
{
public class WindowsGameHost : BasicGameHost
{
public override BasicGameWindow Window => window;
public override GLControl GLControl => window.Form;
public override bool IsActive => Window != null && GetForegroundWindow().Equals(Window.Handle);
private WindowsGameWindow window;
internal WindowsGameHost(GraphicsContextFlags flags)
{
Architecture.SetIncludePath();
window = new WindowsGameWindow(flags);
Application.EnableVisualStyles();
Window.Activated += OnActivated;
Window.Deactivated += OnDeactivated;
}
protected override void OnActivated(object sender, EventArgs args)
{
Execution.SetThreadExecutionState(Execution.ExecutionState.Continuous | Execution.ExecutionState.SystemRequired | Execution.ExecutionState.DisplayRequired);
base.OnActivated(sender, args);
}
protected override void OnDeactivated(object sender, EventArgs args)
{
Execution.SetThreadExecutionState(Execution.ExecutionState.Continuous);
base.OnDeactivated(sender, args);
}
protected override void OnApplicationIdle(object sender, EventArgs e)
{
MSG message;
while (!PeekMessage(out message, IntPtr.Zero, 0, 0, 0))
base.OnApplicationIdle(sender, e);
}
public override void Run()
{
OpenTK.NativeWindow.OsuWindowHandle = window.Handle;
base.Run();
}
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[return: MarshalAs(UnmanagedType.Bool)]
[SuppressUnmanagedCodeSecurity, DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool PeekMessage(out MSG msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax,
uint flags);
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hWnd;
public WindowMessage msg;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public System.Drawing.Point p;
}
public enum WindowMessage : uint
{
ActivateApplication = 0x1c,
Character = 0x102,
Close = 0x10,
Destroy = 2,
EnterMenuLoop = 0x211,
EnterSizeMove = 0x231,
ExitMenuLoop = 530,
ExitSizeMove = 0x232,
GetMinMax = 0x24,
KeyDown = 0x100,
KeyUp = 0x101,
LeftButtonDoubleClick = 0x203,
LeftButtonDown = 0x201,
LeftButtonUp = 0x202,
MiddleButtonDoubleClick = 0x209,
MiddleButtonDown = 0x207,
MiddleButtonUp = 520,
MouseFirst = 0x201,
MouseLast = 0x20d,
MouseMove = 0x200,
MouseWheel = 0x20a,
NonClientHitTest = 0x84,
Paint = 15,
PowerBroadcast = 0x218,
Quit = 0x12,
RightButtonDoubleClick = 0x206,
RightButtonDown = 0x204,
RightButtonUp = 0x205,
SetCursor = 0x20,
Size = 5,
SystemCharacter = 0x106,
SystemCommand = 0x112,
SystemKeyDown = 260,
SystemKeyUp = 0x105,
XButtonDoubleClick = 0x20d,
XButtonDown = 0x20b,
XButtonUp = 0x20c
}
}
}

View File

@@ -0,0 +1,21 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Framework;
using OpenTK.Graphics;
namespace osu.Framework.Desktop.OS.Windows
{
public class WindowsGameWindow : DesktopGameWindow
{
public WindowsGameWindow(GraphicsContextFlags flags)
: base(flags)
{
}
protected override BasicGameForm CreateGameForm(GraphicsContextFlags flags)
{
return new WindowsGameForm(flags);
}
}
}

View File

@@ -0,0 +1,30 @@
<!--
Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
-->
<configuration>
<dllmap os="linux" dll="opengl32.dll" target="libGL.so.1"/>
<dllmap os="linux" dll="glu32.dll" target="libGLU.so.1"/>
<dllmap os="linux" dll="openal32.dll" target="libopenal.so.1"/>
<dllmap os="linux" dll="alut.dll" target="libalut.so.0"/>
<dllmap os="linux" dll="opencl.dll" target="libOpenCL.so"/>
<dllmap os="linux" dll="libX11" target="libX11.so.6"/>
<dllmap os="linux" dll="libXi" target="libXi.so.6"/>
<dllmap os="linux" dll="SDL2.dll" target="libSDL2-2.0.so.0"/>
<dllmap os="osx" dll="opengl32.dll" target="/System/Library/Frameworks/OpenGL.framework/OpenGL"/>
<dllmap os="osx" dll="openal32.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
<dllmap os="osx" dll="alut.dll" target="/System/Library/Frameworks/OpenAL.framework/OpenAL" />
<dllmap os="osx" dll="libGLES.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="libGLESv1_CM.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="libGLESv2.dll" target="/System/Library/Frameworks/OpenGLES.framework/OpenGLES" />
<dllmap os="osx" dll="opencl.dll" target="/System/Library/Frameworks/OpenCL.framework/OpenCL"/>
<dllmap os="osx" dll="SDL2.dll" target="libSDL2.dylib"/>
<!-- XQuartz compatibility (X11 on Mac) -->
<dllmap os="osx" dll="libGL.so.1" target="/usr/X11/lib/libGL.dylib"/>
<dllmap os="osx" dll="libX11" target="/usr/X11/lib/libX11.dylib"/>
<dllmap os="osx" dll="libXcursor.so.1" target="/usr/X11/lib/libXcursor.dylib"/>
<dllmap os="osx" dll="libXi" target="/usr/X11/lib/libXi.dylib"/>
<dllmap os="osx" dll="libXinerama" target="/usr/X11/lib/libXinerama.dylib"/>
<dllmap os="osx" dll="libXrandr.so.2" target="/usr/X11/lib/libXrandr.dylib"/>
</configuration>

View File

@@ -0,0 +1,39 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("osu.Framework.Desktop")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("osu.Framework.Desktop")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("65dc628f-a640-4111-ab35-3a5652bc1e17")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{65DC628F-A640-4111-AB35-3A5652BC1E17}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>osu.Framework.Desktop</RootNamespace>
<AssemblyName>osu.Framework.Desktop</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>..\packages\ppy.OpenTK.1.1.2225.2\lib\net20\OpenTK.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="OpenTK.GLControl, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>..\packages\ppy.OpenTK.GLControl.1.1.2225.0\lib\net40\OpenTK.GLControl.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="Host.cs" />
<Compile Include="OS\DesktopGameWindow.cs" />
<Compile Include="OS\Linux\LinuxGameForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="OS\Linux\LinuxGameHost.cs" />
<Compile Include="OS\Linux\LinuxGameWindow.cs" />
<Compile Include="OS\Windows\Native\Architecture.cs" />
<Compile Include="OS\Windows\Native\Desktop.cs" />
<Compile Include="OS\Windows\Native\Execution.cs" />
<Compile Include="OS\Windows\Native\Input.cs" />
<Compile Include="OS\Windows\Native\Registry.cs" />
<Compile Include="OS\Windows\WindowsGameForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="OS\Windows\WindowsGameHost.cs" />
<Compile Include="OS\Windows\WindowsGameWindow.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Framework\osu.Framework.csproj">
<Project>{c76bf5b3-985e-4d39-95fe-97c9c879b83a}</Project>
<Name>osu.Framework</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="..\osu-framework.licenseheader">
<Link>osu-framework.licenseheader</Link>
</None>
<None Include="libbass.x64.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="libbass.x86.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="libbass_fx.x64.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="libbass_fx.x86.so">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="ManagedBass.PInvoke.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="OpenTK.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="x64\avcodec-51.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\avformat-52.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\avutil-49.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\bass.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\bass_fx.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\d3dcompiler_47.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\libEGL.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\libGLESv2.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x64\pthreadGC2.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\avcodec-51.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\avformat-52.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\avutil-49.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\bass.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\bass_fx.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\d3dcompiler_47.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\libEGL.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\libGLESv2.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="x86\pthreadGC2.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
-->
<packages>
<package id="ppy.OpenTK" version="1.1.2225.2" targetFramework="net452" />
<package id="ppy.OpenTK.GLControl" version="1.1.2225.0" targetFramework="net452" />
</packages>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,155 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Cached;
using osu.Framework.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Framework.Audio
{
public class AdjustableAudioComponent : IDisposable, IUpdateable
{
private List<BindableDouble> volumeAdjustments = new List<BindableDouble>();
private List<BindableDouble> balanceAdjustments = new List<BindableDouble>();
private List<BindableDouble> frequencyAdjustments = new List<BindableDouble>();
/// <summary>
/// Global volume of this component.
/// </summary>
public readonly BindableDouble Volume = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
protected readonly BindableDouble VolumeCalculated = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
/// <summary>
/// Playback balance of this sample (-1 .. 1 where 0 is centered)
/// </summary>
public readonly BindableDouble Balance = new BindableDouble(0) { MinValue = -1, MaxValue = 1 };
protected readonly BindableDouble BalanceCalculated = new BindableDouble(0) { MinValue = -1, MaxValue = 1 };
/// <summary>
/// Rate at which the component is played back (affects pitch). 1 is 100% playback speed, or default frequency.
/// </summary>
public readonly BindableDouble Frequency = new BindableDouble(1);
protected readonly BindableDouble FrequencyCalculated = new BindableDouble(1);
/// <summary>
/// Handles invalidation of the component's state.
/// </summary>
private Cached<double> componentState = new Cached<double>();
protected AdjustableAudioComponent()
{
Volume.ValueChanged += InvalidateState;
Balance.ValueChanged += InvalidateState;
Frequency.ValueChanged += InvalidateState;
}
protected void InvalidateState(object sender = null, EventArgs e = null)
{
componentState.Invalidate();
}
protected virtual void OnStateChanged(object sender, EventArgs e)
{
VolumeCalculated.Value = volumeAdjustments.Aggregate(Volume.Value, (current, adj) => current * adj);
BalanceCalculated.Value = balanceAdjustments.Aggregate(Balance.Value, (current, adj) => current + adj);
FrequencyCalculated.Value = frequencyAdjustments.Aggregate(Frequency.Value, (current, adj) => current * adj);
}
public void AddAdjustmentDependency(AdjustableAudioComponent component)
{
AddAdjustment(AdjustableProperty.Balance, component.BalanceCalculated);
AddAdjustment(AdjustableProperty.Frequency, component.FrequencyCalculated);
AddAdjustment(AdjustableProperty.Volume, component.VolumeCalculated);
}
public void RemoveAdjustmentDependency(AdjustableAudioComponent component)
{
RemoveAdjustment(component.BalanceCalculated);
RemoveAdjustment(component.FrequencyCalculated);
RemoveAdjustment(component.VolumeCalculated);
}
public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
{
switch (type)
{
case AdjustableProperty.Balance:
balanceAdjustments.Add(adjustBindable);
break;
case AdjustableProperty.Frequency:
frequencyAdjustments.Add(adjustBindable);
break;
case AdjustableProperty.Volume:
volumeAdjustments.Add(adjustBindable);
break;
}
adjustBindable.ValueChanged += InvalidateState;
InvalidateState();
}
public void RemoveAdjustment(BindableDouble adjustBindable)
{
balanceAdjustments.Remove(adjustBindable);
frequencyAdjustments.Remove(adjustBindable);
volumeAdjustments.Remove(adjustBindable);
adjustBindable.ValueChanged -= InvalidateState;
InvalidateState();
}
public virtual void Update()
{
componentState.Refresh(delegate
{
OnStateChanged(this, null);
return 1;
});
}
#region IDisposable Support
protected bool IsDisposed = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!IsDisposed)
{
if (disposing)
{
volumeAdjustments.ForEach(d => d.ValueChanged -= InvalidateState);
balanceAdjustments.ForEach(d => d.ValueChanged -= InvalidateState);
frequencyAdjustments.ForEach(d => d.ValueChanged -= InvalidateState);
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
IsDisposed = true;
}
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
Dispose(true);
}
#endregion
}
public enum AdjustableProperty
{
Volume,
Balance,
Frequency
}
}

View File

@@ -0,0 +1,40 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Framework.Configuration;
namespace osu.Framework.Audio
{
/// <summary>
/// A collection of audio components which need central property control.
/// </summary>
public class AudioCollectionManager<T> : AdjustableAudioComponent
where T : AdjustableAudioComponent
{
List<T> ActiveItems = new List<T>();
protected void AddItem(T item)
{
item.AddAdjustmentDependency(this);
ActiveItems.Add(item);
}
public override void Update()
{
base.Update();
ActiveItems.ForEach(s => s.Update());
ActiveItems.FindAll(s => (s as IHasCompletedState)?.HasCompleted ?? false).ForEach(s =>
{
s.Dispose();
ActiveItems.Remove(s);
});
}
}
}

View File

@@ -0,0 +1,242 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Collections.Generic;
using System.IO;
using ManagedBass;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Resources;
using osu.Framework.Threading;
namespace osu.Framework.Audio
{
public class AudioManager : AudioCollectionManager<AdjustableAudioComponent>
{
public TrackManager Track => GetTrackManager();
public SampleManager Sample => GetSampleManager();
internal event VoidDelegate AvailableDevicesChanged;
internal List<DeviceInfo> AudioDevices = new List<DeviceInfo>();
internal string CurrentAudioDevice;
private string lastPreferredDevice;
/// <summary>
/// Volume of all samples played game-wide.
/// </summary>
public readonly BindableDouble VolumeSample = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
/// <summary>
/// Volume of all tracks played game-wide.
/// </summary>
public readonly BindableDouble VolumeTrack = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
private Scheduler scheduler = new Scheduler();
public AudioManager(IResourceStore<byte[]> trackStore, IResourceStore<byte[]> sampleStore)
{
globalTrackManager = GetTrackManager(trackStore);
globalSampleManager = GetSampleManager(sampleStore);
SetAudioDevice();
scheduler.AddDelayed(checkAudioDeviceChanged, 1000, true);
}
private TrackManager globalTrackManager;
private SampleManager globalSampleManager;
public TrackManager GetTrackManager(IResourceStore<byte[]> store = null)
{
if (store == null) return globalTrackManager;
TrackManager tm = new TrackManager(store);
AddItem(tm);
tm.AddAdjustment(AdjustableProperty.Volume, VolumeTrack);
return tm;
}
public SampleManager GetSampleManager(IResourceStore<byte[]> store = null)
{
if (store == null) return globalSampleManager;
SampleManager sm = new SampleManager(store);
AddItem(sm);
sm.AddAdjustment(AdjustableProperty.Volume, VolumeSample);
return sm;
}
internal bool CheckAudioDevice()
{
if (CurrentAudioDevice != null)
return true;
//NotificationManager.ShowMessage("No compatible audio device detected. You must plug in a valid audio device in order to play osu!", Color4.Red, 4000);
return false;
}
private List<DeviceInfo> getAllDevices()
{
int deviceCount = Bass.DeviceCount;
List<DeviceInfo> info = new List<DeviceInfo>();
for (int i = 0; i < deviceCount; i++)
info.Add(Bass.GetDeviceInfo(i));
return info;
}
public bool SetAudioDevice(string preferredDevice = null)
{
lastPreferredDevice = preferredDevice;
AudioDevices = new List<DeviceInfo>(getAllDevices());
AvailableDevicesChanged?.Invoke();
string oldDevice = CurrentAudioDevice;
string newDevice = preferredDevice;
if (string.IsNullOrEmpty(newDevice))
newDevice = AudioDevices.Find(df => df.IsDefault).Name;
bool oldDeviceValid = Bass.CurrentDevice >= 0;
if (oldDeviceValid)
{
DeviceInfo oldDeviceInfo = Bass.GetDeviceInfo(Bass.CurrentDevice);
oldDeviceValid &= oldDeviceInfo.IsEnabled && oldDeviceInfo.IsInitialized;
}
if (newDevice == oldDevice)
{
//check the old device is still valid
if (oldDeviceValid)
return true;
}
if (string.IsNullOrEmpty(newDevice))
return false;
int newDeviceIndex = AudioDevices.FindIndex(df => df.Name == newDevice);
DeviceInfo newDeviceInfo = new DeviceInfo();
try
{
if (newDeviceIndex >= 0)
newDeviceInfo = Bass.GetDeviceInfo(newDeviceIndex);
//we may have previously initialised this device.
}
catch
{
}
if (oldDeviceValid && (newDeviceInfo.Driver == null || !newDeviceInfo.IsEnabled))
{
//handles the case we are trying to load a user setting which is currently unavailable,
//and we have already fallen back to a sane default.
return true;
}
if (newDevice != null && oldDevice != null)
{
//we are preparing to load a new device, so let's clean up any existing device.
clearAllCaches();
Bass.Free();
}
if (!Bass.Init(newDeviceIndex, 44100, 0, Game.Window.Handle))
{
//the new device didn't go as planned. we need another option.
if (preferredDevice == null)
{
//we're fucked. the default device won't initialise.
CurrentAudioDevice = null;
return false;
}
//let's try again using the default device.
return SetAudioDevice();
}
//we have successfully initialised a new device.
CurrentAudioDevice = newDevice;
Bass.PlaybackBufferLength = 100;
Bass.UpdatePeriod = 5;
return true;
}
private void clearAllCaches()
{
}
private int lastDeviceCount;
private void checkAudioDeviceChanged()
{
bool useDefault = string.IsNullOrEmpty(lastPreferredDevice);
if (useDefault)
{
int currentDevice = Bass.CurrentDevice;
try
{
DeviceInfo device = Bass.GetDeviceInfo(currentDevice);
if (device.IsDefault && device.IsEnabled)
return; //early return when nothing has changed.
}
catch
{
return;
}
}
int availableDevices = 0;
foreach (DeviceInfo device in getAllDevices())
{
if (device.Driver == null) continue;
bool isCurrentDevice = device.Name == CurrentAudioDevice;
if (device.IsEnabled)
{
if (isCurrentDevice && !device.IsDefault && useDefault)
//the default device on windows has changed, so we need to update.
SetAudioDevice();
availableDevices++;
}
else if (isCurrentDevice)
SetAudioDevice(lastPreferredDevice);
//the active device has been disabled.
}
if (lastDeviceCount != availableDevices && lastDeviceCount > 0)
{
SetAudioDevice(lastPreferredDevice);
//just update the available devices.
//if (availableDevices > lastDeviceCount)
//NotificationManager.ShowMessage(LocalisationManager.GetString(OsuString.AudioEngine_NewDeviceDetected), Color4.YellowGreen, 5000);
}
lastDeviceCount = availableDevices;
}
public override void Update()
{
base.Update();
scheduler.Update();
}
}
}

View File

@@ -0,0 +1,19 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Framework.Audio
{
public interface IHasCompletedState
{
/// <summary>
/// Becomes true when we are out and done with this object (and pending clean-up).
/// </summary>
bool HasCompleted { get; }
}
}

View File

@@ -0,0 +1,41 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Audio.Sample
{
public abstract class AudioSample : AdjustableAudioComponent, IHasCompletedState, IUpdateable
{
protected bool WasStarted;
/// <summary>
/// Makes this sample fire-and-forget (will be cleaned up automatically).
/// </summary>
public bool OneShot;
public virtual void Play(bool restart = true)
{
WasStarted = true;
}
public virtual void Stop()
{
}
protected override void Dispose(bool disposing)
{
Stop();
base.Dispose(disposing);
}
public abstract bool Playing { get; }
public virtual bool Played => WasStarted && !Playing;
public bool HasCompleted => Played && (OneShot || IsDisposed);
public virtual void Pause()
{
if (!Playing) return;
}
}
}

View File

@@ -0,0 +1,104 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using ManagedBass;
using System;
namespace osu.Framework.Audio.Sample
{
class AudioSampleBass : AudioSample
{
private int channel;
bool hasChannel => channel != 0;
bool hasSample => sample != 0;
private int sample;
float initialFrequency;
private bool freeWhenDone;
public AudioSampleBass(byte[] data) : this(Bass.SampleLoad(data, 0, data.Length, 8, BassFlags.Default))
{
}
protected AudioSampleBass(int sampleId, bool freeWhenDone = false)
{
sample = sampleId;
this.freeWhenDone = freeWhenDone;
}
private int ensureChannel()
{
if (!hasSample) return 0;
if (!hasChannel)
{
InvalidateState();
channel = Bass.SampleGetChannel(sample);
Bass.ChannelGetAttribute(channel, ChannelAttribute.Frequency, out initialFrequency);
Update();
}
return channel;
}
void resetChannel()
{
channel = 0;
}
protected override void OnStateChanged(object sender, EventArgs e)
{
base.OnStateChanged(sender, e);
if (hasChannel)
{
Bass.ChannelSetAttribute(channel, ChannelAttribute.Volume, VolumeCalculated);
Bass.ChannelSetAttribute(channel, ChannelAttribute.Pan, BalanceCalculated);
Bass.ChannelSetAttribute(channel, ChannelAttribute.Frequency, initialFrequency * FrequencyCalculated);
}
}
public override void Play(bool restart = true)
{
if (!hasSample)
return;
base.Play();
Bass.ChannelPlay(ensureChannel(), restart);
}
public override void Stop()
{
if (!hasChannel) return;
base.Stop();
Bass.ChannelStop(channel);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (freeWhenDone)
{
Bass.SampleFree(sample);
sample = 0;
}
}
public override void Pause()
{
if (!hasChannel) return;
base.Pause();
Bass.ChannelPause(channel);
}
public override bool Playing => hasChannel && Bass.ChannelIsActive(channel) != 0;
}
}

View File

@@ -0,0 +1,26 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Resources;
namespace osu.Framework.Audio.Sample
{
public class SampleManager : AudioCollectionManager<AudioSample>
{
IResourceStore<byte[]> store;
public SampleManager(IResourceStore<byte[]> store)
{
this.store = store;
}
public AudioSample GetSample(string name)
{
byte[] data = store.Get(name);
AudioSample sample = new AudioSampleBass(data);
AddItem(sample);
return sample;
}
}
}

View File

@@ -0,0 +1,75 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Timing;
namespace osu.Framework.Audio.Track
{
public abstract class AudioTrack : AdjustableAudioComponent, IAdjustableClock, IHasCompletedState, IUpdateable
{
/// <summary>
/// Is this track capable of producing audio?
/// </summary>
public virtual bool IsDummyDevice => true;
/// <summary>
/// The speed of track playback. Does not affect pitch, but will reduce playback quality due to skipped frames.
/// </summary>
public readonly BindableDouble Tempo = new BindableDouble(1);
public AudioTrack()
{
Tempo.ValueChanged += InvalidateState;
}
/// <summary>
/// Reset this track to a logical default state.
/// </summary>
public virtual void Reset()
{
Frequency.Value = 1;
Stop();
Seek(0);
}
/// <summary>
/// Current position in milliseconds.
/// </summary>
public abstract double CurrentTime { get; }
/// <summary>
/// Lenth of the track in milliseconds.
/// </summary>
public double Length { get; protected set; }
public virtual int? Bitrate => null;
/// <summary>
/// Seek to a new position.
/// </summary>
/// <param name="seek">New position in milliseconds</param>
/// <returns>Whether the seek was successful.</returns>
public abstract bool Seek(double seek);
public abstract void Start();
public abstract void Stop();
public abstract bool IsRunning { get; }
/// <summary>
/// Overall playback rate (1 is 100%, -1 is reversed at 100%).
/// </summary>
public virtual double Rate => Frequency * Tempo;
public bool IsReversed => Rate < 0;
/// <summary>
/// todo: implement
/// </summary>
public bool HasCompleted => IsDisposed;
}
}

View File

@@ -0,0 +1,253 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.IO;
using System.Runtime.InteropServices;
using ManagedBass;
using ManagedBass.Fx;
using osu.Framework.Configuration;
using osu.Framework.IO;
using OpenTK;
namespace osu.Framework.Audio.Track
{
public class AudioTrackBass : AudioTrack
{
private float initialFrequency;
private int audioStreamPrefilter;
private AsyncBufferStream dataStream;
public bool Looping { get; private set; }
/// <summary>
/// Should this track only be used for preview purposes? This suggests it has not yet been fully loaded.
/// </summary>
public bool Preview { get; private set; }
/// <summary>
/// The handle for this track, if there is one.
/// </summary>
private int activeStream;
//must keep a reference to this else it will be garbage collected early.
private DataStreamFileProcedures procs;
public AudioTrackBass(Stream data, bool quick = false, bool loop = false)
{
Preview = quick;
Looping = loop;
BassFlags flags = Preview ? 0 : (BassFlags.Decode | BassFlags.Prescan);
if (data == null)
throw new Exception(@"Data couldn't be loaded!");
else
{
//encapsulate incoming stream with async buffer if it isn't already.
dataStream = data as AsyncBufferStream;
if (dataStream == null) dataStream = new AsyncBufferStream(data, quick ? 8 : -1);
procs = new DataStreamFileProcedures(dataStream);
audioStreamPrefilter = Bass.CreateStream(StreamSystem.NoBuffer, flags, procs.BassProcedures, IntPtr.Zero);
}
if (Preview)
activeStream = audioStreamPrefilter;
else
{
activeStream = BassFx.TempoCreate(audioStreamPrefilter, BassFlags.Decode);
activeStream = BassFx.ReverseCreate(activeStream, 5f, BassFlags.Default);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoUseQuickAlgorithm, 1);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoOverlapMilliseconds, 4);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.TempoSequenceMilliseconds, 30);
}
Length = (Bass.ChannelBytes2Seconds(activeStream, Bass.ChannelGetLength(activeStream)) * 1000);
Bass.ChannelGetAttribute(activeStream, ChannelAttribute.Frequency, out initialFrequency);
}
public override void Reset()
{
Stop();
Seek(0);
Volume.Value = 1;
base.Reset();
}
protected override void Dispose(bool disposing)
{
if (activeStream != 0) Bass.ChannelStop(activeStream);
if (audioStreamPrefilter != 0) Bass.StreamFree(audioStreamPrefilter);
activeStream = 0;
audioStreamPrefilter = 0;
dataStream?.Dispose();
dataStream = null;
base.Dispose(disposing);
}
public override bool IsDummyDevice => false;
public override void Stop()
{
if (IsRunning)
togglePause();
}
private bool togglePause()
{
//if (IsDisposed) return false;
if (PlaybackState.Playing == Bass.ChannelIsActive(activeStream))
{
Bass.ChannelPause(activeStream);
return true;
}
else
{
Bass.ChannelPlay(activeStream, false);
return false;
}
}
int direction = 0;
private void setDirection(bool reverse)
{
int newDirection = reverse ? -1 : 1;
if (direction == newDirection) return;
direction = newDirection;
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.ReverseDirection, direction);
}
public override void Start()
{
Update();
Bass.ChannelPlay(activeStream);
}
public override bool Seek(double seek)
{
double clamped = MathHelper.Clamp(seek, 0, Length);
if (clamped != CurrentTime)
{
long pos = Bass.ChannelSeconds2Bytes(activeStream, clamped / 1000d);
Bass.ChannelSetPosition(activeStream, pos);
}
return clamped == seek;
}
public override double CurrentTime => Bass.ChannelBytes2Seconds(activeStream, Bass.ChannelGetPosition(activeStream)) * 1000;
public override bool IsRunning => Bass.ChannelIsActive(activeStream) == PlaybackState.Playing;
protected override void OnStateChanged(object sender, EventArgs e)
{
base.OnStateChanged(sender, e);
setDirection(FrequencyCalculated.Value < 0);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Volume, VolumeCalculated);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Pan, BalanceCalculated);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Frequency, bassFreq);
Bass.ChannelSetAttribute(activeStream, ChannelAttribute.Tempo, (Math.Abs(Tempo) - 1) * 100);
}
int bassFreq => (int)MathHelper.Clamp(Math.Abs(initialFrequency * FrequencyCalculated), 100, 100000);
public override double Rate => bassFreq / initialFrequency * Tempo * direction;
public override int? Bitrate => (int)Bass.ChannelGetAttribute(activeStream, ChannelAttribute.Bitrate);
private class DataStreamFileProcedures
{
private byte[] readBuffer = new byte[32768];
private AsyncBufferStream dataStream;
public FileProcedures BassProcedures => new FileProcedures()
{
Close = ac_Close,
Length = ac_Length,
Read = ac_Read,
Seek = ac_Seek
};
public DataStreamFileProcedures(AsyncBufferStream data)
{
dataStream = data;
}
void ac_Close(IntPtr user)
{
//manually handle closing of stream
}
long ac_Length(IntPtr user)
{
if (dataStream == null) return 0;
try
{
return dataStream.Length;
}
catch
{
}
return 0;
}
int ac_Read(IntPtr buffer, int length, IntPtr user)
{
if (dataStream == null) return 0;
try
{
if (length > readBuffer.Length)
readBuffer = new byte[length];
if (!dataStream.CanRead)
return 0;
int readBytes = dataStream.Read(readBuffer, 0, length);
Marshal.Copy(readBuffer, 0, buffer, readBytes);
return readBytes;
}
catch
{
}
return 0;
}
bool ac_Seek(long offset, IntPtr user)
{
if (dataStream == null) return false;
try
{
return dataStream.Seek(offset, SeekOrigin.Begin) == offset;
}
catch
{
}
return false;
}
}
}
}

View File

@@ -0,0 +1,55 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Timing;
namespace osu.Framework.Audio.Track
{
class AudioTrackVirtual : AudioTrack
{
StopwatchClock clock = new StopwatchClock();
double seekOffset;
public override bool Seek(double seek)
{
double current = CurrentTime;
seekOffset = seek;
clock.Restart();
if (Length > 0 && seekOffset > Length)
seekOffset = Length;
return current != seekOffset;
}
public override void Start()
{
clock.Start();
}
public override void Reset()
{
clock.Reset();
seekOffset = 0;
base.Reset();
}
public override void Stop()
{
clock.Stop();
}
public override bool IsRunning => clock.IsRunning;
public override double CurrentTime => seekOffset + clock.CurrentTime;
public override void Update()
{
if (Length > 0 && CurrentTime >= Length)
Stop();
}
}
}

View File

@@ -0,0 +1,32 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ManagedBass;
using osu.Framework.Audio.Sample;
using osu.Framework.Resources;
namespace osu.Framework.Audio.Track
{
public class TrackManager : AudioCollectionManager<AudioTrack>
{
IResourceStore<byte[]> store;
public TrackManager(IResourceStore<byte[]> store)
{
this.store = store;
}
public AudioTrack GetTrack(string name)
{
AudioTrackBass track = new AudioTrackBass(store.GetStream(name));
AddItem(track);
return track;
}
}
}

View File

@@ -0,0 +1,99 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using osu.Framework.Timing;
namespace osu.Framework.Cached
{
public class Cached<T>
{
public delegate T PropertyUpdater<T>();
/// <summary>
/// How often this property is refreshed.
/// </summary>
public readonly int RefreshInterval;
/// <summary>
/// Whether we allow reads of stale values. If this is set to false, there may be a potential blocking delay when accessing the property.
/// </summary>
public bool AllowStaleReads = true;
private bool isStale => lastUpdateTime < 0 || (RefreshInterval >= 0 && clock?.CurrentTime > lastUpdateTime + RefreshInterval);
public bool IsValid => !isStale;
private PropertyUpdater<T> updateDelegate;
private readonly IClock clock;
private double lastUpdateTime = -1;
/// <summary>
/// Create a new cached property.
/// </summary>
/// <param name="updateDelegate">The delegate method which will perform future updates to this property.</param>
/// <param name="refreshInterval">How often we should refresh this property. Set to -1 to never update. Set to 0 for once per frame.</param>
public Cached(PropertyUpdater<T> updateDelegate = null, IClock clock = null, int refreshInterval = -1)
{
RefreshInterval = refreshInterval;
this.updateDelegate = updateDelegate;
this.clock = clock;
}
public static implicit operator T(Cached<T> value)
{
return value.Value;
}
/// <summary>
/// Refresh this cached object with a custom delegate.
/// </summary>
/// <param name="providedDelegate"></param>
public T Refresh(PropertyUpdater<T> providedDelegate)
{
if (isStale)
{
updateDelegate = updateDelegate ?? providedDelegate;
Refresh();
}
return value;
}
/// <summary>
/// Refresh this property.
/// </summary>
public void Refresh()
{
if (updateDelegate == null)
throw new Exception("No value cached and no update delegate prepared!");
value = updateDelegate();
lastUpdateTime = clock?.CurrentTime ?? 0;
}
public bool Invalidate()
{
if (lastUpdateTime < 0) return false;
lastUpdateTime = -1;
return true;
}
private T value;
public T Value
{
get
{
if (isStale)
Refresh();
return value;
}
set
{
throw new Exception("Can't manually update value!");
}
}
}
}

View File

@@ -0,0 +1,97 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Configuration
{
public class Bindable<T> : IBindable
where T : IComparable
{
private T value;
public T Default;
public virtual bool IsDefault => Equals(value, Default);
public event EventHandler ValueChanged;
public virtual T Value
{
get { return value; }
set
{
if (this.value?.CompareTo(value) == 0) return;
this.value = value;
TriggerChange();
}
}
public Bindable(T value)
{
Value = value;
}
public static implicit operator T(Bindable<T> value)
{
return value.Value;
}
public object ObjectValue
{
get
{
return Value;
}
set
{
try
{
Value = (T)value;
}
catch { }
}
}
public virtual bool Parse(object s)
{
if (s is T)
Value = (T)s;
else if (typeof(T).IsEnum)
Value = (T)Enum.Parse(typeof(T), s as string);
else
return false;
return true;
}
internal void TriggerChange()
{
if (ValueChanged != null) ValueChanged(this, null);
}
public void UnbindAll()
{
ValueChanged = null;
}
string description;
public string Description
{
get { return description; }
set { description = value; }
}
public override string ToString()
{
return value.ToString();
}
internal void Reset()
{
Value = Default;
}
}
}

View File

@@ -0,0 +1,36 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Configuration
{
public class BindableBool : Bindable<bool>
{
public BindableBool(bool value = false) : base(value)
{
}
public static implicit operator bool(BindableBool value)
{
return value == null ? false : value.Value;
}
public override string ToString()
{
return Value ? @"true" : @"false";
}
public override bool Parse(object s)
{
string str = s as string;
Value = str == @"1" || str == @"true";
return true;
}
public void Toggle()
{
Value = !Value;
}
}
}

View File

@@ -0,0 +1,57 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Globalization;
namespace osu.Framework.Configuration
{
public class BindableDouble : Bindable<double>
{
internal double MinValue = double.MinValue;
internal double MaxValue = double.MaxValue;
public override double Value
{
get { return base.Value; }
set
{
double boundValue = value;
if (boundValue > MaxValue)
boundValue = MaxValue;
else if (boundValue < MinValue)
boundValue = MinValue;
if (Precision > double.Epsilon)
boundValue = Math.Round(boundValue / Precision) * Precision;
base.Value = boundValue;
}
}
public BindableDouble(double value = 0) : base(value)
{
}
public static implicit operator double(BindableDouble value)
{
return value == null ? 0 : value.Value;
}
public override string ToString()
{
return Value.ToString("0.0###", NumberFormatInfo.InvariantInfo);
}
public override bool Parse(object s)
{
Value = double.Parse(s as string, NumberFormatInfo.InvariantInfo);
return true;
}
public double Precision = double.Epsilon;
public override bool IsDefault => Math.Abs(Value - Default) < Precision;
}
}

View File

@@ -0,0 +1,49 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System.Globalization;
namespace osu.Framework.Configuration
{
public class BindableInt : Bindable<int>
{
internal int MinValue = int.MinValue;
internal int MaxValue = int.MaxValue;
public override int Value
{
get { return base.Value; }
set
{
int boundValue = value;
if (boundValue > MaxValue)
boundValue = MaxValue;
else if (boundValue < MinValue)
boundValue = MinValue;
base.Value = boundValue;
}
}
public BindableInt(int value = 0) : base(value)
{
}
public static implicit operator int(BindableInt value)
{
return value == null ? 0 : value.Value;
}
public override bool Parse(object s)
{
Value = int.Parse(s as string, NumberFormatInfo.InvariantInfo);
return true;
}
public override string ToString()
{
return Value.ToString(NumberFormatInfo.InvariantInfo);
}
}
}

View File

@@ -0,0 +1,181 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.IO;
namespace osu.Framework.Configuration
{
public class ConfigManager<T> : IDisposable
where T : struct
{
public string Filename = @"game.ini";
bool hasUnsavedChanges;
Dictionary<T, IBindable> configStore = new Dictionary<T, IBindable>();
public ConfigManager()
{
InitialiseDefaults();
Load();
}
protected virtual void InitialiseDefaults()
{
}
public BindableDouble Set(T lookup, double value)
{
BindableDouble bindable = GetBindable<double>(lookup) as BindableDouble;
if (bindable == null)
{
bindable = new BindableDouble(value);
addBindable(lookup, bindable);
}
else
{
bindable.Value = value;
}
return bindable;
}
private void addBindable(T lookup, IBindable bindable)
{
configStore[lookup] = bindable;
bindable.ValueChanged += delegate { hasUnsavedChanges = true; };
}
public BindableInt Set(T lookup, int value)
{
BindableInt bindable = GetBindable<int>(lookup) as BindableInt;
if (bindable == null)
{
bindable = new BindableInt(value);
addBindable(lookup, bindable);
}
else
{
bindable.Value = value;
}
return bindable;
}
public Bindable<U> Set<U>(T lookup, U value) where U : IComparable
{
Bindable<U> bindable = GetBindable<U>(lookup);
if (bindable == null)
bindable = set(lookup, value);
else
bindable.Value = value;
return bindable;
}
private Bindable<U> set<U>(T lookup, U value) where U : IComparable
{
Bindable<U> bindable = new Bindable<U>(value);
addBindable(lookup, bindable);
return bindable;
}
public U Get<U>(T lookup) where U : IComparable
{
return GetBindable<U>(lookup).Value;
}
public Bindable<U> GetBindable<U>(T lookup) where U : IComparable
{
IBindable obj;
if (configStore.TryGetValue(lookup, out obj))
{
Bindable<U> bindable = obj as Bindable<U>;
return bindable;
}
return set(lookup, default(U));
}
public void Load()
{
if (!File.Exists(Filename)) return;
string[] lines = File.ReadAllLines(Filename);
foreach (string line in lines)
{
int equalsIndex = line.IndexOf('=');
if (line.Length == 0 || line[0] == '#' || equalsIndex < 0) continue;
string key = line.Substring(0, equalsIndex).Trim();
string val = line.Remove(0, equalsIndex + 1).Trim();
T lookup;
if (!Enum.TryParse(key, out lookup))
continue;
IBindable b;
if (!configStore.TryGetValue(lookup, out b))
continue;
b.Parse(val);
}
}
public bool Save()
{
if (!hasUnsavedChanges) return true;
try
{
using (Stream stream = new SafeWriteStream(Filename))
using (StreamWriter w = new StreamWriter(stream))
{
foreach (KeyValuePair<T, IBindable> p in configStore)
w.WriteLine(@"{0} = {1}", p.Key, p.Value);
}
}
catch
{
return false;
}
return true;
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
Save();
disposedValue = true;
}
}
~ConfigManager()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@@ -0,0 +1,9 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Configuration
{
public interface IBindable : IHasObjectValue, IValueChangedObservable
{
}
}

View File

@@ -0,0 +1,12 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Configuration
{
public interface IHasObjectValue
{
object ObjectValue { get; set; }
bool Parse(object s);
}
}

View File

@@ -0,0 +1,16 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
namespace osu.Framework.Configuration
{
public interface IValueChangedObservable
{
event EventHandler ValueChanged;
void UnbindAll();
string Description { get; set; }
}
}

View File

@@ -0,0 +1,68 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.IO;
using osu.Framework.IO;
namespace osu.Framework.Configuration
{
class SafeWriteStream : FileStream
{
static object SafeLock = new object(); //ensure we are only ever writing one stream to disk at a time, application wide.
private bool aborted;
string finalFilename;
string temporaryFilename => base.Name;
public SafeWriteStream(string filename) : base(filename + "." + DateTime.Now.Ticks, FileMode.Create)
{
finalFilename = filename;
}
~SafeWriteStream()
{
if (!isDisposed) Dispose();
}
internal void Abort()
{
aborted = true;
}
public override void Close()
{
lock (SafeLock)
{
base.Close();
if (!File.Exists(temporaryFilename)) return;
if (aborted)
{
FileSafety.FileDelete(temporaryFilename);
return;
}
try
{
FileSafety.FileMove(temporaryFilename, finalFilename);
}
catch
{
}
}
}
bool isDisposed;
protected override void Dispose(bool disposing)
{
if (isDisposed) return;
isDisposed = true;
base.Dispose(disposing);
Close();
}
}
}

View File

@@ -0,0 +1,135 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
// this is an abusive thing to do, but it increases the visibility of Extension Methods to virtually every file.
namespace System
{
/// <summary>
/// This class holds extension methods for various purposes and should not be used explicitly, ever.
/// </summary>
public static class ExtensionMethods
{
/// <summary>
/// Searches for an element that matches the conditions defined by the specified predicate.
/// </summary>
/// <param name="match">The predicate that needs to be matched.</param>
/// <param name="startIndex">The index to start conditional search.</param>
/// <returns>The matched item, or the default value for the type if no item was matched.</returns>
public static T Find<T>(this List<T> list, Predicate<T> match, int startIndex)
{
if (!list.IsValidIndex(startIndex)) return default(T);
int val = list.FindIndex(startIndex, list.Count - startIndex - 1, match);
return list.ValueAtOrDefault(val);
}
/// <summary>
/// Adds the given item to the list according to standard sorting rules. Do not use on unsorted lists.
/// </summary>
/// <param name="item">The item that should be added.</param>
/// <returns>The index in the list where the item was inserted.</returns>
public static int AddInPlace<T>(this List<T> list, T item)
{
int index = list.BinarySearch(item);
if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement.
list.Insert(index, item);
return index;
}
/// <summary>
/// Adds the given item to the list according to the comparers sorting rules. Do not use on unsorted lists.
/// </summary>
/// <param name="item">The item that should be added.</param>
/// <param name="comparer">The comparer that should be used for sorting.</param>
/// <returns>The index in the list where the item was inserted.</returns>
public static int AddInPlace<T>(this List<T> list, T item, IComparer<T> comparer)
{
int index = list.BinarySearch(item, comparer);
if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement.
list.Insert(index, item);
return index;
}
public static bool IsValidIndex<T>(this List<T> list, int index)
{
return index >= 0 && index < list.Count;
}
/// <summary>
/// Validates whether index is valid, before returning the value at the given index.
/// </summary>
/// <typeparam name="T">Probably should limit to nullable types.</typeparam>
/// <param name="list">The list to take values</param>
/// <param name="index">The index to request values from</param>
/// <returns>Value at index, else the default value</returns>
public static T ValueAtOrDefault<T>(this List<T> list, int index)
{
return list.IsValidIndex(index) ? list[index] : default(T);
}
/// <summary>
/// Compares every item in list to given list.
/// </summary>
public static bool CompareTo<T>(this List<T> list, List<T> list2)
{
if (list.Count != list2.Count) return false;
return !list.Where((t, i) => !t.Equals(list2[i])).Any();
}
public static string ToResolutionString(this System.Drawing.Size size)
{
return size.Width.ToString() + 'x' + size.Height.ToString();
}
public static void WriteLineExplicit(this Stream s, string str = @"")
{
byte[] data = Encoding.UTF8.GetBytes($"{str}\r\n");
s.Write(data, 0, data.Length);
}
public static string UnsecureRepresentation(this SecureString s)
{
IntPtr bstr = Marshal.SecureStringToBSTR(s);
try
{
return Marshal.PtrToStringBSTR(bstr);
}
finally
{
Marshal.FreeBSTR(bstr);
}
}
public static long ToUnixTimestamp(this DateTime date)
{
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return Convert.ToInt64((date - epoch).TotalSeconds);
}
public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly)
{
if (assembly == null) throw new ArgumentNullException("assembly");
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
}
}
}

View File

@@ -0,0 +1,42 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using System;
using System.Collections.Generic;
using System.Text;
namespace osu.Framework.Extensions.MatrixExtensions
{
public static class MatrixExtensions
{
public static Matrix3 TranslateTo(this Matrix3 m, Vector2 v)
{
m.Row2 += m.Row0 * v.X + m.Row1 * v.Y;
return m;
}
public static Matrix3 RotateTo(this Matrix3 m, float angle)
{
// Convert to radians
angle = angle / (180 / MathHelper.Pi);
float cos = (float)Math.Cos(angle);
float sin = (float)Math.Sin(angle);
Vector3 temp = m.Row0 * cos + m.Row1 * sin;
m.Row1 = m.Row1 * cos - m.Row0 * sin;
m.Row0 = temp;
return m;
}
public static Matrix3 ScaleTo(this Matrix3 m, Vector2 v)
{
m.Row0 *= v.X;
m.Row1 *= v.Y;
return m;
}
}
}

View File

@@ -0,0 +1,76 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using osu.Framework.Extensions.RectangleExtensions;
using osu.Framework.Graphics.Primitives;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace osu.Framework.Extensions.PolygonExtensions
{
/// <summary>
/// Todo: Support segment containment and circles.
/// Todo: Might be overkill, but possibly support convex decomposition?
/// </summary>
public static class IConvexPolygonExtensions
{
/// <summary>
/// Determines whether two convex polygons intersect.
/// </summary>
/// <param name="first">The first polygon.</param>
/// <param name="second">The second polygon.</param>
/// <returns>Whether the two polygons intersect.</returns>
public static bool Intersects(this IConvexPolygon first, IConvexPolygon second)
{
Vector2[][] bothAxes = { first.GetAxes(), second.GetAxes() };
Vector2[] firstVertices = first.Vertices;
Vector2[] secondVertices = second.Vertices;
foreach (Vector2[] axes in bothAxes)
{
foreach (Vector2 axis in axes)
{
ProjectionRange firstRange = new ProjectionRange(axis, firstVertices);
ProjectionRange secondRange = new ProjectionRange(axis, secondVertices);
if (!firstRange.Overlaps(secondRange))
return false;
}
}
return true;
}
/// <summary>
/// Determines whether two convex polygons intersect.
/// </summary>
/// <param name="first">The first polygon.</param>
/// <param name="second">The second polygon.</param>
/// <returns>Whether the two polygons intersect.</returns>
public static bool Intersects(this IConvexPolygon first, Rectangle second)
{
Vector2[][] bothAxes = { first.GetAxes(), second.GetAxes() };
Vector2[] firstVertices = first.Vertices;
Vector2[] secondVertices = second.GetVertices();
foreach (Vector2[] axes in bothAxes)
{
foreach (Vector2 axis in axes)
{
ProjectionRange firstRange = new ProjectionRange(axis, firstVertices);
ProjectionRange secondRange = new ProjectionRange(axis, secondVertices);
if (!firstRange.Overlaps(secondRange))
return false;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,43 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using osu.Framework.Graphics.Primitives;
using System;
using System.Collections.Generic;
using System.Text;
namespace osu.Framework.Extensions.PolygonExtensions
{
public static class IPolygonExtensions
{
/// <summary>
/// Computes the axes for each edge in a polygon.
/// </summary>
/// <param name="polygon">The polygon to return the axes of.</param>
/// <param name="normalize">Whether the normals should be normalized. Allows computation of the exact intersection point.</param>
/// <returns>The axes of the polygon.</returns>
public static Vector2[] GetAxes(this IPolygon polygon, bool normalize = false)
{
Vector2[] axes = new Vector2[polygon.AxisVertices.Length];
for (int i = 0; i < polygon.AxisVertices.Length; i++)
{
// Construct an edge between two sequential points
Vector2 v1 = polygon.AxisVertices[i];
Vector2 v2 = polygon.AxisVertices[i == polygon.AxisVertices.Length - 1 ? 0 : i + 1];
Vector2 edge = v2 - v1;
// Find the normal to the edge
Vector2 normal = new Vector2(-edge.Y, edge.X);
if (normalize)
normal = Vector2.Normalize(normal);
axes[i] = normal;
}
return axes;
}
}
}

View File

@@ -0,0 +1,49 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace osu.Framework.Extensions.RectangleExtensions
{
public static class RectangleExtensions
{
/// <summary>
/// Computes the axes for each edge in a rectangle.
/// <para>A rectangle has equal normals for opposing edges, so only two axes will be returned.</para>
/// </summary>
/// <param name="rectangle">The rectangle to return the axes of.</param>
/// <param name="normalize">Whether the normals should be normalized. Allows computation of the exact intersection point.</param>
/// <returns>The axes of the rectangle.</returns>
public static Vector2[] GetAxes(this Rectangle rectangle, bool normalize = false)
{
Vector2[] edges = { new Vector2(rectangle.Right - rectangle.Left, rectangle.Top), new Vector2(rectangle.Right, rectangle.Bottom - rectangle.Top) };
for (int i = 0; i < edges.Length; i++)
{
Vector2 normal = new Vector2(-edges[i].Y, edges[i].X);
if (normalize)
normal = Vector2.Normalize(normal);
edges[i] = normal;
}
return edges;
}
public static Vector2[] GetVertices(this Rectangle rectangle)
{
return new[]
{
new Vector2(rectangle.Left, rectangle.Top),
new Vector2(rectangle.Right, rectangle.Top),
new Vector2(rectangle.Right, rectangle.Bottom),
new Vector2(rectangle.Left, rectangle.Bottom)
};
}
}
}

View File

@@ -0,0 +1,39 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Framework.Framework
{
public abstract class BasicGameForm : GLControl
{
public BasicGameForm(GraphicsContextFlags flags) : base(GraphicsMode.Default, 2, 0, flags)
{
}
public event EventHandler ApplicationActivated;
public event EventHandler ApplicationDeactivated;
public abstract event EventHandler ScreenChanged;
public event EventHandler UserResized;
public abstract Rectangle ClientBounds { get; }
public abstract bool IsMinimized { get; }
public abstract void CentreToScreen();
protected virtual void OnActivateApp(bool active)
{
if (active)
ApplicationActivated?.Invoke(this, EventArgs.Empty);
else
ApplicationDeactivated?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@@ -0,0 +1,103 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Windows.Forms;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL;
using OpenTK;
using OpenTK.Graphics.OpenGL;
namespace osu.Framework.Framework
{
public abstract class BasicGameHost : Container
{
public abstract BasicGameWindow Window { get; }
public abstract GLControl GLControl { get; }
public abstract bool IsActive { get; }
public event EventHandler Activated;
public event EventHandler Deactivated;
public event EventHandler Exiting;
public event EventHandler Idle;
public override bool IsVisible => true;
public override Vector2 Size => new Vector2(Window?.Form.ClientSize.Width ?? 0, Window?.Form.ClientSize.Height ?? 0);
protected virtual void OnActivated(object sender, EventArgs args)
{
Activated?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnDeactivated(object sender, EventArgs args)
{
Deactivated?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnExiting(object sender, EventArgs args)
{
Exiting?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnIdle(object sender, EventArgs args)
{
GLWrapper.Reset();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
UpdateSubTree();
DrawSubTree();
Idle?.Invoke(this, EventArgs.Empty);
GLControl.SwapBuffers();
}
private bool exitRequested;
public void Exit()
{
exitRequested = true;
}
public virtual void Run()
{
Window.ClientSizeChanged += delegate { Invalidate(); };
GLControl.Initialize();
Exception error = null;
try
{
Application.Idle += OnApplicationIdle;
Application.Run(Window.Form);
}
catch (OutOfMemoryException e)
{
error = e;
}
finally
{
Application.Idle -= OnApplicationIdle;
if (error == null || !(error is OutOfMemoryException))
//we don't want to attempt a safe shutdown is memory is low; it may corrupt database files.
OnExiting(this, null);
}
}
protected virtual void OnApplicationIdle(object sender, EventArgs e)
{
if (exitRequested)
Window.Close();
else
OnIdle(sender, e);
}
public void Load(Game game)
{
game.SetHost(this);
Add(game);
}
}
}

View File

@@ -0,0 +1,66 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Drawing;
namespace osu.Framework.Framework
{
public abstract class BasicGameWindow
{
public event EventHandler ClientSizeChanged;
public event EventHandler ScreenDeviceNameChanged;
public event EventHandler Activated;
public event EventHandler Deactivated;
public event EventHandler Paint;
public abstract Rectangle ClientBounds { get; }
public abstract IntPtr Handle { get; }
public abstract bool IsMinimized { get; }
public abstract BasicGameForm Form { get; }
public BasicGameWindow() { }
public abstract void Close();
private string title;
public string Title
{
get { return title; }
set
{
if (value == null || title == value)
return;
SetTitle(title = value);
}
}
protected void OnActivated()
{
Activated?.Invoke(this, EventArgs.Empty);
}
protected void OnClientSizeChanged()
{
ClientSizeChanged?.Invoke(this, EventArgs.Empty);
}
protected void OnDeactivated()
{
Deactivated?.Invoke(this, EventArgs.Empty);
}
protected void OnPaint()
{
Paint?.Invoke(this, EventArgs.Empty);
}
protected void OnScreenDeviceNameChanged()
{
ScreenDeviceNameChanged?.Invoke(this, EventArgs.Empty);
}
protected abstract void SetTitle(string title);
}
}

View File

@@ -0,0 +1,99 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Diagnostics;
using System.Text;
using System.Windows.Forms;
using OpenTK.Graphics.ES20;
using OpenTK.Graphics;
using System.Drawing;
using System.IO;
using OpenTK;
using System.Collections.Generic;
using System.Threading;
using osu.Framework.Graphics.Shaders;
using osu.Framework.Logging;
namespace osu.Framework.Framework
{
public class GLControl : OpenTK.GLControl
{
private string SupportedExtensions;
internal Version GLVersion;
internal Version GLSLVersion;
public GLControl(GraphicsMode mode, int major, int minor, GraphicsContextFlags flags)
: base(mode, major, minor, flags)
{
}
public void Initialize()
{
string version = GL.GetString(StringName.Version);
GLVersion = new Version(version.Split(' ')[0]);
version = GL.GetString(StringName.ShadingLanguageVersion);
if (!string.IsNullOrEmpty(version))
{
try
{
GLSLVersion = new Version(version.Split(' ')[0]);
}
catch (Exception e)
{
Logger.Error(e, $@"couldn't set GLSL version using string '{version}'");
}
}
if (GLSLVersion == null)
GLSLVersion = new Version();
//Set up OpenGL related characteristics
GL.Disable(EnableCap.DepthTest);
GL.Disable(EnableCap.StencilTest);
GL.Enable(EnableCap.Blend);
GL.Enable(EnableCap.ScissorTest);
Logger.Log($@"GL Initialized
GL Version: { GL.GetString(StringName.Version)}
GL Renderer: { GL.GetString(StringName.Renderer)}
GL Shader Language version: { GL.GetString(StringName.ShadingLanguageVersion)}
GL Vendor: { GL.GetString(StringName.Vendor)}
GL Extensions: { GL.GetString(StringName.Extensions)}
GL Context: { GraphicsMode}", LoggingTarget.Runtime, LogLevel.Important);
}
protected override void OnMouseEnter(EventArgs e)
{
Cursor.Hide();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
Cursor.Show();
base.OnMouseLeave(e);
}
internal bool CheckExtension(string extensionName)
{
try
{
if (string.IsNullOrEmpty(SupportedExtensions))
SupportedExtensions = GL.GetString(StringName.Extensions);
return SupportedExtensions.Contains(extensionName);
}
catch { }
return false;
}
public void Flush()
{
GL.Flush();
GL.Finish();
}
}
}

View File

@@ -0,0 +1,125 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Drawing;
using OpenTK;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using osu.Framework.Threading;
namespace osu.Framework.Framework
{
public class Window
{
public delegate void ResolutionChangeSucceededEventHandler(bool spriteResultionChanged);
private const int default_width = 1366;
private const int default_height = 768;
public event VoidDelegate OnSizeChanged;
public event BoolDelegate OnMinimizedStateChanged;
public BasicGameForm Form => host?.Window?.Form;
public Size Size
{
get { return Form.ClientSize; }
set { Form.ClientSize = value; }
}
public int Width => Size.Width;
public int Height => Size.Height;
public bool IsMinimized => Form.IsMinimized;
public IntPtr Handle => gameWindow.Handle;
private BasicGameHost host;
private BasicGameWindow gameWindow => host?.Window;
internal Window(BasicGameHost host)
{
this.host = host;
Form.AllowDrop = true;
Form.SizeChanged += Form_SizeChanged;
Size = new Size(default_width, default_height);
}
private void Form_SizeChanged(object sender, EventArgs e)
{
OnSizeChanged?.Invoke();
}
public bool AllowDrop
{
get { return Form.AllowDrop; }
set { Form.AllowDrop = value; }
}
public string Title
{
get { return gameWindow.Title; }
set { gameWindow.Title = value; }
}
public void StealFocus()
{
Form.BringToFront();
Form.Focus();
}
public void BringToFront()
{
Form.BringToFront();
SetForegroundWindow(Form.Handle);
}
private NotifyIcon notifyIcon;
private bool minimizedToTray;
public bool MinimizedToTray
{
get { return minimizedToTray; }
set
{
if (value == minimizedToTray)
return;
minimizedToTray = value;
if (minimizedToTray)
{
if (notifyIcon == null)
{
notifyIcon = new NotifyIcon();
notifyIcon.Icon = Form.Icon;
notifyIcon.Click += (obj, e) => { MinimizedToTray = false; };
}
notifyIcon.Visible = true;
Form.WindowState = FormWindowState.Minimized;
Form.Visible = false;
}
else
{
Form.Visible = true;
Form.WindowState = FormWindowState.Normal;
Form.ShowInTaskbar = true;
notifyIcon.Visible = false;
BringToFront();
}
OnMinimizedStateChanged?.Invoke(minimizedToTray);
}
}
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern bool SetForegroundWindow(IntPtr hwnd);
}
}

194
osu.Framework/Game.cs Normal file
View File

@@ -0,0 +1,194 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Windows.Forms;
using osu.Framework.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using System.Threading;
using osu.Framework.Audio;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Resources;
using osu.Framework.Graphics.Shaders;
using Scheduler = osu.Framework.Threading.Scheduler;
namespace osu.Framework
{
public class Game : LargeContainer
{
public static Window Window { get; private set; }
internal Scheduler Scheduler;
private ThrottledFrameClock clock = new ThrottledFrameClock();
protected override IFrameBasedClock Clock => clock;
public DllResourceStore Resources;
public TextureStore Textures;
protected virtual string MainResourceFile => AppDomain.CurrentDomain.FriendlyName;
protected int MaximumFramesPerSecond
{
get { return clock.MaximumUpdateHz; }
set { clock.MaximumUpdateHz = value; }
}
internal Thread MainThread;
private BasicGameForm form => host?.Window?.Form;
private BasicGameHost host;
private bool exitRequested;
private bool isActive;
public AudioManager Audio;
public ShaderManager Shaders;
public Game()
{
Game = this;
}
public void SetHost(BasicGameHost host)
{
MainThread = Thread.CurrentThread;
this.host = host;
host.Exiting += (sender, args) => { OnExiting(this, args); };
Window = new Window(host);
form.FormClosing += OnFormClosing;
form.DragEnter += dragEnter;
form.DragDrop += dragDrop;
}
public override void Load()
{
base.Load();
Scheduler = new Scheduler();
Resources = new DllResourceStore(MainResourceFile);
Textures = Textures = new TextureStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures"));
Audio = new AudioManager(new NamespacedResourceStore<byte[]>(Resources, @"Shaders"), new NamespacedResourceStore<byte[]>(Resources, @"Samples"));
Shaders = new ShaderManager(new NamespacedResourceStore<byte[]>(Resources, @"Shaders"));
AddProcessingContainer(new UserInputManager());
}
protected override void Update()
{
clock.ProcessFrame();
Scheduler.Update();
Audio.Update();
base.Update();
}
private void dragDrop(object sender, DragEventArgs e)
{
Array fileDrop = e.Data.GetData(DataFormats.FileDrop) as Array;
string textDrop = e.Data.GetData(DataFormats.Text) as string;
if (fileDrop != null)
{
for (int i = 0; i < fileDrop.Length; i++)
OnDroppedFile(fileDrop.GetValue(i).ToString());
}
if (!string.IsNullOrEmpty(textDrop))
OnDroppedText(textDrop);
}
private void dragEnter(object sender, DragEventArgs e)
{
bool isFile = e.Data.GetDataPresent(DataFormats.FileDrop);
bool isUrl = e.Data.GetDataPresent(DataFormats.Text);
e.Effect = isFile || isUrl ? DragDropEffects.Copy : DragDropEffects.None;
}
/// <summary>
/// Whether the Game environment is active (in the foreground).
/// </summary>
public bool IsActive
{
get { return isActive; }
private set
{
if (value == isActive)
return;
isActive = value;
if (isActive)
OnActivated();
else
OnDeactivated();
}
}
protected virtual void OnDroppedText(string text)
{
}
protected virtual void OnDroppedFile(string file)
{
}
protected virtual void OnFormClosing(object sender, FormClosingEventArgs args)
{
}
protected virtual void OnDragEnter(object sender, EventArgs args)
{
}
protected virtual void OnActivated()
{
}
protected virtual void OnDeactivated()
{
}
protected virtual void OnExiting(object sender, EventArgs args)
{
}
/// <summary>
/// Called before a frame cycle has started (Update and Draw).
/// </summary>
protected virtual void PreFrame()
{
}
/// <summary>
/// Called after a frame cycle has been completed (Update and Draw).
/// </summary>
protected virtual void PostFrame()
{
}
private void onWindowSizeChange()
{
Invalidate();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Window.OnSizeChanged -= onWindowSizeChange;
}
}
}

View File

@@ -0,0 +1,61 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics.Containers;
namespace osu.Framework.GameModes
{
public class GameMode : LargeContainer
{
public virtual string Name => @"Unknown";
private GameMode lastGameMode;
private bool modePushed;
/// <summary>
/// Called when this GameMode is being entered.
/// </summary>
/// <param name="last">The last GameMode.</param>
protected virtual void EnterTransition(GameMode last)
{
FadeInFromZero(200);
}
/// <summary>
/// Called when this GameMode is exiting.
/// </summary>
/// <param name="next">The next GameMode.</param>
protected virtual void ExitTransition(GameMode next)
{
FadeOutFromOne(200);
}
/// <summary>
/// Changes to a new GameMode.
/// </summary>
/// <param name="mode">The new GameMode.</param>
protected void PushMode(GameMode mode)
{
if (modePushed)
return;
modePushed = true;
AddTopLevel(mode);
mode.lastGameMode = this;
mode.EnterTransition(this);
}
/// <summary>
/// Exits this GameMode.
/// </summary>
protected void ExitMode()
{
lastGameMode.modePushed = false;
ExitTransition(lastGameMode);
Delay(5000).Expire();
}
}
}

View File

@@ -0,0 +1,10 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
namespace osu.Framework.Graphics.Batches
{
public interface IVertexBatch
{
void Draw();
}
}

View File

@@ -0,0 +1,28 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK.Graphics.ES20;
using osu.Framework.Graphics.OpenGL.Buffers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace osu.Framework.Graphics.Batches
{
public class LinearBatch<T> : VertexBatch<T> where T : struct, IEquatable<T>
{
private BeginMode type;
public LinearBatch(int size, int fixedBufferAmount, BeginMode type)
: base(size, fixedBufferAmount)
{
this.type = type;
}
protected override VertexBuffer<T> CreateVertexBuffer()
{
return new LinearVertexBuffer<T>(Size, type, BufferUsageHint.DynamicDraw);
}
}
}

View File

@@ -0,0 +1,25 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK.Graphics.ES20;
using osu.Framework.Graphics.OpenGL.Buffers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace osu.Framework.Graphics.Batches
{
public class QuadBatch<T> : VertexBatch<T> where T : struct, IEquatable<T>
{
public QuadBatch(int size, int fixedBufferAmount)
: base(size, fixedBufferAmount)
{
}
protected override VertexBuffer<T> CreateVertexBuffer()
{
return new QuadVertexBuffer<T>(Size, BufferUsageHint.DynamicDraw);
}
}
}

View File

@@ -0,0 +1,114 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics.OpenGL;
using osu.Framework.Graphics.OpenGL.Buffers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace osu.Framework.Graphics.Batches
{
public abstract class VertexBatch<T> : IVertexBatch where T : struct, IEquatable<T>
{
public List<VertexBuffer<T>> VertexBuffers = new List<VertexBuffer<T>>();
public int Size { get; private set; }
private int changeBeginIndex = -1;
private int changeEndIndex = -1;
private int currentVertexBuffer = 0;
private int currentVertex = 0;
private int lastVertex = 0;
private int fixedBufferAmount;
private VertexBuffer<T> CurrentVertexBuffer => VertexBuffers[currentVertexBuffer];
public VertexBatch(int size, int fixedBufferAmount)
{
// Vertex buffers of size 0 don't make any sense. Let's not blindly hope for good behavior of OpenGL.
Debug.Assert(size > 0);
Size = size;
this.fixedBufferAmount = fixedBufferAmount;
}
#region Disposal
~VertexBatch()
{
Dispose(false);
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
protected void Dispose(bool disposing)
{
if (disposing)
foreach (VertexBuffer<T> vbo in VertexBuffers)
vbo.Dispose();
}
#endregion
public void ResetCounters()
{
changeBeginIndex = -1;
currentVertexBuffer = 0;
currentVertex = 0;
lastVertex = 0;
}
protected abstract VertexBuffer<T> CreateVertexBuffer();
public void Add(T v)
{
while (currentVertexBuffer >= VertexBuffers.Count)
VertexBuffers.Add(CreateVertexBuffer());
VertexBuffer<T> vertexBuffer = CurrentVertexBuffer;
if (!vertexBuffer.Vertices[currentVertex].Equals(v))
{
if (changeBeginIndex == -1)
changeBeginIndex = currentVertex;
changeEndIndex = currentVertex + 1;
}
vertexBuffer.Vertices[currentVertex] = v;
++currentVertex;
if (currentVertex >= vertexBuffer.Vertices.Length)
{
Draw();
lastVertex = currentVertex = 0;
}
}
public void Draw()
{
if (currentVertex == lastVertex)
return;
GLWrapper.SetActiveBatch(this);
VertexBuffer<T> vertexBuffer = CurrentVertexBuffer;
if (changeBeginIndex >= 0)
vertexBuffer.UpdateRange(changeBeginIndex, changeEndIndex);
vertexBuffer.DrawRange(lastVertex, currentVertex);
// When using multiple buffers we advance to the next one with every draw to prevent contention on the same buffer with future vertex updates.
currentVertexBuffer = (currentVertexBuffer + 1) % fixedBufferAmount;
currentVertex = 0;
lastVertex = currentVertex;
changeBeginIndex = -1;
}
}
}

View File

@@ -0,0 +1,90 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Framework.Cached;
using osu.Framework.Graphics.Primitives;
using OpenTK;
namespace osu.Framework.Graphics.Containers
{
public class AutoSizeContainer : Container
{
protected bool RequireAutoSize => autoSizeUpdatePending;
private bool autoSizeUpdatePending;
public override bool Invalidate(bool affectsSize = true, bool affectsPosition = true, Drawable source = null)
{
if (affectsSize)
autoSizeUpdatePending = true;
bool alreadyInvalidated = base.Invalidate(affectsSize, affectsPosition, source);
return !alreadyInvalidated;
}
protected override Quad DrawQuadForBounds
{
get
{
Vector2 size = Vector2.Zero;
Vector2 maxInheritingSize = Vector2.One;
// Find the maximum width/height of children
foreach (Drawable c in Children)
{
if (!c.IsVisible)
continue;
Vector2 boundingSize = c.GetBoundingSize(this);
Vector2 inheritingSize = c.Size * c.VectorScale * c.Scale;
if ((c.SizeMode & InheritMode.X) == 0)
size.X = Math.Max(size.X, boundingSize.X);
else
maxInheritingSize.X = Math.Max(maxInheritingSize.X, inheritingSize.X);
if ((c.SizeMode & InheritMode.Y) == 0)
size.Y = Math.Max(size.Y, boundingSize.Y);
else
maxInheritingSize.Y = Math.Max(maxInheritingSize.Y, inheritingSize.Y);
}
return new Quad(0, 0, size.X * maxInheritingSize.X, size.Y * maxInheritingSize.Y);
}
}
internal override void UpdateSubTree()
{
base.UpdateSubTree();
if (RequireAutoSize)
{
Vector2 b = GetBoundingSize(this);
if (!HasDefinedSize || b != Size)
{
Size = b;
Invalidate();
UpdateDrawInfoSubtree();
}
autoSizeUpdatePending = false;
}
}
internal override float InheritableWidth => HasDefinedSize ? ActualSize.X : Parent?.InheritableWidth ?? 0;
internal override float InheritableHeight => HasDefinedSize ? ActualSize.Y : Parent?.InheritableHeight ?? 0;
protected override bool HasDefinedSize => !autoSizeUpdatePending;
protected override bool ChildrenShouldInvalidate => true;
}
}

View File

@@ -0,0 +1,76 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using OpenTK.Graphics.ES20;
using osu.Framework.Graphics.OpenGL;
using osu.Framework.Graphics.OpenGL.Buffers;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using osu.Framework.Graphics.Batches;
namespace osu.Framework.Graphics.Containers
{
class BufferedContainer : Container
{
private FrameBuffer frameBuffer;
private QuadBatch<TexturedVertex2d> quadBatch = new QuadBatch<TexturedVertex2d>(1, 3);
protected override IVertexBatch ActiveBatch => quadBatch;
internal BufferedContainer()
{
frameBuffer = new FrameBuffer();
}
internal void Attach(RenderbufferInternalFormat format)
{
frameBuffer.Attach(format);
}
protected override void Update()
{
frameBuffer.Size = new Vector2(ScreenSpaceDrawQuad.Width, ScreenSpaceDrawQuad.Height);
base.Update();
}
protected override void PreDraw()
{
frameBuffer.Bind();
// Set viewport to the texture size
GLWrapper.PushViewport(new Rectangle(0, 0, frameBuffer.Texture.Width, frameBuffer.Texture.Height));
// We need to draw children as if they were zero-based to the top-left of the texture
// so we make the new zero be this container's position without affecting children in any negative ways
GLWrapper.PushOrtho(new Rectangle((int)ScreenSpaceDrawQuad.TopLeft.X, (int)ScreenSpaceDrawQuad.TopLeft.Y, frameBuffer.Texture.Width, frameBuffer.Texture.Height));
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
}
protected override void PostDraw()
{
frameBuffer.Unbind();
GLWrapper.PopOrtho();
GLWrapper.PopViewport();
GLWrapper.SetBlend(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);
Rectangle textureRect = new Rectangle(0, frameBuffer.Texture.Height, frameBuffer.Texture.Width, -frameBuffer.Texture.Height);
frameBuffer.Texture.Draw(ScreenSpaceDrawQuad, textureRect, DrawInfo.Colour, quadBatch);
// In the case of nested framebuffer containerse we need to draw to
// the last framebuffer container immediately, so let's force it
ActiveBatch.Draw();
}
protected override void Dispose(bool isDisposing)
{
frameBuffer.Dispose();
base.Dispose(isDisposing);
}
}
}

View File

@@ -0,0 +1,48 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Text;
using OpenTK;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics;
namespace osu.Framework.Graphics.Containers
{
/// <summary>
/// A drawable which can have children added externally.
/// </summary>
public class Container : Drawable
{
public virtual Drawable Add(Drawable drawable)
{
return base.Add(drawable);
}
public void Add(IEnumerable<Drawable> drawables)
{
base.Add(drawables);
}
public virtual bool Remove(Drawable drawable, bool dispose = true)
{
return base.Remove(drawable, dispose);
}
public void Remove(IEnumerable<Drawable> drawables, bool dispose = true)
{
base.Remove(drawables);
}
public int RemoveAll(Predicate<Drawable> match)
{
return base.RemoveAll(match);
}
public virtual void Clear(bool dispose = true)
{
base.Clear(dispose);
}
}
}

View File

@@ -0,0 +1,152 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using OpenTK;
using osu.Framework.Graphics.Transformations;
namespace osu.Framework.Graphics.Containers
{
public class FlowContainer : AutoSizeContainer
{
public EasingTypes LayoutEasing;
public int LayoutDuration = 0;
/// <summary>
/// Even if we aren't autosizing, we need to ensure invalidation.
/// </summary>
protected override bool ChildrenShouldInvalidate => true;
private FlowDirection direction = FlowDirection.Full;
public FlowDirection Direction
{
get { return direction; }
set
{
if (value == direction) return;
direction = value;
requiresLayout = true;
}
}
private double lastLayout;
private bool requiresLayout = true;
/// <summary>
/// When we are using a transformation for layouts, we want to ensure we don't trigger re-flow due to our own actions.
/// </summary>
private double nextLayout => lastLayout + LayoutDuration;
Vector2 maximumSize;
/// <summary>
/// Optional maximum dimensions for this container.
/// </summary>
public Vector2 MaximumSize
{
get { return maximumSize; }
set
{
if (maximumSize == value) return;
maximumSize = value;
Invalidate();
}
}
Vector2 padding;
public Vector2 Padding
{
get { return padding; }
set
{
if (padding == value) return;
padding = value;
Invalidate();
}
}
public override bool Invalidate(bool affectsSize = true, bool affectsPosition = true, Drawable source = null)
{
if (affectsSize)
requiresLayout = true;
return base.Invalidate(affectsSize, affectsPosition, source);
}
public override Drawable Add(Drawable drawable)
{
//let's force an instant re-flow on adding a new drawable for now.
lastLayout = 0;
return base.Add(drawable);
}
protected override void UpdateLayout()
{
if (!requiresLayout || (nextLayout > 0 && Time < nextLayout)) return;
lastLayout = Time;
requiresLayout = false;
base.UpdateLayout();
if (Children.Count == 0) return;
Vector2 current = new Vector2(Math.Max(0, Padding.X), Math.Max(0, Padding.Y));
Vector2 max = maximumSize;
if (direction == FlowDirection.Full && maximumSize == Vector2.Zero)
{
Drawable sDrawable = Parent;
while (sDrawable is AutoSizeContainer)
sDrawable = sDrawable.Parent;
if (sDrawable != null)
max = sDrawable.ActualSize * sDrawable.VectorScale * sDrawable.Scale;
}
float rowMaxHeight = 0;
foreach (Drawable d in Children)
{
if (!d.IsVisible) continue;
Vector2 size = d.ActualSize * d.VectorScale * d.Scale;
if (Direction != FlowDirection.HorizontalOnly && current.X + size.X > max.X)
{
current.X = Math.Max(0, Padding.X);
current.Y += rowMaxHeight;
rowMaxHeight = 0;
}
//todo: check this is correct
if (size.X > 0) size.X = Math.Max(0, size.X + Padding.X);
if (size.Y > 0) size.Y = Math.Max(0, size.Y + Padding.Y);
if (size.Y > rowMaxHeight) rowMaxHeight = size.Y;
if (current != d.Position)
{
d.MoveTo(current, LayoutDuration, LayoutEasing);
d.UpdateSubTree();
}
current.X += size.X;
}
}
}
public enum FlowDirection
{
VerticalOnly,
HorizontalOnly,
Full
}
}

View File

@@ -0,0 +1,15 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
namespace osu.Framework.Graphics.Containers
{
public class LargeContainer : ProcessingContainer
{
public LargeContainer()
{
SizeMode = InheritMode.XY;
}
}
}

View File

@@ -0,0 +1,24 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics.OpenGL;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace osu.Framework.Graphics.Containers
{
public class MaskingContainer : Container
{
protected override void PreDraw()
{
GLWrapper.PushScissor(ScreenSpaceDrawQuad.BoundingRectangle);
}
protected override void PostDraw()
{
GLWrapper.PopScissor();
}
}
}

View File

@@ -0,0 +1,85 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Graphics.Textures;
using osu.Framework.Resources;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Framework.Graphics.Containers
{
public class ProcessingContainer : Container
{
private Container processingContainer = new Container() { SizeMode = InheritMode.XY };
public override void Load()
{
base.Load();
AddTopLevel(processingContainer);
}
/// <summary>
/// Adds a container and sets it as the new processing container.
/// Any .Add() calls will be redirected to this container.
/// </summary>
/// <param name="container">The container to be the new processing container.</param>
protected void AddProcessingContainer(Container container)
{
Debug.Assert(container != null);
// If the current container is a processing conatiner then we only need
// to add the new container to it (handled below)
if (!(processingContainer is ProcessingContainer))
{
// Move existing children to the new container
List<Drawable> existingChildren = processingContainer.Children.ToList();
existingChildren.ForEach(child => container.Add(child));
}
processingContainer.Add(container);
processingContainer = container;
}
/// <summary>
/// Adds a container at the same level as the first processing container.
/// </summary>
/// <param name="drawable">The drawable to add.</param>
/// <returns>The added drawable.</returns>
protected Drawable AddTopLevel(Drawable drawable) => base.Add(drawable);
/// <summary>
/// Adds a drawable to the processing container.
/// </summary>
/// <param name="drawable">The drawable to add.</param>
/// <returns>The added drawable.</returns>
public override Drawable Add(Drawable drawable)
{
return processingContainer.Add(drawable);
}
/// <summary>
/// Clears the processing container.
/// </summary>
/// <param name="dispose">Whether to dispose contained drawables.</param>
public override void Clear(bool dispose = true)
{
processingContainer.Clear(dispose);
}
/// <summary>
/// Removes a drawable from the processing container.
/// </summary>
/// <param name="drawable">The drawable to remove.</param>
/// <param name="dispose">Whether to dispose the drawable.</param>
/// <returns>Whether the drawable was removed.</returns>
public override bool Remove(Drawable drawable, bool dispose = true)
{
return processingContainer.Remove(drawable, dispose);
}
}
}

View File

@@ -0,0 +1,75 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework;
using osu.Framework.Extensions.MatrixExtensions;
namespace osu.Framework.Graphics
{
public class DrawInfo : IEquatable<DrawInfo>
{
public Matrix3 Matrix = Matrix3.Identity;
public Matrix3 MatrixInverse => matrixInverse ?? (matrixInverse = Matrix.Inverted()).Value;
public Color4 Colour = Color4.White;
private Matrix3? matrixInverse;
public DrawInfo()
{
}
/// <summary>
/// Applies a transformation to the current DrawInfo.
/// </summary>
/// <param name="target">The DrawInfo instance to be filled with the result.</param>
/// <param name="translation">The amount by which to translate the current position.</param>
/// <param name="scale">The amount by which to scale.</param>
/// <param name="rotation">The amount by which to rotate.</param>
/// <param name="origin">The center of rotation and scale.</param>
/// <param name="colour">An optional color to be applied multiplicatively.</param>
/// <param name="viewport">An optional new viewport size.</param>
public void ApplyTransform(DrawInfo target, Vector2 translation, Vector2 scale, float rotation, Vector2 origin, Color4? colour = null)
{
Matrix3 m = Matrix;
if (translation != Vector2.Zero)
m = m.TranslateTo(translation);
if (rotation != 0)
m = m.RotateTo(rotation);
if (scale != Vector2.One)
m = m.ScaleTo(scale);
if (origin != Vector2.Zero)
m = m.TranslateTo(-origin);
target.Matrix = m;
target.Colour = Colour;
if (colour != null)
{
target.Colour.R *= colour.Value.R;
target.Colour.G *= colour.Value.G;
target.Colour.B *= colour.Value.B;
target.Colour.A *= colour.Value.A;
}
}
/// <summary>
/// Copies the current DrawInfo into target.
/// </summary>
/// <param name="target">The DrawInfo to be filled with the copy.</param>
public void Copy(DrawInfo target)
{
target.Matrix = Matrix;
target.Colour = Colour;
}
public bool Equals(DrawInfo other)
{
return Matrix.Equals(other.Matrix) && Colour.Equals(other.Colour);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,204 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using OpenTK.Input;
using OpenTK;
using osu.Framework.Lists;
using osu.Framework.Input;
namespace osu.Framework.Graphics
{
public partial class Drawable : IDisposable, IHasLifetime
{
/// <summary>
/// Find the first parent InputManager which this drawable is contained by.
/// </summary>
private InputManager ourInputManager => this as InputManager ?? Parent?.ourInputManager;
public bool TriggerHover(InputState state)
{
return OnHover(state);
}
protected virtual bool OnHover(InputState state)
{
return false;
}
internal void TriggerHoverLost(InputState state)
{
OnHoverLost(state);
}
protected virtual void OnHoverLost(InputState state)
{
}
public bool TriggerMouseDown(InputState state = null, MouseDownEventArgs args = null) => OnMouseDown(state, args);
protected virtual bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
return false;
}
public bool TriggerMouseUp(InputState state = null, MouseUpEventArgs args = null) => OnMouseUp(state, args);
protected virtual bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
return false;
}
public bool TriggerClick(InputState state = null) => OnClick(state);
protected virtual bool OnClick(InputState state)
{
return false;
}
public bool TriggerDoubleClick(InputState state) => OnDoubleClick(state);
protected virtual bool OnDoubleClick(InputState state)
{
return false;
}
public bool TriggerDragStart(InputState state) => OnDragStart(state);
protected virtual bool OnDragStart(InputState state)
{
return false;
}
public bool TriggerDrag(InputState state) => OnDrag(state);
protected virtual bool OnDrag(InputState state)
{
return false;
}
public bool TriggerDragEnd(InputState state) => OnDragEnd(state);
protected virtual bool OnDragEnd(InputState state)
{
return false;
}
public bool TriggerWheelUp(InputState state) => OnWheelUp(state);
protected virtual bool OnWheelUp(InputState state)
{
return false;
}
public bool TriggerWheelDown(InputState state) => OnWheelDown(state);
protected virtual bool OnWheelDown(InputState state)
{
return false;
}
/// <summary>
/// Focuses this drawable.
/// </summary>
/// <param name="state">The input state.</param>
/// <param name="checkCanFocus">Whether we should check this Drawable's OnFocus returns true before actually providing focus.</param>
public bool TriggerFocus(InputState state = null, bool checkCanFocus = false)
{
if (HasFocus)
return true;
if (checkCanFocus & !OnFocus(state))
return false;
ourInputManager?.ChangeFocus(this);
return true;
}
protected virtual bool OnFocus(InputState state)
{
return false;
}
/// <summary>
/// Unfocuses this drawable.
/// </summary>
/// <param name="state">The input state.</param>
internal void TriggerFocusLost(InputState state = null, bool isCallback = false)
{
if (!HasFocus)
return;
if (state == null)
state = new InputState();
if (!isCallback) ourInputManager.ChangeFocus(null);
OnFocusLost(state);
}
protected virtual void OnFocusLost(InputState state)
{
}
public bool TriggerKeyDown(InputState state, KeyDownEventArgs args) => OnKeyDown(state, args);
protected virtual bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
return false;
}
public bool TriggerKeyUp(InputState state, KeyUpEventArgs args) => OnKeyUp(state, args);
protected virtual bool OnKeyUp(InputState state, KeyUpEventArgs args)
{
return false;
}
public bool TriggerMouseMove(InputState state) => OnMouseMove(state);
protected virtual bool OnMouseMove(InputState state)
{
return false;
}
public bool HandleInput = true;
internal virtual bool HasFocus => ourInputManager?.FocusedDrawable == this;
internal bool Hovering;
/// <summary>
/// Sometimes we need to know the position of the mouse inside the drawable.
/// </summary>
/// <param name="screenSpacePos">A position in screen space (user input device).</param>
/// <returns>The relative (0..1) position inside (or outside) the drawable.</returns>
internal virtual Vector2? GetContainedPosition(Vector2 screenSpacePos)
{
return ScreenSpaceInputQuad.Contains(screenSpacePos);
}
public virtual Vector2 GetLocalPosition(Vector2 screenSpacePos)
{
return screenSpacePos * DrawInfo.MatrixInverse;
}
internal virtual bool Contains(Vector2 screenSpacePos)
{
return ScreenSpaceInputQuad.Contains(screenSpacePos).HasValue;
}
}
public class KeyDownEventArgs : EventArgs
{
public Key Key;
public bool Repeat;
}
public class MouseUpEventArgs : MouseEventArgs { }
public class MouseDownEventArgs : MouseEventArgs { }
public class MouseEventArgs : EventArgs
{
public MouseButton Button;
}
public class KeyUpEventArgs : EventArgs
{
public Key Key;
}
public delegate bool MouseEventHandlerDelegate(object sender, InputState state);
internal delegate bool KeyDownEventHandlerDelegate(object sender, KeyDownEventArgs e, InputState state);
internal delegate bool KeyUpEventHandlerDelegate(object sender, KeyUpEventArgs e, InputState state);
}

View File

@@ -0,0 +1,302 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using System;
using System.Collections.Generic;
using System.Text;
using OpenTK.Graphics;
using System.Diagnostics;
using OpenTK;
using osu.Framework.Graphics.Transformations;
namespace osu.Framework.Graphics
{
public partial class Drawable : IDisposable
{
private double transformationDelay;
public void ClearTransformations()
{
Transformations.Clear();
DelayReset();
}
public Drawable Delay(double duration, bool propagateChildren = false)
{
if (duration == 0) return this;
transformationDelay += duration;
if (propagateChildren)
Children.ForEach(c => c.Delay(duration, propagateChildren));
return this;
}
public Drawable DelayReset()
{
Delay(-transformationDelay);
Children.ForEach(c => c.DelayReset());
return this;
}
public void Loop(int delay = 0)
{
Transformations.ForEach(t =>
{
t.Loop = true;
t.LoopDelay = Math.Max(0, transformationDelay + delay - t.Duration);
});
}
/// <summary>
/// Make this drawable automatically clean itself up after all transformations have finished playing.
/// Can be delayed using Delay().
/// </summary>
public Drawable Expire(bool calculateLifetimeStart = false)
{
//expiry should happen either at the end of the last transformation or using the current sequence delay (whichever is highest).
double max = Time + transformationDelay;
foreach (Transformation t in Transformations)
if (t.Time2 > max) max = t.Time2 + 1; //adding 1ms here ensures we can expire on the current frame without issue.
LifetimeEnd = max;
if (calculateLifetimeStart)
{
double min = double.MaxValue;
foreach (Transformation t in Transformations)
if (t.Time1 < min) min = t.Time1;
LifetimeStart = min < Int32.MaxValue ? min : Int32.MinValue;
}
return this;
}
public void TimeWarp(double change)
{
if (change == 0)
return;
foreach (Transformation t in Transformations)
{
t.Time1 += change;
t.Time2 += change;
}
}
/// <summary>
/// Hide sprite instantly.
/// </summary>
/// <returns></returns>
public virtual void Hide()
{
FadeOut(0);
}
/// <summary>
/// Show sprite instantly.
/// </summary>
public virtual void Show()
{
FadeIn(0);
}
public Drawable FadeIn(double duration, EasingTypes easing = EasingTypes.None)
{
return FadeTo(1, duration, easing);
}
public Transformation FadeInFromZero(double duration)
{
if (transformationDelay == 0)
{
Alpha = 0;
Transformations.RemoveAll(t => t.Type == TransformationType.Fade);
}
double startTime = Time + transformationDelay;
Transformation tr = new Transformation(TransformationType.Fade, 0, 1, startTime, startTime + duration);
Transformations.Add(tr);
return tr;
}
public Drawable FadeOut(double duration, EasingTypes easing = EasingTypes.None)
{
return FadeTo(0, duration, easing);
}
public Transformation FadeOutFromOne(double duration)
{
if (transformationDelay == 0)
{
Alpha = 1;
Transformations.RemoveAll(t => t.Type == TransformationType.Fade);
}
double startTime = Time + transformationDelay;
Transformation tr =
new Transformation(TransformationType.Fade, 1, 0, startTime, startTime + duration);
Transformations.Add(tr);
return tr;
}
#region Float-based helpers
private Drawable transformFloatTo(float startValue, float newValue, double duration, EasingTypes easing, TransformationType transform)
{
if (transformationDelay == 0)
{
Transformations.RemoveAll(t => t.Type == transform);
if (startValue == newValue)
return this;
}
else
startValue = Transformations.FindLast(t => t.Type == transform)?.EndFloat ?? startValue;
double startTime = Time + transformationDelay;
Transformations.Add(new Transformation(transform, startValue, newValue, startTime, startTime + duration, easing));
return this;
}
public Drawable FadeTo(float newAlpha, double duration, EasingTypes easing = EasingTypes.None)
{
if (duration == 0)
{
Alpha = newAlpha;
return this;
}
return transformFloatTo(Alpha, newAlpha, duration, easing, TransformationType.Fade);
}
public Drawable ScaleTo(float newScale, double duration, EasingTypes easing = EasingTypes.None)
{
if (duration == 0)
{
Scale = newScale;
return this;
}
return transformFloatTo(Scale, newScale, duration, easing, TransformationType.Scale);
}
public Drawable RotateTo(float newRotation, double duration, EasingTypes easing = EasingTypes.None)
{
if (duration == 0)
{
Rotation = newRotation;
return this;
}
return transformFloatTo(Rotation, newRotation, duration, easing, TransformationType.Rotation);
}
[Obsolete]
public Drawable MoveToX(float destination, double duration, EasingTypes easing = EasingTypes.None)
{
if (duration == 0)
{
Position = new Vector2(destination, Position.Y);
return this;
}
return transformFloatTo(Position.X, destination, duration, easing, TransformationType.MovementX);
}
[Obsolete]
public Drawable MoveToY(float destination, double duration, EasingTypes easing = EasingTypes.None)
{
if (duration == 0)
{
Position = new Vector2(Position.X, destination);
return this;
}
return transformFloatTo(Position.Y, destination, duration, easing, TransformationType.MovementY);
}
#endregion
#region Vector2-based helpers
private Drawable transformVectorTo(Vector2 startValue, Vector2 newValue, double duration, EasingTypes easing, TransformationType transform)
{
if (transformationDelay == 0)
{
Transformations.RemoveAll(t => t.Type == transform);
if (startValue == newValue)
return this;
}
else
startValue = Transformations.FindLast(t => t.Type == transform)?.EndVector ?? startValue;
double startTime = Time + transformationDelay;
Transformations.Add(new Transformation(transform, startValue, newValue, startTime, startTime + duration, easing));
return this;
}
public Drawable ScaleTo(Vector2 newScale, double duration, EasingTypes easing = EasingTypes.None)
{
if (duration == 0)
{
VectorScale = newScale;
return this;
}
return transformVectorTo(VectorScale, newScale, duration, easing, TransformationType.VectorScale);
}
public Drawable MoveTo(Vector2 newPosition, double duration, EasingTypes easing = EasingTypes.None)
{
if (duration == 0)
{
Position = newPosition;
return this;
}
return transformVectorTo(Position, newPosition, duration, easing, TransformationType.Movement);
}
public Drawable MoveToRelative(Vector2 offset, int duration, EasingTypes easing = EasingTypes.None)
{
return MoveTo(Transformations.FindLast(t => t.Type == TransformationType.Movement)?.EndVector ?? Position + offset, duration, easing);
}
#endregion
#region Color4-based helpers
public Drawable FadeColour(Color4 newColour, int duration, EasingTypes easing = EasingTypes.None)
{
Color4 startValue = Colour;
if (transformationDelay == 0)
{
Transformations.RemoveAll(t => t.Type == TransformationType.Colour);
if (startValue == newColour)
return this;
}
else
startValue = Transformations.FindLast(t => t.Type == TransformationType.Colour)?.EndColour ?? startValue;
double startTime = Time + transformationDelay;
Transformations.Add(new Transformation(startValue, newColour, startTime, startTime + duration, easing));
return this;
}
public Drawable FlashColour(Color4 flashColour, int duration)
{
Debug.Assert(transformationDelay == 0, @"FlashColour doesn't support Delay() currently");
Color4 startValue = Transformations.FindLast(t => t.Type == TransformationType.Colour)?.EndColour ?? Colour;
Transformations.RemoveAll(t => t.Type == TransformationType.Colour);
double startTime = Time + transformationDelay;
Transformations.Add(new Transformation(flashColour, startValue, startTime, startTime + duration));
return this;
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK.Graphics;
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Batches;
using osu.Framework.Graphics.OpenGL;
using osu.Framework.Graphics.Shaders;
namespace osu.Framework.Graphics.Drawables
{
public class Box : Drawable
{
private QuadBatch<Vertex2d> quadBatch = new QuadBatch<Vertex2d>(1, 3);
protected override IVertexBatch ActiveBatch => quadBatch;
private static Shader shader;
protected override void Draw()
{
base.Draw();
if (shader == null)
shader = Game.Shaders.Load(VertexShader.Colour, FragmentShader.Colour);
shader.Bind();
quadBatch.Add(new Vertex2d() { Colour = DrawInfo.Colour, Position = ScreenSpaceDrawQuad.BottomLeft });
quadBatch.Add(new Vertex2d() { Colour = DrawInfo.Colour, Position = ScreenSpaceDrawQuad.BottomRight });
quadBatch.Add(new Vertex2d() { Colour = DrawInfo.Colour, Position = ScreenSpaceDrawQuad.TopRight });
quadBatch.Add(new Vertex2d() { Colour = DrawInfo.Colour, Position = ScreenSpaceDrawQuad.TopLeft });
quadBatch.Draw();
shader.Unbind();
}
}
}

View File

@@ -0,0 +1,132 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using OpenTK.Graphics.ES20;
using osu.Framework.Graphics.OpenGL.Textures;
using System;
using System.Collections.Generic;
using System.Text;
namespace osu.Framework.Graphics.OpenGL.Buffers
{
class FrameBuffer : IDisposable
{
private int lastFramebuffer;
private int frameBuffer = -1;
internal TextureGL Texture { get; private set; }
private bool IsBound => lastFramebuffer != -1;
private List<RenderBuffer> attachedRenderBuffers = new List<RenderBuffer>();
internal FrameBuffer(bool withTexture = true)
{
frameBuffer = GL.GenFramebuffer();
if (withTexture)
{
Texture = new TextureGLSingle(1, 1);
Texture.SetData(new byte[0]);
Texture.Upload();
Bind();
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferSlot.ColorAttachment0, TextureTarget.Texture2D, Texture.TextureId, 0);
GLWrapper.BindTexture(0);
Unbind();
}
}
#region Disposal
~FrameBuffer()
{
Dispose(false);
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
private bool isDisposed;
protected virtual void Dispose(bool disposing)
{
if (isDisposed)
return;
isDisposed = true;
Unbind();
GLWrapper.DeleteFramebuffer(frameBuffer);
frameBuffer = -1;
}
#endregion
private Vector2 size = Vector2.One;
/// <summary>
/// Sets the size of the texture of this framebuffer.
/// </summary>
internal Vector2 Size
{
get { return size; }
set
{
if (value == size)
return;
size = value;
Texture.Width = (int)Math.Ceiling(size.X);
Texture.Height = (int)Math.Ceiling(size.Y);
Texture.SetData(new byte[0]);
Texture.Upload();
}
}
/// <summary>
/// Attaches a RenderBuffer to this framebuffer.
/// </summary>
/// <param name="format">The type of RenderBuffer to attach.</param>
internal void Attach(RenderbufferInternalFormat format)
{
if (attachedRenderBuffers.Exists(r => r.Format == format))
return;
attachedRenderBuffers.Add(new RenderBuffer(format));
}
/// <summary>
/// Binds the framebuffer.
/// <para>Does not clear the buffer or reset the viewport/ortho.</para>
/// </summary>
internal void Bind()
{
if (frameBuffer == -1)
return;
if (lastFramebuffer == frameBuffer)
return;
// Bind framebuffer and all its renderbuffers
lastFramebuffer = GLWrapper.BindFrameBuffer(frameBuffer);
attachedRenderBuffers.ForEach(r => r.Bind(frameBuffer));
}
/// <summary>
/// Unbinds the framebuffer.
/// </summary>
internal void Unbind()
{
if (!IsBound)
return;
GLWrapper.BindFrameBuffer(lastFramebuffer);
attachedRenderBuffers.ForEach(r => r.Unbind());
lastFramebuffer = -1;
}
}
}

View File

@@ -0,0 +1,69 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK.Graphics.ES20;
using osu.Framework.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace osu.Framework.Graphics.OpenGL.Buffers
{
static class LinearIndexData
{
static LinearIndexData()
{
GL.GenBuffers(1, out LinearIndexData.EboId);
}
public static readonly int EboId;
public static int MaxAmountIndices;
}
/// <summary>
/// This type of vertex buffer lets the ith vertex be referenced by the ith index.
/// </summary>
public class LinearVertexBuffer<T> : VertexBuffer<T> where T : struct, IEquatable<T>
{
private BeginMode type;
public LinearVertexBuffer(int amountVertices, BeginMode type, BufferUsageHint usage)
: base(amountVertices, usage)
{
this.type = type;
if (amountVertices > LinearIndexData.MaxAmountIndices)
{
ushort[] indices = new ushort[amountVertices];
for (ushort i = 0; i < amountVertices; i++)
indices[i] = i;
GLWrapper.BindBuffer(BufferTarget.ElementArrayBuffer, LinearIndexData.EboId);
GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(amountVertices * sizeof(ushort)), indices, BufferUsageHint.StaticDraw);
LinearIndexData.MaxAmountIndices = amountVertices;
}
}
public override void Bind(bool forRendering)
{
base.Bind(forRendering);
if (forRendering)
GLWrapper.BindBuffer(BufferTarget.ElementArrayBuffer, LinearIndexData.EboId);
}
public override void Unbind()
{
base.Unbind();
}
protected override BeginMode Type
{
get { return type; }
}
}
}

View File

@@ -0,0 +1,79 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK.Graphics.ES20;
using osu.Framework.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace osu.Framework.Graphics.OpenGL.Buffers
{
static class QuadIndexData
{
static QuadIndexData()
{
GL.GenBuffers(1, out QuadIndexData.EboId);
}
public static readonly int EboId;
public static int MaxAmountIndices;
}
public class QuadVertexBuffer<T> : VertexBuffer<T> where T : struct, IEquatable<T>
{
public QuadVertexBuffer(int amountQuads, BufferUsageHint usage)
: base(amountQuads * 4, usage)
{
int amountIndices = amountQuads * 6;
if (amountIndices > QuadIndexData.MaxAmountIndices)
{
ushort[] indices = new ushort[amountIndices];
for (ushort i = 0, j = 0; j < amountIndices; i += 4, j += 6)
{
indices[j] = i;
indices[j + 1] = (ushort)(i + 1);
indices[j + 2] = (ushort)(i + 3);
indices[j + 3] = (ushort)(i + 2);
indices[j + 4] = (ushort)(i + 3);
indices[j + 5] = (ushort)(i + 1);
}
GLWrapper.BindBuffer(BufferTarget.ElementArrayBuffer, QuadIndexData.EboId);
GL.BufferData(BufferTarget.ElementArrayBuffer, (IntPtr)(amountIndices * sizeof(ushort)), indices, BufferUsageHint.StaticDraw);
QuadIndexData.MaxAmountIndices = amountIndices;
}
}
public override void Bind(bool forRendering)
{
base.Bind(forRendering);
if (forRendering)
GLWrapper.BindBuffer(BufferTarget.ElementArrayBuffer, QuadIndexData.EboId);
}
public override void Unbind()
{
base.Unbind();
}
protected override int ToElements(int vertices)
{
return 3 * vertices / 2;
}
protected override int ToElementIndex(int verticexIndex)
{
return 3 * verticexIndex / 2;
}
protected override BeginMode Type
{
get { return BeginMode.Triangles; }
}
}
}

View File

@@ -0,0 +1,121 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT License - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK.Graphics.ES20;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
namespace osu.Framework.Graphics.OpenGL.Buffers
{
class RenderBuffer : IDisposable
{
private static Dictionary<RenderbufferInternalFormat, ConcurrentStack<RenderBufferInfo>> renderBufferCache = new Dictionary<RenderbufferInternalFormat, ConcurrentStack<RenderBufferInfo>>();
private RenderBufferInfo info;
private bool isDisposed;
internal RenderbufferInternalFormat Format { get; private set; }
internal RenderBuffer(RenderbufferInternalFormat format)
{
this.Format = format;
info.ID = -1;
info.LastFramebuffer = -1;
}
#region Disposal
~RenderBuffer()
{
Dispose(false);
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (isDisposed)
return;
isDisposed = true;
Unbind();
}
#endregion
/// <summary>
/// Binds the renderbuffer to the specfied framebuffer.
/// </summary>
/// <param name="frameBuffer">The framebuffer this renderbuffer should be bound to.</param>
internal void Bind(int frameBuffer)
{
if (info.ID != -1)
return;
if (!renderBufferCache.ContainsKey(Format))
renderBufferCache[Format] = new ConcurrentStack<RenderBufferInfo>();
// Make sure we have renderbuffers available
if (renderBufferCache[Format].Count == 0)
{
int newBuffer = GL.GenRenderbuffer();
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, newBuffer);
GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, Format, Game.Window.Width, Game.Window.Height);
renderBufferCache[Format].Push(new RenderBufferInfo() { ID = newBuffer, LastFramebuffer = -1 });
}
// Get a renderbuffer from the cache
renderBufferCache[Format].TryPop(out info);
// For performance reasons, we only need to re-bind the renderbuffer to
// the framebuffer if it is not already attached to it
if (info.LastFramebuffer != frameBuffer)
{
// Make sure the framebuffer we want to attach to is bound
int lastFrameBuffer = GLWrapper.BindFrameBuffer(frameBuffer);
switch (Format)
{
case RenderbufferInternalFormat.DepthComponent16:
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferSlot.DepthAttachment, RenderbufferTarget.Renderbuffer, info.ID);
break;
case RenderbufferInternalFormat.Rgb565:
case RenderbufferInternalFormat.Rgb5A1:
case RenderbufferInternalFormat.Rgba4:
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferSlot.ColorAttachment0, RenderbufferTarget.Renderbuffer, info.ID);
break;
case RenderbufferInternalFormat.StencilIndex8:
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferSlot.DepthAttachment, RenderbufferTarget.Renderbuffer, info.ID);
break;
}
GLWrapper.BindFrameBuffer(lastFrameBuffer);
}
info.LastFramebuffer = frameBuffer;
}
/// <summary>
/// Unbinds the renderbuffer.
/// <para>The renderbuffer will remain internally attached to the framebuffer.</para>
/// </summary>
internal void Unbind()
{
// Return the renderbuffer to the cache
renderBufferCache[Format].Push(info);
info.ID = -1;
}
private struct RenderBufferInfo
{
public int ID;
public int LastFramebuffer;
}
}
}

Some files were not shown because too many files have changed in this diff Show More