[🐧] Mono work from fork, see #1438
This commit is contained in:
commit
d9a69c0ccb
|
@ -12,6 +12,7 @@ svnrev.cs
|
|||
**/ipch/**
|
||||
|
||||
*.ilk
|
||||
*.il
|
||||
*.tlog
|
||||
*.obj
|
||||
*.o
|
||||
|
@ -73,3 +74,5 @@ ExternalCoreProjects/Virtu/bin/*.*
|
|||
libsnes/vs2015/libsnes.VC.db
|
||||
waterbox/**/*.wbx
|
||||
waterbox/**/*.wbx.in
|
||||
|
||||
mono_crash*
|
||||
|
|
|
@ -9,7 +9,7 @@ if [ "$(command -v lsb_release)" ]; then
|
|||
case "$(lsb_release -i | cut -c17- | tr -d "\n")" in
|
||||
"Arch"|"ManjaroLinux") libpath="/usr/lib/wine";;
|
||||
"Debian"|"LinuxMint") libpath="/usr/lib/x86_64-linux-gnu/wine";;
|
||||
"Ubuntu") libpath="/usr/lib/x86_64-linux-gnu/wine"; MONO_WINFORMS_XIM_STYLE=disabled;; # see https://bugzilla.xamarin.com/show_bug.cgi?id=28047#c9
|
||||
"Ubuntu") libpath="/usr/lib/x86_64-linux-gnu/wine"; export MONO_WINFORMS_XIM_STYLE=disabled;; # see https://bugzilla.xamarin.com/show_bug.cgi?id=28047#c9
|
||||
esac
|
||||
else
|
||||
printf "Distro does not provide LSB release info API! (You've met with a terrible fate, haven't you?)\n"
|
||||
|
@ -18,4 +18,4 @@ if [ -z "$libpath" ]; then
|
|||
printf "%s\n" "Unknown distro, assuming WINE library location is /usr/lib/wine..."
|
||||
libpath="/usr/lib/wine"
|
||||
fi
|
||||
LD_LIBRARY_PATH="$libpath" mono ./EmuHawk.exe
|
||||
LD_LIBRARY_PATH="$libpath" mono ./EmuHawk.exe >mono_lastrun_stdout.txt
|
||||
|
|
|
@ -56,6 +56,9 @@
|
|||
<HintPath>..\output\dll\nlua\NLua.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="SharpCompress">
|
||||
<HintPath>..\References\SharpCompress.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.SQLite, Version=1.0.105.2, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=AMD64">
|
||||
|
@ -255,6 +258,7 @@
|
|||
<Compile Include="RomLoader.cs" />
|
||||
<Compile Include="SaveSlotManager.cs" />
|
||||
<Compile Include="SavestateManager.cs" />
|
||||
<Compile Include="SharpCompressArchiveHandler.cs" />
|
||||
<Compile Include="SevenZipSharpArchiveHandler.cs" />
|
||||
<Compile Include="SevenZipWriter.cs" />
|
||||
<Compile Include="SharpZipWriter.cs" />
|
||||
|
@ -313,12 +317,15 @@
|
|||
<Project>{E6B436B1-A3CD-4C9A-8F76-5D7154726884}</Project>
|
||||
<Name>BizHawk.Bizware.BizwareGL.SlimDX</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Bizware\BizHawk.Bizware.BizwareGL.Vulkan\BizHawk.Bizware.BizwareGL.Vulkan.csproj">
|
||||
<Project>{538947c1-b7f6-8a0d-c976-41ee5160cfb1}</Project>
|
||||
<Name>BizHawk.Bizware.BizwareGL.Vulkan</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Bizware\BizHawk.Bizware.BizwareGL\BizHawk.Bizware.BizwareGL.csproj">
|
||||
<Project>{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}</Project>
|
||||
<Name>BizHawk.Bizware.BizwareGL</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>
|
||||
|
|
|
@ -3,6 +3,7 @@ using BizHawk.Bizware.BizwareGL;
|
|||
using BizHawk.Bizware.BizwareGL.Drivers.GdiPlus;
|
||||
using BizHawk.Bizware.BizwareGL.Drivers.OpenTK;
|
||||
using BizHawk.Bizware.BizwareGL.Drivers.SlimDX;
|
||||
using BizHawk.Bizware.BizwareGL.Drivers.Vulkan;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
|
@ -10,12 +11,7 @@ namespace BizHawk.Client.Common
|
|||
{
|
||||
public static IGuiRenderer CreateRenderer(this IGL gl)
|
||||
{
|
||||
if (gl is IGL_TK)
|
||||
{
|
||||
return new GuiRenderer(gl);
|
||||
}
|
||||
|
||||
if (gl is IGL_SlimDX9)
|
||||
if (gl is IGL_Vulkan || gl is IGL_TK || gl is IGL_SlimDX9)
|
||||
{
|
||||
return new GuiRenderer(gl);
|
||||
}
|
||||
|
|
|
@ -407,9 +407,24 @@ namespace BizHawk.Client.Common
|
|||
GetGlobalBasePathAbsolute() :
|
||||
MakeAbsolutePath(GetPlatformBase(system), system);
|
||||
|
||||
if (IsSubfolder(parentPath, absolutePath))
|
||||
if (!BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
return absolutePath.Replace(parentPath, ".");
|
||||
if (IsSubfolder(parentPath, absolutePath))
|
||||
{
|
||||
return absolutePath.Replace(parentPath, ".");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// weird kludges for linux systems
|
||||
if (IsSubfolder(parentPath, absolutePath))
|
||||
{
|
||||
return absolutePath.Replace(parentPath.TrimEnd('.'), "./");
|
||||
}
|
||||
else if (parentPath.TrimEnd('.') == absolutePath + "/")
|
||||
{
|
||||
return ".";
|
||||
}
|
||||
}
|
||||
|
||||
return absolutePath;
|
||||
|
@ -428,21 +443,51 @@ namespace BizHawk.Client.Common
|
|||
// http://stackoverflow.com/questions/3525775/how-to-check-if-directory-1-is-a-subdirectory-of-dir2-and-vice-versa
|
||||
private static bool IsSubfolder(string parentPath, string childPath)
|
||||
{
|
||||
var parentUri = new Uri(parentPath);
|
||||
|
||||
var childUri = new DirectoryInfo(childPath).Parent;
|
||||
|
||||
while (childUri != null)
|
||||
if (!BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
if (new Uri(childUri.FullName) == parentUri)
|
||||
var parentUri = new Uri(parentPath);
|
||||
|
||||
var childUri = new DirectoryInfo(childPath).Parent;
|
||||
|
||||
while (childUri != null)
|
||||
{
|
||||
return true;
|
||||
if (new Uri(childUri.FullName) == parentUri)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
childUri = childUri.Parent;
|
||||
}
|
||||
|
||||
childUri = childUri.Parent;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// more weird linux stuff
|
||||
var parentUri = new Uri(parentPath.TrimEnd('.'));
|
||||
var childUri = new DirectoryInfo(childPath).Parent;
|
||||
|
||||
return false;
|
||||
try
|
||||
{
|
||||
while (childUri != null)
|
||||
{
|
||||
var ch = new Uri(childUri.FullName).AbsolutePath.TrimEnd('/');
|
||||
var pr = parentUri.AbsolutePath.TrimEnd('/');
|
||||
if (ch == pr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
childUri = childUri.Parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Client.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of IHawkFileArchiveHandler using SharpCompress library
|
||||
/// Pure c# implementation for Mono (although this will work on Windows as well - but probably not as performant as SevenZipSharp)
|
||||
/// </summary>
|
||||
public class SharpCompressArchiveHandler : IHawkFileArchiveHandler
|
||||
{
|
||||
private SharpCompress.Archives.IArchive _archive;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_archive != null)
|
||||
{
|
||||
_archive.Dispose();
|
||||
_archive = null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckSignature(string fileName, out int offset, out bool isExecutable)
|
||||
{
|
||||
offset = 0;
|
||||
isExecutable = false;
|
||||
|
||||
SharpCompress.Archives.IArchive arcTest = null;
|
||||
|
||||
try
|
||||
{
|
||||
arcTest = SharpCompress.Archives.ArchiveFactory.Open(fileName);
|
||||
var aType = arcTest.Type;
|
||||
|
||||
switch(arcTest.Type)
|
||||
{
|
||||
case SharpCompress.Common.ArchiveType.Zip:
|
||||
case SharpCompress.Common.ArchiveType.SevenZip:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
if (arcTest != null)
|
||||
arcTest.Dispose();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IHawkFileArchiveHandler Construct(string path)
|
||||
{
|
||||
var ret = new SharpCompressArchiveHandler();
|
||||
ret.Open(path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void Open(string path)
|
||||
{
|
||||
_archive = SharpCompress.Archives.ArchiveFactory.Open(path);
|
||||
}
|
||||
|
||||
public List<HawkFileArchiveItem> Scan()
|
||||
{
|
||||
var ret = new List<HawkFileArchiveItem>();
|
||||
|
||||
int idx = 0;
|
||||
foreach (var i in _archive.Entries)
|
||||
{
|
||||
if (i.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var ai = new HawkFileArchiveItem
|
||||
{
|
||||
Name = HawkFile.Util_FixArchiveFilename(i.Key),
|
||||
Size = (long)i.Size,
|
||||
ArchiveIndex = idx++,
|
||||
Index = ret.Count
|
||||
};
|
||||
|
||||
ret.Add(ai);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void ExtractFile(int index, Stream stream)
|
||||
{
|
||||
int idx = 0;
|
||||
|
||||
foreach (var i in _archive.Entries)
|
||||
{
|
||||
if (i.IsDirectory)
|
||||
continue;
|
||||
|
||||
if (idx++ == index)
|
||||
{
|
||||
using (var entryStream = i.OpenEntryStream())
|
||||
{
|
||||
entryStream.CopyTo(stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -170,7 +170,7 @@ namespace BizHawk.Client.Common
|
|||
|
||||
public enum EDispMethod
|
||||
{
|
||||
OpenGL, GdiPlus, SlimDX9
|
||||
OpenGL, GdiPlus, SlimDX9, Vulkan
|
||||
}
|
||||
|
||||
public enum ESoundOutputMethod
|
||||
|
@ -499,6 +499,7 @@ namespace BizHawk.Client.Common
|
|||
|
||||
// TAStudio
|
||||
public TasStateManagerSettings DefaultTasProjSettings = new TasStateManagerSettings();
|
||||
public int TasStudioRenderer = 0; // defaults to 0 (GDI) - on linux this is forced to GDI+ later on
|
||||
|
||||
// Macro Tool
|
||||
public RecentFiles RecentMacros = new RecentFiles(8);
|
||||
|
|
|
@ -44,20 +44,23 @@ namespace BizHawk.Client.Common
|
|||
|
||||
// WARNING: setting the current directory is SLOW!!! security checks for some reason.
|
||||
// so we're bypassing it with windows hacks
|
||||
#if WINDOWS
|
||||
if (!BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
fixed (byte* pstr = &System.Text.Encoding.Unicode.GetBytes(target + "\0")[0])
|
||||
return SetCurrentDirectoryW(pstr);
|
||||
#else
|
||||
if (System.IO.Directory.Exists(CurrentDirectory)) // race condition for great justice
|
||||
}
|
||||
else
|
||||
{
|
||||
if (System.IO.Directory.Exists(_currentDirectory)) // race condition for great justice
|
||||
{
|
||||
Environment.CurrentDirectory = CurrentDirectory; // thats right, you can't set a directory as current that doesnt exist because .net's got to do SENSELESS SLOW-ASS SECURITY CHECKS on it and it can't do that on a NONEXISTENT DIRECTORY
|
||||
Environment.CurrentDirectory = _currentDirectory; // thats right, you can't set a directory as current that doesnt exist because .net's got to do SENSELESS SLOW-ASS SECURITY CHECKS on it and it can't do that on a NONEXISTENT DIRECTORY
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private string CoolGetCurrentDirectory()
|
||||
|
@ -66,16 +69,19 @@ namespace BizHawk.Client.Common
|
|||
// .NET DOES A SECURITY CHECK ON THE DIRECTORY WE JUST RETRIEVED
|
||||
// AS IF ASKING FOR THE CURRENT DIRECTORY IS EQUIVALENT TO TRYING TO ACCESS IT
|
||||
// SCREW YOU
|
||||
#if WINDOWS
|
||||
if (!BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
var buf = new byte[32768];
|
||||
fixed(byte* pBuf = &buf[0])
|
||||
fixed (byte* pBuf = &buf[0])
|
||||
{
|
||||
uint ret = GetCurrentDirectoryW(32767, pBuf);
|
||||
return System.Text.Encoding.Unicode.GetString(buf, 0, (int)ret*2);
|
||||
return System.Text.Encoding.Unicode.GetString(buf, 0, (int)ret * 2);
|
||||
}
|
||||
#else
|
||||
}
|
||||
else
|
||||
{
|
||||
return Environment.CurrentDirectory;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void Sandbox(Action callback, Action exceptionCallback)
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
<HintPath>..\output\dll\nlua\NLua.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\References\OpenTK.dll</HintPath>
|
||||
</Reference>
|
||||
|
@ -572,7 +572,7 @@
|
|||
<Compile Include="CustomControls\FolderBrowserDialogEx.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\GDIRenderer.cs" />
|
||||
<Compile Include="CustomControls\GDI.cs" />
|
||||
<Compile Include="CustomControls\HexTextBox.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
|
@ -589,6 +589,14 @@
|
|||
<DependentUpon>InputRoll.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\InputRoll.Drawing.GDI.cs">
|
||||
<DependentUpon>InputRoll.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\InputRoll.Drawing.GDIP.cs">
|
||||
<DependentUpon>InputRoll.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\MenuButton.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
|
@ -601,6 +609,30 @@
|
|||
<Compile Include="CustomControls\MsgBox.designer.cs">
|
||||
<DependentUpon>MsgBox.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\PlatformAgnosticVirtualListView.API.cs">
|
||||
<DependentUpon>PlatformAgnosticVirtualListView.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\PlatformAgnosticVirtualListView.Classes.cs">
|
||||
<DependentUpon>PlatformAgnosticVirtualListView.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\PlatformAgnosticVirtualListView.Drawing.cs">
|
||||
<DependentUpon>PlatformAgnosticVirtualListView.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\PlatformAgnosticVirtualListView.EventHandlers.cs">
|
||||
<DependentUpon>PlatformAgnosticVirtualListView.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\PlatformAgnosticVirtualListView.Helpers.cs">
|
||||
<DependentUpon>PlatformAgnosticVirtualListView.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\PlatformAgnosticVirtualListView.Properties.cs">
|
||||
<DependentUpon>PlatformAgnosticVirtualListView.cs</DependentUpon>
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\PrereqsAlert.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
@ -639,6 +671,7 @@
|
|||
</Compile>
|
||||
<Compile Include="CustomControls\VirtualListView.cs" />
|
||||
<Compile Include="CustomControls\Win32.cs" />
|
||||
<Compile Include="CustomControls\Win32GDIRenderer.cs" />
|
||||
<Compile Include="DisplayManager\DisplayManager.cs" />
|
||||
<Compile Include="DisplayManager\DisplaySurface.cs" />
|
||||
<Compile Include="DisplayManager\FilterManager.cs" />
|
||||
|
@ -738,6 +771,9 @@
|
|||
<Compile Include="OpenAdvancedChooser.Designer.cs">
|
||||
<DependentUpon>OpenAdvancedChooser.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="CustomControls\PlatformAgnosticVirtualListView.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
<Compile Include="PlatformChooser.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
|
@ -1794,6 +1830,10 @@
|
|||
<Project>{E6B436B1-A3CD-4C9A-8F76-5D7154726884}</Project>
|
||||
<Name>BizHawk.Bizware.BizwareGL.SlimDX</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Bizware\BizHawk.Bizware.BizwareGL.Vulkan\BizHawk.Bizware.BizwareGL.Vulkan.csproj">
|
||||
<Project>{538947c1-b7f6-8a0d-c976-41ee5160cfb1}</Project>
|
||||
<Name>BizHawk.Bizware.BizwareGL.Vulkan</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Bizware\BizHawk.Bizware.BizwareGL\BizHawk.Bizware.BizwareGL.csproj">
|
||||
<Project>{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}</Project>
|
||||
<Name>BizHawk.Bizware.BizwareGL</Name>
|
||||
|
@ -2253,6 +2293,9 @@
|
|||
<ItemGroup>
|
||||
<Folder Include="config\Saturn\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<WCFMetadata Include="Connected Services\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\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.
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk.CustomControls
|
||||
{
|
||||
/// <summary>
|
||||
/// Singleton holding GDIRenderer and associated types
|
||||
/// </summary>
|
||||
public sealed class GDI
|
||||
{
|
||||
private GDI() {}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for GDI rendering functions
|
||||
/// Inheritors are not thread-safe as GDI functions should be called from the UI thread
|
||||
/// </summary>
|
||||
public interface GDIRenderer : IDisposable
|
||||
{
|
||||
void CopyToScreen();
|
||||
void DrawBitmap(Bitmap bitmap, Point point, bool blend = false);
|
||||
void DrawRectangle(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
|
||||
void DrawString(string str, Point point);
|
||||
void EndOffScreenBitmap();
|
||||
void FillRectangle(int x, int y, int w, int h);
|
||||
void Line(int x1, int y1, int x2, int y2);
|
||||
GDIGraphicsLock<GDIRenderer> LockGraphics(Graphics g);
|
||||
Size MeasureString(string str, Font font);
|
||||
void PrepDrawString(IntPtr hfont, Color color);
|
||||
void SetBrush(Color color);
|
||||
void SetSolidPen(Color color);
|
||||
void StartOffScreenBitmap(int width, int height);
|
||||
|
||||
/// <summary>
|
||||
/// do not use outside GDI.GDIGraphicsLock<*>
|
||||
/// </summary>
|
||||
void HackDisposeGraphics();
|
||||
}
|
||||
|
||||
public class GDIGraphicsLock<R> : IDisposable where R : GDIRenderer
|
||||
{
|
||||
private readonly R Renderer;
|
||||
public GDIGraphicsLock(R gdi)
|
||||
{
|
||||
Renderer = gdi;
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
Renderer.HackDisposeGraphics();
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ETOOptions : uint
|
||||
{
|
||||
CLIPPED = 0x4,
|
||||
GLYPH_INDEX = 0x10,
|
||||
IGNORELANGUAGE = 0x1000,
|
||||
NUMERICSLATIN = 0x800,
|
||||
NUMERICSLOCAL = 0x400,
|
||||
OPAQUE = 0x2,
|
||||
PDY = 0x2000,
|
||||
RTLREADING = 0x800,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See [http://msdn.microsoft.com/en-us/library/windows/desktop/dd162498(v=vs.85).aspx][15]
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TextFormatFlags : uint
|
||||
{
|
||||
Default = 0x00000000,
|
||||
Center = 0x00000001,
|
||||
Right = 0x00000002,
|
||||
VCenter = 0x00000004,
|
||||
Bottom = 0x00000008,
|
||||
WordBreak = 0x00000010,
|
||||
SingleLine = 0x00000020,
|
||||
ExpandTabs = 0x00000040,
|
||||
TabStop = 0x00000080,
|
||||
NoClip = 0x00000100,
|
||||
ExternalLeading = 0x00000200,
|
||||
CalcRect = 0x00000400,
|
||||
NoPrefix = 0x00000800,
|
||||
Internal = 0x00001000,
|
||||
EditControl = 0x00002000,
|
||||
PathEllipsis = 0x00004000,
|
||||
EndEllipsis = 0x00008000,
|
||||
ModifyString = 0x00010000,
|
||||
RtlReading = 0x00020000,
|
||||
WordEllipsis = 0x00040000,
|
||||
NoFullWidthCharBreak = 0x00080000,
|
||||
HidePrefix = 0x00100000,
|
||||
ProfixOnly = 0x00200000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PenStyles
|
||||
{
|
||||
PS_SOLID = 0x00000000
|
||||
// TODO
|
||||
}
|
||||
|
||||
public enum PaintObjects
|
||||
{
|
||||
WHITE_BRUSH = 0,
|
||||
LTGRAY_BRUSH = 1,
|
||||
GRAY_BRUSH = 2,
|
||||
DKGRAY_BRUSH = 3,
|
||||
BLACK_BRUSH = 4,
|
||||
NULL_BRUSH = 5,
|
||||
WHITE_PEN = 6,
|
||||
BLACK_PEN = 7,
|
||||
NULL_PEN = 8,
|
||||
OEM_FIXED_FONT = 10,
|
||||
ANSI_FIXED_FONT = 11,
|
||||
ANSI_VAR_FONT = 12,
|
||||
SYSTEM_FONT = 13,
|
||||
DEVICE_DEFAULT_FONT = 14,
|
||||
DEFAULT_PALETTE = 15,
|
||||
SYSTEM_FIXED_FONT = 16,
|
||||
DC_BRUSH = 18,
|
||||
DC_PEN = 19,
|
||||
}
|
||||
|
||||
public enum BkModes : int
|
||||
{
|
||||
TRANSPARENT = 1,
|
||||
OPAQUE = 2
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
public class HexView : Control
|
||||
{
|
||||
private readonly GDIRenderer Gdi;
|
||||
private readonly GDI.GDIRenderer Gdi;
|
||||
private readonly Font NormalFont;
|
||||
private Size _charSize;
|
||||
|
||||
|
@ -27,7 +27,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
|
||||
SetStyle(ControlStyles.Opaque, true);
|
||||
|
||||
Gdi = new GDIRenderer();
|
||||
Gdi = new Win32GDIRenderer();
|
||||
|
||||
using (var g = CreateGraphics())
|
||||
using (var LCK = Gdi.LockGraphics(g))
|
||||
|
|
|
@ -0,0 +1,511 @@
|
|||
using BizHawk.Client.EmuHawk.CustomControls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// GDI32.dll related methods are abstracted to here
|
||||
/// </summary>
|
||||
public partial class InputRoll
|
||||
{
|
||||
private readonly GDI.GDIRenderer _gdi;
|
||||
private readonly IntPtr _rotatedFont;
|
||||
private readonly IntPtr _normalFont;
|
||||
private Size _charSize;
|
||||
|
||||
#region Initialization and Destruction
|
||||
|
||||
/// <summary>
|
||||
/// Initializes GDI related stuff
|
||||
/// (called from constructor)
|
||||
/// </summary>
|
||||
private void GDIConstruction()
|
||||
{
|
||||
using (var g = CreateGraphics())
|
||||
using (_gdi.LockGraphics(g))
|
||||
{
|
||||
_charSize = _gdi.MeasureString("A", _commonFont); // TODO make this a property so changing it updates other values.
|
||||
}
|
||||
}
|
||||
|
||||
private void GDIDispose()
|
||||
{
|
||||
_gdi.Dispose();
|
||||
|
||||
Win32GDIRenderer.DestroyHFont(_normalFont);
|
||||
Win32GDIRenderer.DestroyHFont(_rotatedFont);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drawing Methods Using GDI
|
||||
|
||||
private void GDI_OnPaint(PaintEventArgs e)
|
||||
{
|
||||
using (_gdi.LockGraphics(e.Graphics))
|
||||
{
|
||||
_gdi.StartOffScreenBitmap(Width, Height);
|
||||
|
||||
// White Background
|
||||
_gdi.SetBrush(Color.White);
|
||||
_gdi.SetSolidPen(Color.White);
|
||||
_gdi.FillRectangle(0, 0, Width, Height);
|
||||
|
||||
// Lag frame calculations
|
||||
SetLagFramesArray();
|
||||
|
||||
var visibleColumns = _columns.VisibleColumns.ToList();
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
DrawColumnBg(e, visibleColumns);
|
||||
DrawColumnText(e, visibleColumns);
|
||||
}
|
||||
|
||||
// Background
|
||||
DrawBg(e, visibleColumns);
|
||||
|
||||
// Foreground
|
||||
DrawData(e, visibleColumns);
|
||||
|
||||
DrawColumnDrag(e);
|
||||
DrawCellDrag(e);
|
||||
|
||||
_gdi.CopyToScreen();
|
||||
_gdi.EndOffScreenBitmap();
|
||||
}
|
||||
}
|
||||
|
||||
private void GDI_DrawColumnDrag(PaintEventArgs e)
|
||||
{
|
||||
if (_columnDown != null && _columnDownMoved && _currentX.HasValue && _currentY.HasValue && IsHoveringOnColumnCell)
|
||||
{
|
||||
int x1 = _currentX.Value - (_columnDown.Width.Value / 2);
|
||||
int y1 = _currentY.Value - (CellHeight / 2);
|
||||
int x2 = x1 + _columnDown.Width.Value;
|
||||
int y2 = y1 + CellHeight;
|
||||
|
||||
_gdi.SetSolidPen(_backColor);
|
||||
_gdi.DrawRectangle(x1, y1, x2, y2);
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
_gdi.DrawString(_columnDown.Text, new Point(x1 + CellWidthPadding, y1 + CellHeightPadding));
|
||||
}
|
||||
}
|
||||
|
||||
private void GDI_DrawCellDrag(PaintEventArgs e)
|
||||
{
|
||||
if (_draggingCell != null)
|
||||
{
|
||||
var text = "";
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
QueryItemText?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, out text, ref offsetX, ref offsetY);
|
||||
|
||||
Color bgColor = _backColor;
|
||||
QueryItemBkColor?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, ref bgColor);
|
||||
|
||||
int x1 = _currentX.Value - (_draggingCell.Column.Width.Value / 2);
|
||||
int y1 = _currentY.Value - (CellHeight / 2);
|
||||
int x2 = x1 + _draggingCell.Column.Width.Value;
|
||||
int y2 = y1 + CellHeight;
|
||||
|
||||
|
||||
_gdi.SetBrush(bgColor);
|
||||
_gdi.FillRectangle(x1, y1, x2 - x1, y2 - y1);
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
_gdi.DrawString(text, new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY));
|
||||
}
|
||||
}
|
||||
|
||||
private void GDI_DrawColumnText(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
int start = -_vBar.Value;
|
||||
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
var point = new Point(CellWidthPadding, start + CellHeightPadding);
|
||||
|
||||
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, SystemColors.HighlightText);
|
||||
_gdi.DrawString(column.Text, point);
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.DrawString(column.Text, point);
|
||||
}
|
||||
|
||||
start += CellHeight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
var point = new Point(column.Left.Value + 2 * CellWidthPadding - _hBar.Value, CellHeightPadding); // TODO: fix this CellPadding issue (2 * CellPadding vs just CellPadding)
|
||||
|
||||
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, SystemColors.HighlightText);
|
||||
_gdi.DrawString(column.Text, point);
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.DrawString(column.Text, point);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GDI_DrawData(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
// Prevent exceptions with small TAStudio windows
|
||||
if (visibleColumns.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (QueryItemText != null)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
int startRow = FirstVisibleRow;
|
||||
int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1;
|
||||
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
for (int i = 0, f = 0; f < range; i++, f++)
|
||||
{
|
||||
f += _lagFrames[i];
|
||||
int LastVisible = LastVisibleColumnIndex;
|
||||
for (int j = FirstVisibleColumn; j <= LastVisible; j++)
|
||||
{
|
||||
Bitmap image = null;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int bitmapOffsetX = 0;
|
||||
int bitmapOffsetY = 0;
|
||||
|
||||
QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
x = RowsToPixels(i) + CellWidthPadding + bitmapOffsetX;
|
||||
y = (j * CellHeight) + (CellHeightPadding * 2) + bitmapOffsetY;
|
||||
_gdi.DrawBitmap(image, new Point(x, y), true);
|
||||
}
|
||||
|
||||
string text;
|
||||
int strOffsetX = 0;
|
||||
int strOffsetY = 0;
|
||||
QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY);
|
||||
|
||||
// Center Text
|
||||
x = RowsToPixels(i) + ((CellWidth - (text.Length * _charSize.Width)) / 2);
|
||||
y = (j * CellHeight) + CellHeightPadding - _vBar.Value;
|
||||
var point = new Point(x + strOffsetX, y + strOffsetY);
|
||||
|
||||
var rePrep = false;
|
||||
if (j == 1)
|
||||
if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = i + startRow }))
|
||||
{
|
||||
_gdi.PrepDrawString(_rotatedFont, SystemColors.HighlightText);
|
||||
rePrep = true;
|
||||
}
|
||||
else if (j == 1)
|
||||
{
|
||||
// 1. not sure about this; 2. repreps may be excess, but if we render one column at a time, we do need to change back after rendering the header
|
||||
rePrep = true;
|
||||
_gdi.PrepDrawString(_rotatedFont, _foreColor);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
_gdi.DrawString(text, point);
|
||||
}
|
||||
|
||||
if (rePrep)
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int startRow = FirstVisibleRow;
|
||||
int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1;
|
||||
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
int xPadding = CellWidthPadding + 1 - _hBar.Value;
|
||||
for (int i = 0, f = 0; f < range; i++, f++) // Vertical
|
||||
{
|
||||
f += _lagFrames[i];
|
||||
int LastVisible = LastVisibleColumnIndex;
|
||||
for (int j = FirstVisibleColumn; j <= LastVisible; j++) // Horizontal
|
||||
{
|
||||
RollColumn col = visibleColumns[j];
|
||||
|
||||
string text;
|
||||
int strOffsetX = 0;
|
||||
int strOffsetY = 0;
|
||||
Point point = new Point(col.Left.Value + xPadding, RowsToPixels(i) + CellHeightPadding);
|
||||
|
||||
Bitmap image = null;
|
||||
int bitmapOffsetX = 0;
|
||||
int bitmapOffsetY = 0;
|
||||
|
||||
QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
_gdi.DrawBitmap(image, new Point(point.X + bitmapOffsetX, point.Y + bitmapOffsetY + CellHeightPadding), true);
|
||||
}
|
||||
|
||||
QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY);
|
||||
|
||||
bool rePrep = false;
|
||||
if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = f + startRow }))
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, SystemColors.HighlightText);
|
||||
rePrep = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
_gdi.DrawString(text, new Point(point.X + strOffsetX, point.Y + strOffsetY));
|
||||
}
|
||||
|
||||
if (rePrep)
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GDI_DrawColumnBg(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
_gdi.SetBrush(SystemColors.ControlLight);
|
||||
_gdi.SetSolidPen(Color.Black);
|
||||
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
_gdi.FillRectangle(0, 0, ColumnWidth + 1, DrawHeight + 1);
|
||||
_gdi.Line(0, 0, 0, visibleColumns.Count * CellHeight + 1);
|
||||
_gdi.Line(ColumnWidth, 0, ColumnWidth, visibleColumns.Count * CellHeight + 1);
|
||||
|
||||
int start = -_vBar.Value;
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
_gdi.Line(1, start, ColumnWidth, start);
|
||||
start += CellHeight;
|
||||
}
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
_gdi.Line(1, start, ColumnWidth, start);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int bottomEdge = RowsToPixels(0);
|
||||
|
||||
// Gray column box and black line underneath
|
||||
_gdi.FillRectangle(0, 0, Width + 1, bottomEdge + 1);
|
||||
_gdi.Line(0, 0, TotalColWidth.Value + 1, 0);
|
||||
_gdi.Line(0, bottomEdge, TotalColWidth.Value + 1, bottomEdge);
|
||||
|
||||
// Vertical black seperators
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
int pos = visibleColumns[i].Left.Value - _hBar.Value;
|
||||
_gdi.Line(pos, 0, pos, bottomEdge);
|
||||
}
|
||||
|
||||
// Draw right most line
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
int right = TotalColWidth.Value - _hBar.Value;
|
||||
_gdi.Line(right, 0, right, bottomEdge);
|
||||
}
|
||||
}
|
||||
|
||||
// Emphasis
|
||||
foreach (var column in visibleColumns.Where(c => c.Emphasis))
|
||||
{
|
||||
_gdi.SetBrush(SystemColors.ActiveBorder);
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
_gdi.FillRectangle(1, visibleColumns.IndexOf(column) * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.FillRectangle(column.Left.Value + 1 - _hBar.Value, 1, column.Width.Value - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// If the user is hovering over a column
|
||||
if (IsHoveringOnColumnCell)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
if (visibleColumns[i] != CurrentCell.Column)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CurrentCell.Column.Emphasis)
|
||||
{
|
||||
_gdi.SetBrush(Add(SystemColors.Highlight, 0x00222222));
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.SetBrush(SystemColors.Highlight);
|
||||
}
|
||||
|
||||
_gdi.FillRectangle(1, i * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO multiple selected columns
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
if (visibleColumns[i] == CurrentCell.Column)
|
||||
{
|
||||
// Left of column is to the right of the viewable area or right of column is to the left of the viewable area
|
||||
if (visibleColumns[i].Left.Value - _hBar.Value > Width || visibleColumns[i].Right.Value - _hBar.Value < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int left = visibleColumns[i].Left.Value - _hBar.Value;
|
||||
int width = visibleColumns[i].Right.Value - _hBar.Value - left;
|
||||
|
||||
if (CurrentCell.Column.Emphasis)
|
||||
{
|
||||
_gdi.SetBrush(Add(SystemColors.Highlight, 0x00550000));
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.SetBrush(SystemColors.Highlight);
|
||||
}
|
||||
|
||||
_gdi.FillRectangle(left + 1, 1, width - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GDI_DrawBg(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
if (UseCustomBackground && QueryItemBkColor != null)
|
||||
{
|
||||
DoBackGroundCallback(e, visibleColumns);
|
||||
}
|
||||
|
||||
if (GridLines)
|
||||
{
|
||||
_gdi.SetSolidPen(SystemColors.ControlLight);
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
// Columns
|
||||
for (int i = 1; i < VisibleRows + 1; i++)
|
||||
{
|
||||
int x = RowsToPixels(i);
|
||||
_gdi.Line(x, 1, x, DrawHeight);
|
||||
}
|
||||
|
||||
// Rows
|
||||
for (int i = 0; i < visibleColumns.Count + 1; i++)
|
||||
{
|
||||
_gdi.Line(RowsToPixels(0) + 1, i * CellHeight - _vBar.Value, DrawWidth, i * CellHeight - _vBar.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Columns
|
||||
int y = ColumnHeight + 1;
|
||||
int? totalColWidth = TotalColWidth;
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
int x = column.Left.Value - _hBar.Value;
|
||||
_gdi.Line(x, y, x, Height - 1);
|
||||
}
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
_gdi.Line(totalColWidth.Value - _hBar.Value, y, totalColWidth.Value - _hBar.Value, Height - 1);
|
||||
}
|
||||
|
||||
// Rows
|
||||
for (int i = 1; i < VisibleRows + 1; i++)
|
||||
{
|
||||
_gdi.Line(0, RowsToPixels(i), Width + 1, RowsToPixels(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_selectedItems.Any())
|
||||
{
|
||||
DoSelectionBG(e, visibleColumns);
|
||||
}
|
||||
}
|
||||
|
||||
private void GDI_DrawCellBG(PaintEventArgs e, Color color, Cell cell, List<RollColumn> visibleColumns)
|
||||
{
|
||||
int x, y, w, h;
|
||||
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
x = RowsToPixels(cell.RowIndex.Value) + 1;
|
||||
w = CellWidth - 1;
|
||||
y = (CellHeight * visibleColumns.IndexOf(cell.Column)) + 1 - _vBar.Value; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
|
||||
h = CellHeight - 1;
|
||||
if (x < ColumnWidth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
w = cell.Column.Width.Value - 1;
|
||||
x = cell.Column.Left.Value - _hBar.Value + 1;
|
||||
y = RowsToPixels(cell.RowIndex.Value) + 1; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
|
||||
h = CellHeight - 1;
|
||||
if (y < ColumnHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (x > DrawWidth || y > DrawHeight)
|
||||
{
|
||||
return;
|
||||
} // Don't draw if off screen.
|
||||
|
||||
_gdi.SetBrush(color);
|
||||
_gdi.FillRectangle(x, y, w, h);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,559 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// New GDI+ methods live here
|
||||
/// </summary>
|
||||
public partial class InputRoll
|
||||
{
|
||||
// single instance to mirror GDI implementation
|
||||
private Pen sPen = null;
|
||||
// single instance to mirror GDI implementation
|
||||
private Brush sBrush = null;
|
||||
|
||||
// GDI+ uses floats for measure string
|
||||
private SizeF _charSizeF;
|
||||
|
||||
#region Initialization and Destruction
|
||||
|
||||
/// <summary>
|
||||
/// Initializes GDI+ related stuff
|
||||
/// (called from constructor)
|
||||
/// </summary>
|
||||
private void GDIPConstruction()
|
||||
{
|
||||
// HFont?
|
||||
// Rotated HFont?
|
||||
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
|
||||
SetStyle(ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
|
||||
SetStyle(ControlStyles.Opaque, true);
|
||||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||
|
||||
using (var g = CreateGraphics())
|
||||
{
|
||||
_charSizeF = g.MeasureString("A", _commonFont);
|
||||
//_charSize = Size.Round(sizeF);
|
||||
}
|
||||
}
|
||||
|
||||
private void GDIPDispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drawing Methods Using GDI+
|
||||
|
||||
private void GDIP_OnPaint(PaintEventArgs e)
|
||||
{
|
||||
// white background
|
||||
sBrush = new SolidBrush(Color.White);
|
||||
sPen = new Pen(Color.White);
|
||||
|
||||
Rectangle rect = e.ClipRectangle;
|
||||
e.Graphics.FillRectangle(sBrush, rect);
|
||||
e.Graphics.Flush();
|
||||
|
||||
// Lag frame calculations
|
||||
SetLagFramesArray();
|
||||
|
||||
var visibleColumns = _columns.VisibleColumns.ToList();
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
DrawColumnBg(e, visibleColumns);
|
||||
DrawColumnText(e, visibleColumns);
|
||||
}
|
||||
|
||||
// Background
|
||||
DrawBg(e, visibleColumns);
|
||||
|
||||
// Foreground
|
||||
DrawData(e, visibleColumns);
|
||||
|
||||
DrawColumnDrag(e);
|
||||
DrawCellDrag(e);
|
||||
}
|
||||
|
||||
private void GDIP_DrawColumnDrag(PaintEventArgs e)
|
||||
{
|
||||
if (_columnDown != null && _columnDownMoved && _currentX.HasValue && _currentY.HasValue && IsHoveringOnColumnCell)
|
||||
{
|
||||
int x1 = _currentX.Value - (_columnDown.Width.Value / 2);
|
||||
int y1 = _currentY.Value - (CellHeight / 2);
|
||||
int x2 = x1 + _columnDown.Width.Value;
|
||||
int y2 = y1 + CellHeight;
|
||||
|
||||
sPen = new Pen(_backColor);
|
||||
e.Graphics.DrawRectangle(sPen, x1, y1, x2, y2);
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
//e.Graphics.DrawString(_columnDown.Text, _commonFont, sBrush, (PointF)(new Point(x1 + CellWidthPadding, y1 + CellHeightPadding)));
|
||||
GDIP_DrawString(e, _columnDown.Text, _commonFont, new Point(x1 + CellWidthPadding, y1 + CellHeightPadding), _foreColor);
|
||||
}
|
||||
}
|
||||
|
||||
private void GDIP_DrawCellDrag(PaintEventArgs e)
|
||||
{
|
||||
if (_draggingCell != null)
|
||||
{
|
||||
var text = "";
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
QueryItemText?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, out text, ref offsetX, ref offsetY);
|
||||
|
||||
Color bgColor = _backColor;
|
||||
QueryItemBkColor?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, ref bgColor);
|
||||
|
||||
int x1 = _currentX.Value - (_draggingCell.Column.Width.Value / 2);
|
||||
int y1 = _currentY.Value - (CellHeight / 2);
|
||||
int x2 = x1 + _draggingCell.Column.Width.Value;
|
||||
int y2 = y1 + CellHeight;
|
||||
|
||||
sBrush = new SolidBrush(bgColor);
|
||||
e.Graphics.FillRectangle(sBrush, x1, y1, x2 - x1, y2 - y1);
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
//e.Graphics.DrawString(text, _commonFont, sBrush, (PointF)(new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY)));
|
||||
GDIP_DrawString(e, text, _commonFont, new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY), _foreColor);
|
||||
}
|
||||
}
|
||||
|
||||
private void GDIP_DrawColumnText(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
int start = -_vBar.Value;
|
||||
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
var point = new Point(CellWidthPadding, start + CellHeightPadding);
|
||||
|
||||
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
|
||||
{
|
||||
sBrush = new SolidBrush(SystemColors.HighlightText);
|
||||
//e.Graphics.DrawString(column.Text, _commonFont, sBrush, (PointF)(point));
|
||||
GDIP_DrawString(e, column.Text, _commonFont, point, SystemColors.HighlightText);
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
//e.Graphics.DrawString(column.Text, _commonFont, sBrush, (PointF)(point));
|
||||
GDIP_DrawString(e, column.Text, _commonFont, point, _foreColor);
|
||||
}
|
||||
|
||||
start += CellHeight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
int xPadding = CellWidthPadding + 1 - _hBar.Value;
|
||||
var point = new Point(column.Left.Value + xPadding, CellHeightPadding);
|
||||
|
||||
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
|
||||
{
|
||||
sBrush = new SolidBrush(SystemColors.HighlightText);
|
||||
//e.Graphics.DrawString(column.Text, _commonFont, sBrush, (PointF)(point));
|
||||
GDIP_DrawString(e, column.Text, _commonFont, point, SystemColors.HighlightText);
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
//e.Graphics.DrawString(column.Text, _commonFont, sBrush, (PointF)(point));
|
||||
GDIP_DrawString(e, column.Text, _commonFont, point, _foreColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GDIP_DrawData(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
// Prevent exceptions with small TAStudio windows
|
||||
if (visibleColumns.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool isRotated = false;
|
||||
|
||||
if (QueryItemText != null)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
int startRow = FirstVisibleRow;
|
||||
int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1;
|
||||
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
for (int i = 0, f = 0; f < range; i++, f++)
|
||||
{
|
||||
f += _lagFrames[i];
|
||||
int LastVisible = LastVisibleColumnIndex;
|
||||
for (int j = FirstVisibleColumn; j <= LastVisible; j++)
|
||||
{
|
||||
Bitmap image = null;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int bitmapOffsetX = 0;
|
||||
int bitmapOffsetY = 0;
|
||||
|
||||
QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
x = RowsToPixels(i) + CellWidthPadding + bitmapOffsetX;
|
||||
y = (j * CellHeight) + (CellHeightPadding * 2) + bitmapOffsetY;
|
||||
e.Graphics.DrawImage(image, new Point(x, y));
|
||||
}
|
||||
|
||||
string text;
|
||||
int strOffsetX = 0;
|
||||
int strOffsetY = 0;
|
||||
QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY);
|
||||
|
||||
// Center Text
|
||||
x = RowsToPixels(i) + ((CellWidth - (int)Math.Round((text.Length * _charSizeF.Width))) / 2);
|
||||
y = (j * CellHeight) + CellHeightPadding - _vBar.Value;
|
||||
var point = new Point(x + strOffsetX, y + strOffsetY);
|
||||
|
||||
var rePrep = false;
|
||||
if (j == 1)
|
||||
if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = i + startRow }))
|
||||
{
|
||||
isRotated = true;
|
||||
sBrush = new SolidBrush(SystemColors.HighlightText);
|
||||
rePrep = true;
|
||||
}
|
||||
else if (j == 1)
|
||||
{
|
||||
// 1. not sure about this; 2. repreps may be excess, but if we render one column at a time, we do need to change back after rendering the header
|
||||
rePrep = true;
|
||||
isRotated = true;
|
||||
sBrush = new SolidBrush(SystemColors.HighlightText);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
//_gdi.DrawString(text, point);
|
||||
if (isRotated)
|
||||
{
|
||||
SizeF sz = e.Graphics.VisibleClipBounds.Size;
|
||||
e.Graphics.TranslateTransform(sz.Width / 2, sz.Height / 2);
|
||||
e.Graphics.RotateTransform(90);
|
||||
sz = e.Graphics.MeasureString(text, _commonFont);
|
||||
e.Graphics.DrawString(text, _commonFont, sBrush, -(sz.Width / 2), -(sz.Height / 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
//e.Graphics.DrawString(text, _commonFont, sBrush, (PointF)point);
|
||||
GDIP_DrawString(e, text, _commonFont, point, new Pen(sBrush).Color);
|
||||
}
|
||||
}
|
||||
|
||||
if (rePrep)
|
||||
{
|
||||
isRotated = false;
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int startRow = FirstVisibleRow;
|
||||
int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1;
|
||||
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
|
||||
int xPadding = CellWidthPadding + 1 - _hBar.Value;
|
||||
for (int i = 0, f = 0; f < range; i++, f++) // Vertical
|
||||
{
|
||||
f += _lagFrames[i];
|
||||
int LastVisible = LastVisibleColumnIndex;
|
||||
for (int j = FirstVisibleColumn; j <= LastVisible; j++) // Horizontal
|
||||
{
|
||||
RollColumn col = visibleColumns[j];
|
||||
|
||||
string text;
|
||||
int strOffsetX = 0;
|
||||
int strOffsetY = 0;
|
||||
Point point = new Point(col.Left.Value + xPadding, RowsToPixels(i) + CellHeightPadding);
|
||||
|
||||
Bitmap image = null;
|
||||
int bitmapOffsetX = 0;
|
||||
int bitmapOffsetY = 0;
|
||||
|
||||
QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
e.Graphics.DrawImage(image, new Point(point.X + bitmapOffsetX, point.Y + bitmapOffsetY + CellHeightPadding));
|
||||
}
|
||||
|
||||
QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY);
|
||||
|
||||
bool rePrep = false;
|
||||
if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = f + startRow }))
|
||||
{
|
||||
sBrush = new SolidBrush(SystemColors.HighlightText);
|
||||
isRotated = false;
|
||||
rePrep = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
//e.Graphics.DrawString(text, _commonFont, sBrush, (PointF)(new Point(point.X + strOffsetX, point.Y + strOffsetY)));
|
||||
GDIP_DrawString(e, text, _commonFont, new Point(point.X + strOffsetX, point.Y + strOffsetY), new Pen(sBrush).Color);
|
||||
}
|
||||
|
||||
if (rePrep)
|
||||
{
|
||||
isRotated = false;
|
||||
sBrush = new SolidBrush(_foreColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GDIP_DrawColumnBg(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
sBrush = new SolidBrush(SystemColors.ControlLight);
|
||||
sPen = new Pen(Color.Black);
|
||||
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
e.Graphics.FillRectangle(sBrush, 0, 0, ColumnWidth + 1, DrawHeight + 1);
|
||||
e.Graphics.DrawLine(sPen, 0, 0, 0, visibleColumns.Count * CellHeight + 1);
|
||||
e.Graphics.DrawLine(sPen, ColumnWidth, 0, ColumnWidth, visibleColumns.Count * CellHeight + 1);
|
||||
|
||||
int start = -_vBar.Value;
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
e.Graphics.DrawLine(sPen, 1, start, ColumnWidth, start);
|
||||
start += CellHeight;
|
||||
}
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
e.Graphics.DrawLine(sPen, 1, start, ColumnWidth, start);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int bottomEdge = RowsToPixels(0);
|
||||
|
||||
// Gray column box and black line underneath
|
||||
e.Graphics.FillRectangle(sBrush, 0, 0, Width + 1, bottomEdge + 1);
|
||||
e.Graphics.DrawLine(sPen, 0, 0, TotalColWidth.Value + 1, 0);
|
||||
e.Graphics.DrawLine(sPen, 0, bottomEdge, TotalColWidth.Value + 1, bottomEdge);
|
||||
|
||||
// Vertical black seperators
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
int pos = visibleColumns[i].Left.Value - _hBar.Value;
|
||||
e.Graphics.DrawLine(sPen, pos, 0, pos, bottomEdge);
|
||||
}
|
||||
|
||||
// Draw right most line
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
int right = TotalColWidth.Value - _hBar.Value;
|
||||
e.Graphics.DrawLine(sPen, right, 0, right, bottomEdge);
|
||||
}
|
||||
}
|
||||
|
||||
// Emphasis
|
||||
foreach (var column in visibleColumns.Where(c => c.Emphasis))
|
||||
{
|
||||
sBrush = new SolidBrush(SystemColors.ActiveBorder);
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
e.Graphics.FillRectangle(sBrush, 1, visibleColumns.IndexOf(column) * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Graphics.FillRectangle(sBrush, column.Left.Value + 1 - _hBar.Value, 1, column.Width.Value - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// If the user is hovering over a column
|
||||
if (IsHoveringOnColumnCell)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
if (visibleColumns[i] != CurrentCell.Column)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CurrentCell.Column.Emphasis)
|
||||
{
|
||||
sBrush = new SolidBrush(Color.FromArgb(SystemColors.Highlight.ToArgb() + 0x00222222));
|
||||
}
|
||||
else
|
||||
{
|
||||
sBrush = new SolidBrush(SystemColors.Highlight);
|
||||
}
|
||||
|
||||
e.Graphics.FillRectangle(sBrush, 1, i * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO multiple selected columns
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
if (visibleColumns[i] == CurrentCell.Column)
|
||||
{
|
||||
// Left of column is to the right of the viewable area or right of column is to the left of the viewable area
|
||||
if (visibleColumns[i].Left.Value - _hBar.Value > Width || visibleColumns[i].Right.Value - _hBar.Value < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int left = visibleColumns[i].Left.Value - _hBar.Value;
|
||||
int width = visibleColumns[i].Right.Value - _hBar.Value - left;
|
||||
|
||||
if (CurrentCell.Column.Emphasis)
|
||||
{
|
||||
sBrush = new SolidBrush(Color.FromArgb(SystemColors.Highlight.ToArgb() + 0x00550000));
|
||||
}
|
||||
else
|
||||
{
|
||||
sBrush = new SolidBrush(SystemColors.Highlight);
|
||||
}
|
||||
|
||||
e.Graphics.FillRectangle(sBrush, left + 1, 1, width - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GDIP_DrawBg(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
if (UseCustomBackground && QueryItemBkColor != null)
|
||||
{
|
||||
DoBackGroundCallback(e, visibleColumns);
|
||||
}
|
||||
|
||||
if (GridLines)
|
||||
{
|
||||
sPen = new Pen(SystemColors.ControlLight);
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
// Columns
|
||||
for (int i = 1; i < VisibleRows + 1; i++)
|
||||
{
|
||||
int x = RowsToPixels(i);
|
||||
e.Graphics.DrawLine(sPen, x, 1, x, DrawHeight);
|
||||
}
|
||||
|
||||
// Rows
|
||||
for (int i = 0; i < visibleColumns.Count + 1; i++)
|
||||
{
|
||||
e.Graphics.DrawLine(sPen, RowsToPixels(0) + 1, i * CellHeight - _vBar.Value, DrawWidth, i * CellHeight - _vBar.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Columns
|
||||
int y = ColumnHeight + 1;
|
||||
int? totalColWidth = TotalColWidth;
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
int x = column.Left.Value - _hBar.Value;
|
||||
e.Graphics.DrawLine(sPen, x, y, x, Height - 1);
|
||||
}
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
e.Graphics.DrawLine(sPen, totalColWidth.Value - _hBar.Value, y, totalColWidth.Value - _hBar.Value, Height - 1);
|
||||
}
|
||||
|
||||
// Rows
|
||||
for (int i = 1; i < VisibleRows + 1; i++)
|
||||
{
|
||||
e.Graphics.DrawLine(sPen, 0, RowsToPixels(i), Width + 1, RowsToPixels(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_selectedItems.Any())
|
||||
{
|
||||
DoSelectionBG(e, visibleColumns);
|
||||
}
|
||||
}
|
||||
|
||||
private void GDIP_DrawCellBG(PaintEventArgs e, Color color, Cell cell, List<RollColumn> visibleColumns)
|
||||
{
|
||||
int x, y, w, h;
|
||||
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
x = RowsToPixels(cell.RowIndex.Value) + 1;
|
||||
w = CellWidth - 1;
|
||||
y = (CellHeight * visibleColumns.IndexOf(cell.Column)) + 1 - _vBar.Value; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
|
||||
h = CellHeight - 1;
|
||||
if (x < ColumnWidth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
w = cell.Column.Width.Value - 1;
|
||||
x = cell.Column.Left.Value - _hBar.Value + 1;
|
||||
y = RowsToPixels(cell.RowIndex.Value) + 1; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
|
||||
h = CellHeight - 1;
|
||||
if (y < ColumnHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (x > DrawWidth || y > DrawHeight)
|
||||
{
|
||||
return;
|
||||
} // Don't draw if off screen.
|
||||
|
||||
var col = cell.Column.Name;
|
||||
if (color.A == 0)
|
||||
{
|
||||
sBrush = new SolidBrush(Color.FromArgb(255, color));
|
||||
}
|
||||
else
|
||||
{
|
||||
sBrush = new SolidBrush(color);
|
||||
}
|
||||
|
||||
e.Graphics.FillRectangle(sBrush, x, y, w, h);
|
||||
}
|
||||
|
||||
private void GDIP_DrawString(PaintEventArgs e, string text, Font font, Point point, Color color)
|
||||
{
|
||||
//TextRenderer.DrawText(e.Graphics, text, font, point, color);
|
||||
e.Graphics.DrawString(text, font, new SolidBrush(color), (PointF)point);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -6,380 +6,59 @@ using System.Windows.Forms;
|
|||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// This in the most part now contains renderer selection logic
|
||||
/// </summary>
|
||||
public partial class InputRoll
|
||||
{
|
||||
#region Renderer-Based Logic Methods
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
using (_gdi.LockGraphics(e.Graphics))
|
||||
{
|
||||
_gdi.StartOffScreenBitmap(Width, Height);
|
||||
|
||||
// White Background
|
||||
_gdi.SetBrush(Color.White);
|
||||
_gdi.SetSolidPen(Color.White);
|
||||
_gdi.FillRectangle(0, 0, Width, Height);
|
||||
|
||||
// Lag frame calculations
|
||||
SetLagFramesArray();
|
||||
|
||||
var visibleColumns = _columns.VisibleColumns.ToList();
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
DrawColumnBg(e, visibleColumns);
|
||||
DrawColumnText(e, visibleColumns);
|
||||
}
|
||||
|
||||
// Background
|
||||
DrawBg(e, visibleColumns);
|
||||
|
||||
// Foreground
|
||||
DrawData(e, visibleColumns);
|
||||
|
||||
DrawColumnDrag(e);
|
||||
DrawCellDrag(e);
|
||||
|
||||
_gdi.CopyToScreen();
|
||||
_gdi.EndOffScreenBitmap();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs pevent)
|
||||
{
|
||||
// Do nothing, and this should never be called
|
||||
{
|
||||
if (Renderer == RollRenderer.GDIPlus)
|
||||
GDIP_OnPaint(e);
|
||||
else if (Renderer == RollRenderer.GDI)
|
||||
GDI_OnPaint(e);
|
||||
}
|
||||
|
||||
private void DrawColumnDrag(PaintEventArgs e)
|
||||
{
|
||||
if (_columnDown != null && _columnDownMoved && _currentX.HasValue && _currentY.HasValue && IsHoveringOnColumnCell)
|
||||
{
|
||||
int x1 = _currentX.Value - (_columnDown.Width.Value / 2);
|
||||
int y1 = _currentY.Value - (CellHeight / 2);
|
||||
int x2 = x1 + _columnDown.Width.Value;
|
||||
int y2 = y1 + CellHeight;
|
||||
|
||||
_gdi.SetSolidPen(_backColor);
|
||||
_gdi.DrawRectangle(x1, y1, x2, y2);
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
_gdi.DrawString(_columnDown.Text, new Point(x1 + CellWidthPadding, y1 + CellHeightPadding));
|
||||
}
|
||||
if (Renderer == RollRenderer.GDIPlus)
|
||||
GDIP_DrawColumnDrag(e);
|
||||
else if (Renderer == RollRenderer.GDI)
|
||||
GDI_DrawColumnDrag(e);
|
||||
}
|
||||
|
||||
private void DrawCellDrag(PaintEventArgs e)
|
||||
{
|
||||
if (_draggingCell != null)
|
||||
{
|
||||
var text = "";
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
QueryItemText?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, out text, ref offsetX, ref offsetY);
|
||||
|
||||
Color bgColor = _backColor;
|
||||
QueryItemBkColor?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, ref bgColor);
|
||||
|
||||
int x1 = _currentX.Value - (_draggingCell.Column.Width.Value / 2);
|
||||
int y1 = _currentY.Value - (CellHeight / 2);
|
||||
int x2 = x1 + _draggingCell.Column.Width.Value;
|
||||
int y2 = y1 + CellHeight;
|
||||
|
||||
|
||||
_gdi.SetBrush(bgColor);
|
||||
_gdi.FillRectangle(x1, y1, x2 - x1, y2 - y1);
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
_gdi.DrawString(text, new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY));
|
||||
}
|
||||
if (Renderer == RollRenderer.GDIPlus)
|
||||
GDIP_DrawCellDrag(e);
|
||||
else if (Renderer == RollRenderer.GDI)
|
||||
GDIP_DrawCellDrag(e);
|
||||
}
|
||||
|
||||
private void DrawColumnText(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
int start = -_vBar.Value;
|
||||
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
var point = new Point(CellWidthPadding, start + CellHeightPadding);
|
||||
|
||||
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, SystemColors.HighlightText);
|
||||
_gdi.DrawString(column.Text, point);
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.DrawString(column.Text, point);
|
||||
}
|
||||
|
||||
start += CellHeight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
var point = new Point(column.Left.Value + 2 * CellWidthPadding - _hBar.Value, CellHeightPadding); // TODO: fix this CellPadding issue (2 * CellPadding vs just CellPadding)
|
||||
|
||||
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, SystemColors.HighlightText);
|
||||
_gdi.DrawString(column.Text, point);
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.DrawString(column.Text, point);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Renderer == RollRenderer.GDIPlus)
|
||||
GDIP_DrawColumnText(e, visibleColumns);
|
||||
else if (Renderer == RollRenderer.GDI)
|
||||
GDI_DrawColumnText(e, visibleColumns);
|
||||
}
|
||||
|
||||
private void DrawData(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
// Prevent exceptions with small TAStudio windows
|
||||
if (visibleColumns.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (QueryItemText != null)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
int startRow = FirstVisibleRow;
|
||||
int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1;
|
||||
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
for (int i = 0, f = 0; f < range; i++, f++)
|
||||
{
|
||||
f += _lagFrames[i];
|
||||
int LastVisible = LastVisibleColumnIndex;
|
||||
for (int j = FirstVisibleColumn; j <= LastVisible; j++)
|
||||
{
|
||||
Bitmap image = null;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int bitmapOffsetX = 0;
|
||||
int bitmapOffsetY = 0;
|
||||
|
||||
QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
x = RowsToPixels(i) + CellWidthPadding + bitmapOffsetX;
|
||||
y = (j * CellHeight) + (CellHeightPadding * 2) + bitmapOffsetY;
|
||||
_gdi.DrawBitmap(image, new Point(x, y), true);
|
||||
}
|
||||
|
||||
string text;
|
||||
int strOffsetX = 0;
|
||||
int strOffsetY = 0;
|
||||
QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY);
|
||||
|
||||
// Center Text
|
||||
x = RowsToPixels(i) + ((CellWidth - (text.Length * _charSize.Width)) / 2);
|
||||
y = (j * CellHeight) + CellHeightPadding - _vBar.Value;
|
||||
var point = new Point(x + strOffsetX, y + strOffsetY);
|
||||
|
||||
var rePrep = false;
|
||||
if (j == 1)
|
||||
if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = i + startRow }))
|
||||
{
|
||||
_gdi.PrepDrawString(_rotatedFont, SystemColors.HighlightText);
|
||||
rePrep = true;
|
||||
}
|
||||
else if (j == 1)
|
||||
{
|
||||
// 1. not sure about this; 2. repreps may be excess, but if we render one column at a time, we do need to change back after rendering the header
|
||||
rePrep = true;
|
||||
_gdi.PrepDrawString(_rotatedFont, _foreColor);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
_gdi.DrawString(text, point);
|
||||
}
|
||||
|
||||
if (rePrep)
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int startRow = FirstVisibleRow;
|
||||
int range = Math.Min(LastVisibleRow, RowCount - 1) - startRow + 1;
|
||||
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
int xPadding = CellWidthPadding + 1 - _hBar.Value;
|
||||
for (int i = 0, f = 0; f < range; i++, f++) // Vertical
|
||||
{
|
||||
f += _lagFrames[i];
|
||||
int LastVisible = LastVisibleColumnIndex;
|
||||
for (int j = FirstVisibleColumn; j <= LastVisible; j++) // Horizontal
|
||||
{
|
||||
RollColumn col = visibleColumns[j];
|
||||
|
||||
string text;
|
||||
int strOffsetX = 0;
|
||||
int strOffsetY = 0;
|
||||
Point point = new Point(col.Left.Value + xPadding, RowsToPixels(i) + CellHeightPadding);
|
||||
|
||||
Bitmap image = null;
|
||||
int bitmapOffsetX = 0;
|
||||
int bitmapOffsetY = 0;
|
||||
|
||||
QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
_gdi.DrawBitmap(image, new Point(point.X + bitmapOffsetX, point.Y + bitmapOffsetY + CellHeightPadding), true);
|
||||
}
|
||||
|
||||
QueryItemText(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY);
|
||||
|
||||
bool rePrep = false;
|
||||
if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = f + startRow }))
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, SystemColors.HighlightText);
|
||||
rePrep = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
_gdi.DrawString(text, new Point(point.X + strOffsetX, point.Y + strOffsetY));
|
||||
}
|
||||
|
||||
if (rePrep)
|
||||
{
|
||||
_gdi.PrepDrawString(_normalFont, _foreColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Renderer == RollRenderer.GDIPlus)
|
||||
GDIP_DrawData(e, visibleColumns);
|
||||
else if (Renderer == RollRenderer.GDI)
|
||||
GDI_DrawData(e, visibleColumns);
|
||||
}
|
||||
|
||||
private void DrawColumnBg(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
_gdi.SetBrush(SystemColors.ControlLight);
|
||||
_gdi.SetSolidPen(Color.Black);
|
||||
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
_gdi.FillRectangle(0, 0, ColumnWidth + 1, DrawHeight + 1);
|
||||
_gdi.Line(0, 0, 0, visibleColumns.Count * CellHeight + 1);
|
||||
_gdi.Line(ColumnWidth, 0, ColumnWidth, visibleColumns.Count * CellHeight + 1);
|
||||
|
||||
int start = -_vBar.Value;
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
_gdi.Line(1, start, ColumnWidth, start);
|
||||
start += CellHeight;
|
||||
}
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
_gdi.Line(1, start, ColumnWidth, start);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int bottomEdge = RowsToPixels(0);
|
||||
|
||||
// Gray column box and black line underneath
|
||||
_gdi.FillRectangle(0, 0, Width + 1, bottomEdge + 1);
|
||||
_gdi.Line(0, 0, TotalColWidth.Value + 1, 0);
|
||||
_gdi.Line(0, bottomEdge, TotalColWidth.Value + 1, bottomEdge);
|
||||
|
||||
// Vertical black seperators
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
int pos = visibleColumns[i].Left.Value - _hBar.Value;
|
||||
_gdi.Line(pos, 0, pos, bottomEdge);
|
||||
}
|
||||
|
||||
// Draw right most line
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
int right = TotalColWidth.Value - _hBar.Value;
|
||||
_gdi.Line(right, 0, right, bottomEdge);
|
||||
}
|
||||
}
|
||||
|
||||
// Emphasis
|
||||
foreach (var column in visibleColumns.Where(c => c.Emphasis))
|
||||
{
|
||||
_gdi.SetBrush(SystemColors.ActiveBorder);
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
_gdi.FillRectangle(1, visibleColumns.IndexOf(column) * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.FillRectangle(column.Left.Value + 1 - _hBar.Value, 1, column.Width.Value - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// If the user is hovering over a column
|
||||
if (IsHoveringOnColumnCell)
|
||||
{
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
if (visibleColumns[i] != CurrentCell.Column)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CurrentCell.Column.Emphasis)
|
||||
{
|
||||
_gdi.SetBrush(Add(SystemColors.Highlight, 0x00222222));
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.SetBrush(SystemColors.Highlight);
|
||||
}
|
||||
|
||||
_gdi.FillRectangle(1, i * CellHeight + 1, ColumnWidth - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO multiple selected columns
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
if (visibleColumns[i] == CurrentCell.Column)
|
||||
{
|
||||
// Left of column is to the right of the viewable area or right of column is to the left of the viewable area
|
||||
if (visibleColumns[i].Left.Value - _hBar.Value > Width || visibleColumns[i].Right.Value - _hBar.Value < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int left = visibleColumns[i].Left.Value - _hBar.Value;
|
||||
int width = visibleColumns[i].Right.Value - _hBar.Value - left;
|
||||
|
||||
if (CurrentCell.Column.Emphasis)
|
||||
{
|
||||
_gdi.SetBrush(Add(SystemColors.Highlight, 0x00550000));
|
||||
}
|
||||
else
|
||||
{
|
||||
_gdi.SetBrush(SystemColors.Highlight);
|
||||
}
|
||||
|
||||
_gdi.FillRectangle(left + 1, 1, width - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Renderer == RollRenderer.GDIPlus)
|
||||
GDIP_DrawColumnBg(e, visibleColumns);
|
||||
else if (Renderer == RollRenderer.GDI)
|
||||
GDI_DrawColumnBg(e, visibleColumns);
|
||||
}
|
||||
|
||||
// TODO refactor this and DoBackGroundCallback functions.
|
||||
|
@ -388,57 +67,30 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
private void DrawBg(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
{
|
||||
if (UseCustomBackground && QueryItemBkColor != null)
|
||||
{
|
||||
DoBackGroundCallback(e, visibleColumns);
|
||||
}
|
||||
if (Renderer == RollRenderer.GDIPlus)
|
||||
GDIP_DrawBg(e, visibleColumns);
|
||||
else if (Renderer == RollRenderer.GDI)
|
||||
GDI_DrawBg(e, visibleColumns);
|
||||
}
|
||||
|
||||
if (GridLines)
|
||||
{
|
||||
_gdi.SetSolidPen(SystemColors.ControlLight);
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
// Columns
|
||||
for (int i = 1; i < VisibleRows + 1; i++)
|
||||
{
|
||||
int x = RowsToPixels(i);
|
||||
_gdi.Line(x, 1, x, DrawHeight);
|
||||
}
|
||||
/// <summary>
|
||||
/// Given a cell with rowindex inbetween 0 and VisibleRows, it draws the background color specified. Do not call with absolute rowindices.
|
||||
/// </summary>
|
||||
private void DrawCellBG(PaintEventArgs e, Color color, Cell cell, List<RollColumn> visibleColumns)
|
||||
{
|
||||
if (Renderer == RollRenderer.GDIPlus)
|
||||
GDIP_DrawCellBG(e, color, cell, visibleColumns);
|
||||
else if (Renderer == RollRenderer.GDI)
|
||||
GDI_DrawCellBG(e, color, cell, visibleColumns);
|
||||
}
|
||||
|
||||
// Rows
|
||||
for (int i = 0; i < visibleColumns.Count + 1; i++)
|
||||
{
|
||||
_gdi.Line(RowsToPixels(0) + 1, i * CellHeight - _vBar.Value, DrawWidth, i * CellHeight - _vBar.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Columns
|
||||
int y = ColumnHeight + 1;
|
||||
int? totalColWidth = TotalColWidth;
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
int x = column.Left.Value - _hBar.Value;
|
||||
_gdi.Line(x, y, x, Height - 1);
|
||||
}
|
||||
#endregion
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
_gdi.Line(totalColWidth.Value - _hBar.Value, y, totalColWidth.Value - _hBar.Value, Height - 1);
|
||||
}
|
||||
#region Non-Renderer-Specific Methods
|
||||
|
||||
// Rows
|
||||
for (int i = 1; i < VisibleRows + 1; i++)
|
||||
{
|
||||
_gdi.Line(0, RowsToPixels(i), Width + 1, RowsToPixels(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_selectedItems.Any())
|
||||
{
|
||||
DoSelectionBG(e, visibleColumns);
|
||||
}
|
||||
protected override void OnPaintBackground(PaintEventArgs pevent)
|
||||
{
|
||||
// Do nothing, and this should never be called
|
||||
}
|
||||
|
||||
private void DoSelectionBG(PaintEventArgs e, List<RollColumn> visibleColumns)
|
||||
|
@ -484,49 +136,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
cellColor = Color.FromArgb(cellColor.R - (int)((cellColor.R - SystemColors.Highlight.R) * alpha),
|
||||
cellColor.G - (int)((cellColor.G - SystemColors.Highlight.G) * alpha),
|
||||
cellColor.B - (int)((cellColor.B - SystemColors.Highlight.B) * alpha));
|
||||
DrawCellBG(cellColor, relativeCell, visibleColumns);
|
||||
DrawCellBG(e, cellColor, relativeCell, visibleColumns);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a cell with rowindex inbetween 0 and VisibleRows, it draws the background color specified. Do not call with absolute rowindices.
|
||||
/// </summary>
|
||||
private void DrawCellBG(Color color, Cell cell, List<RollColumn> visibleColumns)
|
||||
{
|
||||
int x, y, w, h;
|
||||
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
x = RowsToPixels(cell.RowIndex.Value) + 1;
|
||||
w = CellWidth - 1;
|
||||
y = (CellHeight * visibleColumns.IndexOf(cell.Column)) + 1 - _vBar.Value; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
|
||||
h = CellHeight - 1;
|
||||
if (x < ColumnWidth)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
w = cell.Column.Width.Value - 1;
|
||||
x = cell.Column.Left.Value - _hBar.Value + 1;
|
||||
y = RowsToPixels(cell.RowIndex.Value) + 1; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
|
||||
h = CellHeight - 1;
|
||||
if (y < ColumnHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (x > DrawWidth || y > DrawHeight)
|
||||
{
|
||||
return;
|
||||
} // Don't draw if off screen.
|
||||
|
||||
_gdi.SetBrush(color);
|
||||
_gdi.FillRectangle(x, y, w, h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls QueryItemBkColor callback for all visible cells and fills in the background of those cells.
|
||||
/// </summary>
|
||||
|
@ -547,7 +160,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
for (int i = 0, f = 0; f < range; i++, f++)
|
||||
{
|
||||
f += _lagFrames[i];
|
||||
|
||||
|
||||
Color rowColor = Color.White;
|
||||
QueryRowBkColor?.Invoke(f + startIndex, ref rowColor);
|
||||
|
||||
|
@ -574,7 +187,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
Column = visibleColumns[j],
|
||||
RowIndex = i
|
||||
};
|
||||
DrawCellBG(itemColor, cell, visibleColumns);
|
||||
DrawCellBG(e, itemColor, cell, visibleColumns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -584,7 +197,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
for (int i = 0, f = 0; f < range; i++, f++) // Vertical
|
||||
{
|
||||
f += _lagFrames[i];
|
||||
|
||||
|
||||
Color rowColor = Color.White;
|
||||
QueryRowBkColor?.Invoke(f + startIndex, ref rowColor);
|
||||
|
||||
|
@ -611,11 +224,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
Column = visibleColumns[j],
|
||||
RowIndex = i
|
||||
};
|
||||
DrawCellBG(itemColor, cell, visibleColumns);
|
||||
DrawCellBG(e, itemColor, cell, visibleColumns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Drawing;
|
|||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Client.Common;
|
||||
using BizHawk.Client.EmuHawk.CustomControls;
|
||||
|
||||
|
@ -15,7 +16,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
// Row width is specified for horizontal orientation
|
||||
public partial class InputRoll : Control
|
||||
{
|
||||
private readonly GDIRenderer _gdi;
|
||||
private RollRenderer Renderer = RollRenderer.GDIPlus;
|
||||
|
||||
private Font _commonFont;
|
||||
|
||||
private readonly SortedSet<Cell> _selectedItems = new SortedSet<Cell>(new SortCell());
|
||||
|
||||
private readonly VScrollBar _vBar;
|
||||
|
@ -23,9 +27,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private readonly Timer _hoverTimer = new Timer();
|
||||
private readonly byte[] _lagFrames = new byte[256]; // Large enough value that it shouldn't ever need resizing. // apparently not large enough for 4K
|
||||
|
||||
private readonly IntPtr _rotatedFont;
|
||||
private readonly IntPtr _normalFont;
|
||||
|
||||
private readonly Color _foreColor;
|
||||
private readonly Color _backColor;
|
||||
|
||||
|
@ -34,8 +36,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
private bool _programmaticallyUpdatingScrollBarValues;
|
||||
private int _maxCharactersInHorizontal = 1;
|
||||
|
||||
private int _rowCount;
|
||||
private Size _charSize;
|
||||
private int _rowCount;
|
||||
|
||||
private RollColumn _columnDown;
|
||||
|
||||
|
@ -52,32 +53,41 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
public InputRoll()
|
||||
{
|
||||
// set renderer once at InputRoll instantiation
|
||||
Renderer = (RollRenderer)TAStudio.InputRollRenderer;
|
||||
|
||||
UseCustomBackground = true;
|
||||
GridLines = true;
|
||||
CellWidthPadding = 3;
|
||||
CellHeightPadding = 0;
|
||||
CurrentCell = null;
|
||||
ScrollMethod = "near";
|
||||
|
||||
var commonFont = new Font("Arial", 8, FontStyle.Bold);
|
||||
_normalFont = GDIRenderer.CreateNormalHFont(commonFont, 6);
|
||||
|
||||
// PrepDrawString doesn't actually set the font, so this is rather useless.
|
||||
// I'm leaving this stuff as-is so it will be a bit easier to fix up with another rendering method.
|
||||
_rotatedFont = GDIRenderer.CreateRotatedHFont(commonFont, true);
|
||||
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
|
||||
SetStyle(ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
|
||||
SetStyle(ControlStyles.Opaque, true);
|
||||
|
||||
_gdi = new GDIRenderer();
|
||||
|
||||
using (var g = CreateGraphics())
|
||||
using (_gdi.LockGraphics(g))
|
||||
|
||||
switch (Renderer)
|
||||
{
|
||||
_charSize = _gdi.MeasureString("A", commonFont); // TODO make this a property so changing it updates other values.
|
||||
}
|
||||
case RollRenderer.GDI:
|
||||
_commonFont = new Font("Arial", 8, FontStyle.Bold);
|
||||
_normalFont = Win32GDIRenderer.CreateNormalHFont(_commonFont, 6);
|
||||
|
||||
// PrepDrawString doesn't actually set the font, so this is rather useless.
|
||||
// I'm leaving this stuff as-is so it will be a bit easier to fix up with another rendering method.
|
||||
_rotatedFont = Win32GDIRenderer.CreateRotatedHFont(_commonFont, true);
|
||||
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
|
||||
SetStyle(ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
|
||||
SetStyle(ControlStyles.Opaque, true);
|
||||
|
||||
_gdi = new Win32GDIRenderer();
|
||||
|
||||
GDIConstruction();
|
||||
break;
|
||||
case RollRenderer.GDIPlus:
|
||||
_commonFont = new Font("Arial", 8, FontStyle.Bold);
|
||||
//_commonFont = new Font("Courier New", 8F, FontStyle.Bold);
|
||||
GDIPConstruction();
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateCellSize();
|
||||
ColumnWidth = CellWidth;
|
||||
|
@ -126,10 +136,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_gdi.Dispose();
|
||||
|
||||
GDIRenderer.DestroyHFont(_normalFont);
|
||||
GDIRenderer.DestroyHFont(_rotatedFont);
|
||||
if (Renderer == RollRenderer.GDI)
|
||||
{
|
||||
GDIDispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
@ -193,7 +203,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
return _hBar.SmallChange / CellWidth;
|
||||
}
|
||||
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
return _vBar.SmallChange / CellHeight;
|
||||
}
|
||||
|
||||
|
@ -241,7 +251,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
public bool AllowColumnReorder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the entire row will always be selected
|
||||
/// Gets or sets a value indicating whether the entire row will always be selected
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
[DefaultValue(false)]
|
||||
|
@ -644,7 +654,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
return _hBar.Value / CellWidth;
|
||||
}
|
||||
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
return _vBar.Value / CellHeight;
|
||||
}
|
||||
|
||||
|
@ -772,6 +782,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
get
|
||||
{
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
return (DrawWidth - ColumnWidth) / CellWidth;
|
||||
|
@ -790,6 +801,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
get
|
||||
{
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
return _vBar.Value / CellHeight;
|
||||
|
@ -806,6 +818,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
get
|
||||
{
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
List<RollColumn> columnList = VisibleColumns.ToList();
|
||||
int ret;
|
||||
if (HorizontalOrientation)
|
||||
|
@ -1580,10 +1593,14 @@ namespace BizHawk.Client.EmuHawk
|
|||
// See MSDN Page for more information on the dumb ScrollBar.Maximum Property
|
||||
private void RecalculateScrollBars()
|
||||
{
|
||||
if (_vBar == null || _hBar == null)
|
||||
return;
|
||||
|
||||
UpdateDrawSize();
|
||||
|
||||
var columns = _columns.VisibleColumns.ToList();
|
||||
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
if (HorizontalOrientation)
|
||||
{
|
||||
NeedsVScrollbar = columns.Count > DrawHeight / CellHeight;
|
||||
|
@ -1764,6 +1781,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
newCell.RowIndex = PixelsToRows(x);
|
||||
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
|
||||
int colIndex = (y + _vBar.Value) / CellHeight;
|
||||
if (colIndex >= 0 && colIndex < columns.Count)
|
||||
{
|
||||
|
@ -1799,7 +1818,16 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// <returns>The new width of the RollColumn object.</returns>
|
||||
private int UpdateWidth(RollColumn col)
|
||||
{
|
||||
col.Width = (col.Text.Length * _charSize.Width) + (CellWidthPadding * 4);
|
||||
switch (Renderer)
|
||||
{
|
||||
case RollRenderer.GDI:
|
||||
col.Width = (col.Text.Length * _charSize.Width) + (CellWidthPadding * 4);
|
||||
break;
|
||||
case RollRenderer.GDIPlus:
|
||||
col.Width = (int)Math.Round((col.Text.Length * _charSizeF.Width) + (CellWidthPadding * 4));
|
||||
break;
|
||||
}
|
||||
|
||||
return col.Width.Value;
|
||||
}
|
||||
|
||||
|
@ -1864,6 +1892,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
return (int)Math.Floor((float)(pixels - ColumnWidth) / CellWidth);
|
||||
}
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
return (int)Math.Floor((float)(pixels - ColumnHeight) / CellHeight);
|
||||
}
|
||||
|
||||
|
@ -1890,8 +1919,19 @@ namespace BizHawk.Client.EmuHawk
|
|||
/// </summary>
|
||||
private void UpdateCellSize()
|
||||
{
|
||||
CellHeight = _charSize.Height + (CellHeightPadding * 2);
|
||||
CellWidth = (_charSize.Width * MaxCharactersInHorizontal) + (CellWidthPadding * 4); // Double the padding for horizontal because it looks better
|
||||
switch (Renderer)
|
||||
{
|
||||
case RollRenderer.GDI:
|
||||
CellHeight = _charSize.Height + (CellHeightPadding * 2);
|
||||
CellWidth = (_charSize.Width * MaxCharactersInHorizontal) + (CellWidthPadding * 4); // Double the padding for horizontal because it looks better
|
||||
break;
|
||||
case RollRenderer.GDIPlus:
|
||||
CellHeight = (int)Math.Round(_charSizeF.Height + (CellHeightPadding * 2) + 1); // needed for GDI+ to match GDI cell height
|
||||
CellWidth = (int)Math.Round((_charSizeF.Width * MaxCharactersInHorizontal) + (CellWidthPadding * 4)); // Double the padding for horizontal because it looks better
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// SuuperW: Count lag frames between FirstDisplayed and given display position
|
||||
|
@ -2261,6 +2301,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
public enum RollRenderer
|
||||
{
|
||||
GDI = 0,
|
||||
GDIPlus = 1
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// A performant VirtualListView implementation that doesn't rely on native Win32 API calls
|
||||
/// (and in fact does not inherit the ListView class at all)
|
||||
/// It is an enhanced version of the work done with GDI+ rendering in InputRoll.cs
|
||||
/// -------------------
|
||||
/// *** API Related ***
|
||||
/// -------------------
|
||||
/// </summary>
|
||||
public partial class PlatformAgnosticVirtualListView
|
||||
{
|
||||
private Cell _draggingCell;
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Parent form calls this to add columns
|
||||
/// </summary>
|
||||
/// <param name="columnName"></param>
|
||||
/// <param name="columnText"></param>
|
||||
/// <param name="columnWidth"></param>
|
||||
/// <param name="columnType"></param>
|
||||
public void AddColumn(string columnName, string columnText, int columnWidth, ListColumn.InputType columnType = ListColumn.InputType.Boolean)
|
||||
{
|
||||
if (AllColumns[columnName] == null)
|
||||
{
|
||||
var column = new ListColumn
|
||||
{
|
||||
Name = columnName,
|
||||
Text = columnText,
|
||||
Width = columnWidth,
|
||||
Type = columnType
|
||||
};
|
||||
|
||||
AllColumns.Add(column);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the state of the passed row index
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="val"></param>
|
||||
public void SelectItem(int index, bool val)
|
||||
{
|
||||
if (_columns.VisibleColumns.Any())
|
||||
{
|
||||
if (val)
|
||||
{
|
||||
SelectCell(new Cell
|
||||
{
|
||||
RowIndex = index,
|
||||
Column = _columns[0]
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<Cell> items = _selectedItems.Where(cell => cell.RowIndex == index);
|
||||
_selectedItems.RemoveWhere(items.Contains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectAll()
|
||||
{
|
||||
var oldFullRowVal = FullRowSelect;
|
||||
FullRowSelect = true;
|
||||
for (int i = 0; i < ItemCount; i++)
|
||||
{
|
||||
SelectItem(i, true);
|
||||
}
|
||||
|
||||
FullRowSelect = oldFullRowVal;
|
||||
}
|
||||
|
||||
public void DeselectAll()
|
||||
{
|
||||
_selectedItems.Clear();
|
||||
}
|
||||
|
||||
public void TruncateSelection(int index)
|
||||
{
|
||||
_selectedItems.RemoveWhere(cell => cell.RowIndex > index);
|
||||
}
|
||||
|
||||
public bool IsVisible(int index)
|
||||
{
|
||||
return (index >= FirstVisibleRow) && (index <= LastFullyVisibleRow);
|
||||
}
|
||||
|
||||
public bool IsPartiallyVisible(int index)
|
||||
{
|
||||
return index >= FirstVisibleRow && index <= LastVisibleRow;
|
||||
}
|
||||
|
||||
public void DragCurrentCell()
|
||||
{
|
||||
_draggingCell = CurrentCell;
|
||||
}
|
||||
|
||||
public void ReleaseCurrentCell()
|
||||
{
|
||||
if (_draggingCell != null)
|
||||
{
|
||||
var draggedCell = _draggingCell;
|
||||
_draggingCell = null;
|
||||
|
||||
if (CurrentCell != draggedCell)
|
||||
{
|
||||
CellDropped?.Invoke(this, new CellEventArgs(draggedCell, CurrentCell));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls to the given index, according to the scroll settings.
|
||||
/// </summary>
|
||||
public void ScrollToIndex(int index)
|
||||
{
|
||||
if (ScrollMethod == "near")
|
||||
{
|
||||
MakeIndexVisible(index);
|
||||
}
|
||||
|
||||
if (!IsVisible(index) || AlwaysScroll)
|
||||
{
|
||||
if (ScrollMethod == "top")
|
||||
{
|
||||
FirstVisibleRow = index;
|
||||
}
|
||||
else if (ScrollMethod == "bottom")
|
||||
{
|
||||
LastVisibleRow = index;
|
||||
}
|
||||
else if (ScrollMethod == "center")
|
||||
{
|
||||
FirstVisibleRow = Math.Max(index - (VisibleRows / 2), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scrolls so that the given index is visible, if it isn't already; doesn't use scroll settings.
|
||||
/// </summary>
|
||||
public void MakeIndexVisible(int index)
|
||||
{
|
||||
if (!IsVisible(index))
|
||||
{
|
||||
if (FirstVisibleRow > index)
|
||||
{
|
||||
FirstVisibleRow = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastVisibleRow = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSelectedRows()
|
||||
{
|
||||
_selectedItems.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// A performant VirtualListView implementation that doesn't rely on native Win32 API calls
|
||||
/// (and in fact does not inherit the ListView class at all)
|
||||
/// It is an enhanced version of the work done with GDI+ rendering in InputRoll.cs
|
||||
/// ---------------
|
||||
/// *** Classes ***
|
||||
/// ---------------
|
||||
/// </summary>
|
||||
public partial class PlatformAgnosticVirtualListView
|
||||
{
|
||||
#region Event Args
|
||||
|
||||
public class CellEventArgs
|
||||
{
|
||||
public CellEventArgs(Cell oldCell, Cell newCell)
|
||||
{
|
||||
OldCell = oldCell;
|
||||
NewCell = newCell;
|
||||
}
|
||||
|
||||
public Cell OldCell { get; private set; }
|
||||
public Cell NewCell { get; private set; }
|
||||
}
|
||||
|
||||
public class ColumnClickEventArgs
|
||||
{
|
||||
public ColumnClickEventArgs(ListColumn column)
|
||||
{
|
||||
Column = column;
|
||||
}
|
||||
|
||||
public ListColumn Column { get; private set; }
|
||||
}
|
||||
|
||||
public class ColumnReorderedEventArgs
|
||||
{
|
||||
public ColumnReorderedEventArgs(int oldDisplayIndex, int newDisplayIndex, ListColumn column)
|
||||
{
|
||||
Column = column;
|
||||
OldDisplayIndex = oldDisplayIndex;
|
||||
NewDisplayIndex = newDisplayIndex;
|
||||
}
|
||||
|
||||
public ListColumn Column { get; private set; }
|
||||
public int OldDisplayIndex { get; private set; }
|
||||
public int NewDisplayIndex { get; private set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Columns
|
||||
|
||||
public class ListColumn
|
||||
{
|
||||
public enum InputType { Boolean, Float, Text, Image }
|
||||
|
||||
public int Index { get; set; }
|
||||
public int OriginalIndex { get; set; } // for implementations that dont use ColumnReorderedEventArgs
|
||||
public string Group { get; set; }
|
||||
public int? Width { get; set; }
|
||||
public int? Left { get; set; }
|
||||
public int? Right { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Text { get; set; }
|
||||
public InputType Type { get; set; }
|
||||
public bool Visible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Column will be drawn with an emphasized look, if true
|
||||
/// </summary>
|
||||
private bool _emphasis;
|
||||
public bool Emphasis
|
||||
{
|
||||
get { return _emphasis; }
|
||||
set { _emphasis = value; }
|
||||
}
|
||||
|
||||
public ListColumn()
|
||||
{
|
||||
Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class ListColumns : List<ListColumn>
|
||||
{
|
||||
public ListColumn this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.SingleOrDefault(column => column.Name == name);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ListColumn> VisibleColumns
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Where(c => c.Visible);
|
||||
}
|
||||
}
|
||||
|
||||
public Action ChangedCallback { get; set; }
|
||||
|
||||
private void DoChangeCallback()
|
||||
{
|
||||
// no check will make it crash for user too, not sure which way of alarm we prefer. no alarm at all will cause all sorts of subtle bugs
|
||||
if (ChangedCallback == null)
|
||||
{
|
||||
System.Diagnostics.Debug.Fail("ColumnChangedCallback has died!");
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this shouldn't be exposed. But in order to not expose it, each RollColumn must have a change callback, and all property changes must call it, it is quicker and easier to just call this when needed
|
||||
public void ColumnsChanged()
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
var columns = VisibleColumns.ToList();
|
||||
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
columns[i].Left = pos;
|
||||
pos += columns[i].Width.Value;
|
||||
columns[i].Right = pos;
|
||||
}
|
||||
|
||||
DoChangeCallback();
|
||||
}
|
||||
|
||||
public new void Add(ListColumn column)
|
||||
{
|
||||
if (this.Any(c => c.Name == column.Name))
|
||||
{
|
||||
// The designer sucks, doing nothing for now
|
||||
return;
|
||||
//throw new InvalidOperationException("A column with this name already exists.");
|
||||
}
|
||||
|
||||
base.Add(column);
|
||||
// save the original index for implementations that do not use ColumnReorderedEventArgs
|
||||
column.OriginalIndex = this.IndexOf(column);
|
||||
ColumnsChanged();
|
||||
}
|
||||
|
||||
public new void AddRange(IEnumerable<ListColumn> collection)
|
||||
{
|
||||
foreach (var column in collection)
|
||||
{
|
||||
if (this.Any(c => c.Name == column.Name))
|
||||
{
|
||||
// The designer sucks, doing nothing for now
|
||||
return;
|
||||
|
||||
throw new InvalidOperationException("A column with this name already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
base.AddRange(collection);
|
||||
ColumnsChanged();
|
||||
}
|
||||
|
||||
public new void Insert(int index, ListColumn column)
|
||||
{
|
||||
if (this.Any(c => c.Name == column.Name))
|
||||
{
|
||||
throw new InvalidOperationException("A column with this name already exists.");
|
||||
}
|
||||
|
||||
base.Insert(index, column);
|
||||
ColumnsChanged();
|
||||
}
|
||||
|
||||
public new void InsertRange(int index, IEnumerable<ListColumn> collection)
|
||||
{
|
||||
foreach (var column in collection)
|
||||
{
|
||||
if (this.Any(c => c.Name == column.Name))
|
||||
{
|
||||
throw new InvalidOperationException("A column with this name already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
base.InsertRange(index, collection);
|
||||
ColumnsChanged();
|
||||
}
|
||||
|
||||
public new bool Remove(ListColumn column)
|
||||
{
|
||||
var result = base.Remove(column);
|
||||
ColumnsChanged();
|
||||
return result;
|
||||
}
|
||||
|
||||
public new int RemoveAll(Predicate<ListColumn> match)
|
||||
{
|
||||
var result = base.RemoveAll(match);
|
||||
ColumnsChanged();
|
||||
return result;
|
||||
}
|
||||
|
||||
public new void RemoveAt(int index)
|
||||
{
|
||||
base.RemoveAt(index);
|
||||
ColumnsChanged();
|
||||
}
|
||||
|
||||
public new void RemoveRange(int index, int count)
|
||||
{
|
||||
base.RemoveRange(index, count);
|
||||
ColumnsChanged();
|
||||
}
|
||||
|
||||
public new void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
ColumnsChanged();
|
||||
}
|
||||
|
||||
public IEnumerable<string> Groups
|
||||
{
|
||||
get
|
||||
{
|
||||
return this
|
||||
.Select(x => x.Group)
|
||||
.Distinct();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cells
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single cell of the Roll
|
||||
/// </summary>
|
||||
public class Cell
|
||||
{
|
||||
public ListColumn Column { get; internal set; }
|
||||
public int? RowIndex { get; internal set; }
|
||||
public string CurrentText { get; internal set; }
|
||||
|
||||
public Cell() { }
|
||||
|
||||
public Cell(Cell cell)
|
||||
{
|
||||
Column = cell.Column;
|
||||
RowIndex = cell.RowIndex;
|
||||
}
|
||||
|
||||
public bool IsDataCell => Column != null && RowIndex.HasValue;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Cell)
|
||||
{
|
||||
var cell = obj as Cell;
|
||||
return this.Column == cell.Column && this.RowIndex == cell.RowIndex;
|
||||
}
|
||||
|
||||
return base.Equals(obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Column.GetHashCode() + RowIndex.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private class SortCell : IComparer<Cell>
|
||||
{
|
||||
int IComparer<Cell>.Compare(Cell a, Cell b)
|
||||
{
|
||||
Cell c1 = a as Cell;
|
||||
Cell c2 = b as Cell;
|
||||
if (c1.RowIndex.HasValue)
|
||||
{
|
||||
if (c2.RowIndex.HasValue)
|
||||
{
|
||||
int row = c1.RowIndex.Value.CompareTo(c2.RowIndex.Value);
|
||||
if (row == 0)
|
||||
{
|
||||
return c1.Column.Name.CompareTo(c2.Column.Name);
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (c2.RowIndex.HasValue)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return c1.Column.Name.CompareTo(c2.Column.Name);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,523 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// A performant VirtualListView implementation that doesn't rely on native Win32 API calls
|
||||
/// (and in fact does not inherit the ListView class at all)
|
||||
/// It is an enhanced version of the work done with GDI+ rendering in InputRoll.cs
|
||||
/// ------------------------------
|
||||
/// *** GDI+ Rendering Methods ***
|
||||
/// ------------------------------
|
||||
/// </summary>
|
||||
public partial class PlatformAgnosticVirtualListView
|
||||
{
|
||||
// reusable Pen and Brush objects
|
||||
private Pen sPen = null;
|
||||
private Brush sBrush = null;
|
||||
|
||||
/// <summary>
|
||||
/// Called when font sizes are changed
|
||||
/// Recalculates cell sizes
|
||||
/// </summary>
|
||||
private void SetCharSize()
|
||||
{
|
||||
using (var g = CreateGraphics())
|
||||
{
|
||||
var sizeC = Size.Round(g.MeasureString("A", ColumnHeaderFont));
|
||||
var sizeI = Size.Round(g.MeasureString("A", CellFont));
|
||||
if (sizeC.Width > sizeI.Width)
|
||||
_charSize = sizeC;
|
||||
else
|
||||
_charSize = sizeI;
|
||||
}
|
||||
|
||||
UpdateCellSize();
|
||||
ColumnWidth = CellWidth;
|
||||
ColumnHeight = CellHeight + 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We draw everthing manually and never call base.OnPaint()
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
// white background
|
||||
sBrush = new SolidBrush(Color.White);
|
||||
sPen = new Pen(Color.White);
|
||||
|
||||
Rectangle rect = e.ClipRectangle;
|
||||
|
||||
e.Graphics.FillRectangle(sBrush, rect);
|
||||
e.Graphics.Flush();
|
||||
|
||||
var visibleColumns = _columns.VisibleColumns.ToList();
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
DrawColumnBg(e, visibleColumns);
|
||||
DrawColumnText(e, visibleColumns);
|
||||
}
|
||||
|
||||
// Background
|
||||
DrawBg(e, visibleColumns);
|
||||
|
||||
// Foreground
|
||||
DrawData(e, visibleColumns);
|
||||
|
||||
DrawColumnDrag(e);
|
||||
DrawCellDrag(e);
|
||||
|
||||
if (BorderSize > 0)
|
||||
{
|
||||
// paint parent border
|
||||
using (var gParent = this.Parent.CreateGraphics())
|
||||
{
|
||||
Pen borderPen = new Pen(BorderColor);
|
||||
for (int b = 1, c = 1; b <= BorderSize; b++, c += 2)
|
||||
{
|
||||
gParent.DrawRectangle(borderPen, this.Left - b, this.Top - b, this.Width + c, this.Height + c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawColumnDrag(PaintEventArgs e)
|
||||
{
|
||||
if (_draggingCell != null)
|
||||
{
|
||||
var text = "";
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
|
||||
QueryItemText?.Invoke(_draggingCell.RowIndex.Value, _columns.IndexOf(_draggingCell.Column), out text);
|
||||
QueryItemTextAdvanced?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, out text, ref offsetX, ref offsetY);
|
||||
|
||||
Color bgColor = ColumnHeaderBackgroundColor;
|
||||
QueryItemBkColor?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, ref bgColor);
|
||||
|
||||
int x1 = _currentX.Value - (_draggingCell.Column.Width.Value / 2);
|
||||
int y1 = _currentY.Value - (CellHeight / 2);
|
||||
int x2 = x1 + _draggingCell.Column.Width.Value;
|
||||
int y2 = y1 + CellHeight;
|
||||
|
||||
sBrush = new SolidBrush(bgColor);
|
||||
e.Graphics.FillRectangle(sBrush, x1, y1, x2 - x1, y2 - y1);
|
||||
sBrush = new SolidBrush(ColumnHeaderFontColor);
|
||||
e.Graphics.DrawString(text, ColumnHeaderFont, sBrush, (PointF)(new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY)));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCellDrag(PaintEventArgs e)
|
||||
{
|
||||
if (_draggingCell != null)
|
||||
{
|
||||
var text = "";
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
QueryItemText?.Invoke(_draggingCell.RowIndex.Value, _columns.IndexOf(_draggingCell.Column), out text);
|
||||
QueryItemTextAdvanced?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, out text, ref offsetX, ref offsetY);
|
||||
|
||||
Color bgColor = CellBackgroundColor;
|
||||
QueryItemBkColor?.Invoke(_draggingCell.RowIndex.Value, _draggingCell.Column, ref bgColor);
|
||||
|
||||
int x1 = _currentX.Value - (_draggingCell.Column.Width.Value / 2);
|
||||
int y1 = _currentY.Value - (CellHeight / 2);
|
||||
int x2 = x1 + _draggingCell.Column.Width.Value;
|
||||
int y2 = y1 + CellHeight;
|
||||
|
||||
sBrush = new SolidBrush(bgColor);
|
||||
e.Graphics.FillRectangle(sBrush, x1, y1, x2 - x1, y2 - y1);
|
||||
sBrush = new SolidBrush(CellFontColor);
|
||||
e.Graphics.DrawString(text, CellFont, sBrush, (PointF)(new Point(x1 + CellWidthPadding + offsetX, y1 + CellHeightPadding + offsetY)));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawColumnText(PaintEventArgs e, List<ListColumn> visibleColumns)
|
||||
{
|
||||
sBrush = new SolidBrush(ColumnHeaderFontColor);
|
||||
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
var point = new Point(column.Left.Value + 2 * CellWidthPadding - _hBar.Value, CellHeightPadding); // TODO: fix this CellPadding issue (2 * CellPadding vs just CellPadding)
|
||||
|
||||
string t = column.Text;
|
||||
ResizeTextToFit(ref t, column.Width.Value, ColumnHeaderFont);
|
||||
|
||||
if (IsHoveringOnColumnCell && column == CurrentCell.Column)
|
||||
{
|
||||
sBrush = new SolidBrush(InvertColor(ColumnHeaderBackgroundHighlightColor));
|
||||
e.Graphics.DrawString(t, ColumnHeaderFont, sBrush, (PointF)(point));
|
||||
sBrush = new SolidBrush(ColumnHeaderFontColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Graphics.DrawString(t, ColumnHeaderFont, sBrush, (PointF)(point));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawData(PaintEventArgs e, List<ListColumn> visibleColumns)
|
||||
{
|
||||
// Prevent exceptions with small windows
|
||||
if (visibleColumns.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (QueryItemText != null || QueryItemTextAdvanced != null)
|
||||
{
|
||||
int startRow = FirstVisibleRow;
|
||||
int range = Math.Min(LastVisibleRow, ItemCount - 1) - startRow + 1;
|
||||
|
||||
sBrush = new SolidBrush(CellFontColor);
|
||||
|
||||
int xPadding = CellWidthPadding + 1 - _hBar.Value;
|
||||
for (int i = 0, f = 0; f < range; i++, f++) // Vertical
|
||||
{
|
||||
//f += _lagFrames[i];
|
||||
int LastVisible = LastVisibleColumnIndex;
|
||||
for (int j = FirstVisibleColumn; j <= LastVisible; j++) // Horizontal
|
||||
{
|
||||
ListColumn col = visibleColumns[j];
|
||||
|
||||
string text = "";
|
||||
int strOffsetX = 0;
|
||||
int strOffsetY = 0;
|
||||
Point point = new Point(col.Left.Value + xPadding, RowsToPixels(i) + CellHeightPadding);
|
||||
|
||||
Bitmap image = null;
|
||||
int bitmapOffsetX = 0;
|
||||
int bitmapOffsetY = 0;
|
||||
|
||||
QueryItemIcon?.Invoke(f + startRow, visibleColumns[j], ref image, ref bitmapOffsetX, ref bitmapOffsetY);
|
||||
|
||||
if (image != null)
|
||||
{
|
||||
e.Graphics.DrawImage(image, new Point(point.X + bitmapOffsetX, point.Y + bitmapOffsetY + CellHeightPadding));
|
||||
}
|
||||
|
||||
QueryItemText?.Invoke(f + startRow, _columns.IndexOf(visibleColumns[j]), out text);
|
||||
QueryItemTextAdvanced?.Invoke(f + startRow, visibleColumns[j], out text, ref strOffsetX, ref strOffsetY);
|
||||
|
||||
bool rePrep = false;
|
||||
if (_selectedItems.Contains(new Cell { Column = visibleColumns[j], RowIndex = f + startRow }))
|
||||
{
|
||||
sBrush = new SolidBrush(InvertColor(CellBackgroundHighlightColor));
|
||||
rePrep = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
ResizeTextToFit(ref text, col.Width.Value, CellFont);
|
||||
e.Graphics.DrawString(text, CellFont, sBrush, (PointF)(new Point(point.X + strOffsetX, point.Y + strOffsetY)));
|
||||
}
|
||||
|
||||
if (rePrep)
|
||||
{
|
||||
sBrush = new SolidBrush(CellFontColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeTextToFit(ref string text, int destinationSize, Font font)
|
||||
{
|
||||
Size strLen;
|
||||
using (var g = CreateGraphics())
|
||||
{
|
||||
strLen = Size.Round(g.MeasureString(text, font));
|
||||
}
|
||||
if (strLen.Width > destinationSize - CellWidthPadding)
|
||||
{
|
||||
// text needs trimming
|
||||
List<char> chars = new List<char>();
|
||||
|
||||
for (int s = 0; s < text.Length; s++)
|
||||
{
|
||||
chars.Add(text[s]);
|
||||
Size tS;
|
||||
Size dotS;
|
||||
using (var g = CreateGraphics())
|
||||
{
|
||||
tS = Size.Round(g.MeasureString(new string(chars.ToArray()), CellFont));
|
||||
dotS = Size.Round(g.MeasureString(".", CellFont));
|
||||
}
|
||||
int dotWidth = dotS.Width * 3;
|
||||
if (tS.Width >= destinationSize - CellWidthPadding - dotWidth)
|
||||
{
|
||||
text = new string(chars.ToArray()) + "...";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/34107015/6813055
|
||||
private Color InvertColor(Color color)
|
||||
{
|
||||
var inverted = Color.FromArgb(color.ToArgb() ^ 0xffffff);
|
||||
|
||||
if (inverted.R > 110 && inverted.R < 150 &&
|
||||
inverted.G > 110 && inverted.G < 150 &&
|
||||
inverted.B > 110 && inverted.B < 150)
|
||||
{
|
||||
int avg = (inverted.R + inverted.G + inverted.B) / 3;
|
||||
avg = avg > 128 ? 200 : 60;
|
||||
inverted = Color.FromArgb(avg, avg, avg);
|
||||
}
|
||||
|
||||
return inverted;
|
||||
}
|
||||
|
||||
private void DrawColumnBg(PaintEventArgs e, List<ListColumn> visibleColumns)
|
||||
{
|
||||
sBrush = new SolidBrush(ColumnHeaderBackgroundColor);
|
||||
sPen = new Pen(ColumnHeaderOutlineColor);
|
||||
|
||||
int bottomEdge = RowsToPixels(0);
|
||||
|
||||
// Gray column box and black line underneath
|
||||
e.Graphics.FillRectangle(sBrush, 0, 0, Width + 1, bottomEdge + 1);
|
||||
e.Graphics.DrawLine(sPen, 0, 0, TotalColWidth.Value + 1, 0);
|
||||
e.Graphics.DrawLine(sPen, 0, bottomEdge, TotalColWidth.Value + 1, bottomEdge);
|
||||
|
||||
// Vertical black seperators
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
int pos = visibleColumns[i].Left.Value - _hBar.Value;
|
||||
e.Graphics.DrawLine(sPen, pos, 0, pos, bottomEdge);
|
||||
}
|
||||
|
||||
// Draw right most line
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
int right = TotalColWidth.Value - _hBar.Value;
|
||||
e.Graphics.DrawLine(sPen, right, 0, right, bottomEdge);
|
||||
}
|
||||
|
||||
// Emphasis
|
||||
foreach (var column in visibleColumns.Where(c => c.Emphasis))
|
||||
{
|
||||
sBrush = new SolidBrush(SystemColors.ActiveBorder);
|
||||
e.Graphics.FillRectangle(sBrush, column.Left.Value + 1 - _hBar.Value, 1, column.Width.Value - 1, ColumnHeight - 1);
|
||||
}
|
||||
|
||||
// If the user is hovering over a column
|
||||
if (IsHoveringOnColumnCell)
|
||||
{
|
||||
// TODO multiple selected columns
|
||||
for (int i = 0; i < visibleColumns.Count; i++)
|
||||
{
|
||||
if (visibleColumns[i] == CurrentCell.Column)
|
||||
{
|
||||
// Left of column is to the right of the viewable area or right of column is to the left of the viewable area
|
||||
if (visibleColumns[i].Left.Value - _hBar.Value > Width || visibleColumns[i].Right.Value - _hBar.Value < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int left = visibleColumns[i].Left.Value - _hBar.Value;
|
||||
int width = visibleColumns[i].Right.Value - _hBar.Value - left;
|
||||
|
||||
if (CurrentCell.Column.Emphasis)
|
||||
{
|
||||
sBrush = new SolidBrush(Color.FromArgb(ColumnHeaderBackgroundHighlightColor.ToArgb() + 0x00550000));
|
||||
}
|
||||
else
|
||||
{
|
||||
sBrush = new SolidBrush(ColumnHeaderBackgroundHighlightColor);
|
||||
}
|
||||
|
||||
e.Graphics.FillRectangle(sBrush, left + 1, 1, width - 1, ColumnHeight - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO refactor this and DoBackGroundCallback functions.
|
||||
/// <summary>
|
||||
/// Draw Gridlines and background colors using QueryItemBkColor.
|
||||
/// </summary>
|
||||
private void DrawBg(PaintEventArgs e, List<ListColumn> visibleColumns)
|
||||
{
|
||||
if (UseCustomBackground && QueryItemBkColor != null)
|
||||
{
|
||||
DoBackGroundCallback(e, visibleColumns);
|
||||
}
|
||||
|
||||
if (GridLines)
|
||||
{
|
||||
sPen = new Pen(GridLineColor);
|
||||
|
||||
// Columns
|
||||
int y = ColumnHeight + 1;
|
||||
int? totalColWidth = TotalColWidth;
|
||||
foreach (var column in visibleColumns)
|
||||
{
|
||||
int x = column.Left.Value - _hBar.Value;
|
||||
e.Graphics.DrawLine(sPen, x, y, x, Height - 1);
|
||||
}
|
||||
|
||||
if (visibleColumns.Any())
|
||||
{
|
||||
e.Graphics.DrawLine(sPen, totalColWidth.Value - _hBar.Value, y, totalColWidth.Value - _hBar.Value, Height - 1);
|
||||
}
|
||||
|
||||
// Rows
|
||||
for (int i = 1; i < VisibleRows + 1; i++)
|
||||
{
|
||||
e.Graphics.DrawLine(sPen, 0, RowsToPixels(i), Width + 1, RowsToPixels(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (_selectedItems.Any() && !HideSelection)
|
||||
{
|
||||
DoSelectionBG(e, visibleColumns);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a cell with rowindex inbetween 0 and VisibleRows, it draws the background color specified. Do not call with absolute rowindices.
|
||||
/// </summary>
|
||||
private void DrawCellBG(PaintEventArgs e, Color color, Cell cell, List<ListColumn> visibleColumns)
|
||||
{
|
||||
int x, y, w, h;
|
||||
|
||||
w = cell.Column.Width.Value - 1;
|
||||
x = cell.Column.Left.Value - _hBar.Value + 1;
|
||||
y = RowsToPixels(cell.RowIndex.Value) + 1; // We can't draw without row and column, so assume they exist and fail catastrophically if they don't
|
||||
h = CellHeight - 1;
|
||||
if (y < ColumnHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (x > DrawWidth || y > DrawHeight)
|
||||
{
|
||||
return;
|
||||
} // Don't draw if off screen.
|
||||
|
||||
var col = cell.Column.Name;
|
||||
if (color.A == 0)
|
||||
{
|
||||
sBrush = new SolidBrush(Color.FromArgb(255, color));
|
||||
}
|
||||
else
|
||||
{
|
||||
sBrush = new SolidBrush(color);
|
||||
}
|
||||
|
||||
e.Graphics.FillRectangle(sBrush, x, y, w, h);
|
||||
}
|
||||
|
||||
protected override void OnPaintBackground(PaintEventArgs pevent)
|
||||
{
|
||||
// Do nothing, and this should never be called
|
||||
}
|
||||
|
||||
private void DoSelectionBG(PaintEventArgs e, List<ListColumn> visibleColumns)
|
||||
{
|
||||
// SuuperW: This allows user to see other colors in selected frames.
|
||||
Color rowColor = CellBackgroundColor; // Color.White;
|
||||
int _lastVisibleRow = LastVisibleRow;
|
||||
int lastRow = -1;
|
||||
foreach (Cell cell in _selectedItems)
|
||||
{
|
||||
if (cell.RowIndex > _lastVisibleRow || cell.RowIndex < FirstVisibleRow || !VisibleColumns.Contains(cell.Column))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Cell relativeCell = new Cell
|
||||
{
|
||||
RowIndex = cell.RowIndex - FirstVisibleRow,
|
||||
Column = cell.Column,
|
||||
};
|
||||
|
||||
if (QueryRowBkColor != null && lastRow != cell.RowIndex.Value)
|
||||
{
|
||||
QueryRowBkColor(cell.RowIndex.Value, ref rowColor);
|
||||
lastRow = cell.RowIndex.Value;
|
||||
}
|
||||
|
||||
Color cellColor = rowColor;
|
||||
QueryItemBkColor?.Invoke(cell.RowIndex.Value, cell.Column, ref cellColor);
|
||||
|
||||
// Alpha layering for cell before selection
|
||||
float alpha = (float)cellColor.A / 255;
|
||||
if (cellColor.A != 255 && cellColor.A != 0)
|
||||
{
|
||||
cellColor = Color.FromArgb(rowColor.R - (int)((rowColor.R - cellColor.R) * alpha),
|
||||
rowColor.G - (int)((rowColor.G - cellColor.G) * alpha),
|
||||
rowColor.B - (int)((rowColor.B - cellColor.B) * alpha));
|
||||
}
|
||||
|
||||
// Alpha layering for selection
|
||||
alpha = 0.85f;
|
||||
cellColor = Color.FromArgb(cellColor.R - (int)((cellColor.R - CellBackgroundHighlightColor.R) * alpha),
|
||||
cellColor.G - (int)((cellColor.G - CellBackgroundHighlightColor.G) * alpha),
|
||||
cellColor.B - (int)((cellColor.B - CellBackgroundHighlightColor.B) * alpha));
|
||||
|
||||
DrawCellBG(e, cellColor, relativeCell, visibleColumns);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls QueryItemBkColor callback for all visible cells and fills in the background of those cells.
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
private void DoBackGroundCallback(PaintEventArgs e, List<ListColumn> visibleColumns)
|
||||
{
|
||||
int startIndex = FirstVisibleRow;
|
||||
int range = Math.Min(LastVisibleRow, ItemCount - 1) - startIndex + 1;
|
||||
int lastVisible = LastVisibleColumnIndex;
|
||||
int firstVisibleColumn = FirstVisibleColumn;
|
||||
// Prevent exceptions with small windows
|
||||
if (firstVisibleColumn < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int i = 0, f = 0; f < range; i++, f++) // Vertical
|
||||
{
|
||||
//f += _lagFrames[i];
|
||||
|
||||
Color rowColor = CellBackgroundColor;
|
||||
QueryRowBkColor?.Invoke(f + startIndex, ref rowColor);
|
||||
|
||||
for (int j = FirstVisibleColumn; j <= lastVisible; j++) // Horizontal
|
||||
{
|
||||
Color itemColor = CellBackgroundColor;
|
||||
QueryItemBkColor(f + startIndex, visibleColumns[j], ref itemColor);
|
||||
if (itemColor == CellBackgroundColor)
|
||||
{
|
||||
itemColor = rowColor;
|
||||
}
|
||||
else if (itemColor.A != 255 && itemColor.A != 0)
|
||||
{
|
||||
float alpha = (float)itemColor.A / 255;
|
||||
itemColor = Color.FromArgb(rowColor.R - (int)((rowColor.R - itemColor.R) * alpha),
|
||||
rowColor.G - (int)((rowColor.G - itemColor.G) * alpha),
|
||||
rowColor.B - (int)((rowColor.B - itemColor.B) * alpha));
|
||||
}
|
||||
|
||||
if (itemColor != Color.White) // An easy optimization, don't draw unless the user specified something other than the default
|
||||
{
|
||||
var cell = new Cell
|
||||
{
|
||||
Column = visibleColumns[j],
|
||||
RowIndex = i
|
||||
};
|
||||
DrawCellBG(e, itemColor, cell, visibleColumns);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,696 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// A performant VirtualListView implementation that doesn't rely on native Win32 API calls
|
||||
/// (and in fact does not inherit the ListView class at all)
|
||||
/// It is an enhanced version of the work done with GDI+ rendering in InputRoll.cs
|
||||
/// -----------------------------------
|
||||
/// *** Events ***
|
||||
/// -----------------------------------
|
||||
/// </summary>
|
||||
public partial class PlatformAgnosticVirtualListView
|
||||
{
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Fire the <see cref="QueryItemText"/> event which requests the text for the passed cell
|
||||
/// </summary>
|
||||
[Category("Virtual")]
|
||||
public event QueryItemTextHandler QueryItemText;
|
||||
|
||||
/// <summary>
|
||||
/// Fire the <see cref="QueryItemTextAdvanced"/> event which requests the text for the passed cell
|
||||
/// </summary>
|
||||
[Category("Virtual")]
|
||||
public event QueryItemTextHandlerAdvanced QueryItemTextAdvanced;
|
||||
|
||||
/// <summary>
|
||||
/// Fire the <see cref="QueryItemBkColor"/> event which requests the background color for the passed cell
|
||||
/// </summary>
|
||||
[Category("Virtual")]
|
||||
public event QueryItemBkColorHandler QueryItemBkColor;
|
||||
|
||||
[Category("Virtual")]
|
||||
public event QueryRowBkColorHandler QueryRowBkColor;
|
||||
|
||||
/// <summary>
|
||||
/// Fire the <see cref="QueryItemIconHandler"/> event which requests an icon for a given cell
|
||||
/// </summary>
|
||||
[Category("Virtual")]
|
||||
public event QueryItemIconHandler QueryItemIcon;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when the mouse moves from one cell to another (including column header cells)
|
||||
/// </summary>
|
||||
[Category("Mouse")]
|
||||
public event CellChangeEventHandler PointedCellChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Fires when a cell is hovered on
|
||||
/// </summary>
|
||||
[Category("Mouse")]
|
||||
public event HoverEventHandler CellHovered;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a column header is clicked
|
||||
/// </summary>
|
||||
[Category("Action")]
|
||||
public event ColumnClickEventHandler ColumnClick;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a column header is right-clicked
|
||||
/// </summary>
|
||||
[Category("Action")]
|
||||
public event ColumnClickEventHandler ColumnRightClick;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever the 'SelectedItems' property for this control changes
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public event EventHandler SelectedIndexChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever the mouse wheel is scrolled while the right mouse button is held
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public event RightMouseScrollEventHandler RightMouseScrolled;
|
||||
|
||||
[Category("Property Changed")]
|
||||
[Description("Occurs when the column header has been reordered")]
|
||||
public event ColumnReorderedEventHandler ColumnReordered;
|
||||
|
||||
[Category("Action")]
|
||||
[Description("Occurs when the scroll value of the visible rows change (in vertical orientation this is the vertical scroll bar change, and in horizontal it is the horizontal scroll bar)")]
|
||||
public event RowScrollEvent RowScroll;
|
||||
|
||||
[Category("Action")]
|
||||
[Description("Occurs when the scroll value of the columns (in vertical orientation this is the horizontal scroll bar change, and in horizontal it is the vertical scroll bar)")]
|
||||
public event ColumnScrollEvent ColumnScroll;
|
||||
|
||||
[Category("Action")]
|
||||
[Description("Occurs when a cell is dragged and then dropped into a new cell, old cell is the cell that was being dragged, new cell is its new destination")]
|
||||
public event CellDroppedEvent CellDropped;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delegates
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the text for a cell
|
||||
/// </summary>
|
||||
public delegate void QueryItemTextHandlerAdvanced(int index, ListColumn column, out string text, ref int offsetX, ref int offsetY);
|
||||
public delegate void QueryItemTextHandler(int index, int column, out string text);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the background color for a cell
|
||||
/// </summary>
|
||||
public delegate void QueryItemBkColorHandler(int index, ListColumn column, ref Color color);
|
||||
public delegate void QueryRowBkColorHandler(int index, ref Color color);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the image for a given cell
|
||||
/// </summary>
|
||||
public delegate void QueryItemIconHandler(int index, ListColumn column, ref Bitmap icon, ref int offsetX, ref int offsetY);
|
||||
|
||||
public delegate void CellChangeEventHandler(object sender, CellEventArgs e);
|
||||
|
||||
public delegate void HoverEventHandler(object sender, CellEventArgs e);
|
||||
|
||||
public delegate void RightMouseScrollEventHandler(object sender, MouseEventArgs e);
|
||||
|
||||
public delegate void ColumnClickEventHandler(object sender, ColumnClickEventArgs e);
|
||||
|
||||
public delegate void ColumnReorderedEventHandler(object sender, ColumnReorderedEventArgs e);
|
||||
|
||||
public delegate void RowScrollEvent(object sender, EventArgs e);
|
||||
|
||||
public delegate void ColumnScrollEvent(object sender, EventArgs e);
|
||||
|
||||
public delegate void CellDroppedEvent(object sender, CellEventArgs e);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mouse and Key Events
|
||||
|
||||
private bool _columnDownMoved;
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
_currentX = e.X;
|
||||
_currentY = e.Y;
|
||||
|
||||
if (_columnDown != null)
|
||||
{
|
||||
_columnDownMoved = true;
|
||||
}
|
||||
|
||||
Cell newCell = CalculatePointedCell(_currentX.Value, _currentY.Value);
|
||||
|
||||
newCell.RowIndex += FirstVisibleRow;
|
||||
if (newCell.RowIndex < 0)
|
||||
{
|
||||
newCell.RowIndex = 0;
|
||||
}
|
||||
|
||||
if (!newCell.Equals(CurrentCell))
|
||||
{
|
||||
CellChanged(newCell);
|
||||
|
||||
if (IsHoveringOnColumnCell ||
|
||||
(WasHoveringOnColumnCell && !IsHoveringOnColumnCell))
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
else if (_columnDown != null)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
else if (_columnDown != null) // Kind of silly feeling to have this check twice, but the only alternative I can think of has it refreshing twice when pointed column changes with column down, and speed matters
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
if (_columnSeparatorDown != null)
|
||||
{
|
||||
// column is being resized
|
||||
DoColumnResize();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// cursor changes
|
||||
if (IsHoveringOnDraggableColumnDivide && AllowColumnResize)
|
||||
Cursor.Current = Cursors.VSplit;
|
||||
else if (IsHoveringOnColumnCell && AllowColumnReorder)
|
||||
Cursor.Current = Cursors.Hand;
|
||||
else
|
||||
Cursor.Current = Cursors.Default;
|
||||
|
||||
base.OnMouseMove(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseEnter(EventArgs e)
|
||||
{
|
||||
CurrentCell = new Cell
|
||||
{
|
||||
Column = null,
|
||||
RowIndex = null
|
||||
};
|
||||
|
||||
base.OnMouseEnter(e);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(EventArgs e)
|
||||
{
|
||||
_currentX = null;
|
||||
_currentY = null;
|
||||
CurrentCell = null;
|
||||
IsPaintDown = false;
|
||||
_hoverTimer.Stop();
|
||||
Cursor.Current = Cursors.Default;
|
||||
Refresh();
|
||||
base.OnMouseLeave(e);
|
||||
}
|
||||
|
||||
// TODO add query callback of whether to select the cell or not
|
||||
protected override void OnMouseDown(MouseEventArgs e)
|
||||
{
|
||||
if (!GlobalWin.MainForm.EmulatorPaused && _currentX.HasValue)
|
||||
{
|
||||
// copypaste from OnMouseMove()
|
||||
Cell newCell = CalculatePointedCell(_currentX.Value, _currentY.Value);
|
||||
|
||||
newCell.RowIndex += FirstVisibleRow;
|
||||
if (newCell.RowIndex < 0)
|
||||
{
|
||||
newCell.RowIndex = 0;
|
||||
}
|
||||
|
||||
if (!newCell.Equals(CurrentCell))
|
||||
{
|
||||
CellChanged(newCell);
|
||||
|
||||
if (IsHoveringOnColumnCell ||
|
||||
(WasHoveringOnColumnCell && !IsHoveringOnColumnCell))
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
else if (_columnDown != null)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
else if (_columnDown != null)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
if (IsHoveringOnDraggableColumnDivide && AllowColumnResize)
|
||||
{
|
||||
_columnSeparatorDown = ColumnAtX(_currentX.Value);
|
||||
}
|
||||
else if (IsHoveringOnColumnCell && AllowColumnReorder)
|
||||
{
|
||||
_columnDown = CurrentCell.Column;
|
||||
}
|
||||
else if (InputPaintingMode)
|
||||
{
|
||||
IsPaintDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Button == MouseButtons.Right)
|
||||
{
|
||||
if (!IsHoveringOnColumnCell)
|
||||
{
|
||||
RightButtonHeld = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
if (IsHoveringOnDataCell)
|
||||
{
|
||||
if (ModifierKeys == Keys.Alt)
|
||||
{
|
||||
// do marker drag here
|
||||
}
|
||||
else if (ModifierKeys == Keys.Shift && (CurrentCell.Column.Type == ListColumn.InputType.Text))
|
||||
{
|
||||
if (_selectedItems.Any())
|
||||
{
|
||||
if (FullRowSelect)
|
||||
{
|
||||
var selected = _selectedItems.Any(c => c.RowIndex.HasValue && CurrentCell.RowIndex.HasValue && c.RowIndex == CurrentCell.RowIndex);
|
||||
|
||||
if (!selected)
|
||||
{
|
||||
var rowIndices = _selectedItems
|
||||
.Where(c => c.RowIndex.HasValue)
|
||||
.Select(c => c.RowIndex ?? -1)
|
||||
.Where(c => c >= 0) // Hack to avoid possible Nullable exceptions
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var firstIndex = rowIndices.Min();
|
||||
var lastIndex = rowIndices.Max();
|
||||
|
||||
if (CurrentCell.RowIndex.Value < firstIndex)
|
||||
{
|
||||
for (int i = CurrentCell.RowIndex.Value; i < firstIndex; i++)
|
||||
{
|
||||
SelectCell(new Cell
|
||||
{
|
||||
RowIndex = i,
|
||||
Column = CurrentCell.Column
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (CurrentCell.RowIndex.Value > lastIndex)
|
||||
{
|
||||
for (int i = lastIndex + 1; i <= CurrentCell.RowIndex.Value; i++)
|
||||
{
|
||||
SelectCell(new Cell
|
||||
{
|
||||
RowIndex = i,
|
||||
Column = CurrentCell.Column
|
||||
});
|
||||
}
|
||||
}
|
||||
else // Somewhere in between, a scenario that can happen with ctrl-clicking, find the previous and highlight from there
|
||||
{
|
||||
var nearest = rowIndices
|
||||
.Where(x => x < CurrentCell.RowIndex.Value)
|
||||
.Max();
|
||||
|
||||
for (int i = nearest + 1; i <= CurrentCell.RowIndex.Value; i++)
|
||||
{
|
||||
SelectCell(new Cell
|
||||
{
|
||||
RowIndex = i,
|
||||
Column = CurrentCell.Column
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("Shift click logic for individual cells has not yet implemented");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectCell(CurrentCell);
|
||||
}
|
||||
}
|
||||
else if (ModifierKeys == Keys.Control && (CurrentCell.Column.Type == ListColumn.InputType.Text))
|
||||
{
|
||||
SelectCell(CurrentCell, toggle: true);
|
||||
}
|
||||
else if (ModifierKeys != Keys.Shift)
|
||||
{
|
||||
var hadIndex = _selectedItems.Any();
|
||||
_selectedItems.Clear();
|
||||
SelectCell(CurrentCell);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
|
||||
SelectedIndexChanged?.Invoke(this, new EventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
base.OnMouseDown(e);
|
||||
|
||||
if (AllowRightClickSelecton && e.Button == MouseButtons.Right)
|
||||
{
|
||||
if (!IsHoveringOnColumnCell)
|
||||
{
|
||||
_currentX = e.X;
|
||||
_currentY = e.Y;
|
||||
Cell newCell = CalculatePointedCell(_currentX.Value, _currentY.Value);
|
||||
newCell.RowIndex += FirstVisibleRow;
|
||||
CellChanged(newCell);
|
||||
SelectCell(CurrentCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseEventArgs e)
|
||||
{
|
||||
if (_columnSeparatorDown != null && AllowColumnResize)
|
||||
{
|
||||
DoColumnResize();
|
||||
Refresh();
|
||||
}
|
||||
else if (IsHoveringOnColumnCell && AllowColumnReorder)
|
||||
{
|
||||
if (_columnDown != null && _columnDownMoved)
|
||||
{
|
||||
DoColumnReorder();
|
||||
_columnDown = null;
|
||||
Refresh();
|
||||
}
|
||||
else if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
ColumnClickEvent(ColumnAtX(e.X));
|
||||
}
|
||||
else if (e.Button == MouseButtons.Right)
|
||||
{
|
||||
ColumnRightClickEvent(ColumnAtX(e.X));
|
||||
}
|
||||
}
|
||||
|
||||
_columnDown = null;
|
||||
_columnDownMoved = false;
|
||||
_columnSeparatorDown = null;
|
||||
RightButtonHeld = false;
|
||||
IsPaintDown = false;
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
|
||||
private void IncrementScrollBar(ScrollBar bar, bool increment)
|
||||
{
|
||||
int newVal;
|
||||
if (increment)
|
||||
{
|
||||
newVal = bar.Value + (bar.SmallChange * ScrollSpeed);
|
||||
if (newVal > bar.Maximum - bar.LargeChange)
|
||||
{
|
||||
newVal = bar.Maximum - bar.LargeChange;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newVal = bar.Value - (bar.SmallChange * ScrollSpeed);
|
||||
if (newVal < 0)
|
||||
{
|
||||
newVal = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_programmaticallyUpdatingScrollBarValues = true;
|
||||
bar.Value = newVal;
|
||||
_programmaticallyUpdatingScrollBarValues = false;
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseEventArgs e)
|
||||
{
|
||||
IncrementScrollBar(_vBar, e.Delta < 0);
|
||||
if (_currentX != null)
|
||||
{
|
||||
OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, _currentX.Value, _currentY.Value, 0));
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void DoRightMouseScroll(object sender, MouseEventArgs e)
|
||||
{
|
||||
RightMouseScrolled?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void ColumnClickEvent(ListColumn column)
|
||||
{
|
||||
ColumnClick?.Invoke(this, new ColumnClickEventArgs(column));
|
||||
}
|
||||
|
||||
private void ColumnRightClickEvent(ListColumn column)
|
||||
{
|
||||
ColumnRightClick?.Invoke(this, new ColumnClickEventArgs(column));
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (!SuspendHotkeys)
|
||||
{
|
||||
if (e.Control && !e.Alt && e.Shift && e.KeyCode == Keys.F) // Ctrl+Shift+F
|
||||
{
|
||||
//HorizontalOrientation ^= true;
|
||||
}
|
||||
// Scroll
|
||||
else if (!e.Control && !e.Alt && !e.Shift && e.KeyCode == Keys.PageUp) // Page Up
|
||||
{
|
||||
if (FirstVisibleRow > 0)
|
||||
{
|
||||
LastVisibleRow = FirstVisibleRow;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
else if (!e.Control && !e.Alt && !e.Shift && e.KeyCode == Keys.PageDown) // Page Down
|
||||
{
|
||||
var totalRows = LastVisibleRow - FirstVisibleRow;
|
||||
if (totalRows <= ItemCount)
|
||||
{
|
||||
var final = LastVisibleRow + totalRows;
|
||||
if (final > ItemCount)
|
||||
{
|
||||
final = ItemCount;
|
||||
}
|
||||
|
||||
LastVisibleRow = final;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
else if (!e.Control && !e.Alt && !e.Shift && e.KeyCode == Keys.Home) // Home
|
||||
{
|
||||
FirstVisibleRow = 0;
|
||||
Refresh();
|
||||
}
|
||||
else if (!e.Control && !e.Alt && !e.Shift && e.KeyCode == Keys.End) // End
|
||||
{
|
||||
LastVisibleRow = ItemCount;
|
||||
Refresh();
|
||||
}
|
||||
else if (!e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Up) // Up
|
||||
{
|
||||
if (FirstVisibleRow > 0)
|
||||
{
|
||||
FirstVisibleRow--;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
else if (!e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Down) // Down
|
||||
{
|
||||
if (FirstVisibleRow < ItemCount - 1)
|
||||
{
|
||||
FirstVisibleRow++;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
// Selection courser
|
||||
else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Up) // Ctrl + Up
|
||||
{
|
||||
if (SelectedRows.Any() && LetKeysModifySelection && SelectedRows.First() > 0)
|
||||
{
|
||||
foreach (var row in SelectedRows.ToList())
|
||||
{
|
||||
SelectItem(row - 1, true);
|
||||
SelectItem(row, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Down) // Ctrl + Down
|
||||
{
|
||||
if (SelectedRows.Any() && LetKeysModifySelection)
|
||||
{
|
||||
foreach (var row in SelectedRows.Reverse().ToList())
|
||||
{
|
||||
SelectItem(row + 1, true);
|
||||
SelectItem(row, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Left) // Ctrl + Left
|
||||
{
|
||||
if (SelectedRows.Any() && LetKeysModifySelection)
|
||||
{
|
||||
SelectItem(SelectedRows.Last(), false);
|
||||
}
|
||||
}
|
||||
else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Right) // Ctrl + Right
|
||||
{
|
||||
if (SelectedRows.Any() && LetKeysModifySelection && SelectedRows.Last() < _itemCount - 1)
|
||||
{
|
||||
SelectItem(SelectedRows.Last() + 1, true);
|
||||
}
|
||||
}
|
||||
else if (e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Left) // Ctrl + Shift + Left
|
||||
{
|
||||
if (SelectedRows.Any() && LetKeysModifySelection && SelectedRows.First() > 0)
|
||||
{
|
||||
SelectItem(SelectedRows.First() - 1, true);
|
||||
}
|
||||
}
|
||||
else if (e.Control && e.Shift && !e.Alt && e.KeyCode == Keys.Right) // Ctrl + Shift + Right
|
||||
{
|
||||
if (SelectedRows.Any() && LetKeysModifySelection)
|
||||
{
|
||||
SelectItem(SelectedRows.First(), false);
|
||||
}
|
||||
}
|
||||
else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.PageUp) // Ctrl + Page Up
|
||||
{
|
||||
//jump to above marker with selection courser
|
||||
if (LetKeysModifySelection)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.PageDown) // Ctrl + Page Down
|
||||
{
|
||||
//jump to below marker with selection courser
|
||||
if (LetKeysModifySelection)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.Home) // Ctrl + Home
|
||||
{
|
||||
//move selection courser to frame 0
|
||||
if (LetKeysModifySelection)
|
||||
{
|
||||
DeselectAll();
|
||||
SelectItem(0, true);
|
||||
}
|
||||
}
|
||||
else if (e.Control && !e.Shift && !e.Alt && e.KeyCode == Keys.End) // Ctrl + End
|
||||
{
|
||||
//move selection courser to end of movie
|
||||
if (LetKeysModifySelection)
|
||||
{
|
||||
DeselectAll();
|
||||
SelectItem(ItemCount - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Change Events
|
||||
|
||||
protected override void OnResize(EventArgs e)
|
||||
{
|
||||
RecalculateScrollBars();
|
||||
if (BorderSize > 0 && this.Parent != null)
|
||||
{
|
||||
// refresh the parent control to regen the border
|
||||
this.Parent.Refresh();
|
||||
}
|
||||
base.OnResize(e);
|
||||
Refresh();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this function to change the CurrentCell to newCell
|
||||
/// </summary>
|
||||
private void CellChanged(Cell newCell)
|
||||
{
|
||||
LastCell = CurrentCell;
|
||||
CurrentCell = newCell;
|
||||
|
||||
if (PointedCellChanged != null &&
|
||||
(LastCell.Column != CurrentCell.Column || LastCell.RowIndex != CurrentCell.RowIndex))
|
||||
{
|
||||
PointedCellChanged(this, new CellEventArgs(LastCell, CurrentCell));
|
||||
}
|
||||
|
||||
if (CurrentCell?.Column != null && CurrentCell.RowIndex.HasValue)
|
||||
{
|
||||
_hoverTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_hoverTimer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void VerticalBar_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!_programmaticallyUpdatingScrollBarValues)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
RowScroll?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void HorizontalBar_ValueChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (!_programmaticallyUpdatingScrollBarValues)
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
ColumnScroll?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void ColumnChangedCallback()
|
||||
{
|
||||
RecalculateScrollBars();
|
||||
if (_columns.VisibleColumns.Any())
|
||||
{
|
||||
ColumnWidth = _columns.VisibleColumns.Max(c => c.Width.Value) + CellWidthPadding * 4;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// A performant VirtualListView implementation that doesn't rely on native Win32 API calls
|
||||
/// (and in fact does not inherit the ListView class at all)
|
||||
/// It is an enhanced version of the work done with GDI+ rendering in InputRoll.cs
|
||||
/// ----------------------
|
||||
/// *** Helper Methods ***
|
||||
/// ----------------------
|
||||
/// </summary>
|
||||
public partial class PlatformAgnosticVirtualListView
|
||||
{
|
||||
// TODO: Make into an extension method
|
||||
private static Color Add(Color color, int val)
|
||||
{
|
||||
var col = color.ToArgb();
|
||||
col += val;
|
||||
return Color.FromArgb(col);
|
||||
}
|
||||
|
||||
private void DoColumnReorder()
|
||||
{
|
||||
if (_columnDown != CurrentCell.Column)
|
||||
{
|
||||
var oldIndex = _columns.IndexOf(_columnDown);
|
||||
var newIndex = _columns.IndexOf(CurrentCell.Column);
|
||||
|
||||
ColumnReordered?.Invoke(this, new ColumnReorderedEventArgs(oldIndex, newIndex, _columnDown));
|
||||
|
||||
_columns.Remove(_columnDown);
|
||||
_columns.Insert(newIndex, _columnDown);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for implementations that do not make use of ColumnReorderedEventArgs related callbacks
|
||||
/// Basically, each column stores its initial index when added in .OriginalIndex
|
||||
/// </summary>
|
||||
/// <param name="currIndex"></param>
|
||||
/// <returns></returns>
|
||||
public int GetOriginalColumnIndex(int currIndex)
|
||||
{
|
||||
return AllColumns[currIndex].OriginalIndex;
|
||||
}
|
||||
|
||||
// ScrollBar.Maximum = DesiredValue + ScrollBar.LargeChange - 1
|
||||
// See MSDN Page for more information on the dumb ScrollBar.Maximum Property
|
||||
private void RecalculateScrollBars()
|
||||
{
|
||||
if (_vBar == null || _hBar == null)
|
||||
return;
|
||||
|
||||
UpdateDrawSize();
|
||||
|
||||
var columns = _columns.VisibleColumns.ToList();
|
||||
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
NeedsVScrollbar = ItemCount > 1;
|
||||
NeedsHScrollbar = TotalColWidth.HasValue && TotalColWidth.Value - DrawWidth + 1 > 0;
|
||||
|
||||
UpdateDrawSize();
|
||||
if (VisibleRows > 0)
|
||||
{
|
||||
_vBar.Maximum = Math.Max((VisibleRows - 1) * CellHeight, _vBar.Maximum); // ScrollBar.Maximum is dumb
|
||||
_vBar.LargeChange = (VisibleRows - 1) * CellHeight;
|
||||
// DrawWidth can be negative if the TAStudio window is small enough
|
||||
// Clamp LargeChange to 0 here to prevent exceptions
|
||||
_hBar.LargeChange = Math.Max(0, DrawWidth / 2);
|
||||
}
|
||||
|
||||
// Update VBar
|
||||
if (NeedsVScrollbar)
|
||||
{
|
||||
_vBar.Maximum = RowsToPixels(ItemCount + 1) - (CellHeight * 3) + _vBar.LargeChange - 1;
|
||||
|
||||
_vBar.Location = new Point(Width - _vBar.Width, 0);
|
||||
_vBar.Height = Height;
|
||||
_vBar.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_vBar.Visible = false;
|
||||
_vBar.Value = 0;
|
||||
}
|
||||
|
||||
// Update HBar
|
||||
if (NeedsHScrollbar)
|
||||
{
|
||||
_hBar.Maximum = TotalColWidth.Value - DrawWidth + _hBar.LargeChange;
|
||||
|
||||
_hBar.Location = new Point(0, Height - _hBar.Height);
|
||||
_hBar.Width = Width - (NeedsVScrollbar ? (_vBar.Width + 1) : 0);
|
||||
_hBar.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hBar.Visible = false;
|
||||
_hBar.Value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDrawSize()
|
||||
{
|
||||
if (NeedsVScrollbar)
|
||||
{
|
||||
DrawWidth = Width - _vBar.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawWidth = Width;
|
||||
}
|
||||
if (NeedsHScrollbar)
|
||||
{
|
||||
DrawHeight = Height - _hBar.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawHeight = Height;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If FullRowSelect is enabled, selects all cells in the row that contains the given cell. Otherwise only given cell is added.
|
||||
/// </summary>
|
||||
/// <param name="cell">The cell to select.</param>
|
||||
private void SelectCell(Cell cell, bool toggle = false)
|
||||
{
|
||||
if (cell.RowIndex.HasValue && cell.RowIndex < ItemCount)
|
||||
{
|
||||
if (!MultiSelect)
|
||||
{
|
||||
_selectedItems.Clear();
|
||||
}
|
||||
|
||||
if (FullRowSelect)
|
||||
{
|
||||
if (toggle && _selectedItems.Any(x => x.RowIndex.HasValue && x.RowIndex == cell.RowIndex))
|
||||
{
|
||||
var items = _selectedItems
|
||||
.Where(x => x.RowIndex.HasValue && x.RowIndex == cell.RowIndex)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
_selectedItems.Remove(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var column in _columns)
|
||||
{
|
||||
_selectedItems.Add(new Cell
|
||||
{
|
||||
RowIndex = cell.RowIndex,
|
||||
Column = column
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (toggle && _selectedItems.Any(x => x.RowIndex.HasValue && x.RowIndex == cell.RowIndex))
|
||||
{
|
||||
var item = _selectedItems
|
||||
.FirstOrDefault(x => x.Equals(cell));
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
_selectedItems.Remove(item);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedItems.Add(CurrentCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsHoveringOnDraggableColumnDivide =>
|
||||
IsHoveringOnColumnCell &&
|
||||
((_currentX <= CurrentCell.Column.Left + 2 && CurrentCell.Column.Index != 0) ||
|
||||
(_currentX >= CurrentCell.Column.Right - 2 && CurrentCell.Column.Index != _columns.Count - 1));
|
||||
|
||||
private bool IsHoveringOnColumnCell => CurrentCell?.Column != null && !CurrentCell.RowIndex.HasValue;
|
||||
|
||||
private bool IsHoveringOnDataCell => CurrentCell?.Column != null && CurrentCell.RowIndex.HasValue;
|
||||
|
||||
private bool WasHoveringOnColumnCell => LastCell?.Column != null && !LastCell.RowIndex.HasValue;
|
||||
|
||||
private bool WasHoveringOnDataCell => LastCell?.Column != null && LastCell.RowIndex.HasValue;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the specific cell that contains the (x, y) coordinate.
|
||||
/// </summary>
|
||||
/// <remarks>The row number that it returns will be between 0 and VisibleRows, NOT the absolute row number.</remarks>
|
||||
/// <param name="x">X coordinate point.</param>
|
||||
/// <param name="y">Y coordinate point.</param>
|
||||
/// <returns>The cell with row number and RollColumn reference, both of which can be null. </returns>
|
||||
private Cell CalculatePointedCell(int x, int y)
|
||||
{
|
||||
var newCell = new Cell();
|
||||
var columns = _columns.VisibleColumns.ToList();
|
||||
|
||||
// If pointing to a column header
|
||||
if (columns.Any())
|
||||
{
|
||||
newCell.RowIndex = PixelsToRows(y);
|
||||
newCell.Column = ColumnAtX(x);
|
||||
}
|
||||
|
||||
if (!(IsPaintDown || RightButtonHeld) && newCell.RowIndex <= -1) // -2 if we're entering from the top
|
||||
{
|
||||
newCell.RowIndex = null;
|
||||
}
|
||||
|
||||
return newCell;
|
||||
}
|
||||
|
||||
|
||||
private void CalculateColumnToResize()
|
||||
{
|
||||
// if this is reached, we are already over a selectable column divide
|
||||
_columnSeparatorDown = ColumnAtX(_currentX.Value);
|
||||
}
|
||||
|
||||
private void DoColumnResize()
|
||||
{
|
||||
var widthChange = _currentX - _columnSeparatorDown.Right;
|
||||
_columnSeparatorDown.Width += widthChange;
|
||||
if (_columnSeparatorDown.Width < MinimumColumnSize)
|
||||
_columnSeparatorDown.Width = MinimumColumnSize;
|
||||
AllColumns.ColumnsChanged();
|
||||
}
|
||||
|
||||
// A boolean that indicates if the InputRoll is too large vertically and requires a vertical scrollbar.
|
||||
private bool NeedsVScrollbar { get; set; }
|
||||
|
||||
// A boolean that indicates if the InputRoll is too large horizontally and requires a horizontal scrollbar.
|
||||
private bool NeedsHScrollbar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the width of the supplied column.
|
||||
/// <remarks>Call when changing the ColumnCell text, CellPadding, or text font.</remarks>
|
||||
/// </summary>
|
||||
/// <param name="col">The RollColumn object to update.</param>
|
||||
/// <returns>The new width of the RollColumn object.</returns>
|
||||
private int UpdateWidth(ListColumn col)
|
||||
{
|
||||
col.Width = (col.Text.Length * _charSize.Width) + (CellWidthPadding * 4);
|
||||
return col.Width.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total width of all the columns by using the last column's Right property.
|
||||
/// </summary>
|
||||
/// <returns>A nullable Int representing total width.</returns>
|
||||
private int? TotalColWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_columns.VisibleColumns.Any())
|
||||
{
|
||||
return _columns.VisibleColumns.Last().Right;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the RollColumn object at the specified visible x coordinate. Coordinate should be between 0 and Width of the InputRoll Control.
|
||||
/// </summary>
|
||||
/// <param name="x">The x coordinate.</param>
|
||||
/// <returns>RollColumn object that contains the x coordinate or null if none exists.</returns>
|
||||
private ListColumn ColumnAtX(int x)
|
||||
{
|
||||
foreach (ListColumn column in _columns.VisibleColumns)
|
||||
{
|
||||
if (column.Left.Value - _hBar.Value <= x && column.Right.Value - _hBar.Value >= x)
|
||||
{
|
||||
return column;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a row number to a horizontal or vertical coordinate.
|
||||
/// </summary>
|
||||
/// <returns>A vertical coordinate if Vertical Oriented, otherwise a horizontal coordinate.</returns>
|
||||
private int RowsToPixels(int index)
|
||||
{
|
||||
return (index * CellHeight) + ColumnHeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a horizontal or vertical coordinate to a row number.
|
||||
/// </summary>
|
||||
/// <param name="pixels">A vertical coordinate if Vertical Oriented, otherwise a horizontal coordinate.</param>
|
||||
/// <returns>A row number between 0 and VisibleRows if it is a Datarow, otherwise a negative number if above all Datarows.</returns>
|
||||
private int PixelsToRows(int pixels)
|
||||
{
|
||||
// Using Math.Floor and float because integer division rounds towards 0 but we want to round down.
|
||||
if (CellHeight == 0)
|
||||
CellHeight++;
|
||||
|
||||
return (int)Math.Floor((float)(pixels - ColumnHeight) / CellHeight);
|
||||
}
|
||||
|
||||
// The width of the largest column cell in Horizontal Orientation
|
||||
private int ColumnWidth { get; set; }
|
||||
|
||||
// The height of a column cell in Vertical Orientation.
|
||||
private int ColumnHeight { get; set; }
|
||||
|
||||
// The width of a cell in Horizontal Orientation. Only can be changed by changing the Font or CellPadding.
|
||||
private int CellWidth { get; set; }
|
||||
|
||||
[Browsable(false)]
|
||||
public int RowHeight => CellHeight;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the height of a cell in Vertical Orientation. Only can be changed by changing the Font or CellPadding.
|
||||
/// </summary>
|
||||
private int CellHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Call when _charSize, MaxCharactersInHorizontal, or CellPadding is changed.
|
||||
/// </summary>
|
||||
private void UpdateCellSize()
|
||||
{
|
||||
CellHeight = _charSize.Height + (CellHeightPadding * 2);
|
||||
CellWidth = (_charSize.Width/* * MaxCharactersInHorizontal*/) + (CellWidthPadding * 4); // Double the padding for horizontal because it looks better
|
||||
}
|
||||
/*
|
||||
/// <summary>
|
||||
/// Call when _charSize, MaxCharactersInHorizontal, or CellPadding is changed.
|
||||
/// </summary>
|
||||
private void UpdateColumnSize()
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
// Number of displayed + hidden frames, if fps is as expected
|
||||
private int ExpectedDisplayRange()
|
||||
{
|
||||
return (VisibleRows + 1); // * LagFramesToHide;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,780 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// A performant VirtualListView implementation that doesn't rely on native Win32 API calls
|
||||
/// (and in fact does not inherit the ListView class at all)
|
||||
/// It is an enhanced version of the work done with GDI+ rendering in InputRoll.cs
|
||||
/// -------------------------
|
||||
/// *** Public Properties ***
|
||||
/// -------------------------
|
||||
/// </summary>
|
||||
public partial class PlatformAgnosticVirtualListView
|
||||
{
|
||||
#region ListView Compatibility Properties
|
||||
|
||||
/// <summary>
|
||||
/// This VirtualListView implementation doesn't really need this, but it is here for compatibility
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public int VirtualListSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _itemCount;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_itemCount = value;
|
||||
RecalculateScrollBars();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ListView compatibility property
|
||||
/// THIS DOES NOT WORK PROPERLY - AVOID!
|
||||
/// </summary>
|
||||
[System.ComponentModel.Browsable(false)]
|
||||
public System.Windows.Forms.ListView.SelectedIndexCollection SelectedIndices
|
||||
{
|
||||
// !!! does not work properly, avoid using this in the calling implementation !!!
|
||||
get
|
||||
{
|
||||
var tmpListView = new System.Windows.Forms.ListView();
|
||||
//tmpListView.VirtualMode = true;
|
||||
//var selectedIndexCollection = new System.Windows.Forms.ListView.SelectedIndexCollection(tmpListView);
|
||||
//tmpListView.VirtualListSize = ItemCount;
|
||||
for (int i = 0; i < ItemCount; i++)
|
||||
{
|
||||
tmpListView.Items.Add(i.ToString());
|
||||
}
|
||||
|
||||
//tmpListView.Refresh();
|
||||
|
||||
if (AnyRowsSelected)
|
||||
{
|
||||
var indices = SelectedRows.ToList();
|
||||
foreach (var i in indices)
|
||||
{
|
||||
tmpListView.SelectedIndices.Add(i);
|
||||
//selectedIndexCollection.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
return tmpListView.SelectedIndices; // selectedIndexCollection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compatibility property
|
||||
/// With a standard ListView you can add columns in the Designer
|
||||
/// We will ignore this (but leave it here for compatibility)
|
||||
/// Columns must be added through the AddColumns() public method
|
||||
/// </summary>
|
||||
public System.Windows.Forms.ListView.ColumnHeaderCollection Columns = new System.Windows.Forms.ListView.ColumnHeaderCollection(new System.Windows.Forms.ListView());
|
||||
|
||||
/// <summary>
|
||||
/// Compatibility with ListView class
|
||||
/// This is not used in this implementation
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public bool VirtualMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the selected item in the control remains highlighted when the control loses focus
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public bool HideSelection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the ListView uses state image behavior that is compatible with the .NET Framework 1.1 or the .NET Framework 2.0.
|
||||
/// Here for ListView api compatibility (we dont care about this)
|
||||
/// </summary>
|
||||
[System.ComponentModel.Browsable(false)]
|
||||
public bool UseCompatibleStateImageBehavior { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how items are displayed in the control.
|
||||
/// Here for ListView api compatibility (we dont care about this)
|
||||
/// </summary>
|
||||
public System.Windows.Forms.View View { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region VirtualListView Compatibility Properties
|
||||
|
||||
/// <summary>
|
||||
/// Informs user that a select all event is in place, can be used in change events to wait until this is false
|
||||
/// Not used in this implementation (yet)
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public bool SelectAllInProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the selected item
|
||||
/// Here for compatibility with VirtualListView.cs
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int selectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SelectedRows.Count() == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SelectedRows.First();
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
SelectItem(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Category("Behavior")]
|
||||
public bool BlazingFast { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Behavior
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of left and right padding on the text inside a cell
|
||||
/// </summary>
|
||||
[DefaultValue(3)]
|
||||
[Category("Behavior")]
|
||||
public int CellWidthPadding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of top and bottom padding on the text inside a cell
|
||||
/// </summary>
|
||||
[DefaultValue(1)]
|
||||
[Category("Behavior")]
|
||||
public int CellHeightPadding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scrolling speed
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public int ScrollSpeed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CellHeight == 0)
|
||||
CellHeight++;
|
||||
return _vBar.SmallChange / CellHeight;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_vBar.SmallChange = value * CellHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether columns can be resized
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
[DefaultValue(true)]
|
||||
public bool AllowColumnResize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether columns can be reordered
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
[DefaultValue(true)]
|
||||
public bool AllowColumnReorder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether multiple items can to be selected
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
[DefaultValue(true)]
|
||||
public bool MultiSelect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the control is in input painting mode
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
[DefaultValue(false)]
|
||||
public bool InputPaintingMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how the InputRoll scrolls when calling ScrollToIndex.
|
||||
/// </summary>
|
||||
[DefaultValue("near")]
|
||||
[Category("Behavior")]
|
||||
public string ScrollMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating how the Intever for the hover event
|
||||
/// </summary>
|
||||
[DefaultValue(false)]
|
||||
[Category("Behavior")]
|
||||
public bool AlwaysScroll { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the lowest seek interval to activate the progress bar
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public int SeekingCutoffInterval { get; set; }
|
||||
|
||||
[DefaultValue(750)]
|
||||
[Category("Behavior")]
|
||||
public int HoverInterval
|
||||
{
|
||||
get { return _hoverTimer.Interval; }
|
||||
set { _hoverTimer.Interval = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether you can use right click to select things
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public bool AllowRightClickSelecton { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether keys can modify selection
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public bool LetKeysModifySelection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether hot keys are suspended
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public bool SuspendHotkeys { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Appearance
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether grid lines are displayed around cells
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
[DefaultValue(true)]
|
||||
public bool GridLines { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the entire row will always be selected
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
[DefaultValue(false)]
|
||||
public bool FullRowSelect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the font used for the column header text
|
||||
/// Also forces a cell size re-evaluation
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Font ColumnHeaderFont
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_columnHeaderFont == null)
|
||||
{
|
||||
ColumnHeaderFont = new Font("Arial", 8, FontStyle.Bold);
|
||||
}
|
||||
|
||||
return _columnHeaderFont;
|
||||
}
|
||||
set
|
||||
{
|
||||
_columnHeaderFont = value;
|
||||
SetCharSize();
|
||||
}
|
||||
}
|
||||
private Font _columnHeaderFont;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the column header text
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color ColumnHeaderFontColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_columnHeaderFontColor == null)
|
||||
_columnHeaderFontColor = Color.Black;
|
||||
return _columnHeaderFontColor;
|
||||
}
|
||||
set { _columnHeaderFontColor = value; }
|
||||
}
|
||||
private Color _columnHeaderFontColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color of the column header cells
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color ColumnHeaderBackgroundColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_columnHeaderBackgroundColor == null)
|
||||
_columnHeaderBackgroundColor = Color.LightGray;
|
||||
return _columnHeaderBackgroundColor;
|
||||
}
|
||||
set { _columnHeaderBackgroundColor = value; }
|
||||
}
|
||||
private Color _columnHeaderBackgroundColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color of the column header cells when they are highlighted
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color ColumnHeaderBackgroundHighlightColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_columnHeaderBackgroundHighlightColor == null)
|
||||
_columnHeaderBackgroundHighlightColor = SystemColors.HighlightText;
|
||||
return _columnHeaderBackgroundHighlightColor;
|
||||
}
|
||||
set { _columnHeaderBackgroundHighlightColor = value; }
|
||||
}
|
||||
private Color _columnHeaderBackgroundHighlightColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the column header outline
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color ColumnHeaderOutlineColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_columnHeaderOutlineColor == null)
|
||||
_columnHeaderOutlineColor = Color.Black;
|
||||
return _columnHeaderOutlineColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
_columnHeaderOutlineColor = value;
|
||||
}
|
||||
}
|
||||
private Color _columnHeaderOutlineColor;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the font used for every row cell
|
||||
/// Also forces a cell size re-evaluation
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Font CellFont
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cellFont == null)
|
||||
{
|
||||
CellFont = new Font("Arial", 8, FontStyle.Regular);
|
||||
}
|
||||
return _cellFont;
|
||||
}
|
||||
set
|
||||
{
|
||||
_cellFont = value;
|
||||
SetCharSize();
|
||||
}
|
||||
}
|
||||
private Font _cellFont;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the font used for every row cell
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color CellFontColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cellFontColor == null)
|
||||
_cellFontColor = Color.Black;
|
||||
return _cellFontColor;
|
||||
}
|
||||
set { _cellFontColor = value; }
|
||||
}
|
||||
private Color _cellFontColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color for every row cell
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color CellBackgroundColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cellBackgroundColor == null)
|
||||
_cellBackgroundColor = Color.White;
|
||||
return _cellBackgroundColor;
|
||||
}
|
||||
set { _cellBackgroundColor = value; }
|
||||
}
|
||||
private Color _cellBackgroundColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color for every row cell that is highlighted
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color CellBackgroundHighlightColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cellBackgroundHighlightColor == null)
|
||||
_cellBackgroundHighlightColor = Color.Blue;
|
||||
return _cellBackgroundHighlightColor;
|
||||
}
|
||||
set { _cellBackgroundHighlightColor = value; }
|
||||
}
|
||||
private Color _cellBackgroundHighlightColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color used to draw the ListView gridlines
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color GridLineColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_gridLineColor == null)
|
||||
_gridLineColor = SystemColors.ControlLight;
|
||||
return _gridLineColor;
|
||||
}
|
||||
set { _gridLineColor = value; }
|
||||
}
|
||||
private Color _gridLineColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of control's border
|
||||
/// Note: this is drawn directly onto the parent control, so large values will probably look terrible
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public int BorderSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines the absolute minimum column size (used when manually resizing columns)
|
||||
/// </summary>
|
||||
[DefaultValue(50)]
|
||||
[Category("Appearance")]
|
||||
public int MinimumColumnSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The padding property is disabled for this control (as this is handled internally)
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public new System.Windows.Forms.Padding Padding
|
||||
{
|
||||
get { return new System.Windows.Forms.Padding(0); }
|
||||
set { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the control's border
|
||||
/// </summary>
|
||||
[Category("Appearance")]
|
||||
public Color BorderColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_borderColor == null)
|
||||
_borderColor = SystemColors.InactiveBorder;
|
||||
return _borderColor;
|
||||
}
|
||||
set { _borderColor = value; }
|
||||
}
|
||||
private Color _borderColor;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region API
|
||||
|
||||
/// <summary>
|
||||
/// All visible columns
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public IEnumerable<ListColumn> VisibleColumns => _columns.VisibleColumns;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sets the virtual number of rows to be displayed. Does not include the column header row.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int ItemCount
|
||||
{
|
||||
get { return _itemCount; }
|
||||
set
|
||||
{
|
||||
_itemCount = value;
|
||||
RecalculateScrollBars();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all columns including those that are not visible
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public ListColumns AllColumns => _columns;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the mouse is currently over a column cell
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public bool IsPointingAtColumnHeader => IsHoveringOnColumnCell;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the first selected row (null if no selection)
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int? FirstSelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (AnyRowsSelected)
|
||||
{
|
||||
return SelectedRows.Min();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the index of the last selected row (null if no selection)
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int? LastSelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (AnyRowsSelected)
|
||||
{
|
||||
return SelectedRows.Max();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the first visible row index, if scrolling is needed
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int FirstVisibleRow
|
||||
{
|
||||
get // SuuperW: This was checking if the scroll bars were needed, which is useless because their Value is 0 if they aren't needed.
|
||||
{
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
return _vBar.Value / CellHeight;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (NeedsVScrollbar)
|
||||
{
|
||||
_programmaticallyUpdatingScrollBarValues = true;
|
||||
if (value * CellHeight <= _vBar.Maximum)
|
||||
{
|
||||
_vBar.Value = value * CellHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
_vBar.Value = _vBar.Maximum;
|
||||
}
|
||||
|
||||
_programmaticallyUpdatingScrollBarValues = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last row that is fully visible
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
private int LastFullyVisibleRow
|
||||
{
|
||||
get
|
||||
{
|
||||
int halfRow = 0;
|
||||
if ((DrawHeight - ColumnHeight - 3) % CellHeight < CellHeight / 2)
|
||||
{
|
||||
halfRow = 1;
|
||||
}
|
||||
|
||||
return FirstVisibleRow + VisibleRows - halfRow; // + CountLagFramesDisplay(VisibleRows - halfRow);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last visible row
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int LastVisibleRow
|
||||
{
|
||||
get
|
||||
{
|
||||
return FirstVisibleRow + VisibleRows; // + CountLagFramesDisplay(VisibleRows);
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
int halfRow = 0;
|
||||
if ((DrawHeight - ColumnHeight - 3) % CellHeight < CellHeight / 2)
|
||||
{
|
||||
halfRow = 1;
|
||||
}
|
||||
|
||||
FirstVisibleRow = Math.Max(value - (VisibleRows - halfRow), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of rows currently visible including partially visible rows.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int VisibleRows
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
return (DrawHeight - ColumnHeight - 3) / CellHeight; // Minus three makes it work
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first visible column index, if scrolling is needed
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int FirstVisibleColumn
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
var columnList = VisibleColumns.ToList();
|
||||
return columnList.FindIndex(c => c.Right > _hBar.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last visible column index, if scrolling is needed
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int LastVisibleColumnIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CellHeight == 0) CellHeight++;
|
||||
List<ListColumn> columnList = VisibleColumns.ToList();
|
||||
int ret;
|
||||
ret = columnList.FindLastIndex(c => c.Left <= DrawWidth + _hBar.Value);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current Cell that the mouse was in.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public Cell CurrentCell { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the current cell is a data cell or not
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public bool CurrentCellIsDataCell => CurrentCell?.RowIndex != null && CurrentCell.Column != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of selected row indexes
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public IEnumerable<int> SelectedRows
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedItems
|
||||
.Where(cell => cell.RowIndex.HasValue)
|
||||
.Select(cell => cell.RowIndex.Value)
|
||||
.Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether any rows are selected
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public bool AnyRowsSelected
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedItems.Any(cell => cell.RowIndex.HasValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the previous Cell that the mouse was in.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public Cell LastCell { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether paint down is happening
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public bool IsPaintDown { get; private set; }
|
||||
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public bool UseCustomBackground { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current draw height
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int DrawHeight { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current draw width
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public int DrawWidth { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the right mouse button is held down
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public bool RightButtonHeld { get; private set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,356 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
/// <summary>
|
||||
/// A performant VirtualListView implementation that doesn't rely on native Win32 API calls
|
||||
/// (and in fact does not inherit the ListView class at all)
|
||||
/// It is an enhanced version of the work done with GDI+ rendering in InputRoll.cs
|
||||
/// </summary>
|
||||
public partial class PlatformAgnosticVirtualListView : Control
|
||||
{
|
||||
private readonly SortedSet<Cell> _selectedItems = new SortedSet<Cell>(new SortCell());
|
||||
|
||||
private readonly VScrollBar _vBar;
|
||||
private readonly HScrollBar _hBar;
|
||||
|
||||
private readonly Timer _hoverTimer = new Timer();
|
||||
|
||||
private ListColumns _columns = new ListColumns();
|
||||
|
||||
private bool _programmaticallyUpdatingScrollBarValues;
|
||||
|
||||
private int _itemCount;
|
||||
private Size _charSize;
|
||||
|
||||
private ListColumn _columnDown;
|
||||
private ListColumn _columnSeparatorDown;
|
||||
|
||||
private int? _currentX;
|
||||
private int? _currentY;
|
||||
|
||||
public PlatformAgnosticVirtualListView()
|
||||
{
|
||||
ColumnHeaderFont = new Font("Arial", 8, FontStyle.Bold);
|
||||
ColumnHeaderFontColor = Color.Black;
|
||||
ColumnHeaderBackgroundColor = Color.LightGray;
|
||||
ColumnHeaderBackgroundHighlightColor = SystemColors.HighlightText;
|
||||
ColumnHeaderOutlineColor = Color.Black;
|
||||
|
||||
CellFont = new Font("Arial", 8, FontStyle.Regular);
|
||||
CellFontColor = Color.Black;
|
||||
CellBackgroundColor = Color.White;
|
||||
CellBackgroundHighlightColor = Color.Blue;
|
||||
|
||||
GridLines = true;
|
||||
GridLineColor = SystemColors.ControlLight;
|
||||
|
||||
UseCustomBackground = true;
|
||||
|
||||
BorderColor = Color.DarkGray;
|
||||
BorderSize = 1;
|
||||
|
||||
MinimumColumnSize = 50;
|
||||
|
||||
CellWidthPadding = 3;
|
||||
CellHeightPadding = 0;
|
||||
CurrentCell = null;
|
||||
ScrollMethod = "near";
|
||||
|
||||
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
|
||||
SetStyle(ControlStyles.UserPaint, true);
|
||||
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
|
||||
SetStyle(ControlStyles.Opaque, true);
|
||||
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
|
||||
|
||||
_vBar = new VScrollBar
|
||||
{
|
||||
// Location gets calculated later (e.g. on resize)
|
||||
Visible = false,
|
||||
SmallChange = CellHeight,
|
||||
LargeChange = CellHeight * 20
|
||||
};
|
||||
|
||||
_hBar = new HScrollBar
|
||||
{
|
||||
// Location gets calculated later (e.g. on resize)
|
||||
Visible = false,
|
||||
SmallChange = CellWidth,
|
||||
LargeChange = 20
|
||||
};
|
||||
|
||||
Controls.Add(_vBar);
|
||||
Controls.Add(_hBar);
|
||||
|
||||
_vBar.ValueChanged += VerticalBar_ValueChanged;
|
||||
_hBar.ValueChanged += HorizontalBar_ValueChanged;
|
||||
|
||||
RecalculateScrollBars();
|
||||
|
||||
_columns.ChangedCallback = ColumnChangedCallback;
|
||||
|
||||
_hoverTimer.Interval = 750;
|
||||
_hoverTimer.Tick += HoverTimerEventProcessor;
|
||||
_hoverTimer.Stop();
|
||||
}
|
||||
|
||||
private void HoverTimerEventProcessor(object sender, EventArgs e)
|
||||
{
|
||||
_hoverTimer.Stop();
|
||||
|
||||
CellHovered?.Invoke(this, new CellEventArgs(LastCell, CurrentCell));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Pending Removal
|
||||
|
||||
/*
|
||||
*
|
||||
|
||||
//private readonly byte[] _lagFrames = new byte[256]; // Large enough value that it shouldn't ever need resizing. // apparently not large enough for 4K
|
||||
|
||||
private int _maxCharactersInHorizontal = 1;
|
||||
private bool _horizontalOrientation;
|
||||
|
||||
public string UserSettingsSerialized()
|
||||
{
|
||||
var settings = ConfigService.SaveWithType(Settings);
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void LoadSettingsSerialized(string settingsJson)
|
||||
{
|
||||
var settings = ConfigService.LoadWithType(settingsJson);
|
||||
|
||||
// TODO: don't silently fail, inform the user somehow
|
||||
if (settings is InputRollSettings)
|
||||
{
|
||||
var rollSettings = settings as InputRollSettings;
|
||||
_columns = rollSettings.Columns;
|
||||
_columns.ChangedCallback = ColumnChangedCallback;
|
||||
HorizontalOrientation = rollSettings.HorizontalOrientation;
|
||||
//LagFramesToHide = rollSettings.LagFramesToHide;
|
||||
//HideWasLagFrames = rollSettings.HideWasLagFrames;
|
||||
}
|
||||
}
|
||||
|
||||
private InputRollSettings Settings => new InputRollSettings
|
||||
{
|
||||
Columns = _columns,
|
||||
HorizontalOrientation = HorizontalOrientation,
|
||||
//LagFramesToHide = LagFramesToHide,
|
||||
//HideWasLagFrames = HideWasLagFrames
|
||||
};
|
||||
|
||||
public class InputRollSettings
|
||||
{
|
||||
public RollColumns Columns { get; set; }
|
||||
public bool HorizontalOrientation { get; set; }
|
||||
public int LagFramesToHide { get; set; }
|
||||
public bool HideWasLagFrames { get; set; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of data cells when in Horizontal orientation.
|
||||
/// </summary>
|
||||
public int MaxCharactersInHorizontal
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maxCharactersInHorizontal;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_maxCharactersInHorizontal = value;
|
||||
UpdateCellSize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the control is horizontal or vertical
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public bool HorizontalOrientation
|
||||
{
|
||||
get
|
||||
{
|
||||
return _horizontalOrientation;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_horizontalOrientation != value)
|
||||
{
|
||||
int temp = ScrollSpeed;
|
||||
_horizontalOrientation = value;
|
||||
OrientationChanged();
|
||||
_hBar.SmallChange = CellWidth;
|
||||
_vBar.SmallChange = CellHeight;
|
||||
ScrollSpeed = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ToolStripItem> GenerateContextMenuItems()
|
||||
{
|
||||
yield return new ToolStripSeparator();
|
||||
|
||||
var rotate = new ToolStripMenuItem
|
||||
{
|
||||
Name = "RotateMenuItem",
|
||||
Text = "Rotate",
|
||||
ShortcutKeyDisplayString = RotateHotkeyStr,
|
||||
};
|
||||
|
||||
rotate.Click += (o, ev) =>
|
||||
{
|
||||
HorizontalOrientation ^= true;
|
||||
};
|
||||
|
||||
yield return rotate;
|
||||
}
|
||||
|
||||
// SuuperW: Count lag frames between FirstDisplayed and given display position
|
||||
private int CountLagFramesDisplay(int relativeIndex)
|
||||
{
|
||||
if (QueryFrameLag != null && LagFramesToHide != 0)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i <= relativeIndex; i++)
|
||||
{
|
||||
count += _lagFrames[i];
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Count lag frames between FirstDisplayed and given relative frame index
|
||||
private int CountLagFramesAbsolute(int relativeIndex)
|
||||
{
|
||||
if (QueryFrameLag != null) // && LagFramesToHide != 0)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i + count <= relativeIndex; i++)
|
||||
{
|
||||
count += _lagFrames[i];
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void SetLagFramesArray()
|
||||
{
|
||||
if (QueryFrameLag != null) // && LagFramesToHide != 0)
|
||||
{
|
||||
bool showNext = false;
|
||||
|
||||
// First one needs to check BACKWARDS for lag frame count.
|
||||
SetLagFramesFirst();
|
||||
int f = _lagFrames[0];
|
||||
|
||||
if (QueryFrameLag(FirstVisibleRow + f, HideWasLagFrames))
|
||||
{
|
||||
showNext = true;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= VisibleRows; i++)
|
||||
{
|
||||
_lagFrames[i] = 0;
|
||||
if (!showNext)
|
||||
{
|
||||
for (; _lagFrames[i] < LagFramesToHide; _lagFrames[i]++)
|
||||
{
|
||||
if (!QueryFrameLag(FirstVisibleRow + i + f, HideWasLagFrames))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
f++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!QueryFrameLag(FirstVisibleRow + i + f, HideWasLagFrames))
|
||||
{
|
||||
showNext = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_lagFrames[i] == LagFramesToHide && QueryFrameLag(FirstVisibleRow + i + f, HideWasLagFrames))
|
||||
{
|
||||
showNext = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i <= VisibleRows; i++)
|
||||
{
|
||||
_lagFrames[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLagFramesFirst()
|
||||
{
|
||||
if (QueryFrameLag != null && LagFramesToHide != 0)
|
||||
{
|
||||
// Count how many lag frames are above displayed area.
|
||||
int count = 0;
|
||||
do
|
||||
{
|
||||
count++;
|
||||
}
|
||||
while (QueryFrameLag(FirstVisibleRow - count, HideWasLagFrames) && count <= LagFramesToHide);
|
||||
count--;
|
||||
|
||||
// Count forward
|
||||
int fCount = -1;
|
||||
do
|
||||
{
|
||||
fCount++;
|
||||
}
|
||||
while (QueryFrameLag(FirstVisibleRow + fCount, HideWasLagFrames) && count + fCount < LagFramesToHide);
|
||||
_lagFrames[0] = (byte)fCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lagFrames[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public string RotateHotkeyStr => "Ctrl+Shift+F";
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given frame is a lag frame
|
||||
/// </summary>
|
||||
public delegate bool QueryFrameLagHandler(int index, bool hideWasLag);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fire the QueryFrameLag event which checks if a given frame is a lag frame
|
||||
/// </summary>
|
||||
[Category("Virtual")]
|
||||
public event QueryFrameLagHandler QueryFrameLag;
|
||||
|
||||
*/
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,16 +1,11 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk.CustomControls
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper for GDI rendering functions
|
||||
/// This class is not thread-safe as GDI functions should be called from the UI thread
|
||||
/// </summary>
|
||||
public sealed class GDIRenderer : IDisposable
|
||||
public sealed class Win32GDIRenderer : GDI.GDIRenderer
|
||||
{
|
||||
/// <summary>
|
||||
/// used for <see cref="MeasureString(string, System.Drawing.Font, float, out int, out int)"/> calculation.
|
||||
|
@ -43,10 +38,10 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
|
||||
#region Construct and Destroy
|
||||
|
||||
public GDIRenderer()
|
||||
public Win32GDIRenderer()
|
||||
{
|
||||
//zero 04-16-2016 : this can't be legal, theres no HDC yet
|
||||
//SetBkMode(_hdc, BkModes.OPAQUE);
|
||||
//SetBkMode(_hdc, GDI.BkModes.OPAQUE);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -64,8 +59,8 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
|
||||
EndOffScreenBitmap();
|
||||
|
||||
System.Diagnostics.Debug.Assert(_hdc == IntPtr.Zero, "Disposed a GDIRenderer while it held an HDC");
|
||||
System.Diagnostics.Debug.Assert(_g == null, "Disposed a GDIRenderer while it held a Graphics");
|
||||
System.Diagnostics.Debug.Assert(_hdc == IntPtr.Zero, "Disposed a Win32GDIRenderer while it held an HDC");
|
||||
System.Diagnostics.Debug.Assert(_g == null, "Disposed a Win32GDIRenderer while it held a Graphics");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -96,12 +91,14 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
/// <summary>
|
||||
/// Required to use before calling drawing methods
|
||||
/// </summary>
|
||||
public GdiGraphicsLock LockGraphics(Graphics g)
|
||||
public GDI.GDIGraphicsLock<GDI.GDIRenderer> LockGraphics(Graphics g)
|
||||
{
|
||||
_g = g;
|
||||
_hdc = g.GetHdc();
|
||||
SetBkMode(_hdc, BkModes.TRANSPARENT);
|
||||
return new GdiGraphicsLock(this);
|
||||
SetBkMode(_hdc, GDI.BkModes.TRANSPARENT);
|
||||
// return (GDI.GDIGraphicsLock<GDI.GDIRenderer>) new GDI.GDIGraphicsLock<Win32GDIRenderer>(this);
|
||||
// going to need this explained to me, the below works as expected but the above does not --Yoshi
|
||||
return new GDI.GDIGraphicsLock<GDI.GDIRenderer>((GDI.GDIRenderer) this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -117,15 +114,15 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
return size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Measure the width and height of string <paramref name="str"/> when drawn on device context HDC
|
||||
/// using the given font <paramref name="font"/>
|
||||
/// Restrict the width of the string and get the number of characters able to fit in the restriction and
|
||||
/// the width those characters take
|
||||
/// </summary>
|
||||
/// <param name="maxWidth">the max width to render the string in</param>
|
||||
/// <param name="charFit">the number of characters that will fit under <see cref="maxWidth"/> restriction</param>
|
||||
/// <param name="charFitWidth"></param>
|
||||
/// <param name="charFit">the number of characters that will fit under <see cref="maxWidth"/> restriction</param>
|
||||
/// <param name="charFitWidth"></param>
|
||||
public Size MeasureString(string str, Font font, float maxWidth, out int charFit, out int charFitWidth)
|
||||
{
|
||||
SetFont(font);
|
||||
|
@ -192,7 +189,7 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
/// Draw the given string using the given font and foreground color at given location
|
||||
/// See [http://msdn.microsoft.com/en-us/library/windows/desktop/dd162498(v=vs.85).aspx][15]
|
||||
/// </summary>
|
||||
public void DrawString(string str, Font font, Color color, Rectangle rect, TextFormatFlags flags)
|
||||
public void DrawString(string str, Font font, Color color, Rectangle rect, GDI.TextFormatFlags flags)
|
||||
{
|
||||
SetFont(font);
|
||||
SetTextColor(color);
|
||||
|
@ -251,7 +248,7 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
public void SetSolidPen(Color color)
|
||||
{
|
||||
int rgb = (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R;
|
||||
SelectObject(CurrentHDC, GetStockObject((int)PaintObjects.DC_PEN));
|
||||
SelectObject(CurrentHDC, GetStockObject((int)GDI.PaintObjects.DC_PEN));
|
||||
SetDCPenColor(CurrentHDC, rgb);
|
||||
}
|
||||
|
||||
|
@ -279,14 +276,14 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
_bitHDC = CreateCompatibleDC(_hdc);
|
||||
_bitMap = CreateCompatibleBitmap(_hdc, width, height);
|
||||
SelectObject(_bitHDC, _bitMap);
|
||||
SetBkMode(_bitHDC, BkModes.TRANSPARENT);
|
||||
SetBkMode(_bitHDC, GDI.BkModes.TRANSPARENT);
|
||||
}
|
||||
|
||||
public void EndOffScreenBitmap()
|
||||
{
|
||||
_bitW = 0;
|
||||
_bitH = 0;
|
||||
|
||||
|
||||
DeleteObject(_bitMap);
|
||||
DeleteObject(_bitHDC);
|
||||
|
||||
|
@ -299,12 +296,19 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
BitBlt(_hdc, 0, 0, _bitW, _bitH, _bitHDC, 0, 0, 0x00CC0020);
|
||||
}
|
||||
|
||||
public void HackDisposeGraphics()
|
||||
{
|
||||
_g.ReleaseHdc(_hdc);
|
||||
_hdc = IntPtr.Zero;
|
||||
_g = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Set a resource (e.g. a font) for the specified device context.
|
||||
/// Set a resource (e.g. a font) for the specified device context.
|
||||
/// </summary>
|
||||
private void SetFont(Font font)
|
||||
{
|
||||
|
@ -354,7 +358,7 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
private static extern int FillRect(IntPtr hdc, [In] ref GDIRect lprc, IntPtr hbr);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern int SetBkMode(IntPtr hdc, BkModes mode);
|
||||
private static extern int SetBkMode(IntPtr hdc, GDI.BkModes mode);
|
||||
|
||||
[DllImport("gdi32.dll")]
|
||||
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiObj);
|
||||
|
@ -532,7 +536,7 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
public byte lfPitchAndFamily = 0;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string lfFaceName = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The graphics mode that can be set by SetGraphicsMode.
|
||||
|
@ -597,7 +601,7 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
return new XFORM(elems[0], elems[1], elems[2], elems[3], elems[4], elems[5]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct BLENDFUNCTION
|
||||
{
|
||||
|
@ -625,23 +629,6 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
|
||||
#region Classes, Structs, and Enums
|
||||
|
||||
public class GdiGraphicsLock : IDisposable
|
||||
{
|
||||
private readonly GDIRenderer Gdi;
|
||||
|
||||
public GdiGraphicsLock(GDIRenderer gdi)
|
||||
{
|
||||
this.Gdi = gdi;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Gdi._g.ReleaseHdc(Gdi._hdc);
|
||||
Gdi._hdc = IntPtr.Zero;
|
||||
Gdi._g = null;
|
||||
}
|
||||
}
|
||||
|
||||
private struct Rect
|
||||
{
|
||||
private int _left;
|
||||
|
@ -674,97 +661,6 @@ namespace BizHawk.Client.EmuHawk.CustomControls
|
|||
}
|
||||
}
|
||||
|
||||
private struct GDIPoint
|
||||
{
|
||||
private int x;
|
||||
private int y;
|
||||
|
||||
private GDIPoint(int x, int y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ETOOptions : uint
|
||||
{
|
||||
CLIPPED = 0x4,
|
||||
GLYPH_INDEX = 0x10,
|
||||
IGNORELANGUAGE = 0x1000,
|
||||
NUMERICSLATIN = 0x800,
|
||||
NUMERICSLOCAL = 0x400,
|
||||
OPAQUE = 0x2,
|
||||
PDY = 0x2000,
|
||||
RTLREADING = 0x800,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See [http://msdn.microsoft.com/en-us/library/windows/desktop/dd162498(v=vs.85).aspx][15]
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TextFormatFlags : uint
|
||||
{
|
||||
Default = 0x00000000,
|
||||
Center = 0x00000001,
|
||||
Right = 0x00000002,
|
||||
VCenter = 0x00000004,
|
||||
Bottom = 0x00000008,
|
||||
WordBreak = 0x00000010,
|
||||
SingleLine = 0x00000020,
|
||||
ExpandTabs = 0x00000040,
|
||||
TabStop = 0x00000080,
|
||||
NoClip = 0x00000100,
|
||||
ExternalLeading = 0x00000200,
|
||||
CalcRect = 0x00000400,
|
||||
NoPrefix = 0x00000800,
|
||||
Internal = 0x00001000,
|
||||
EditControl = 0x00002000,
|
||||
PathEllipsis = 0x00004000,
|
||||
EndEllipsis = 0x00008000,
|
||||
ModifyString = 0x00010000,
|
||||
RtlReading = 0x00020000,
|
||||
WordEllipsis = 0x00040000,
|
||||
NoFullWidthCharBreak = 0x00080000,
|
||||
HidePrefix = 0x00100000,
|
||||
ProfixOnly = 0x00200000,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PenStyles
|
||||
{
|
||||
PS_SOLID = 0x00000000
|
||||
// TODO
|
||||
}
|
||||
|
||||
public enum PaintObjects
|
||||
{
|
||||
WHITE_BRUSH = 0,
|
||||
LTGRAY_BRUSH = 1,
|
||||
GRAY_BRUSH = 2,
|
||||
DKGRAY_BRUSH = 3,
|
||||
BLACK_BRUSH = 4,
|
||||
NULL_BRUSH = 5,
|
||||
WHITE_PEN = 6,
|
||||
BLACK_PEN = 7,
|
||||
NULL_PEN = 8,
|
||||
OEM_FIXED_FONT = 10,
|
||||
ANSI_FIXED_FONT = 11,
|
||||
ANSI_VAR_FONT = 12,
|
||||
SYSTEM_FONT = 13,
|
||||
DEVICE_DEFAULT_FONT = 14,
|
||||
DEFAULT_PALETTE = 15,
|
||||
SYSTEM_FIXED_FONT = 16,
|
||||
DC_BRUSH = 18,
|
||||
DC_PEN = 19,
|
||||
}
|
||||
|
||||
public enum BkModes : int
|
||||
{
|
||||
TRANSPARENT = 1,
|
||||
OPAQUE = 2
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -126,7 +126,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
OTK_Keyboard.Initialize();
|
||||
// OTK_Gamepad.Initialize();
|
||||
OTK_GamePad.Initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -134,6 +134,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
IPCKeyInput.Initialize();
|
||||
GamePad.Initialize();
|
||||
GamePad360.Initialize();
|
||||
//OTK_GamePad.Initialize();
|
||||
}
|
||||
Instance = new Input();
|
||||
}
|
||||
|
@ -336,12 +337,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
: KeyInput.Update().Concat(IPCKeyInput.Update());
|
||||
if (PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
//TODO
|
||||
OTK_GamePad.UpdateAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
GamePad.UpdateAll();
|
||||
GamePad360.UpdateAll();
|
||||
//OTK_GamePad.UpdateAll();
|
||||
}
|
||||
|
||||
//this block is going to massively modify data structures that the binding method uses, so we have to lock it all
|
||||
|
@ -357,6 +359,27 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
//FloatValues.Clear();
|
||||
|
||||
//analyze OTK
|
||||
foreach (var pad in OTK_GamePad.EnumerateDevices())
|
||||
{
|
||||
string cType = pad.MappedGamePad ? "X" : "J";
|
||||
string xname = cType + pad.ID + " ";
|
||||
|
||||
foreach (var but in pad.buttonObjects)
|
||||
{
|
||||
HandleButton(xname + but.ButtonName, but.ButtonAction());
|
||||
}
|
||||
|
||||
foreach (var sv in pad.GetFloats())
|
||||
{
|
||||
string n = xname + sv.Item1 + " Axis";
|
||||
float f = sv.Item2;
|
||||
if (trackdeltas)
|
||||
FloatDeltas[n] += Math.Abs(f - FloatValues[n]);
|
||||
FloatValues[n] = f;
|
||||
}
|
||||
}
|
||||
|
||||
//analyze xinput
|
||||
foreach (var pad in GamePad360.EnumerateDevices())
|
||||
{
|
||||
|
|
|
@ -1,41 +1,62 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
public class OTK_GamePad
|
||||
{
|
||||
//Note: OpenTK has both Gamepad and Joystick classes. An OpenTK Gamepad is a simplified version of Joystick
|
||||
//with pre-defined features that match an XInput controller. They did this to mimic XNA's controller API.
|
||||
//We're going to use Joystick directly, because it gives us full access to all possible buttons.
|
||||
//And it looks like GamePad itself isn't supported on OpenTK OS X.
|
||||
// Modified OpenTK Gamepad Handler
|
||||
// OpenTK v3.x.x.x breaks the original OpenTK.Input.Joystick implementation, but enables OpenTK.Input.Gamepad
|
||||
// compatibility with OSX / linux. However, the gamepad auto-mapping is a little suspect, so we will have to use both methods
|
||||
// This should also give us vibration support (if we ever implement it)
|
||||
|
||||
public static List<OTK_GamePad> Devices;
|
||||
private const int MAX_JOYSTICKS = 4; //They don't have a way to query this for some reason. 4 is the minimum promised.
|
||||
#region Static Members
|
||||
|
||||
private static readonly object _syncObj = new object();
|
||||
private const int MAX_GAMEPADS = 4; //They don't have a way to query this for some reason. 4 is the minimum promised.
|
||||
public static List<OTK_GamePad> Devices = new List<OTK_GamePad>();
|
||||
|
||||
/// <summary>
|
||||
/// Initialization is only called once when MainForm loads
|
||||
/// </summary>
|
||||
public static void Initialize()
|
||||
{
|
||||
Devices = new List<OTK_GamePad>();
|
||||
Devices.Clear();
|
||||
|
||||
for (int i = 0; i < MAX_JOYSTICKS; i++)
|
||||
int playerCount = 0;
|
||||
for (int i = 0; i < MAX_GAMEPADS; i++)
|
||||
{
|
||||
JoystickState jss = Joystick.GetState(i);
|
||||
if (jss.IsConnected)
|
||||
if (OpenTK.Input.GamePad.GetState(i).IsConnected || Joystick.GetState(i).IsConnected)
|
||||
{
|
||||
Console.WriteLine(string.Format("joydevice index: {0}", i)); //OpenTK doesn't expose the GUID, even though it stores it internally...
|
||||
|
||||
OTK_GamePad ogp = new OTK_GamePad(i);
|
||||
Console.WriteLine(string.Format("OTK GamePad/Joystick index: {0}", i));
|
||||
OTK_GamePad ogp = new OTK_GamePad(i, ++playerCount);
|
||||
Devices.Add(ogp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<OTK_GamePad> EnumerateDevices()
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
foreach (var device in Devices)
|
||||
{
|
||||
yield return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateAll()
|
||||
{
|
||||
foreach (var device in Devices)
|
||||
device.Update();
|
||||
lock (_syncObj)
|
||||
{
|
||||
foreach (var device in Devices)
|
||||
{
|
||||
device.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CloseAll()
|
||||
|
@ -46,103 +67,363 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
// ********************************** Instance Members **********************************
|
||||
#endregion
|
||||
|
||||
readonly Guid _guid;
|
||||
readonly int _stickIdx;
|
||||
JoystickState state = new JoystickState();
|
||||
#region Instance Members
|
||||
|
||||
OTK_GamePad(int index)
|
||||
/// <summary>
|
||||
/// The GUID as detected by OpenTK.Input.Joystick
|
||||
/// (or auto generated if this failed)
|
||||
/// </summary>
|
||||
readonly Guid _guid = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// Signs whether OpenTK returned a GUID for this device
|
||||
/// </summary>
|
||||
readonly bool _guidObtained;
|
||||
|
||||
/// <summary>
|
||||
/// The OpenTK device index
|
||||
/// </summary>
|
||||
readonly int _deviceIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The index to lookup into Devices
|
||||
/// </summary>
|
||||
readonly int _playerIndex;
|
||||
|
||||
/// <summary>
|
||||
/// The name (if any) that OpenTK GamePad has resolved via its internal mapping database
|
||||
/// </summary>
|
||||
readonly string _name;
|
||||
|
||||
/// <summary>
|
||||
/// The object as returned by OpenTK.Input.Gamepad.GetCapabilities();
|
||||
/// </summary>
|
||||
readonly GamePadCapabilities? _gamePadCapabilities;
|
||||
|
||||
/// <summary>
|
||||
/// The object as returned by OpenTK.Input.Joystick.GetCapabilities();
|
||||
/// </summary>
|
||||
readonly JoystickCapabilities? _joystickCapabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Public check on whether mapped gamepad config is being used
|
||||
/// </summary>
|
||||
public bool MappedGamePad
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
_stickIdx = index;
|
||||
get
|
||||
{
|
||||
if (_gamePadCapabilities.HasValue && _gamePadCapabilities.Value.IsMapped)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gamepad Device state information - updated constantly
|
||||
/// </summary>
|
||||
GamePadState state = new GamePadState();
|
||||
|
||||
/// <summary>
|
||||
/// Joystick Device state information - updated constantly
|
||||
/// </summary>
|
||||
JoystickState jState = new JoystickState();
|
||||
|
||||
OTK_GamePad(int index, int playerIndex)
|
||||
{
|
||||
_deviceIndex = index;
|
||||
_playerIndex = playerIndex;
|
||||
|
||||
var gameState = OpenTK.Input.GamePad.GetState(_deviceIndex);
|
||||
var joyState = Joystick.GetState(_deviceIndex);
|
||||
|
||||
if (joyState.IsConnected)
|
||||
{
|
||||
_guid = Joystick.GetGuid(_deviceIndex);
|
||||
_guidObtained = true;
|
||||
_joystickCapabilities = Joystick.GetCapabilities(_deviceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_guid = Guid.NewGuid();
|
||||
}
|
||||
|
||||
if (gameState.IsConnected)
|
||||
{
|
||||
_name = OpenTK.Input.GamePad.GetName(_deviceIndex);
|
||||
_gamePadCapabilities = OpenTK.Input.GamePad.GetCapabilities(_deviceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_name = "OTK GamePad Undetermined Name";
|
||||
}
|
||||
|
||||
Update();
|
||||
InitializeCallbacks();
|
||||
|
||||
Console.WriteLine("Initialising OpenTK GamePad: " + _guid);
|
||||
Console.WriteLine("OpenTK Mapping: " + _name);
|
||||
|
||||
InitializeMappings();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
state = Joystick.GetState(_stickIdx);
|
||||
// update both here just in case
|
||||
var tmpState = OpenTK.Input.GamePad.GetState(_deviceIndex);
|
||||
if (!tmpState.Equals(state))
|
||||
{
|
||||
state = tmpState;
|
||||
DebugGamepadState();
|
||||
}
|
||||
|
||||
var tmpJstate = Joystick.GetState(_deviceIndex);
|
||||
if (!tmpJstate.Equals(jState))
|
||||
{
|
||||
jState = tmpJstate;
|
||||
DebugJoystickState();
|
||||
}
|
||||
}
|
||||
|
||||
private void DebugGamepadState()
|
||||
{
|
||||
Debug.WriteLine("GamePad State:\t" + state.ToString());
|
||||
}
|
||||
|
||||
private void DebugJoystickState()
|
||||
{
|
||||
Debug.WriteLine("Joystick State:\t" + jState.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The things that use GetFloats() (analog controls) appear to require values -10000 to 10000
|
||||
/// rather than the -1.0 to 1.0 that OpenTK returns (although even then the results may be slightly outside of these bounds)
|
||||
/// Note: is there a better/more perfomant way to do this?
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <returns></returns>
|
||||
private float SetBounds(float num)
|
||||
{
|
||||
if (num > 1)
|
||||
num = 1;
|
||||
if (num < -1)
|
||||
num = -1;
|
||||
|
||||
return num * 10000;
|
||||
}
|
||||
|
||||
public IEnumerable<Tuple<string, float>> GetFloats()
|
||||
{
|
||||
for (int pi = 0; pi < 64; pi++)
|
||||
yield return new Tuple<string, float>(pi.ToString(), 10.0f * state.GetAxis(pi));
|
||||
if (_gamePadCapabilities.HasValue && _gamePadCapabilities.Value.IsMapped)
|
||||
{
|
||||
// automapping identified - use OpenTK.Input.GamePad class
|
||||
yield return new Tuple<string, float>("LeftThumbX", SetBounds(state.ThumbSticks.Left.X));
|
||||
yield return new Tuple<string, float>("LeftThumbY", SetBounds(state.ThumbSticks.Left.Y));
|
||||
yield return new Tuple<string, float>("RightThumbX", SetBounds(state.ThumbSticks.Right.X));
|
||||
yield return new Tuple<string, float>("RightThumbY", SetBounds(state.ThumbSticks.Right.Y));
|
||||
yield return new Tuple<string, float>("LeftTrigger", SetBounds(state.Triggers.Left));
|
||||
yield return new Tuple<string, float>("RightTrigger", SetBounds(state.Triggers.Right));
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// use OpenTK.Input.Joystick class
|
||||
yield return new Tuple<string, float>("X", SetBounds(jState.GetAxis(0)));
|
||||
yield return new Tuple<string, float>("Y", SetBounds(jState.GetAxis(1)));
|
||||
yield return new Tuple<string, float>("Z", SetBounds(jState.GetAxis(2)));
|
||||
yield return new Tuple<string, float>("W", SetBounds(jState.GetAxis(3)));
|
||||
yield return new Tuple<string, float>("V", SetBounds(jState.GetAxis(4)));
|
||||
yield return new Tuple<string, float>("S", SetBounds(jState.GetAxis(5)));
|
||||
yield return new Tuple<string, float>("Q", SetBounds(jState.GetAxis(6)));
|
||||
yield return new Tuple<string, float>("P", SetBounds(jState.GetAxis(7)));
|
||||
yield return new Tuple<string, float>("N", SetBounds(jState.GetAxis(8)));
|
||||
|
||||
for (int i = 9; i < 64; i++)
|
||||
{
|
||||
int j = i;
|
||||
yield return new Tuple<string, float>(string.Format("Axis{0}", j.ToString()), SetBounds(jState.GetAxis(j)));
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>FOR DEBUGGING ONLY</summary>
|
||||
public JoystickState GetInternalState()
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
public string Name { get { return "Joystick " + _stickIdx; } }
|
||||
public string Name { get { return "Joystick " + _playerIndex + string.Format(" ({0})", _name); } }
|
||||
public string ID { get { return (_playerIndex).ToString(); } }
|
||||
public Guid Guid { get { return _guid; } }
|
||||
|
||||
/// <summary>
|
||||
/// Contains name and delegate function for all buttons, hats and axis
|
||||
/// </summary>
|
||||
public List<ButtonObject> buttonObjects = new List<ButtonObject>();
|
||||
|
||||
public string ButtonName(int index)
|
||||
void AddItem(string _name, Func<bool> pressed)
|
||||
{
|
||||
return names[index];
|
||||
}
|
||||
public bool Pressed(int index)
|
||||
{
|
||||
return actions[index]();
|
||||
}
|
||||
public int NumButtons { get; private set; }
|
||||
|
||||
private readonly List<string> names = new List<string>();
|
||||
private readonly List<Func<bool>> actions = new List<Func<bool>>();
|
||||
|
||||
void AddItem(string _name, Func<bool> callback)
|
||||
{
|
||||
names.Add(_name);
|
||||
actions.Add(callback);
|
||||
NumButtons++;
|
||||
}
|
||||
|
||||
void InitializeCallbacks()
|
||||
{
|
||||
const int dzp = 400;
|
||||
const int dzn = -400;
|
||||
|
||||
names.Clear();
|
||||
actions.Clear();
|
||||
NumButtons = 0;
|
||||
|
||||
AddItem("X+", () => state.GetAxis(0) >= dzp);
|
||||
AddItem("X-", () => state.GetAxis(0) <= dzn);
|
||||
AddItem("Y+", () => state.GetAxis(1) >= dzp);
|
||||
AddItem("Y-", () => state.GetAxis(1) <= dzn);
|
||||
AddItem("Z+", () => state.GetAxis(2) >= dzp);
|
||||
AddItem("Z-", () => state.GetAxis(2) <= dzn);
|
||||
|
||||
// Enjoy our delicious sliders. They're smaller than regular burgers but cost more.
|
||||
|
||||
int jb = 1;
|
||||
for (int i = 0; i < 64; i++)
|
||||
ButtonObject b = new ButtonObject
|
||||
{
|
||||
AddItem(string.Format("B{0}", jb), () => state.GetButton(i)==ButtonState.Pressed);
|
||||
jb++;
|
||||
ButtonName = _name,
|
||||
ButtonAction = pressed
|
||||
};
|
||||
|
||||
buttonObjects.Add(b);
|
||||
}
|
||||
|
||||
public struct ButtonObject
|
||||
{
|
||||
public string ButtonName;
|
||||
public Func<bool> ButtonAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup mappings prior to button initialization
|
||||
/// This is also here in case in the future we want users to be able to supply their own mappings for a device,
|
||||
/// perhaps via an input form. Possibly wishful thinking/overly complex.
|
||||
/// </summary>
|
||||
void InitializeMappings()
|
||||
{
|
||||
if (_guidObtained)
|
||||
{
|
||||
// placeholder for if/when we figure out how to supply OpenTK with custom GamePadConfigurationDatabase entries
|
||||
}
|
||||
|
||||
jb = 1;
|
||||
foreach (JoystickHat enval in Enum.GetValues(typeof(JoystickHat)))
|
||||
// currently OpenTK has an internal database of mappings for the GamePad class: https://github.com/opentk/opentk/blob/master/src/OpenTK/Input/GamePadConfigurationDatabase.cs
|
||||
// if an internal mapping is detected, use that. otherwise, use the joystick class to instantiate the controller
|
||||
if (_gamePadCapabilities.HasValue && _gamePadCapabilities.Value.IsMapped)
|
||||
{
|
||||
AddItem(string.Format("POV{0}U", jb), () => state.GetHat(enval).IsUp);
|
||||
AddItem(string.Format("POV{0}D", jb), () => state.GetHat(enval).IsDown);
|
||||
AddItem(string.Format("POV{0}L", jb), () => state.GetHat(enval).IsLeft);
|
||||
AddItem(string.Format("POV{0}R", jb), () => state.GetHat(enval).IsRight);
|
||||
jb++;
|
||||
// internal map detected - use the GamePad class
|
||||
InitializeGamePadControls();
|
||||
}
|
||||
else
|
||||
{
|
||||
// no internal map detected - use the joystick class
|
||||
InitializeJoystickControls();
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this does not appear to work at this time. I probably need to have more infos.
|
||||
public void SetVibration(int left, int right)
|
||||
void InitializeJoystickControls()
|
||||
{
|
||||
//Not supported in OTK Joystick. It is supported for OTK Gamepad, but I have to use Joystick for reasons mentioned above.
|
||||
// OpenTK GamePad axis return float values (as opposed to the shorts of SlimDX)
|
||||
const float ConversionFactor = 1.0f / short.MaxValue;
|
||||
const float dzp = (short)4000 * ConversionFactor;
|
||||
const float dzn = (short)-4000 * ConversionFactor;
|
||||
//const float dzt = 0.6f;
|
||||
|
||||
// axis
|
||||
AddItem("X+", () => jState.GetAxis(0) >= dzp);
|
||||
AddItem("X-", () => jState.GetAxis(0) <= dzn);
|
||||
AddItem("Y+", () => jState.GetAxis(1) >= dzp);
|
||||
AddItem("Y-", () => jState.GetAxis(1) <= dzn);
|
||||
AddItem("Z+", () => jState.GetAxis(2) >= dzp);
|
||||
AddItem("Z-", () => jState.GetAxis(2) <= dzn);
|
||||
AddItem("W+", () => jState.GetAxis(3) >= dzp);
|
||||
AddItem("W-", () => jState.GetAxis(3) <= dzn);
|
||||
AddItem("V+", () => jState.GetAxis(4) >= dzp);
|
||||
AddItem("V-", () => jState.GetAxis(4) <= dzn);
|
||||
AddItem("S+", () => jState.GetAxis(5) >= dzp);
|
||||
AddItem("S-", () => jState.GetAxis(5) <= dzn);
|
||||
AddItem("Q+", () => jState.GetAxis(6) >= dzp);
|
||||
AddItem("Q-", () => jState.GetAxis(6) <= dzn);
|
||||
AddItem("P+", () => jState.GetAxis(7) >= dzp);
|
||||
AddItem("P-", () => jState.GetAxis(7) <= dzn);
|
||||
AddItem("N+", () => jState.GetAxis(8) >= dzp);
|
||||
AddItem("N-", () => jState.GetAxis(8) <= dzn);
|
||||
// should be enough axis, but just in case:
|
||||
for (int i = 9; i < 64; i++)
|
||||
{
|
||||
int j = i;
|
||||
AddItem(string.Format("Axis{0}+", j.ToString()), () => jState.GetAxis(j) >= dzp);
|
||||
AddItem(string.Format("Axis{0}-", j.ToString()), () => jState.GetAxis(j) <= dzn);
|
||||
}
|
||||
|
||||
// buttons
|
||||
for (int i = 0; i < _joystickCapabilities.Value.ButtonCount; i++)
|
||||
{
|
||||
int j = i;
|
||||
AddItem(string.Format("B{0}", i + 1), () => jState.GetButton(j) == ButtonState.Pressed);
|
||||
}
|
||||
|
||||
// hats
|
||||
AddItem("POV1U", () => jState.GetHat(JoystickHat.Hat0).IsUp);
|
||||
AddItem("POV1D", () => jState.GetHat(JoystickHat.Hat0).IsDown);
|
||||
AddItem("POV1L", () => jState.GetHat(JoystickHat.Hat0).IsLeft);
|
||||
AddItem("POV1R", () => jState.GetHat(JoystickHat.Hat0).IsRight);
|
||||
AddItem("POV2U", () => jState.GetHat(JoystickHat.Hat1).IsUp);
|
||||
AddItem("POV2D", () => jState.GetHat(JoystickHat.Hat1).IsDown);
|
||||
AddItem("POV2L", () => jState.GetHat(JoystickHat.Hat1).IsLeft);
|
||||
AddItem("POV2R", () => jState.GetHat(JoystickHat.Hat1).IsRight);
|
||||
AddItem("POV3U", () => jState.GetHat(JoystickHat.Hat2).IsUp);
|
||||
AddItem("POV3D", () => jState.GetHat(JoystickHat.Hat2).IsDown);
|
||||
AddItem("POV3L", () => jState.GetHat(JoystickHat.Hat2).IsLeft);
|
||||
AddItem("POV3R", () => jState.GetHat(JoystickHat.Hat2).IsRight);
|
||||
AddItem("POV4U", () => jState.GetHat(JoystickHat.Hat3).IsUp);
|
||||
AddItem("POV4D", () => jState.GetHat(JoystickHat.Hat3).IsDown);
|
||||
AddItem("POV4L", () => jState.GetHat(JoystickHat.Hat3).IsLeft);
|
||||
AddItem("POV4R", () => jState.GetHat(JoystickHat.Hat3).IsRight);
|
||||
}
|
||||
|
||||
void InitializeGamePadControls()
|
||||
{
|
||||
// OpenTK GamePad axis return float values (as opposed to the shorts of SlimDX)
|
||||
const float ConversionFactor = 1.0f / short.MaxValue;
|
||||
const float dzp = (short)4000 * ConversionFactor;
|
||||
const float dzn = (short)-4000 * ConversionFactor;
|
||||
const float dzt = 0.6f;
|
||||
|
||||
// buttons
|
||||
AddItem("A", () => state.Buttons.A == ButtonState.Pressed);
|
||||
AddItem("B", () => state.Buttons.B == ButtonState.Pressed);
|
||||
AddItem("X", () => state.Buttons.X == ButtonState.Pressed);
|
||||
AddItem("Y", () => state.Buttons.Y == ButtonState.Pressed);
|
||||
AddItem("Guide", () => state.Buttons.BigButton == ButtonState.Pressed);
|
||||
AddItem("Start", () => state.Buttons.Start == ButtonState.Pressed);
|
||||
AddItem("Back", () => state.Buttons.Back == ButtonState.Pressed);
|
||||
AddItem("LeftThumb", () => state.Buttons.LeftStick == ButtonState.Pressed);
|
||||
AddItem("RightThumb", () => state.Buttons.RightStick == ButtonState.Pressed);
|
||||
AddItem("LeftShoulder", () => state.Buttons.LeftShoulder == ButtonState.Pressed);
|
||||
AddItem("RightShoulder", () => state.Buttons.RightShoulder == ButtonState.Pressed);
|
||||
|
||||
// dpad
|
||||
AddItem("DpadUp", () => state.DPad.Up == ButtonState.Pressed);
|
||||
AddItem("DpadDown", () => state.DPad.Down == ButtonState.Pressed);
|
||||
AddItem("DpadLeft", () => state.DPad.Left == ButtonState.Pressed);
|
||||
AddItem("DpadRight", () => state.DPad.Right == ButtonState.Pressed);
|
||||
|
||||
// sticks
|
||||
AddItem("LStickUp", () => state.ThumbSticks.Left.Y >= dzp);
|
||||
AddItem("LStickDown", () => state.ThumbSticks.Left.Y <= dzn);
|
||||
AddItem("LStickLeft", () => state.ThumbSticks.Left.X <= dzn);
|
||||
AddItem("LStickRight", () => state.ThumbSticks.Left.X >= dzp);
|
||||
AddItem("RStickUp", () => state.ThumbSticks.Right.Y >= dzp);
|
||||
AddItem("RStickDown", () => state.ThumbSticks.Right.Y <= dzn);
|
||||
AddItem("RStickLeft", () => state.ThumbSticks.Right.X <= dzn);
|
||||
AddItem("RStickRight", () => state.ThumbSticks.Right.X >= dzp);
|
||||
|
||||
// triggers
|
||||
AddItem("LeftTrigger", () => state.Triggers.Left > dzt);
|
||||
AddItem("RightTrigger", () => state.Triggers.Right > dzt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the gamepad's left and right vibration
|
||||
/// We don't currently use this in Bizhawk - do we have any cores that support this?
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
public void SetVibration(float left, float right)
|
||||
{
|
||||
float _l = 0;
|
||||
float _r = 0;
|
||||
|
||||
if (_gamePadCapabilities.Value.HasLeftVibrationMotor)
|
||||
_l = left;
|
||||
if (_gamePadCapabilities.Value.HasRightVibrationMotor)
|
||||
_r = right;
|
||||
|
||||
OpenTK.Input.GamePad.SetVibration(_deviceIndex, left, right);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -157,10 +157,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
argParser.ParseArguments(args);
|
||||
|
||||
Database.LoadDatabase(Path.Combine(PathManager.GetExeDirectoryAbsolute(), "gamedb", "gamedb.txt"));
|
||||
Database.LoadDatabase(Path.Combine(PathManager.GetExeDirectoryAbsolute(), "gamedb", "gamedb.txt"));
|
||||
|
||||
// TODO GL - a lot of disorganized wiring-up here
|
||||
CGC.CGCBinPath = Path.Combine(PathManager.GetDllDirectory(), "cgc.exe");
|
||||
CGC.CGCBinPath = !PlatformLinkedLibSingleton.RunningOnUnix
|
||||
? Path.Combine(PathManager.GetDllDirectory(), "cgc.exe")
|
||||
: "cgc"; // mono requires 'nvidia-cg-toolkit' dep (https://developer.nvidia.com/cg-toolkit-download)
|
||||
|
||||
PresentationPanel = new PresentationPanel();
|
||||
PresentationPanel.GraphicsControl.MainWindow = true;
|
||||
GlobalWin.DisplayManager = new DisplayManager(PresentationPanel);
|
||||
|
@ -318,7 +320,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
else
|
||||
{
|
||||
// fix movie extension to something palatable for these purposes.
|
||||
// fix movie extension to something palatable for these purposes.
|
||||
// for instance, something which doesnt clobber movies you already may have had.
|
||||
// i'm evenly torn between this, and a file in %TEMP%, but since we dont really have a way to clean up this tempfile, i choose this:
|
||||
StartNewMovie(imported, false);
|
||||
|
@ -406,7 +408,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
PauseEmulator();
|
||||
}
|
||||
|
||||
|
||||
// start dumping, if appropriate
|
||||
if (argParser.cmdDumpType != null && argParser.cmdDumpName != null)
|
||||
{
|
||||
|
@ -753,7 +755,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
// ordinarily, an alt release with nothing else would move focus to the menubar. but that is sort of useless, and hard to implement exactly right.
|
||||
}
|
||||
|
||||
// zero 09-sep-2012 - all input is eligible for controller input. not sure why the above was done.
|
||||
// zero 09-sep-2012 - all input is eligible for controller input. not sure why the above was done.
|
||||
// maybe because it doesnt make sense to me to bind hotkeys and controller inputs to the same keystrokes
|
||||
|
||||
// adelikat 02-dec-2012 - implemented options for how to handle controller vs hotkey conflicts. This is primarily motivated by computer emulation and thus controller being nearly the entire keyboard
|
||||
|
@ -822,9 +824,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
conInput.AcceptNewFloats(Input.Instance.GetFloats().Select(o =>
|
||||
{
|
||||
// mitigate MonoDevelop debug exceptions
|
||||
if (GlobalWin.DisplayManager == null) return o;
|
||||
|
||||
// hackish
|
||||
if (GlobalWin.DisplayManager == null) return o;
|
||||
|
||||
// hackish
|
||||
if (o.Item1 == "WMouse X")
|
||||
{
|
||||
var p = GlobalWin.DisplayManager.UntransformPoint(new Point((int)o.Item2, 0));
|
||||
|
@ -906,8 +908,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
|
||||
private void TakeScreenshotClientToClipboard()
|
||||
{
|
||||
// Mitigate MonoDevelop exceptions
|
||||
{
|
||||
// Mitigate MonoDevelop exceptions
|
||||
if (GlobalWin.DisplayManager == null) return;
|
||||
|
||||
using (var bb = GlobalWin.DisplayManager.RenderOffscreen(_currentVideoProvider, Global.Config.Screenshot_CaptureOSD))
|
||||
|
@ -1074,7 +1076,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
#if WINDOWS
|
||||
// Work around an AMD driver bug in >= vista:
|
||||
// It seems windows will activate opengl fullscreen mode when a GL control is occupying the exact space of a screen (0,0 and dimensions=screensize)
|
||||
// AMD cards manifest a problem under these circumstances, flickering other monitors.
|
||||
// AMD cards manifest a problem under these circumstances, flickering other monitors.
|
||||
// It isnt clear whether nvidia cards are failing to employ this optimization, or just not flickering.
|
||||
// (this could be determined with more work; other side affects of the fullscreen mode include: corrupted taskbar, no modal boxes on top of GL control, no screenshots)
|
||||
// At any rate, we can solve this by adding a 1px black border around the GL control
|
||||
|
@ -1082,7 +1084,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (Global.Config.DispFullscreenHacks && Global.Config.DispMethod == Config.EDispMethod.OpenGL)
|
||||
{
|
||||
//ATTENTION: this causes the statusbar to not work well, since the backcolor is now set to black instead of SystemColors.Control.
|
||||
//It seems that some statusbar elements composite with the backcolor.
|
||||
//It seems that some statusbar elements composite with the backcolor.
|
||||
//Maybe we could add another control under the statusbar. with a different backcolor
|
||||
Padding = new Padding(1);
|
||||
BackColor = Color.Black;
|
||||
|
@ -1625,7 +1627,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
GlobalWin.OSD.AddMessage("An error occurred while loading Sram");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FlushSaveRAM(bool autosave = false)
|
||||
{
|
||||
|
@ -1654,7 +1656,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
catch
|
||||
{
|
||||
GlobalWin.OSD.AddMessage("Unable to flush SaveRAM to: " + newFile.Directory);
|
||||
GlobalWin.OSD.AddMessage("Unable to flush SaveRAM to: " + newFile.Directory);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1819,7 +1821,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
else
|
||||
{
|
||||
DGBSubMenu.Visible = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "WSWAN":
|
||||
wonderSwanToolStripMenuItem.Visible = true;
|
||||
|
@ -2051,7 +2053,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
bool isZero = false;
|
||||
if (currVideoSize.Width == 0 || currVideoSize.Height == 0 || currVirtualSize.Width == 0 || currVirtualSize.Height == 0)
|
||||
isZero = true;
|
||||
|
||||
|
||||
//don't resize if the new size is 0 somehow; we'll wait until we have a sensible size
|
||||
if(isZero)
|
||||
resizeFramebuffer = false;
|
||||
|
@ -2061,9 +2063,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
_lastVideoSize = currVideoSize;
|
||||
_lastVirtualSize = currVirtualSize;
|
||||
FrameBufferResized();
|
||||
}
|
||||
|
||||
// Mitigate MonoDevelop exception
|
||||
}
|
||||
|
||||
// Mitigate MonoDevelop exception
|
||||
if (GlobalWin.DisplayManager == null) return;
|
||||
|
||||
//rendering flakes out egregiously if we have a zero size
|
||||
|
@ -2084,7 +2086,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
// sends an alt+mnemonic combination
|
||||
private void SendAltKeyChar(char c)
|
||||
{
|
||||
typeof(ToolStrip).InvokeMember("ProcessMnemonicInternal", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Instance, null, MainformMenu, new object[] { c });
|
||||
if (PlatformLinkedLibSingleton.RunningOnUnix) {} // no mnemonics for you
|
||||
else typeof(ToolStrip).InvokeMember("ProcessMnemonicInternal", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Instance, null, MainformMenu, new object[] { c });
|
||||
}
|
||||
|
||||
public static string FormatFilter(params string[] args)
|
||||
|
@ -3605,7 +3608,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
// if the core is managing its own DE through SyncSettings a 'deterministic' bool can be passed into the core's constructor
|
||||
// it is then up to the core itself to override its own local DeterministicEmulation setting
|
||||
bool deterministic = args.Deterministic ?? Global.MovieSession.QueuedMovie != null;
|
||||
|
||||
|
||||
if (!GlobalWin.Tools.AskSave())
|
||||
{
|
||||
return false;
|
||||
|
|
|
@ -48,13 +48,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
sw.WriteLine("[{0}] Visual C++ 2010 SP1 Runtime", (vc2010 == IntPtr.Zero || vc2010p == IntPtr.Zero) ? "FAIL" : " OK ");
|
||||
sw.WriteLine("[{0}] Visual C++ 2012 Runtime", (vc2012 == IntPtr.Zero) ? "FAIL" : " OK ");
|
||||
sw.WriteLine("[{0}] Visual C++ 2015 Runtime", (vc2015 == IntPtr.Zero) ? "FAIL" : " OK ");
|
||||
var str = sw.ToString();
|
||||
var box = new BizHawk.Client.EmuHawk.CustomControls.PrereqsAlert(!fail);
|
||||
box.textBox1.Text = str;
|
||||
box.textBox1.Text = sw.ToString();
|
||||
box.ShowDialog();
|
||||
if (!fail) { }
|
||||
else
|
||||
System.Diagnostics.Process.GetCurrentProcess().Kill();
|
||||
if (fail) System.Diagnostics.Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
|
||||
libLoader.FreePlatformSpecific(d3dx9);
|
||||
|
@ -79,21 +76,31 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
//We need to do it here too... otherwise people get exceptions when externaltools we distribute try to startup
|
||||
}
|
||||
else
|
||||
{
|
||||
else
|
||||
{
|
||||
// on mono we skip all the SetDllDirectory stuff
|
||||
// just wire up the event handler
|
||||
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
|
||||
// just wire up the event handler
|
||||
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
|
||||
}
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
static int Main(string[] args)
|
||||
{
|
||||
return SubMain(args);
|
||||
int sm = SubMain(args);
|
||||
// We have a problem on mono where the application (most of the time) does not terminate correctly
|
||||
// and we have to CTRL-C to kill the mono process
|
||||
// This forces mono to go away immediately
|
||||
if (EXE_PROJECT.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
Console.WriteLine("BizHawk has completed it's shutdown routines");
|
||||
Console.WriteLine("Attempting to kill mono...");
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
return sm;
|
||||
}
|
||||
|
||||
//NoInlining should keep this code from getting jammed into Main() which would create dependencies on types which havent been setup by the resolver yet... or something like that
|
||||
|
||||
//NoInlining should keep this code from getting jammed into Main() which would create dependencies on types which havent been setup by the resolver yet... or something like that
|
||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
|
||||
static int SubMain(string[] args)
|
||||
{
|
||||
|
@ -113,9 +120,15 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
BizHawk.Common.TempFileManager.Start();
|
||||
BizHawk.Common.TempFileManager.Start();
|
||||
|
||||
if (PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
HawkFile.ArchiveHandlerFactory = new SharpCompressArchiveHandler();
|
||||
else
|
||||
HawkFile.ArchiveHandlerFactory = new SevenZipSharpArchiveHandler();
|
||||
|
||||
HawkFile.ArchiveHandlerFactory = new SevenZipSharpArchiveHandler();
|
||||
// Uncomment for system-agnostic glory!
|
||||
//HawkFile.ArchiveHandlerFactory = new SharpCompressArchiveHandler();
|
||||
|
||||
ArgParser argParser = new ArgParser();
|
||||
argParser.ParseArguments(args);
|
||||
|
@ -137,13 +150,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
BizHawk.Client.Common.StringLogUtil.DefaultToAWE = Global.Config.MoviesInAWE;
|
||||
|
||||
// super hacky! this needs to be done first. still not worth the trouble to make this system fully proper
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
if (Array.Exists(args, arg => arg.ToLower().StartsWith("--gdi")))
|
||||
{
|
||||
var arg = args[i].ToLower();
|
||||
if (arg.StartsWith("--gdi"))
|
||||
{
|
||||
Global.Config.DispMethod = Config.EDispMethod.GdiPlus;
|
||||
}
|
||||
Global.Config.DispMethod = Config.EDispMethod.GdiPlus;
|
||||
}
|
||||
|
||||
// create IGL context. we do this whether or not the user has selected OpenGL, so that we can run opengl-based emulator cores
|
||||
|
@ -154,11 +163,27 @@ namespace BizHawk.Client.EmuHawk
|
|||
GlobalWin.GLManager = GLManager.Instance;
|
||||
|
||||
//now create the "GL" context for the display method. we can reuse the IGL_TK context if opengl display method is chosen
|
||||
if (EXE_PROJECT.PlatformLinkedLibSingleton.RunningOnUnix) Global.Config.DispMethod = Config.EDispMethod.GdiPlus;
|
||||
|
||||
REDO_DISPMETHOD:
|
||||
if (Global.Config.DispMethod == Config.EDispMethod.GdiPlus)
|
||||
GlobalWin.GL = new Bizware.BizwareGL.Drivers.GdiPlus.IGL_GdiPlus();
|
||||
else if (Global.Config.DispMethod == Config.EDispMethod.SlimDX9)
|
||||
else if (Global.Config.DispMethod == Config.EDispMethod.Vulkan)
|
||||
{
|
||||
try
|
||||
{
|
||||
GlobalWin.GL = new Bizware.BizwareGL.Drivers.Vulkan.IGL_Vulkan();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
new ExceptionBox(new Exception("Something in Vulkan init failed, using GDI+ as fallback", ex))
|
||||
.ShowDialog();
|
||||
|
||||
// fallback
|
||||
Global.Config.DispMethod = Config.EDispMethod.GdiPlus;
|
||||
goto REDO_DISPMETHOD;
|
||||
}
|
||||
}
|
||||
else if (Global.Config.DispMethod == Config.EDispMethod.SlimDX9 && !EXE_PROJECT.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -166,8 +191,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
var e2 = new Exception("Initialization of Direct3d 9 Display Method failed; falling back to GDI+", ex);
|
||||
new ExceptionBox(e2).ShowDialog();
|
||||
new ExceptionBox(new Exception("Initialization of Direct3d 9 Display Method failed; falling back to GDI+", ex))
|
||||
.ShowDialog();
|
||||
|
||||
// fallback
|
||||
Global.Config.DispMethod = Config.EDispMethod.GdiPlus;
|
||||
|
@ -179,8 +204,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
GlobalWin.GL = GlobalWin.IGL_GL;
|
||||
|
||||
// check the opengl version and dont even try to boot this crap up if its too old
|
||||
int version = GlobalWin.IGL_GL.Version;
|
||||
if (version < 200)
|
||||
if (GlobalWin.IGL_GL.Version < 200)
|
||||
{
|
||||
// fallback
|
||||
Global.Config.DispMethod = Config.EDispMethod.GdiPlus;
|
||||
|
@ -195,8 +219,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
var e2 = new Exception("Initialization of Display Method failed; falling back to GDI+", ex);
|
||||
new ExceptionBox(e2).ShowDialog();
|
||||
new ExceptionBox(new Exception("Initialization of Display Method failed; falling back to GDI+", ex))
|
||||
.ShowDialog();
|
||||
//fallback
|
||||
Global.Config.DispMethod = Config.EDispMethod.GdiPlus;
|
||||
goto REDO_DISPMETHOD;
|
||||
|
@ -209,8 +233,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
//It isn't clear whether we need the earlier SetDllDirectory(), but I think we do.
|
||||
//note: this is pasted instead of being put in a static method due to this initialization code being sensitive to things like that, and not wanting to cause it to break
|
||||
//pasting should be safe (not affecting the jit order of things)
|
||||
string dllDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "dll");
|
||||
SetDllDirectory(dllDir);
|
||||
SetDllDirectory(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "dll"));
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -219,11 +242,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
try
|
||||
{
|
||||
new SingleInstanceController(args).Run(args);
|
||||
new SingleInstanceController(args).RunWithArgs();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Eat it, MainForm disposed itself and Run attempts to dispose of itself. Eventually we would want to figure out a way to prevent that, but in the meantime it is harmless, so just eat the error
|
||||
{
|
||||
// Eat it, MainForm disposed itself and Run attempts to dispose of itself. Eventually we would want to figure out a way to prevent that, but in the meantime it is harmless, so just eat the error
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -266,18 +289,18 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
GlobalWin.GL.Dispose();
|
||||
Input.Cleanup();
|
||||
}
|
||||
|
||||
//cleanup:
|
||||
//cleanup IGL stuff so we can get better refcounts when exiting process, for debugging
|
||||
//DOESNT WORK FOR SOME REASON
|
||||
//GlobalWin.IGL_GL = new Bizware.BizwareGL.Drivers.OpenTK.IGL_TK();
|
||||
//GLManager.Instance.Dispose();
|
||||
//if (GlobalWin.IGL_GL != GlobalWin.GL)
|
||||
// GlobalWin.GL.Dispose();
|
||||
//((IDisposable)GlobalWin.IGL_GL).Dispose();
|
||||
|
||||
//return 0 assuming things have gone well, non-zero values could be used as error codes or for scripting purposes
|
||||
}
|
||||
|
||||
//cleanup:
|
||||
//cleanup IGL stuff so we can get better refcounts when exiting process, for debugging
|
||||
//DOESNT WORK FOR SOME REASON
|
||||
//GlobalWin.IGL_GL = new Bizware.BizwareGL.Drivers.OpenTK.IGL_TK();
|
||||
//GLManager.Instance.Dispose();
|
||||
//if (GlobalWin.IGL_GL != GlobalWin.GL)
|
||||
// GlobalWin.GL.Dispose();
|
||||
//((IDisposable)GlobalWin.IGL_GL).Dispose();
|
||||
|
||||
//return 0 assuming things have gone well, non-zero values could be used as error codes or for scripting purposes
|
||||
return GlobalWin.ExitCode;
|
||||
} //SubMain
|
||||
|
||||
|
@ -321,7 +344,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
//3. When NLua assembly attempts to load, it can't find it
|
||||
//I. if LuaInterface is selected by the user, we switch to requesting that.
|
||||
// (those DLLs are built into the output/DLL directory)
|
||||
//II. if NLua is selected by the user, we skip over this part;
|
||||
//II. if NLua is selected by the user, we skip over this part;
|
||||
// later, we look for NLua or KopiLua assembly names and redirect them to files located in the output/DLL/nlua directory
|
||||
if (new AssemblyName(requested).Name == "NLua")
|
||||
{
|
||||
|
@ -330,30 +353,26 @@ namespace BizHawk.Client.EmuHawk
|
|||
//avert your eyes.
|
||||
bool UseNLua = true;
|
||||
string configPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "config.ini");
|
||||
if (File.Exists(configPath))
|
||||
if (File.Exists(configPath) && (
|
||||
File.ReadAllLines(configPath)
|
||||
.FirstOrDefault(line => line.Contains(" \"UseNLua\": "))
|
||||
?.Contains("false")
|
||||
?? false))
|
||||
{
|
||||
var cfg = File.ReadAllLines(configPath);
|
||||
var usenlua_key = cfg.FirstOrDefault(line=>line.Contains(" \"UseNLua\": "));
|
||||
if (usenlua_key != null)
|
||||
if (usenlua_key.Contains("false"))
|
||||
UseNLua = false;
|
||||
UseNLua = false;
|
||||
}
|
||||
|
||||
if (UseNLua) { }
|
||||
else if (EXE_PROJECT.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
// currently LuaInterface is not working/implemented on Mono
|
||||
// so we always force NLua
|
||||
|
||||
if (!UseNLua && !EXE_PROJECT.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
// currently LuaInterface is not working/implemented on Mono so we always force NLua, otherwise:
|
||||
requested = "LuaInterface";
|
||||
}
|
||||
else requested = "LuaInterface";
|
||||
}
|
||||
|
||||
lock (AppDomain.CurrentDomain)
|
||||
{
|
||||
var asms = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach (var asm in asms)
|
||||
if (asm.FullName == requested)
|
||||
return asm;
|
||||
var firstAsm = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), asm => asm.FullName == requested);
|
||||
if (firstAsm != null) return firstAsm;
|
||||
|
||||
//load missing assemblies by trying to find them in the dll directory
|
||||
string dllname = new AssemblyName(requested).Name + ".dll";
|
||||
|
@ -378,6 +397,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
StartupNextInstance += this_StartupNextInstance;
|
||||
}
|
||||
|
||||
public void RunWithArgs()
|
||||
{
|
||||
Run(cmdArgs);
|
||||
}
|
||||
|
||||
void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
|
||||
{
|
||||
if (e.CommandLine.Count >= 1)
|
||||
|
|
|
@ -24,16 +24,20 @@ namespace BizHawk.Client.EmuHawk
|
|||
private IBufferedSoundProvider _bufferedProvider; // One of the preceding buffers, or null if no source is set
|
||||
|
||||
public Sound(IntPtr mainWindowHandle)
|
||||
{
|
||||
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.OpenAL)
|
||||
{
|
||||
// at the moment unix/mono can only support OpenAL (so ignore whatever is set in the config)
|
||||
if (PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
_outputDevice = new OpenALSoundOutput(this);
|
||||
if (!PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
else
|
||||
{
|
||||
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.OpenAL)
|
||||
_outputDevice = new OpenALSoundOutput(this);
|
||||
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.DirectSound)
|
||||
_outputDevice = new DirectSoundSoundOutput(this, mainWindowHandle);
|
||||
if (Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.XAudio2)
|
||||
_outputDevice = new XAudio2SoundOutput(this);
|
||||
_outputDevice = new XAudio2SoundOutput(this);
|
||||
}
|
||||
|
||||
if (_outputDevice == null)
|
||||
_outputDevice = new DummySoundOutput(this);
|
||||
}
|
||||
|
|
|
@ -69,8 +69,10 @@
|
|||
this.label10 = new System.Windows.Forms.Label();
|
||||
this.nudPrescale = new System.Windows.Forms.NumericUpDown();
|
||||
this.tpDispMethod = new System.Windows.Forms.TabPage();
|
||||
this.label14 = new System.Windows.Forms.Label();
|
||||
this.label6 = new System.Windows.Forms.Label();
|
||||
this.groupBox3 = new System.Windows.Forms.GroupBox();
|
||||
this.rbVulkan = new System.Windows.Forms.RadioButton();
|
||||
this.label13 = new System.Windows.Forms.Label();
|
||||
this.cbAlternateVsync = new System.Windows.Forms.CheckBox();
|
||||
this.label8 = new System.Windows.Forms.Label();
|
||||
|
@ -122,7 +124,7 @@
|
|||
//
|
||||
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.btnCancel.Location = new System.Drawing.Point(473, 339);
|
||||
this.btnCancel.Location = new System.Drawing.Point(473, 365);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Size = new System.Drawing.Size(75, 23);
|
||||
this.btnCancel.TabIndex = 5;
|
||||
|
@ -132,7 +134,7 @@
|
|||
// btnOk
|
||||
//
|
||||
this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnOk.Location = new System.Drawing.Point(392, 339);
|
||||
this.btnOk.Location = new System.Drawing.Point(392, 365);
|
||||
this.btnOk.Name = "btnOk";
|
||||
this.btnOk.Size = new System.Drawing.Size(75, 23);
|
||||
this.btnOk.TabIndex = 4;
|
||||
|
@ -469,7 +471,7 @@
|
|||
this.tabControl1.Location = new System.Drawing.Point(12, 12);
|
||||
this.tabControl1.Name = "tabControl1";
|
||||
this.tabControl1.SelectedIndex = 0;
|
||||
this.tabControl1.Size = new System.Drawing.Size(536, 317);
|
||||
this.tabControl1.Size = new System.Drawing.Size(536, 343);
|
||||
this.tabControl1.TabIndex = 17;
|
||||
//
|
||||
// tpAR
|
||||
|
@ -487,7 +489,7 @@
|
|||
this.tpAR.Location = new System.Drawing.Point(4, 22);
|
||||
this.tpAR.Name = "tpAR";
|
||||
this.tpAR.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tpAR.Size = new System.Drawing.Size(528, 291);
|
||||
this.tpAR.Size = new System.Drawing.Size(528, 317);
|
||||
this.tpAR.TabIndex = 0;
|
||||
this.tpAR.Text = "Scaling & Filtering";
|
||||
this.tpAR.UseVisualStyleBackColor = true;
|
||||
|
@ -544,18 +546,27 @@
|
|||
//
|
||||
// tpDispMethod
|
||||
//
|
||||
this.tpDispMethod.Controls.Add(this.label14);
|
||||
this.tpDispMethod.Controls.Add(this.label6);
|
||||
this.tpDispMethod.Controls.Add(this.groupBox3);
|
||||
this.tpDispMethod.Location = new System.Drawing.Point(4, 22);
|
||||
this.tpDispMethod.Name = "tpDispMethod";
|
||||
this.tpDispMethod.Size = new System.Drawing.Size(528, 291);
|
||||
this.tpDispMethod.Size = new System.Drawing.Size(528, 317);
|
||||
this.tpDispMethod.TabIndex = 2;
|
||||
this.tpDispMethod.Text = "Display Method";
|
||||
this.tpDispMethod.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// label14
|
||||
//
|
||||
this.label14.Location = new System.Drawing.Point(31, 260);
|
||||
this.label14.Name = "label14";
|
||||
this.label14.Size = new System.Drawing.Size(359, 18);
|
||||
this.label14.TabIndex = 24;
|
||||
this.label14.Text = " • Experimental, high-performance, and open.\r\n";
|
||||
//
|
||||
// label6
|
||||
//
|
||||
this.label6.Location = new System.Drawing.Point(3, 258);
|
||||
this.label6.Location = new System.Drawing.Point(3, 293);
|
||||
this.label6.Name = "label6";
|
||||
this.label6.Size = new System.Drawing.Size(359, 23);
|
||||
this.label6.TabIndex = 18;
|
||||
|
@ -563,6 +574,7 @@
|
|||
//
|
||||
// groupBox3
|
||||
//
|
||||
this.groupBox3.Controls.Add(this.rbVulkan);
|
||||
this.groupBox3.Controls.Add(this.label13);
|
||||
this.groupBox3.Controls.Add(this.cbAlternateVsync);
|
||||
this.groupBox3.Controls.Add(this.label8);
|
||||
|
@ -573,10 +585,22 @@
|
|||
this.groupBox3.Controls.Add(this.rbOpenGL);
|
||||
this.groupBox3.Location = new System.Drawing.Point(6, 5);
|
||||
this.groupBox3.Name = "groupBox3";
|
||||
this.groupBox3.Size = new System.Drawing.Size(415, 241);
|
||||
this.groupBox3.Size = new System.Drawing.Size(415, 276);
|
||||
this.groupBox3.TabIndex = 16;
|
||||
this.groupBox3.TabStop = false;
|
||||
//
|
||||
// rbVulkan
|
||||
//
|
||||
this.rbVulkan.AutoSize = true;
|
||||
this.rbVulkan.Checked = true;
|
||||
this.rbVulkan.Location = new System.Drawing.Point(6, 235);
|
||||
this.rbVulkan.Name = "rbVulkan";
|
||||
this.rbVulkan.Size = new System.Drawing.Size(65, 17);
|
||||
this.rbVulkan.TabIndex = 23;
|
||||
this.rbVulkan.TabStop = true;
|
||||
this.rbVulkan.Text = "Vulkan";
|
||||
this.rbVulkan.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// label13
|
||||
//
|
||||
this.label13.Location = new System.Drawing.Point(45, 60);
|
||||
|
@ -602,8 +626,7 @@
|
|||
this.label8.Name = "label8";
|
||||
this.label8.Size = new System.Drawing.Size(359, 27);
|
||||
this.label8.TabIndex = 20;
|
||||
this.label8.Text = " • Best compatibility\r\n • May have trouble with OpenGL-based cores (N64)\r\n" +
|
||||
"";
|
||||
this.label8.Text = " • Best compatibility\r\n • May have trouble with OpenGL-based cores (N64)\r\n";
|
||||
//
|
||||
// rbD3D9
|
||||
//
|
||||
|
@ -645,7 +668,7 @@
|
|||
this.tpMisc.Controls.Add(this.checkSnowyNullEmulator);
|
||||
this.tpMisc.Location = new System.Drawing.Point(4, 22);
|
||||
this.tpMisc.Name = "tpMisc";
|
||||
this.tpMisc.Size = new System.Drawing.Size(528, 291);
|
||||
this.tpMisc.Size = new System.Drawing.Size(528, 317);
|
||||
this.tpMisc.TabIndex = 3;
|
||||
this.tpMisc.Text = "Misc";
|
||||
this.tpMisc.UseVisualStyleBackColor = true;
|
||||
|
@ -704,7 +727,7 @@
|
|||
this.tabPage1.Location = new System.Drawing.Point(4, 22);
|
||||
this.tabPage1.Name = "tabPage1";
|
||||
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage1.Size = new System.Drawing.Size(528, 291);
|
||||
this.tabPage1.Size = new System.Drawing.Size(528, 317);
|
||||
this.tabPage1.TabIndex = 4;
|
||||
this.tabPage1.Text = "Window";
|
||||
this.tabPage1.UseVisualStyleBackColor = true;
|
||||
|
@ -897,7 +920,7 @@
|
|||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.btnCancel;
|
||||
this.ClientSize = new System.Drawing.Size(564, 374);
|
||||
this.ClientSize = new System.Drawing.Size(564, 400);
|
||||
this.Controls.Add(this.linkLabel1);
|
||||
this.Controls.Add(this.tabControl1);
|
||||
this.Controls.Add(this.btnCancel);
|
||||
|
@ -908,6 +931,7 @@
|
|||
this.Text = "Display Configuration";
|
||||
this.groupBox1.ResumeLayout(false);
|
||||
this.groupBox1.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.tbScanlineIntensity)).EndInit();
|
||||
this.grpFinalFilter.ResumeLayout(false);
|
||||
this.grpFinalFilter.PerformLayout();
|
||||
this.grpARSelection.ResumeLayout(false);
|
||||
|
@ -929,7 +953,6 @@
|
|||
this.groupBox4.PerformLayout();
|
||||
this.groupBox2.ResumeLayout(false);
|
||||
this.groupBox2.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.tbScanlineIntensity)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.trackbarFrameSizeWindowed)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
@ -1009,5 +1032,7 @@
|
|||
private System.Windows.Forms.CheckBox cbFullscreenHacks;
|
||||
private System.Windows.Forms.Button btnDefaults;
|
||||
private System.Windows.Forms.ToolTip toolTip1;
|
||||
private System.Windows.Forms.Label label14;
|
||||
private System.Windows.Forms.RadioButton rbVulkan;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@ using System.IO;
|
|||
using System.Windows.Forms;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Client.Common;
|
||||
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
public partial class DisplayConfigLite : Form
|
||||
|
@ -43,6 +44,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
rbOpenGL.Checked = Global.Config.DispMethod == Config.EDispMethod.OpenGL;
|
||||
rbGDIPlus.Checked = Global.Config.DispMethod == Config.EDispMethod.GdiPlus;
|
||||
rbD3D9.Checked = Global.Config.DispMethod == Config.EDispMethod.SlimDX9;
|
||||
rbVulkan.Checked = Global.Config.DispMethod == Config.EDispMethod.Vulkan;
|
||||
|
||||
cbStatusBarWindowed.Checked = Global.Config.DispChrome_StatusBarWindowed;
|
||||
cbCaptionWindowed.Checked = Global.Config.DispChrome_CaptionWindowed;
|
||||
|
@ -86,6 +88,22 @@ namespace BizHawk.Client.EmuHawk
|
|||
txtCustomARY.Text = Global.Config.DispCustomUserARY.ToString();
|
||||
|
||||
RefreshAspectRatioOptions();
|
||||
|
||||
if (PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
// no slimdx
|
||||
rbD3D9.Enabled = false;
|
||||
rbD3D9.AutoCheck = false;
|
||||
cbAlternateVsync.Enabled = false;
|
||||
label13.Enabled = false;
|
||||
label8.Enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Disallow Vulkan selection on Windows
|
||||
rbVulkan.Enabled = false;
|
||||
label14.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void btnOk_Click(object sender, EventArgs e)
|
||||
|
@ -172,6 +190,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
Global.Config.DispMethod = Config.EDispMethod.GdiPlus;
|
||||
if(rbD3D9.Checked)
|
||||
Global.Config.DispMethod = Config.EDispMethod.SlimDX9;
|
||||
if (rbVulkan.Checked)
|
||||
Global.Config.DispMethod = Config.EDispMethod.Vulkan;
|
||||
|
||||
if (oldDisplayMethod != Global.Config.DispMethod)
|
||||
NeedReset = true;
|
||||
|
|
|
@ -357,7 +357,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
private void tbbOpenFolder_Click(object sender, EventArgs e)
|
||||
{
|
||||
var frmWares = PathManager.MakeAbsolutePath(Global.Config.PathEntries.FirmwaresPathFragment, null);
|
||||
System.Diagnostics.Process.Start(frmWares);
|
||||
if (PlatformLinkedLibSingleton.RunningOnUnix && !Directory.Exists(frmWares))
|
||||
{
|
||||
Directory.CreateDirectory(frmWares);
|
||||
}
|
||||
|
||||
System.Diagnostics.Process.Start(frmWares);
|
||||
}
|
||||
|
||||
private void lvFirmwares_KeyDown(object sender, KeyEventArgs e)
|
||||
|
|
|
@ -218,15 +218,36 @@ namespace BizHawk.Client.EmuHawk
|
|||
system = null;
|
||||
}
|
||||
|
||||
var f = new FolderBrowserEx
|
||||
DialogResult result = new DialogResult();
|
||||
string selectedPath = "";
|
||||
|
||||
if (!BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
Description = "Set the directory for " + name,
|
||||
SelectedPath = PathManager.MakeAbsolutePath(box.Text, system)
|
||||
};
|
||||
var result = f.ShowDialog();
|
||||
var f = new FolderBrowserEx
|
||||
{
|
||||
Description = "Set the directory for " + name,
|
||||
SelectedPath = PathManager.MakeAbsolutePath(box.Text, system)
|
||||
};
|
||||
result = f.ShowDialog();
|
||||
selectedPath = f.SelectedPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// mono does not like FolderBrowserEx because of its managed calls
|
||||
// do standard winforms on *nix
|
||||
var f = new FolderBrowserDialog
|
||||
{
|
||||
Description = "Set the directory for " + name,
|
||||
SelectedPath = PathManager.MakeAbsolutePath(box.Text, system)
|
||||
};
|
||||
result = f.ShowDialog();
|
||||
selectedPath = f.SelectedPath;
|
||||
}
|
||||
|
||||
|
||||
if (result == DialogResult.OK)
|
||||
{
|
||||
box.Text = PathManager.TryMakeRelative(f.SelectedPath, system);
|
||||
box.Text = PathManager.TryMakeRelative(selectedPath, system);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ using System.Linq;
|
|||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Client.Common;
|
||||
|
||||
using BizHawk.Common;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
public partial class SoundConfig : Form
|
||||
|
@ -24,10 +25,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
cbEnableNormal.Checked = Global.Config.SoundEnabledNormal;
|
||||
cbEnableRWFF.Checked = Global.Config.SoundEnabledRWFF;
|
||||
cbMuteFrameAdvance.Checked = Global.Config.MuteFrameAdvance;
|
||||
#if !WINDOWS
|
||||
rbOutputMethodDirectSound.Enabled = false;
|
||||
rbOutputMethodXAudio2.Enabled = false;
|
||||
#endif
|
||||
|
||||
if (PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
rbOutputMethodDirectSound.Enabled = false;
|
||||
rbOutputMethodXAudio2.Enabled = false;
|
||||
}
|
||||
|
||||
rbOutputMethodDirectSound.Checked = Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.DirectSound;
|
||||
rbOutputMethodXAudio2.Checked = Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.XAudio2;
|
||||
rbOutputMethodOpenAL.Checked = Global.Config.SoundOutputMethod == Config.ESoundOutputMethod.OpenAL;
|
||||
|
@ -83,10 +87,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
private void PopulateDeviceList()
|
||||
{
|
||||
IEnumerable<string> deviceNames = Enumerable.Empty<string>();
|
||||
#if WINDOWS
|
||||
if (rbOutputMethodDirectSound.Checked) deviceNames = DirectSoundSoundOutput.GetDeviceNames();
|
||||
if (rbOutputMethodXAudio2.Checked) deviceNames = XAudio2SoundOutput.GetDeviceNames();
|
||||
#endif
|
||||
|
||||
if (!PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
if (rbOutputMethodDirectSound.Checked) deviceNames = DirectSoundSoundOutput.GetDeviceNames();
|
||||
if (rbOutputMethodXAudio2.Checked) deviceNames = XAudio2SoundOutput.GetDeviceNames();
|
||||
}
|
||||
|
||||
if (rbOutputMethodOpenAL.Checked) deviceNames = OpenALSoundOutput.GetDeviceNames();
|
||||
listBoxSoundDevices.Items.Clear();
|
||||
listBoxSoundDevices.Items.Add("<default>");
|
||||
|
|
|
@ -58,8 +58,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (AskSaveChanges())
|
||||
{
|
||||
SaveColumnInfo(LuaListView, Settings.Columns);
|
||||
|
||||
GlobalWin.DisplayManager.ClearLuaSurfaces();
|
||||
if (GlobalWin.DisplayManager != null)
|
||||
GlobalWin.DisplayManager.ClearLuaSurfaces();
|
||||
LuaImp.GuiLibrary.DrawFinish();
|
||||
CloseLua();
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
var currentScripts = LuaImp?.ScriptList; // Temp fix for now
|
||||
LuaImp = PlatformLinkedLibSingleton.RunningOnUnix
|
||||
? (PlatformEmuLuaLibrary) new NotReallyLuaLibrary()
|
||||
? (PlatformEmuLuaLibrary) new EmuLuaLibrary(Emulator.ServiceProvider)//NotReallyLuaLibrary()
|
||||
: (PlatformEmuLuaLibrary) new EmuLuaLibrary(Emulator.ServiceProvider);
|
||||
if (currentScripts != null)
|
||||
{
|
||||
|
|
|
@ -280,21 +280,56 @@ namespace BizHawk.Client.EmuHawk
|
|||
// http://stackoverflow.com/questions/275689/how-to-get-relative-path-from-absolute-path
|
||||
public static string GetRelativePath(string fromPath, string toPath)
|
||||
{
|
||||
Win32.FileAttributes fromAttr = GetPathAttribute(fromPath);
|
||||
Win32.FileAttributes toAttr = GetPathAttribute(toPath);
|
||||
|
||||
var path = new StringBuilder(260); // MAX_PATH
|
||||
if (Win32.PathRelativePathTo(
|
||||
path,
|
||||
fromPath,
|
||||
fromAttr,
|
||||
toPath,
|
||||
toAttr) == false)
|
||||
if (!BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
throw new ArgumentException("Paths must have a common prefix");
|
||||
}
|
||||
Win32.FileAttributes fromAttr = GetPathAttribute(fromPath);
|
||||
Win32.FileAttributes toAttr = GetPathAttribute(toPath);
|
||||
|
||||
return path.ToString();
|
||||
var path = new StringBuilder(260); // MAX_PATH
|
||||
if (Win32.PathRelativePathTo(
|
||||
path,
|
||||
fromPath,
|
||||
fromAttr,
|
||||
toPath,
|
||||
toAttr) == false)
|
||||
{
|
||||
throw new ArgumentException("Paths must have a common prefix");
|
||||
}
|
||||
|
||||
return path.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// mono doesnt know about shlwapi.dll that the code above tries to call
|
||||
// it is available in WINE, but since this is the only call to that function,
|
||||
// it's probably easier to get the relative path a different way
|
||||
// this may actually work on windows as well - so maybe can replace the above when we move to .netcore
|
||||
var dirSepChar = Path.DirectorySeparatorChar;
|
||||
string from = !fromPath.EndsWith(dirSepChar.ToString())
|
||||
? fromPath + dirSepChar
|
||||
: fromPath;
|
||||
string to = !toPath.EndsWith(dirSepChar.ToString())
|
||||
? toPath + dirSepChar
|
||||
: toPath;
|
||||
|
||||
Uri fromUri = new Uri(from);
|
||||
Uri toUri = new Uri(to);
|
||||
|
||||
if (fromUri.Scheme != toUri.Scheme)
|
||||
{
|
||||
return toPath;
|
||||
}
|
||||
|
||||
Uri relativeUri = fromUri.MakeRelativeUri(toUri);
|
||||
string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
|
||||
|
||||
if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
return relativePath.TrimEnd(dirSepChar);
|
||||
}
|
||||
}
|
||||
|
||||
private static Win32.FileAttributes GetPathAttribute(string path)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1082,6 +1082,53 @@ namespace BizHawk.Client.EmuHawk
|
|||
RotateMenuItem.ShortcutKeyDisplayString = TasView.RotateHotkeyStr;
|
||||
}
|
||||
|
||||
private void SelectedRendererSubMenu_DropDownOpened(object sender, EventArgs e)
|
||||
{
|
||||
if (BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
{
|
||||
SetRenderer0.Checked = false;
|
||||
SetRenderer0.Enabled = false;
|
||||
SetRenderer0.Visible = false;
|
||||
SetRenderer1.Checked = true;
|
||||
SetRenderer1.Enabled = false;
|
||||
SetRenderer1.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
if (Global.Config.TasStudioRenderer != InputRollRenderer)
|
||||
{
|
||||
switch (Global.Config.TasStudioRenderer)
|
||||
{
|
||||
case 0:
|
||||
SetRenderer0.Text = "GDI (Pending TAStudio Restart)";
|
||||
SetRenderer0.Enabled = false;
|
||||
SetRenderer0.Checked = true;
|
||||
SetRenderer1.Text = "GDI+ (Experimental)";
|
||||
SetRenderer1.Checked = false;
|
||||
break;
|
||||
case 1:
|
||||
SetRenderer1.Text = "GDI+ (Pending TAStudio Restart)";
|
||||
SetRenderer1.Enabled = false;
|
||||
SetRenderer1.Checked = true;
|
||||
SetRenderer0.Text = "GDI";
|
||||
SetRenderer0.Checked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetRenderer0.Text = "GDI";
|
||||
SetRenderer0.Enabled = true;
|
||||
SetRenderer1.Text = "GDI+ (Experimental)";
|
||||
SetRenderer1.Enabled = true;
|
||||
SetRenderer0.Checked = Global.Config.TasStudioRenderer == 0;
|
||||
SetRenderer1.Checked = Global.Config.TasStudioRenderer == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HideLagFramesSubMenu_DropDownOpened(object sender, EventArgs e)
|
||||
{
|
||||
HideLagFrames0.Checked = TasView.LagFramesToHide == 0;
|
||||
|
@ -1130,6 +1177,17 @@ namespace BizHawk.Client.EmuHawk
|
|||
CurrentTasMovie.FlagChanges();
|
||||
}
|
||||
|
||||
private void SetRenderer_Click(object sender, EventArgs e)
|
||||
{
|
||||
var incoming = (int)(sender as ToolStripMenuItem).Tag;
|
||||
if (incoming != InputRollRenderer)
|
||||
{
|
||||
MessageBox.Show("Changing the input roll renderer requires a \nmanual restart of TAStudio", "Renderer Change Warning", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
|
||||
Global.Config.TasStudioRenderer = incoming;
|
||||
}
|
||||
|
||||
private void HideLagFramesX_Click(object sender, EventArgs e)
|
||||
{
|
||||
TasView.LagFramesToHide = (int)(sender as ToolStripMenuItem).Tag;
|
||||
|
|
|
@ -25,6 +25,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
public TasMovie CurrentTasMovie => Global.MovieSession.Movie as TasMovie;
|
||||
private MainForm Mainform => GlobalWin.MainForm;
|
||||
|
||||
/// <summary>
|
||||
/// Static settings so that InputRoll.cs can determine its renderer ahead of instantiation
|
||||
/// 0: GDI
|
||||
/// 1: GDI+
|
||||
/// </summary>
|
||||
public static int InputRollRenderer = BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix ? 1 : 0;
|
||||
|
||||
public bool IsInMenuLoop { get; private set; }
|
||||
public string StatesPath => PathManager.MakeAbsolutePath(Global.Config.PathEntries["Global", "TAStudio states"].Path, null);
|
||||
|
||||
|
@ -113,6 +120,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
public TAStudio()
|
||||
{
|
||||
Settings = new TAStudioSettings();
|
||||
|
||||
// input roll renderer must be set before InputRoll initialisation
|
||||
InputRollRenderer = BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix ? 1 : Global.Config.TasStudioRenderer;
|
||||
|
||||
InitializeComponent();
|
||||
InitializeSeekWorker();
|
||||
|
||||
|
|
|
@ -123,19 +123,19 @@
|
|||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="RecentSubMenu.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAlwSFlzAAALAQAA
|
||||
CwEBNnBPWwAAAk1JREFUOE+VkktvUlEUhVsfsdHoREcOHJr4C/RPONHoXzBxoOlAJybOlAZtkdSa2piY
|
||||
NsjjFiiX8qY8ChYL+ECB2lKB8rq0k5bEmiiRbtc+6b2B6MTBR/ZZa+919j3tEBH9RbudHD6E63/2qAwc
|
||||
treT58BVRVnWl8vBbLEg7wNC/QPaMrwb4GT/jFa024mzQLe56c9GwjM7klXXlcw6ksyPSbLoKByc/lUq
|
||||
+TbQMwrODARAGAF3SxtexSMbf8vOCVp9ZyK+/euaW9TO+SfksOlprSjvoteAjU5rAYqSuFyvR1PR8Ewv
|
||||
GJii8rcAoYFSb+d4gDAgNI/8jGTHOFUroT3410QAHuk4Am4Vi/KOzz2JGxfFcLMZI3wK5T7ZqaXEhcYb
|
||||
WU2PKJM2H7Ra8XE14AQO91dTpk4k9JLq9YgYHghoxcWZPa/bSCH/C2o0orPaBo1GbDQee9VJxF+zoYFP
|
||||
wtpGWgpN0/uMRWgcyiG1WsSkBhxFwG0E7AV8z2lrKyxuYvgBs2kLr4z1XcLj4SA2gD+nBhxB8p1sxtKZ
|
||||
t4xR/otTDNdqS1oQw7ezx2/AfxVok1oAmh+WSt7v/MKLLgOtr3tEQD+sseeyPyX0dqHdVAOGq9XQPazX
|
||||
/JyzH9itY+SQ9LSSnKV8fkHANWvsoYc/JYaZERHAPzicBw9AoZBf+BnwTZEN/4G2N4egZg1eDz05cIHn
|
||||
tACmUgmeAtdhRsvlwH6x6Dr4+EESoO5B68JLo+eSOjMQwKDpGLgCJtDoBysgBXzQDOBifz8zcPh/aOgP
|
||||
7nYTiVA2JaoAAAAASUVORK5CYII=
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6
|
||||
JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAsBAAALAQE2cE9bAAACTUlE
|
||||
QVQ4T5WSS29SURSFWx+x0ehERw4cmvgL9E840ehfMHGg6UAnJs6UBm2R1JramJg2yOMWKJfypjwKFgv4
|
||||
QIHaUoHyurSTlsSaKJFu1z7pvYHoxMFH9llr73X2Pe0QEf1Fu50cPoTrf/aoDBy2t5PnwFVFWdaXy8Fs
|
||||
sSDvA0L9A9oyvBvgZP+MVrTbibNAt7npz0bCMzuSVdeVzDqSzI9JsugoHJz+VSr5NtAzCs4MBEAYAXdL
|
||||
G17FIxt/y84JWn1nIr7965pb1M75J+Sw6WmtKO+i14CNTmsBipK4XK9HU9HwTC8YmKLytwChgVJv53iA
|
||||
MCA0j/yMZMc4VSuhPfjXRAAe6TgCbhWL8o7PPYkbF8VwsxkjfArlPtmppcSFxhtZTY8okzYftFrxcTXg
|
||||
BA73V1OmTiT0kur1iBgeCGjFxZk9r9tIIf8LajSis9oGjUZsNB571UnEX7OhgU/C2kZaCk3T+4xFaBzK
|
||||
IbVaxKQGHEXAbQTsBXzPaWsrLG5i+AGzaQuvjPVdwuPhIDaAP6cGHEHynWzG0pm3jFH+i1MM12pLWhDD
|
||||
t7PHb8B/FWiTWgCaH5ZK3u/8wosuA62ve0RAP6yx57I/JfR2od1UA4ar1dA9rNf8nLMf2K1j5JD0tJKc
|
||||
pXx+QcA1a+yhhz8lhpkREcA/OJwHD0ChkF/4GfBNkQ3/gbY3h6BmDV4PPTlwgee0AKZSCZ4C12FGy+XA
|
||||
frHoOvj4QRKg7kHrwkuj55I6MxDAoOkYuAIm0OgHKyAFfNAM4GJ/PzNw+H9o6A/udhOJUDYlqgAAAABJ
|
||||
RU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="TasStatusStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
|
|
|
@ -67,6 +67,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (!ServiceInjector.IsAvailable(Emulator.ServiceProvider, t))
|
||||
continue;
|
||||
|
||||
// Skip this tool on linux. It isnt finished and it causes exceptions
|
||||
if (t == typeof(HexView) && BizHawk.Common.PlatformLinkedLibSingleton.RunningOnUnix)
|
||||
continue;
|
||||
|
||||
var instance = Activator.CreateInstance(t);
|
||||
|
||||
var tsb = new ToolStripButton
|
||||
|
|
|
@ -732,7 +732,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
return false;
|
||||
}
|
||||
|
||||
if (t == typeof(LuaConsole) && PlatformLinkedLibSingleton.RunningOnUnix) return false;
|
||||
//if (t == typeof(LuaConsole) && PlatformLinkedLibSingleton.RunningOnUnix) return false;
|
||||
|
||||
var tool = Assembly
|
||||
.GetExecutingAssembly()
|
||||
|
|
|
@ -31,13 +31,13 @@
|
|||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TraceLogger));
|
||||
this.TracerBox = new System.Windows.Forms.GroupBox();
|
||||
this.TraceView = new BizHawk.Client.EmuHawk.VirtualListView();
|
||||
this.Disasm = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.Registers = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.TraceView = new BizHawk.Client.EmuHawk.PlatformAgnosticVirtualListView();
|
||||
this.TraceContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
|
||||
this.CopyContextMenu = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.SelectAllContextMenu = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.ClearContextMenu = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.Disasm = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.Registers = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
|
||||
this.menuStrip1 = new MenuStripEx();
|
||||
this.FileSubMenu = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.SaveLogMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
|
@ -49,6 +49,8 @@
|
|||
this.ClearMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.OptionsSubMenu = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.MaxLinesMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.SegmentSizeMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.AutoScrollMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.groupBox2 = new System.Windows.Forms.GroupBox();
|
||||
this.OpenLogFile = new System.Windows.Forms.Button();
|
||||
this.BrowseBox = new System.Windows.Forms.Button();
|
||||
|
@ -56,7 +58,6 @@
|
|||
this.ToFileRadio = new System.Windows.Forms.RadioButton();
|
||||
this.ToWindowRadio = new System.Windows.Forms.RadioButton();
|
||||
this.LoggingEnabled = new System.Windows.Forms.CheckBox();
|
||||
this.SegmentSizeMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.TracerBox.SuspendLayout();
|
||||
this.TraceContextMenu.SuspendLayout();
|
||||
this.menuStrip1.SuspendLayout();
|
||||
|
@ -78,39 +79,44 @@
|
|||
//
|
||||
// TraceView
|
||||
//
|
||||
this.TraceView.AllowColumnReorder = false;
|
||||
this.TraceView.AllowColumnResize = false;
|
||||
this.TraceView.AllowRightClickSelecton = false;
|
||||
this.TraceView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.TraceView.BlazingFast = false;
|
||||
this.TraceView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.Disasm,
|
||||
this.Registers});
|
||||
this.TraceView.BorderColor = System.Drawing.Color.DarkGray;
|
||||
this.TraceView.BorderSize = 1;
|
||||
this.TraceView.CellBackgroundColor = System.Drawing.Color.White;
|
||||
this.TraceView.CellBackgroundHighlightColor = System.Drawing.Color.Blue;
|
||||
this.TraceView.CellFont = new System.Drawing.Font("Arial", 8F);
|
||||
this.TraceView.CellFontColor = System.Drawing.Color.Black;
|
||||
this.TraceView.CellHeightPadding = 0;
|
||||
this.TraceView.ColumnHeaderBackgroundColor = System.Drawing.Color.LightGray;
|
||||
this.TraceView.ColumnHeaderBackgroundHighlightColor = System.Drawing.SystemColors.HighlightText;
|
||||
this.TraceView.ColumnHeaderFont = new System.Drawing.Font("Arial", 8F, System.Drawing.FontStyle.Bold);
|
||||
this.TraceView.ColumnHeaderFontColor = System.Drawing.Color.Black;
|
||||
this.TraceView.ColumnHeaderOutlineColor = System.Drawing.Color.Black;
|
||||
this.TraceView.ContextMenuStrip = this.TraceContextMenu;
|
||||
this.TraceView.Font = new System.Drawing.Font("Courier New", 8F);
|
||||
this.TraceView.FullRowSelect = true;
|
||||
this.TraceView.GridLines = true;
|
||||
this.TraceView.GridLineColor = System.Drawing.SystemColors.ControlLight;
|
||||
this.TraceView.HideSelection = false;
|
||||
this.TraceView.ItemCount = 0;
|
||||
this.TraceView.LetKeysModifySelection = false;
|
||||
this.TraceView.Location = new System.Drawing.Point(8, 18);
|
||||
this.TraceView.MultiSelect = false;
|
||||
this.TraceView.Name = "TraceView";
|
||||
this.TraceView.SelectAllInProgress = false;
|
||||
this.TraceView.selectedItem = -1;
|
||||
this.TraceView.ScrollSpeed = 1;
|
||||
this.TraceView.SeekingCutoffInterval = 0;
|
||||
this.TraceView.Size = new System.Drawing.Size(603, 414);
|
||||
this.TraceView.SuspendHotkeys = false;
|
||||
this.TraceView.TabIndex = 4;
|
||||
this.TraceView.TabStop = false;
|
||||
this.TraceView.UseCompatibleStateImageBehavior = false;
|
||||
this.TraceView.UseCustomBackground = true;
|
||||
this.TraceView.View = System.Windows.Forms.View.Details;
|
||||
//
|
||||
// Disasm
|
||||
//
|
||||
this.Disasm.Text = "Disasm";
|
||||
this.Disasm.Width = 239;
|
||||
//
|
||||
// Registers
|
||||
//
|
||||
this.Registers.Text = "Registers";
|
||||
this.Registers.Width = 357;
|
||||
this.TraceView.VirtualListSize = 0;
|
||||
this.TraceView.VirtualMode = false;
|
||||
//
|
||||
// TraceContextMenu
|
||||
//
|
||||
|
@ -144,6 +150,16 @@
|
|||
this.ClearContextMenu.Text = "Clear";
|
||||
this.ClearContextMenu.Click += new System.EventHandler(this.ClearMenuItem_Click);
|
||||
//
|
||||
// Disasm
|
||||
//
|
||||
this.Disasm.Text = "Disasm";
|
||||
this.Disasm.Width = 239;
|
||||
//
|
||||
// Registers
|
||||
//
|
||||
this.Registers.Text = "Registers";
|
||||
this.Registers.Width = 357;
|
||||
//
|
||||
// menuStrip1
|
||||
//
|
||||
this.menuStrip1.ClickThrough = true;
|
||||
|
@ -164,27 +180,27 @@
|
|||
this.toolStripSeparator1,
|
||||
this.ExitMenuItem});
|
||||
this.FileSubMenu.Name = "FileSubMenu";
|
||||
this.FileSubMenu.Size = new System.Drawing.Size(35, 20);
|
||||
this.FileSubMenu.Size = new System.Drawing.Size(37, 20);
|
||||
this.FileSubMenu.Text = "&File";
|
||||
//
|
||||
// SaveLogMenuItem
|
||||
//
|
||||
this.SaveLogMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.SaveAs;
|
||||
this.SaveLogMenuItem.Name = "SaveLogMenuItem";
|
||||
this.SaveLogMenuItem.Size = new System.Drawing.Size(143, 22);
|
||||
this.SaveLogMenuItem.Size = new System.Drawing.Size(134, 22);
|
||||
this.SaveLogMenuItem.Text = "&Save Log";
|
||||
this.SaveLogMenuItem.Click += new System.EventHandler(this.SaveLogMenuItem_Click);
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
this.toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(140, 6);
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(131, 6);
|
||||
//
|
||||
// ExitMenuItem
|
||||
//
|
||||
this.ExitMenuItem.Name = "ExitMenuItem";
|
||||
this.ExitMenuItem.ShortcutKeyDisplayString = "Alt+F4";
|
||||
this.ExitMenuItem.Size = new System.Drawing.Size(143, 22);
|
||||
this.ExitMenuItem.Size = new System.Drawing.Size(134, 22);
|
||||
this.ExitMenuItem.Text = "E&xit";
|
||||
this.ExitMenuItem.Click += new System.EventHandler(this.ExitMenuItem_Click);
|
||||
//
|
||||
|
@ -195,7 +211,7 @@
|
|||
this.SelectAllMenuItem,
|
||||
this.ClearMenuItem});
|
||||
this.EditSubMenu.Name = "EditSubMenu";
|
||||
this.EditSubMenu.Size = new System.Drawing.Size(37, 20);
|
||||
this.EditSubMenu.Size = new System.Drawing.Size(39, 20);
|
||||
this.EditSubMenu.Text = "Edit";
|
||||
//
|
||||
// CopyMenuItem
|
||||
|
@ -203,7 +219,7 @@
|
|||
this.CopyMenuItem.Name = "CopyMenuItem";
|
||||
this.CopyMenuItem.ShortcutKeyDisplayString = "";
|
||||
this.CopyMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.C)));
|
||||
this.CopyMenuItem.Size = new System.Drawing.Size(167, 22);
|
||||
this.CopyMenuItem.Size = new System.Drawing.Size(164, 22);
|
||||
this.CopyMenuItem.Text = "&Copy";
|
||||
this.CopyMenuItem.Click += new System.EventHandler(this.CopyMenuItem_Click);
|
||||
//
|
||||
|
@ -212,14 +228,14 @@
|
|||
this.SelectAllMenuItem.Name = "SelectAllMenuItem";
|
||||
this.SelectAllMenuItem.ShortcutKeyDisplayString = "";
|
||||
this.SelectAllMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.A)));
|
||||
this.SelectAllMenuItem.Size = new System.Drawing.Size(167, 22);
|
||||
this.SelectAllMenuItem.Size = new System.Drawing.Size(164, 22);
|
||||
this.SelectAllMenuItem.Text = "Select &All";
|
||||
this.SelectAllMenuItem.Click += new System.EventHandler(this.SelectAllMenuItem_Click);
|
||||
//
|
||||
// ClearMenuItem
|
||||
//
|
||||
this.ClearMenuItem.Name = "ClearMenuItem";
|
||||
this.ClearMenuItem.Size = new System.Drawing.Size(167, 22);
|
||||
this.ClearMenuItem.Size = new System.Drawing.Size(164, 22);
|
||||
this.ClearMenuItem.Text = "Clear";
|
||||
this.ClearMenuItem.Click += new System.EventHandler(this.ClearMenuItem_Click);
|
||||
//
|
||||
|
@ -227,9 +243,10 @@
|
|||
//
|
||||
this.OptionsSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.MaxLinesMenuItem,
|
||||
this.SegmentSizeMenuItem});
|
||||
this.SegmentSizeMenuItem,
|
||||
this.AutoScrollMenuItem});
|
||||
this.OptionsSubMenu.Name = "OptionsSubMenu";
|
||||
this.OptionsSubMenu.Size = new System.Drawing.Size(58, 20);
|
||||
this.OptionsSubMenu.Size = new System.Drawing.Size(61, 20);
|
||||
this.OptionsSubMenu.Text = "&Settings";
|
||||
//
|
||||
// MaxLinesMenuItem
|
||||
|
@ -239,6 +256,21 @@
|
|||
this.MaxLinesMenuItem.Text = "&Set Max Lines...";
|
||||
this.MaxLinesMenuItem.Click += new System.EventHandler(this.MaxLinesMenuItem_Click);
|
||||
//
|
||||
// SegmentSizeMenuItem
|
||||
//
|
||||
this.SegmentSizeMenuItem.Name = "SegmentSizeMenuItem";
|
||||
this.SegmentSizeMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.SegmentSizeMenuItem.Text = "Set Segment Size...";
|
||||
this.SegmentSizeMenuItem.Click += new System.EventHandler(this.SegmentSizeMenuItem_Click);
|
||||
//
|
||||
// AutoScrollMenuItem
|
||||
//
|
||||
this.AutoScrollMenuItem.CheckOnClick = true;
|
||||
this.AutoScrollMenuItem.Name = "AutoScrollMenuItem";
|
||||
this.AutoScrollMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.AutoScrollMenuItem.Text = "Auto Scroll";
|
||||
this.AutoScrollMenuItem.Click += new System.EventHandler(this.AutoScrollMenuItem_Click);
|
||||
//
|
||||
// groupBox2
|
||||
//
|
||||
this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
|
@ -326,13 +358,6 @@
|
|||
this.LoggingEnabled.UseVisualStyleBackColor = true;
|
||||
this.LoggingEnabled.CheckedChanged += new System.EventHandler(this.LoggingEnabled_CheckedChanged);
|
||||
//
|
||||
// SegmentSizeMenuItem
|
||||
//
|
||||
this.SegmentSizeMenuItem.Name = "SegmentSizeMenuItem";
|
||||
this.SegmentSizeMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.SegmentSizeMenuItem.Text = "Set Segment Size...";
|
||||
this.SegmentSizeMenuItem.Click += new System.EventHandler(this.SegmentSizeMenuItem_Click);
|
||||
//
|
||||
// TraceLogger
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
|
@ -371,7 +396,7 @@
|
|||
private System.Windows.Forms.GroupBox groupBox2;
|
||||
private System.Windows.Forms.CheckBox LoggingEnabled;
|
||||
private System.Windows.Forms.ToolStripMenuItem OptionsSubMenu;
|
||||
private VirtualListView TraceView;
|
||||
private BizHawk.Client.EmuHawk.PlatformAgnosticVirtualListView TraceView;
|
||||
public System.Windows.Forms.ColumnHeader Disasm;
|
||||
private System.Windows.Forms.ToolStripMenuItem MaxLinesMenuItem;
|
||||
private System.Windows.Forms.RadioButton ToFileRadio;
|
||||
|
@ -389,5 +414,6 @@
|
|||
private System.Windows.Forms.ToolStripMenuItem ClearContextMenu;
|
||||
private System.Windows.Forms.Button OpenLogFile;
|
||||
private System.Windows.Forms.ToolStripMenuItem SegmentSizeMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem AutoScrollMenuItem;
|
||||
}
|
||||
}
|
|
@ -36,6 +36,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
set { this.Registers.Width = value; }
|
||||
}
|
||||
|
||||
[ConfigPersist]
|
||||
public override bool AutoScroll { get; set; }
|
||||
|
||||
private FileInfo _logFile;
|
||||
private FileInfo LogFile
|
||||
{
|
||||
|
@ -72,6 +75,33 @@ namespace BizHawk.Client.EmuHawk
|
|||
MaxLines = 10000;
|
||||
FileSizeCap = 150; // make 1 frame of tracelog for n64/psx fit in
|
||||
_splitFile = FileSizeCap != 0;
|
||||
|
||||
SetupTraceViewSettings();
|
||||
}
|
||||
|
||||
private void SetupColumns()
|
||||
{
|
||||
TraceView.AllColumns.Clear();
|
||||
TraceView.AddColumn("Disasm", "Disasm", 239, PlatformAgnosticVirtualListView.ListColumn.InputType.Text);
|
||||
TraceView.AddColumn("Registers", "Registers", 357, PlatformAgnosticVirtualListView.ListColumn.InputType.Text);
|
||||
}
|
||||
|
||||
private void SetupTraceViewSettings()
|
||||
{
|
||||
TraceView.MultiSelect = true;
|
||||
TraceView.CellWidthPadding = 3;
|
||||
TraceView.CellHeightPadding = 2;
|
||||
TraceView.ScrollSpeed = 5;
|
||||
TraceView.AllowColumnResize = true;
|
||||
TraceView.AllowColumnReorder = true;
|
||||
TraceView.ColumnHeaderFont = new System.Drawing.Font("Courier New", 8F);
|
||||
TraceView.ColumnHeaderFontColor = System.Drawing.Color.Black;
|
||||
TraceView.ColumnHeaderBackgroundColor = System.Drawing.Color.White;
|
||||
TraceView.ColumnHeaderBackgroundHighlightColor = System.Drawing.Color.LightSteelBlue;
|
||||
TraceView.ColumnHeaderOutlineColor = System.Drawing.Color.White;
|
||||
TraceView.CellFont = new System.Drawing.Font("Courier New", 8F);
|
||||
TraceView.CellFontColor = System.Drawing.Color.Black;
|
||||
TraceView.CellBackgroundColor = System.Drawing.Color.White;
|
||||
}
|
||||
|
||||
public bool UpdateBefore
|
||||
|
@ -94,7 +124,9 @@ namespace BizHawk.Client.EmuHawk
|
|||
text = "";
|
||||
if (index < _instructions.Count)
|
||||
{
|
||||
switch (column)
|
||||
var test = TraceView.AllColumns;
|
||||
|
||||
switch (TraceView.GetOriginalColumnIndex(column))
|
||||
{
|
||||
case 0:
|
||||
text = _instructions[index].Disassembly.TrimEnd();
|
||||
|
@ -111,8 +143,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
ClearList();
|
||||
OpenLogFile.Enabled = false;
|
||||
LoggingEnabled.Checked = false;
|
||||
AutoScrollMenuItem.Checked = AutoScroll;
|
||||
Tracer.Sink = null;
|
||||
SetTracerBoxTitle();
|
||||
SetupColumns();
|
||||
}
|
||||
|
||||
class CallbackSink : ITraceSink
|
||||
|
@ -134,6 +168,12 @@ namespace BizHawk.Client.EmuHawk
|
|||
if (ToWindowRadio.Checked)
|
||||
{
|
||||
TraceView.VirtualListSize = _instructions.Count;
|
||||
if (GlobalWin.MainForm.EmulatorPaused)
|
||||
{
|
||||
if (AutoScroll && _instructions.Count != 0)
|
||||
TraceView.ScrollToIndex(_instructions.IndexOf(_instructions.Last()));
|
||||
TraceView.Refresh();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -148,8 +188,10 @@ namespace BizHawk.Client.EmuHawk
|
|||
//connect tracer to sink for next frame
|
||||
if (ToWindowRadio.Checked)
|
||||
{
|
||||
//update listview with most recentr results
|
||||
TraceView.BlazingFast = !GlobalWin.MainForm.EmulatorPaused;
|
||||
if (AutoScroll && _instructions.Count != 0)
|
||||
TraceView.ScrollToIndex(_instructions.IndexOf(_instructions.Last()));
|
||||
|
||||
TraceView.Refresh();
|
||||
|
||||
Tracer.Sink = new CallbackSink()
|
||||
{
|
||||
|
@ -161,8 +203,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
_instructions.Add(info);
|
||||
}
|
||||
};
|
||||
_instructions.Clear();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -327,7 +368,8 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
private void CopyMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
var indices = TraceView.SelectedIndices;
|
||||
//var indices = TraceView.SelectedIndices;
|
||||
var indices = TraceView.SelectedRows.ToList();
|
||||
|
||||
if (indices.Count > 0)
|
||||
{
|
||||
|
@ -389,6 +431,11 @@ namespace BizHawk.Client.EmuHawk
|
|||
}
|
||||
}
|
||||
|
||||
private void AutoScrollMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
AutoScroll = ((ToolStripMenuItem)sender as ToolStripMenuItem).Checked;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dialog and ListView Events
|
||||
|
|
|
@ -64,6 +64,8 @@
|
|||
<Compile Include="BizInvoke\CallingConventionAdapter.cs" />
|
||||
<Compile Include="BizInvoke\DynamicLibraryImportResolver.cs" />
|
||||
<Compile Include="BizInvoke\MemoryBlock.cs" />
|
||||
<Compile Include="BizInvoke\MemoryBlockUnix.cs" />
|
||||
<Compile Include="BizInvoke\MemoryBlockWin32.cs" />
|
||||
<Compile Include="BizInvoke\WaterboxUtils.cs" />
|
||||
<Compile Include="Buffer.cs" />
|
||||
<Compile Include="Colors.cs" />
|
||||
|
|
|
@ -140,7 +140,7 @@ namespace BizHawk.Common.BizInvoke
|
|||
public SysVHostMsGuest()
|
||||
{
|
||||
int size = 4 * 1024 * 1024;
|
||||
_memory = new MemoryBlock((ulong)size);
|
||||
_memory = MemoryBlock.PlatformConstructor((ulong) size);
|
||||
_memory.Activate();
|
||||
_refs = new WeakReference[size / BlockSize];
|
||||
}
|
||||
|
|
|
@ -1,128 +1,67 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
|
||||
namespace BizHawk.Common.BizInvoke
|
||||
{
|
||||
public sealed class MemoryBlock : IDisposable
|
||||
public abstract class MemoryBlock : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// starting address of the memory block
|
||||
/// </summary>
|
||||
public ulong Start { get; private set; }
|
||||
public ulong Start { get; protected set; }
|
||||
/// <summary>
|
||||
/// total size of the memory block
|
||||
/// </summary>
|
||||
public ulong Size { get; private set; }
|
||||
public ulong Size { get; protected set; }
|
||||
/// <summary>
|
||||
/// ending address of the memory block; equal to start + size
|
||||
/// </summary>
|
||||
public ulong End { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// handle returned by CreateFileMapping
|
||||
/// </summary>
|
||||
private IntPtr _handle;
|
||||
public ulong End { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// true if this is currently swapped in
|
||||
/// </summary>
|
||||
public bool Active { get; private set; }
|
||||
public bool Active { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores last set memory protection value for each page
|
||||
/// </summary>
|
||||
private readonly Protection[] _pageData;
|
||||
protected Protection[] _pageData;
|
||||
|
||||
/// <summary>
|
||||
/// snapshot for XOR buffer
|
||||
/// </summary>
|
||||
private byte[] _snapshot;
|
||||
protected byte[] _snapshot;
|
||||
|
||||
public byte[] XorHash { get; private set; }
|
||||
public byte[] XorHash { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// get a page index within the block
|
||||
/// </summary>
|
||||
private int GetPage(ulong addr)
|
||||
protected int GetPage(ulong addr)
|
||||
{
|
||||
if (addr < Start || addr >= End)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
return (int)((addr - Start) >> WaterboxUtils.PageShift);
|
||||
if (addr < Start || addr >= End) throw new ArgumentOutOfRangeException();
|
||||
return (int) ((addr - Start) >> WaterboxUtils.PageShift);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get a start address for a page index within the block
|
||||
/// </summary>
|
||||
private ulong GetStartAddr(int page)
|
||||
protected ulong GetStartAddr(int page)
|
||||
{
|
||||
return ((ulong)page << WaterboxUtils.PageShift) + Start;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// allocate size bytes at any address
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
public MemoryBlock(ulong size)
|
||||
: this(0, size)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// allocate size bytes starting at a particular address
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="size"></param>
|
||||
public MemoryBlock(ulong start, ulong size)
|
||||
{
|
||||
if (!WaterboxUtils.Aligned(start))
|
||||
throw new ArgumentOutOfRangeException();
|
||||
if (size == 0)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
size = WaterboxUtils.AlignUp(size);
|
||||
|
||||
_handle = Kernel32.CreateFileMapping(Kernel32.INVALID_HANDLE_VALUE, IntPtr.Zero,
|
||||
Kernel32.FileMapProtection.PageExecuteReadWrite | Kernel32.FileMapProtection.SectionCommit, (uint)(size >> 32), (uint)size, null);
|
||||
|
||||
if (_handle == IntPtr.Zero)
|
||||
throw new InvalidOperationException("CreateFileMapping() returned NULL");
|
||||
Start = start;
|
||||
End = start + size;
|
||||
Size = size;
|
||||
_pageData = new Protection[GetPage(End - 1) + 1];
|
||||
return ((ulong) page << WaterboxUtils.PageShift) + Start;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// activate the memory block, swapping it in at the specified address
|
||||
/// </summary>
|
||||
public void Activate()
|
||||
{
|
||||
if (Active)
|
||||
throw new InvalidOperationException("Already active");
|
||||
if (Kernel32.MapViewOfFileEx(_handle, Kernel32.FileMapAccessType.Read | Kernel32.FileMapAccessType.Write | Kernel32.FileMapAccessType.Execute,
|
||||
0, 0, Z.UU(Size), Z.US(Start)) != Z.US(Start))
|
||||
{
|
||||
throw new InvalidOperationException("MapViewOfFileEx() returned NULL");
|
||||
}
|
||||
ProtectAll();
|
||||
Active = true;
|
||||
}
|
||||
public abstract void Activate();
|
||||
|
||||
/// <summary>
|
||||
/// deactivate the memory block, removing it from RAM but leaving it immediately available to swap back in
|
||||
/// </summary>
|
||||
public void Deactivate()
|
||||
{
|
||||
if (!Active)
|
||||
throw new InvalidOperationException("Not active");
|
||||
if (!Kernel32.UnmapViewOfFile(Z.US(Start)))
|
||||
throw new InvalidOperationException("UnmapViewOfFile() returned NULL");
|
||||
Active = false;
|
||||
}
|
||||
public abstract void Deactivate();
|
||||
|
||||
/// <summary>
|
||||
/// Memory protection constant
|
||||
|
@ -133,16 +72,13 @@ namespace BizHawk.Common.BizInvoke
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a stream that can be used to read or write from part of the block. Does not check for or change Protect()!
|
||||
/// Get a stream that can be used to read or write from part of the block. Does not check for or change Protect()!
|
||||
/// </summary>
|
||||
public Stream GetStream(ulong start, ulong length, bool writer)
|
||||
{
|
||||
if (start < Start)
|
||||
throw new ArgumentOutOfRangeException(nameof(start));
|
||||
if (start + length > End)
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
|
||||
return new MemoryViewStream(!writer, writer, (long)start, (long)length, this);
|
||||
if (start < Start) throw new ArgumentOutOfRangeException(nameof(start));
|
||||
if (start + length > End) throw new ArgumentOutOfRangeException(nameof(length));
|
||||
return new MemoryViewStream(!writer, writer, (long) start, (long) length, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -151,119 +87,32 @@ namespace BizHawk.Common.BizInvoke
|
|||
/// </summary>
|
||||
public Stream GetXorStream(ulong start, ulong length, bool writer)
|
||||
{
|
||||
if (start < Start)
|
||||
throw new ArgumentOutOfRangeException(nameof(start));
|
||||
if (start + length > End)
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
if (_snapshot == null)
|
||||
throw new InvalidOperationException("No snapshot taken!");
|
||||
|
||||
return new MemoryViewXorStream(!writer, writer, (long)start, (long)length, this, _snapshot, (long)(start - Start));
|
||||
if (start < Start) throw new ArgumentOutOfRangeException(nameof(start));
|
||||
if (start + length > End) throw new ArgumentOutOfRangeException(nameof(length));
|
||||
if (_snapshot == null) throw new InvalidOperationException("No snapshot taken!");
|
||||
return new MemoryViewXorStream(!writer, writer, (long) start, (long) length, this, _snapshot, (long) (start - Start));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// take a snapshot of the entire memory block's contents, for use in GetXorStream
|
||||
/// </summary>
|
||||
public void SaveXorSnapshot()
|
||||
{
|
||||
if (_snapshot != null)
|
||||
throw new InvalidOperationException("Snapshot already taken");
|
||||
if (!Active)
|
||||
throw new InvalidOperationException("Not active");
|
||||
|
||||
// temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want
|
||||
// that to complicate things
|
||||
Kernel32.MemoryProtection old;
|
||||
if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old))
|
||||
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||
|
||||
_snapshot = new byte[Size];
|
||||
var ds = new MemoryStream(_snapshot, true);
|
||||
var ss = GetStream(Start, Size, false);
|
||||
ss.CopyTo(ds);
|
||||
XorHash = WaterboxUtils.Hash(_snapshot);
|
||||
|
||||
ProtectAll();
|
||||
}
|
||||
public abstract void SaveXorSnapshot();
|
||||
|
||||
/// <summary>
|
||||
/// take a hash of the current full contents of the block, including unreadable areas
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public byte[] FullHash()
|
||||
{
|
||||
if (!Active)
|
||||
throw new InvalidOperationException("Not active");
|
||||
// temporarily switch the entire block to `R`
|
||||
Kernel32.MemoryProtection old;
|
||||
if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old))
|
||||
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||
var ret = WaterboxUtils.Hash(GetStream(Start, Size, false));
|
||||
ProtectAll();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Kernel32.MemoryProtection GetKernelMemoryProtectionValue(Protection prot)
|
||||
{
|
||||
Kernel32.MemoryProtection p;
|
||||
switch (prot)
|
||||
{
|
||||
case Protection.None: p = Kernel32.MemoryProtection.NOACCESS; break;
|
||||
case Protection.R: p = Kernel32.MemoryProtection.READONLY; break;
|
||||
case Protection.RW: p = Kernel32.MemoryProtection.READWRITE; break;
|
||||
case Protection.RX: p = Kernel32.MemoryProtection.EXECUTE_READ; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(prot));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
public abstract byte[] FullHash();
|
||||
|
||||
/// <summary>
|
||||
/// restore all recorded protections
|
||||
/// </summary>
|
||||
private void ProtectAll()
|
||||
{
|
||||
int ps = 0;
|
||||
for (int i = 0; i < _pageData.Length; i++)
|
||||
{
|
||||
if (i == _pageData.Length - 1 || _pageData[i] != _pageData[i + 1])
|
||||
{
|
||||
var p = GetKernelMemoryProtectionValue(_pageData[i]);
|
||||
ulong zstart = GetStartAddr(ps);
|
||||
ulong zend = GetStartAddr(i + 1);
|
||||
Kernel32.MemoryProtection old;
|
||||
if (!Kernel32.VirtualProtect(Z.UU(zstart), Z.UU(zend - zstart), p, out old))
|
||||
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||
ps = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
protected abstract void ProtectAll();
|
||||
|
||||
/// <summary>
|
||||
/// set r/w/x protection on a portion of memory. rounded to encompassing pages
|
||||
/// set r/w/x protection on a portion of memory. rounded to encompassing pages
|
||||
/// </summary>
|
||||
public void Protect(ulong start, ulong length, Protection prot)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
int pstart = GetPage(start);
|
||||
int pend = GetPage(start + length - 1);
|
||||
|
||||
var p = GetKernelMemoryProtectionValue(prot);
|
||||
for (int i = pstart; i <= pend; i++)
|
||||
_pageData[i] = prot; // also store the value for later use
|
||||
|
||||
if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation
|
||||
{
|
||||
var computedStart = WaterboxUtils.AlignDown(start);
|
||||
var computedEnd = WaterboxUtils.AlignUp(start + length);
|
||||
var computedLength = computedEnd - computedStart;
|
||||
|
||||
Kernel32.MemoryProtection old;
|
||||
if (!Kernel32.VirtualProtect(Z.UU(computedStart),
|
||||
Z.UU(computedLength), p, out old))
|
||||
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||
}
|
||||
}
|
||||
public abstract void Protect(ulong start, ulong length, Protection prot);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -271,22 +120,32 @@ namespace BizHawk.Common.BizInvoke
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_handle != IntPtr.Zero)
|
||||
{
|
||||
if (Active)
|
||||
Deactivate();
|
||||
Kernel32.CloseHandle(_handle);
|
||||
_handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
protected abstract void Dispose(bool disposing);
|
||||
|
||||
~MemoryBlock()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// allocate size bytes at any address
|
||||
/// </summary>
|
||||
public static MemoryBlock PlatformConstructor(ulong size)
|
||||
{
|
||||
return PlatformConstructor(0, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// allocate size bytes starting at a particular address
|
||||
/// </summary>
|
||||
public static MemoryBlock PlatformConstructor(ulong start, ulong size)
|
||||
{
|
||||
return PlatformLinkedLibSingleton.RunningOnUnix
|
||||
// ? (MemoryBlock) new MemoryBlockUnix(start, size)
|
||||
? throw new InvalidOperationException("ctor of nonfunctional MemoryBlockUnix class")
|
||||
: (MemoryBlock) new MemoryBlockWin32(start, size);
|
||||
}
|
||||
|
||||
private class MemoryViewStream : Stream
|
||||
{
|
||||
public MemoryViewStream(bool readable, bool writable, long ptr, long length, MemoryBlock owner)
|
||||
|
@ -301,8 +160,7 @@ namespace BizHawk.Common.BizInvoke
|
|||
|
||||
private void EnsureNotDisposed()
|
||||
{
|
||||
if (_owner.Start == 0)
|
||||
throw new ObjectDisposedException("MemoryBlock");
|
||||
if (_owner.Start == 0) throw new ObjectDisposedException("MemoryBlock");
|
||||
}
|
||||
|
||||
private MemoryBlock _owner;
|
||||
|
@ -325,20 +183,17 @@ namespace BizHawk.Common.BizInvoke
|
|||
get { return _pos; }
|
||||
set
|
||||
{
|
||||
if (value < 0 || value > _length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
if (value < 0 || value > _length) throw new ArgumentOutOfRangeException();
|
||||
_pos = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (!_readable)
|
||||
throw new InvalidOperationException();
|
||||
if (count < 0 || count + offset > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
if (!_readable) throw new InvalidOperationException();
|
||||
if (count < 0 || count + offset > buffer.Length) throw new ArgumentOutOfRangeException();
|
||||
EnsureNotDisposed();
|
||||
count = (int)Math.Min(count, _length - _pos);
|
||||
count = (int) Math.Min(count, _length - _pos);
|
||||
Marshal.Copy(Z.SS(_ptr + _pos), buffer, offset, count);
|
||||
_pos += count;
|
||||
return count;
|
||||
|
@ -371,11 +226,8 @@ namespace BizHawk.Common.BizInvoke
|
|||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (!_writable)
|
||||
throw new InvalidOperationException();
|
||||
if (count < 0 || count + offset > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
if (count > _length - _pos)
|
||||
if (!_writable) throw new InvalidOperationException();
|
||||
if (count < 0 || count + offset > buffer.Length || count > _length - _pos)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
EnsureNotDisposed();
|
||||
Marshal.Copy(buffer, offset, Z.SS(_ptr + _pos), count);
|
||||
|
@ -390,13 +242,14 @@ namespace BizHawk.Common.BizInvoke
|
|||
: base(readable, writable, ptr, length, owner)
|
||||
{
|
||||
_initial = initial;
|
||||
_offset = (int)offset;
|
||||
_offset = (int) offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the initial data to XOR against for both reading and writing
|
||||
/// </summary>
|
||||
private readonly byte[] _initial;
|
||||
|
||||
/// <summary>
|
||||
/// offset into the XOR data that this stream is representing
|
||||
/// </summary>
|
||||
|
@ -404,7 +257,7 @@ namespace BizHawk.Common.BizInvoke
|
|||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int pos = (int)Position;
|
||||
int pos = (int) Position;
|
||||
count = base.Read(buffer, offset, count);
|
||||
XorTransform(_initial, _offset + pos, buffer, offset, count);
|
||||
return count;
|
||||
|
@ -412,10 +265,8 @@ namespace BizHawk.Common.BizInvoke
|
|||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int pos = (int)Position;
|
||||
if (count < 0 || count + offset > buffer.Length)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
if (count > Length - pos)
|
||||
int pos = (int) Position;
|
||||
if (count < 0 || count + offset > buffer.Length || count > Length - pos)
|
||||
throw new ArgumentOutOfRangeException();
|
||||
// is mutating the buffer passed to Stream.Write kosher?
|
||||
XorTransform(_initial, _offset + pos, buffer, offset, count);
|
||||
|
@ -426,7 +277,7 @@ namespace BizHawk.Common.BizInvoke
|
|||
{
|
||||
// we don't do any bounds check because MemoryViewStream.Read and MemoryViewXorStream.Write already did it
|
||||
|
||||
// TODO: C compilers can make this pretty snappy, but can the C# jitter? Or do we need intrinsics
|
||||
// TODO: C compilers can make this pretty snappy, but can the C# jitter? Or do we need intrinsics
|
||||
fixed (byte* _s = source, _d = dest)
|
||||
{
|
||||
byte* s = _s + sourceOffset;
|
||||
|
@ -439,74 +290,5 @@ namespace BizHawk.Common.BizInvoke
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Kernel32
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool VirtualProtect(UIntPtr lpAddress, UIntPtr dwSize,
|
||||
MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);
|
||||
|
||||
[Flags]
|
||||
public enum MemoryProtection : uint
|
||||
{
|
||||
EXECUTE = 0x10,
|
||||
EXECUTE_READ = 0x20,
|
||||
EXECUTE_READWRITE = 0x40,
|
||||
EXECUTE_WRITECOPY = 0x80,
|
||||
NOACCESS = 0x01,
|
||||
READONLY = 0x02,
|
||||
READWRITE = 0x04,
|
||||
WRITECOPY = 0x08,
|
||||
GUARD_Modifierflag = 0x100,
|
||||
NOCACHE_Modifierflag = 0x200,
|
||||
WRITECOMBINE_Modifierflag = 0x400
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr CreateFileMapping(
|
||||
IntPtr hFile,
|
||||
IntPtr lpFileMappingAttributes,
|
||||
FileMapProtection flProtect,
|
||||
uint dwMaximumSizeHigh,
|
||||
uint dwMaximumSizeLow,
|
||||
string lpName);
|
||||
|
||||
[Flags]
|
||||
public enum FileMapProtection : uint
|
||||
{
|
||||
PageReadonly = 0x02,
|
||||
PageReadWrite = 0x04,
|
||||
PageWriteCopy = 0x08,
|
||||
PageExecuteRead = 0x20,
|
||||
PageExecuteReadWrite = 0x40,
|
||||
SectionCommit = 0x8000000,
|
||||
SectionImage = 0x1000000,
|
||||
SectionNoCache = 0x10000000,
|
||||
SectionReserve = 0x4000000,
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr MapViewOfFileEx(IntPtr hFileMappingObject,
|
||||
FileMapAccessType dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow,
|
||||
UIntPtr dwNumberOfBytesToMap, IntPtr lpBaseAddress);
|
||||
|
||||
[Flags]
|
||||
public enum FileMapAccessType : uint
|
||||
{
|
||||
Copy = 0x01,
|
||||
Write = 0x02,
|
||||
Read = 0x04,
|
||||
AllAccess = 0x08,
|
||||
Execute = 0x20,
|
||||
}
|
||||
|
||||
public static readonly IntPtr INVALID_HANDLE_VALUE = Z.US(0xffffffffffffffff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BizHawk.Common.BizInvoke
|
||||
{
|
||||
public sealed class MemoryBlockUnix : MemoryBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// handle returned by memfd_create
|
||||
/// </summary>
|
||||
private int _fd;
|
||||
|
||||
/// <summary>
|
||||
/// allocate size bytes starting at a particular address
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="size"></param>
|
||||
public MemoryBlockUnix(ulong start, ulong size)
|
||||
{
|
||||
if (!WaterboxUtils.Aligned(start)) throw new ArgumentOutOfRangeException();
|
||||
if (size == 0) throw new ArgumentOutOfRangeException();
|
||||
size = WaterboxUtils.AlignUp(size);
|
||||
_fd = memfd_create("MemoryBlockUnix", 0);
|
||||
if (_fd == -1) throw new InvalidOperationException("memfd_create() returned -1");
|
||||
Start = start;
|
||||
End = start + size;
|
||||
Size = size;
|
||||
_pageData = new Protection[GetPage(End - 1) + 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// activate the memory block, swapping it in at the specified address
|
||||
/// </summary>
|
||||
public override void Activate()
|
||||
{
|
||||
if (Active) throw new InvalidOperationException("Already active");
|
||||
if (mmap(Z.US(Start), Z.UU(Size), MemoryProtection.Read | MemoryProtection.Write | MemoryProtection.Execute, 16, _fd, IntPtr.Zero) != Z.US(Start))
|
||||
throw new InvalidOperationException("mmap() returned NULL");
|
||||
ProtectAll();
|
||||
Active = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// deactivate the memory block, removing it from RAM but leaving it immediately available to swap back in
|
||||
/// </summary>
|
||||
public override void Deactivate()
|
||||
{
|
||||
if (!Active)
|
||||
throw new InvalidOperationException("Not active");
|
||||
if (munmap(Z.US(Start), Z.UU(Size)) != 0)
|
||||
throw new InvalidOperationException("munmap() returned -1");
|
||||
Active = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// take a snapshot of the entire memory block's contents, for use in GetXorStream
|
||||
/// </summary>
|
||||
public override void SaveXorSnapshot()
|
||||
{
|
||||
if (_snapshot != null)
|
||||
throw new InvalidOperationException("Snapshot already taken");
|
||||
if (!Active)
|
||||
throw new InvalidOperationException("Not active");
|
||||
|
||||
// temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want
|
||||
// that to complicate things
|
||||
if (mprotect(Z.US(Start), Z.UU(Size), MemoryProtection.Read) != 0)
|
||||
throw new InvalidOperationException("mprotect() returned -1!");
|
||||
|
||||
_snapshot = new byte[Size];
|
||||
var ds = new MemoryStream(_snapshot, true);
|
||||
var ss = GetStream(Start, Size, false);
|
||||
ss.CopyTo(ds);
|
||||
XorHash = WaterboxUtils.Hash(_snapshot);
|
||||
|
||||
ProtectAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// take a hash of the current full contents of the block, including unreadable areas
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override byte[] FullHash()
|
||||
{
|
||||
if (!Active)
|
||||
throw new InvalidOperationException("Not active");
|
||||
// temporarily switch the entire block to `R`
|
||||
if (mprotect(Z.US(Start), Z.UU(Size), MemoryProtection.Read) != 0)
|
||||
throw new InvalidOperationException("mprotect() returned -1!");
|
||||
var ret = WaterboxUtils.Hash(GetStream(Start, Size, false));
|
||||
ProtectAll();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static MemoryProtection GetMemoryProtectionValue(Protection prot)
|
||||
{
|
||||
switch (prot)
|
||||
{
|
||||
case Protection.None: return 0;
|
||||
case Protection.R: return MemoryProtection.Read;
|
||||
case Protection.RW: return MemoryProtection.Read | MemoryProtection.Write;
|
||||
case Protection.RX: return MemoryProtection.Read | MemoryProtection.Execute;
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(nameof(prot));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// restore all recorded protections
|
||||
/// </summary>
|
||||
protected override void ProtectAll()
|
||||
{
|
||||
int ps = 0;
|
||||
for (int i = 0; i < _pageData.Length; i++)
|
||||
{
|
||||
if (i == _pageData.Length - 1 || _pageData[i] != _pageData[i + 1])
|
||||
{
|
||||
var p = GetMemoryProtectionValue(_pageData[i]);
|
||||
ulong zstart = GetStartAddr(ps);
|
||||
ulong zend = GetStartAddr(i + 1);
|
||||
if (mprotect(Z.US(zstart), Z.UU(zend - zstart), p) != 0)
|
||||
throw new InvalidOperationException("mprotect() returned -1!");
|
||||
ps = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// set r/w/x protection on a portion of memory. rounded to encompassing pages
|
||||
/// </summary>
|
||||
public override void Protect(ulong start, ulong length, Protection prot)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
int pstart = GetPage(start);
|
||||
int pend = GetPage(start + length - 1);
|
||||
|
||||
var p = GetMemoryProtectionValue(prot);
|
||||
for (int i = pstart; i <= pend; i++)
|
||||
_pageData[i] = prot; // also store the value for later use
|
||||
|
||||
if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation
|
||||
{
|
||||
var computedStart = WaterboxUtils.AlignDown(start);
|
||||
var computedEnd = WaterboxUtils.AlignUp(start + length);
|
||||
var computedLength = computedEnd - computedStart;
|
||||
|
||||
if (mprotect(Z.US(computedStart), Z.UU(computedLength), p) != 0)
|
||||
throw new InvalidOperationException("mprotect() returned -1!");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_fd != 0)
|
||||
{
|
||||
if (Active) Deactivate();
|
||||
close(_fd);
|
||||
_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
~MemoryBlockUnix()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
[DllImport("libc.so.6")]
|
||||
private static extern int close(int fd);
|
||||
|
||||
[DllImport("libc.so.6")]
|
||||
private static extern int memfd_create(string name, uint flags);
|
||||
|
||||
[DllImport("libc.so.6")]
|
||||
private static extern IntPtr mmap(IntPtr addr, UIntPtr length, int prot, int flags, int fd, IntPtr offset);
|
||||
private static IntPtr mmap(IntPtr addr, UIntPtr length, MemoryProtection prot, int flags, int fd, IntPtr offset)
|
||||
{
|
||||
return mmap(addr, length, (int) prot, flags, fd, offset);
|
||||
}
|
||||
|
||||
[DllImport("libc.so.6")]
|
||||
private static extern int mprotect(IntPtr addr, UIntPtr len, int prot);
|
||||
private static int mprotect(IntPtr addr, UIntPtr len, MemoryProtection prot)
|
||||
{
|
||||
return mprotect(addr, len, (int) prot);
|
||||
}
|
||||
|
||||
[DllImport("libc.so.6")]
|
||||
private static extern int munmap(IntPtr addr, UIntPtr length);
|
||||
|
||||
[Flags]
|
||||
private enum MemoryProtection : int
|
||||
{
|
||||
Read = 0x1,
|
||||
Write = 0x2,
|
||||
Execute = 0x4
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
|
||||
namespace BizHawk.Common.BizInvoke
|
||||
{
|
||||
public sealed class MemoryBlockWin32 : MemoryBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// handle returned by CreateFileMapping
|
||||
/// </summary>
|
||||
private IntPtr _handle;
|
||||
|
||||
/// <summary>
|
||||
/// allocate size bytes starting at a particular address
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="size"></param>
|
||||
public MemoryBlockWin32(ulong start, ulong size)
|
||||
{
|
||||
if (!WaterboxUtils.Aligned(start)) throw new ArgumentOutOfRangeException();
|
||||
if (size == 0) throw new ArgumentOutOfRangeException();
|
||||
size = WaterboxUtils.AlignUp(size);
|
||||
|
||||
_handle = Kernel32.CreateFileMapping(Kernel32.INVALID_HANDLE_VALUE, IntPtr.Zero, Kernel32.FileMapProtection.PageExecuteReadWrite | Kernel32.FileMapProtection.SectionCommit, (uint) (size >> 32), (uint) size, null);
|
||||
if (_handle == IntPtr.Zero) throw new InvalidOperationException("CreateFileMapping() returned NULL");
|
||||
Start = start;
|
||||
End = start + size;
|
||||
Size = size;
|
||||
_pageData = new Protection[GetPage(End - 1) + 1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// activate the memory block, swapping it in at the specified address
|
||||
/// </summary>
|
||||
public override void Activate()
|
||||
{
|
||||
if (Active) throw new InvalidOperationException("Already active");
|
||||
if (Kernel32.MapViewOfFileEx(_handle, Kernel32.FileMapAccessType.Read | Kernel32.FileMapAccessType.Write | Kernel32.FileMapAccessType.Execute, 0, 0, Z.UU(Size), Z.US(Start)) != Z.US(Start))
|
||||
throw new InvalidOperationException("MapViewOfFileEx() returned NULL");
|
||||
ProtectAll();
|
||||
Active = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// deactivate the memory block, removing it from RAM but leaving it immediately available to swap back in
|
||||
/// </summary>
|
||||
public override void Deactivate()
|
||||
{
|
||||
if (!Active) throw new InvalidOperationException("Not active");
|
||||
if (!Kernel32.UnmapViewOfFile(Z.US(Start))) throw new InvalidOperationException("UnmapViewOfFile() returned NULL");
|
||||
Active = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// take a snapshot of the entire memory block's contents, for use in GetXorStream
|
||||
/// </summary>
|
||||
public override void SaveXorSnapshot()
|
||||
{
|
||||
if (_snapshot != null) throw new InvalidOperationException("Snapshot already taken");
|
||||
if (!Active) throw new InvalidOperationException("Not active");
|
||||
|
||||
// temporarily switch the entire block to `R`: in case some areas are unreadable, we don't want
|
||||
// that to complicate things
|
||||
Kernel32.MemoryProtection old;
|
||||
if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old))
|
||||
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||
|
||||
_snapshot = new byte[Size];
|
||||
var ds = new MemoryStream(_snapshot, true);
|
||||
var ss = GetStream(Start, Size, false);
|
||||
ss.CopyTo(ds);
|
||||
XorHash = WaterboxUtils.Hash(_snapshot);
|
||||
|
||||
ProtectAll();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// take a hash of the current full contents of the block, including unreadable areas
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override byte[] FullHash()
|
||||
{
|
||||
if (!Active) throw new InvalidOperationException("Not active");
|
||||
// temporarily switch the entire block to `R`
|
||||
Kernel32.MemoryProtection old;
|
||||
if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old))
|
||||
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||
var ret = WaterboxUtils.Hash(GetStream(Start, Size, false));
|
||||
ProtectAll();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Kernel32.MemoryProtection GetKernelMemoryProtectionValue(Protection prot)
|
||||
{
|
||||
switch (prot)
|
||||
{
|
||||
case Protection.None: return Kernel32.MemoryProtection.NOACCESS;
|
||||
case Protection.R: return Kernel32.MemoryProtection.READONLY;
|
||||
case Protection.RW: return Kernel32.MemoryProtection.READWRITE;
|
||||
case Protection.RX: return Kernel32.MemoryProtection.EXECUTE_READ;
|
||||
}
|
||||
throw new ArgumentOutOfRangeException(nameof(prot));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// restore all recorded protections
|
||||
/// </summary>
|
||||
protected override void ProtectAll()
|
||||
{
|
||||
int ps = 0;
|
||||
for (int i = 0; i < _pageData.Length; i++)
|
||||
{
|
||||
if (i == _pageData.Length - 1 || _pageData[i] != _pageData[i + 1])
|
||||
{
|
||||
var p = GetKernelMemoryProtectionValue(_pageData[i]);
|
||||
ulong zstart = GetStartAddr(ps);
|
||||
ulong zend = GetStartAddr(i + 1);
|
||||
Kernel32.MemoryProtection old;
|
||||
if (!Kernel32.VirtualProtect(Z.UU(zstart), Z.UU(zend - zstart), p, out old))
|
||||
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||
ps = i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// set r/w/x protection on a portion of memory. rounded to encompassing pages
|
||||
/// </summary>
|
||||
public override void Protect(ulong start, ulong length, Protection prot)
|
||||
{
|
||||
if (length == 0) return;
|
||||
int pstart = GetPage(start);
|
||||
int pend = GetPage(start + length - 1);
|
||||
|
||||
var p = GetKernelMemoryProtectionValue(prot);
|
||||
for (int i = pstart; i <= pend; i++) _pageData[i] = prot; // also store the value for later use
|
||||
|
||||
if (Active) // it's legal to Protect() if we're not active; the information is just saved for the next activation
|
||||
{
|
||||
var computedStart = WaterboxUtils.AlignDown(start);
|
||||
var computedEnd = WaterboxUtils.AlignUp(start + length);
|
||||
var computedLength = computedEnd - computedStart;
|
||||
|
||||
Kernel32.MemoryProtection old;
|
||||
if (!Kernel32.VirtualProtect(Z.UU(computedStart), Z.UU(computedLength), p, out old))
|
||||
throw new InvalidOperationException("VirtualProtect() returned FALSE!");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_handle != IntPtr.Zero)
|
||||
{
|
||||
if (Active) Deactivate();
|
||||
Kernel32.CloseHandle(_handle);
|
||||
_handle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
~MemoryBlockWin32()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
private static class Kernel32
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool VirtualProtect(UIntPtr lpAddress, UIntPtr dwSize,
|
||||
MemoryProtection flNewProtect, out MemoryProtection lpflOldProtect);
|
||||
|
||||
[Flags]
|
||||
public enum MemoryProtection : uint
|
||||
{
|
||||
EXECUTE = 0x10,
|
||||
EXECUTE_READ = 0x20,
|
||||
EXECUTE_READWRITE = 0x40,
|
||||
EXECUTE_WRITECOPY = 0x80,
|
||||
NOACCESS = 0x01,
|
||||
READONLY = 0x02,
|
||||
READWRITE = 0x04,
|
||||
WRITECOPY = 0x08,
|
||||
GUARD_Modifierflag = 0x100,
|
||||
NOCACHE_Modifierflag = 0x200,
|
||||
WRITECOMBINE_Modifierflag = 0x400
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr CreateFileMapping(
|
||||
IntPtr hFile,
|
||||
IntPtr lpFileMappingAttributes,
|
||||
FileMapProtection flProtect,
|
||||
uint dwMaximumSizeHigh,
|
||||
uint dwMaximumSizeLow,
|
||||
string lpName);
|
||||
|
||||
[Flags]
|
||||
public enum FileMapProtection : uint
|
||||
{
|
||||
PageReadonly = 0x02,
|
||||
PageReadWrite = 0x04,
|
||||
PageWriteCopy = 0x08,
|
||||
PageExecuteRead = 0x20,
|
||||
PageExecuteReadWrite = 0x40,
|
||||
SectionCommit = 0x8000000,
|
||||
SectionImage = 0x1000000,
|
||||
SectionNoCache = 0x10000000,
|
||||
SectionReserve = 0x4000000,
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr MapViewOfFileEx(IntPtr hFileMappingObject,
|
||||
FileMapAccessType dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow,
|
||||
UIntPtr dwNumberOfBytesToMap, IntPtr lpBaseAddress);
|
||||
|
||||
[Flags]
|
||||
public enum FileMapAccessType : uint
|
||||
{
|
||||
Copy = 0x01,
|
||||
Write = 0x02,
|
||||
Read = 0x04,
|
||||
AllAccess = 0x08,
|
||||
Execute = 0x20,
|
||||
}
|
||||
|
||||
public static readonly IntPtr INVALID_HANDLE_VALUE = Z.US(0xFFFFFFFFFFFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,8 +7,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
|
|||
{
|
||||
public abstract class LibQuickNES
|
||||
{
|
||||
public const string dllname = "libquicknes.dll";
|
||||
|
||||
/// <summary>
|
||||
/// setup extra mappers. should be done before anything else
|
||||
/// </summary>
|
||||
|
|
|
@ -3,8 +3,9 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.BizInvoke;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
|
||||
{
|
||||
|
@ -21,7 +22,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
|
|||
{
|
||||
static QuickNES()
|
||||
{
|
||||
Resolver = new DynamicLibraryImportResolver(LibQuickNES.dllname);
|
||||
Resolver = new DynamicLibraryImportResolver("libquicknes.dll" + (PlatformLinkedLibSingleton.RunningOnUnix ? ".so" : String.Empty));
|
||||
QN = BizInvoker.GetInvoker<LibQuickNES>(Resolver, CallingConventionAdapters.Native);
|
||||
QN.qn_setup_mappers();
|
||||
}
|
||||
|
|
|
@ -77,14 +77,14 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
long orig_end = loadsegs.Max(s => s.Address + s.Size);
|
||||
if (HasRelocations())
|
||||
{
|
||||
_base = new MemoryBlock((ulong)(orig_end - orig_start));
|
||||
_base = MemoryBlock.PlatformConstructor((ulong) (orig_end - orig_start));
|
||||
_loadoffset = (long)_base.Start - orig_start;
|
||||
Initialize(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Initialize((ulong)orig_start);
|
||||
_base = new MemoryBlock((ulong)orig_start, (ulong)(orig_end - orig_start));
|
||||
_base = MemoryBlock.PlatformConstructor((ulong) orig_start, (ulong) (orig_end - orig_start));
|
||||
_loadoffset = 0;
|
||||
Enter();
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
|
||||
public Heap(ulong start, ulong size, string name)
|
||||
{
|
||||
Memory = new MemoryBlock(start, size);
|
||||
Memory = MemoryBlock.PlatformConstructor(start, size);
|
||||
Used = 0;
|
||||
Name = name;
|
||||
Console.WriteLine("Created heap `{1}` at {0:x16}:{2:x16}", start, name, start + size);
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
public MapHeap(ulong start, ulong size, string name)
|
||||
{
|
||||
size = WaterboxUtils.AlignUp(size);
|
||||
Memory = new MemoryBlock(start, size);
|
||||
Memory = MemoryBlock.PlatformConstructor(start, size);
|
||||
Name = name;
|
||||
_pagesAsBytes = new byte[size >> WaterboxUtils.PageShift];
|
||||
_pages = (MemoryBlock.Protection[])(object)_pagesAsBytes;
|
||||
|
|
|
@ -172,7 +172,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
|
|||
// OK, NOW MOUNT
|
||||
|
||||
LoadOffset = (long)Start - (long)_pe.ImageNtHeaders.OptionalHeader.ImageBase;
|
||||
Memory = new MemoryBlock(Start, Size);
|
||||
Memory = MemoryBlock.PlatformConstructor(Start, Size);
|
||||
Memory.Activate();
|
||||
Memory.Protect(Start, Size, MemoryBlock.Protection.RW);
|
||||
|
||||
|
|
18
BizHawk.sln
18
BizHawk.sln
|
@ -59,6 +59,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Client.MultiHawk",
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Client.ApiHawk", "BizHawk.Client.ApiHawk\BizHawk.Client.ApiHawk.csproj", "{8E2F11F2-3955-4382-8C3A-CEBA1276CAEA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BizHawk.Bizware.BizwareGL.Vulkan", "Bizware\BizHawk.Bizware.BizwareGL.Vulkan\BizHawk.Bizware.BizwareGL.Vulkan.csproj", "{538947C1-B7F6-8A0D-C976-41EE5160CFB1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -245,6 +247,18 @@ Global
|
|||
{8E2F11F2-3955-4382-8C3A-CEBA1276CAEA}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8E2F11F2-3955-4382-8C3A-CEBA1276CAEA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8E2F11F2-3955-4382-8C3A-CEBA1276CAEA}.Release|x86.Build.0 = Release|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Release|x64.Build.0 = Release|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -263,6 +277,10 @@ Global
|
|||
{E6B436B1-A3CD-4C9A-8F76-5D7154726884} = {0540A9A6-977E-466D-8BD3-1D8590BD5282}
|
||||
{B95649F5-A0AE-41EB-B62B-578A2AFF5E18} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
|
||||
{8E2F11F2-3955-4382-8C3A-CEBA1276CAEA} = {B51F1139-3D2C-41BE-A762-EF1F9B41EACA}
|
||||
{538947C1-B7F6-8A0D-C976-41EE5160CFB1} = {0540A9A6-977E-466D-8BD3-1D8590BD5282}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6BC2A276-7A5D-41C2-A756-36EB1FF7FBEF}
|
||||
EndGlobalSection
|
||||
GlobalSection(MonoDevelopProperties) = preSolution
|
||||
StartupItem = BizHawk.Client.EmuHawk\BizHawk.Client.EmuHawk.csproj
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<CodeAnalysisRuleSet Condition=" '$(OS)' == 'Windows_NT' ">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\References\OpenTK.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
|
@ -41,11 +41,11 @@
|
|||
<CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\References\OpenTK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpenTK.GLControl, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<Reference Include="OpenTK.GLControl, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\References\OpenTK.GLControl.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<CodeAnalysisRuleSet Condition=" '$(OS)' == 'Windows_NT' ">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\References\OpenTK.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
|
||||
<ProductVersion>8.0.30703</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{538947C1-B7F6-8A0D-C976-41EE5160CFB1}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>BizHawk.Bizware.BizwareGL.Vulkan</RootNamespace>
|
||||
<AssemblyName>BizHawk.Bizware.BizwareGL.Vulkan</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>..\..\output\dll\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<!--<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>-->
|
||||
<CodeAnalysisRuleSet Condition=" '$(OS)' == 'Windows_NT' ">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisIgnoreBuiltInRuleSets>false</CodeAnalysisIgnoreBuiltInRuleSets>
|
||||
<CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
|
||||
<OutputPath>..\..\output\dll\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<!--<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>-->
|
||||
<CodeAnalysisRuleSet Condition=" '$(OS)' == 'Windows_NT' ">MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<CodeAnalysisIgnoreBuiltInRuleSets>false</CodeAnalysisIgnoreBuiltInRuleSets>
|
||||
<CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\References\OpenTK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OpenTK.GLControl, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\References\OpenTK.GLControl.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\BizHawk.Common\BizHawk.Common.csproj">
|
||||
<Project>{866F8D13-0678-4FF9-80A4-A3993FD4D8A3}</Project>
|
||||
<Name>BizHawk.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\BizHawk.Bizware.BizwareGL\BizHawk.Bizware.BizwareGL.csproj">
|
||||
<Project>{9F84A0B2-861E-4EF4-B89B-5E2A3F38A465}</Project>
|
||||
<Name>BizHawk.Bizware.BizwareGL</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GraphicsControl_Vulkan.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="IGL_Vulkan.cs" />
|
||||
</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>
|
|
@ -0,0 +1,2 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp60</s:String></wpf:ResourceDictionary>
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using BizHawk.Bizware.BizwareGL;
|
||||
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace BizHawk.Bizware.BizwareGL.Drivers.Vulkan
|
||||
{
|
||||
class GLControlWrapper_Vulkan : GLControl, IGraphicsControl
|
||||
{
|
||||
//Note: In order to work around bugs in OpenTK which sometimes do things to a context without making that context active first...
|
||||
//we are going to push and pop the context before doing stuff
|
||||
|
||||
public GLControlWrapper_Vulkan(IGL_Vulkan owner)
|
||||
: base(GraphicsMode.Default, 2, 0, GraphicsContextFlags.Default)
|
||||
{
|
||||
Owner = owner;
|
||||
GLControl = this;
|
||||
}
|
||||
|
||||
global::OpenTK.GLControl GLControl;
|
||||
IGL_Vulkan Owner;
|
||||
|
||||
public Control Control { get { return this; } }
|
||||
|
||||
|
||||
public void SetVsync(bool state)
|
||||
{
|
||||
//IGraphicsContext curr = global::OpenTK.Graphics.GraphicsContext.CurrentContext;
|
||||
GLControl.MakeCurrent();
|
||||
GLControl.VSync = state;
|
||||
//Owner.MakeContextCurrent(curr, Owner.NativeWindowsForContexts[curr]);
|
||||
}
|
||||
|
||||
public void Begin()
|
||||
{
|
||||
if (!GLControl.Context.IsCurrent)
|
||||
Owner.MakeContextCurrent(GLControl.Context, GLControl.WindowInfo);
|
||||
}
|
||||
|
||||
public void End()
|
||||
{
|
||||
Owner.MakeDefaultCurrent();
|
||||
}
|
||||
|
||||
public new void SwapBuffers()
|
||||
{
|
||||
if (!GLControl.Context.IsCurrent)
|
||||
MakeCurrent();
|
||||
base.SwapBuffers();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,883 @@
|
|||
//regarding binding and vertex arrays:
|
||||
//http://stackoverflow.com/questions/8704801/glvertexattribpointer-clarification
|
||||
//http://stackoverflow.com/questions/9536973/oes-vertex-array-object-and-client-state
|
||||
//http://www.opengl.org/wiki/Vertex_Specification
|
||||
|
||||
//etc
|
||||
//glBindAttribLocation (programID, 0, "vertexPosition_modelspace");
|
||||
|
||||
//for future reference: c# tesselators
|
||||
//http://www.opentk.com/node/437 (AGG#, codes on Tao forums)
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using sd = System.Drawing;
|
||||
using sdi = System.Drawing.Imaging;
|
||||
using swf=System.Windows.Forms;
|
||||
|
||||
using BizHawk.Bizware.BizwareGL;
|
||||
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using otkg = OpenTK.Graphics;
|
||||
|
||||
namespace BizHawk.Bizware.BizwareGL.Drivers.Vulkan
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenTK implementation of the BizwareGL.IGL interface.
|
||||
/// TODO - can we have more than one of these? could be dangerous. such dangerous things to be possibly reconsidered are marked with HAMNUTS
|
||||
/// TODO - if we have any way of making contexts, we also need a way of freeing it, and then we can cleanup our dictionaries
|
||||
/// </summary>
|
||||
public class IGL_Vulkan : IGL
|
||||
{
|
||||
//rendering state
|
||||
Pipeline _CurrPipeline;
|
||||
RenderTarget _CurrRenderTarget;
|
||||
|
||||
static IGL_Vulkan()
|
||||
{
|
||||
//make sure OpenTK initializes without getting wrecked on the SDL check and throwing an exception to annoy our MDA's
|
||||
var toolkitOptions = global::OpenTK.ToolkitOptions.Default;
|
||||
toolkitOptions.Backend = PlatformBackend.PreferNative;
|
||||
global::OpenTK.Toolkit.Init(toolkitOptions);
|
||||
//NOTE: this throws EGL exceptions anyway. I'm going to ignore it and whine about it later
|
||||
}
|
||||
|
||||
public string API { get { return "OPENGL"; } }
|
||||
|
||||
public int Version
|
||||
{
|
||||
get
|
||||
{
|
||||
//doesnt work on older than gl3 maybe
|
||||
//int major, minor;
|
||||
////other overloads may not exist...
|
||||
//GL.GetInteger(GetPName.MajorVersion, out major);
|
||||
//GL.GetInteger(GetPName.MinorVersion, out minor);
|
||||
|
||||
//supposedly the standard dictates that whatever junk is in the version string, some kind of version is at the beginning
|
||||
string version_string = GL.GetString(StringName.Version);
|
||||
var version_parts = version_string.Split('.');
|
||||
int major = int.Parse(version_parts[0]);
|
||||
//getting a minor version out is too hard and not needed now
|
||||
return major * 100;
|
||||
}
|
||||
}
|
||||
|
||||
public IGL_Vulkan(int major_version = 0, int minor_version = 0, bool forward_compatible = false)
|
||||
{
|
||||
//make an 'offscreen context' so we can at least do things without having to create a window
|
||||
OffscreenNativeWindow = new NativeWindow();
|
||||
OffscreenNativeWindow.ClientSize = new sd.Size(8, 8);
|
||||
this.GraphicsContext = new GraphicsContext(GraphicsMode.Default, OffscreenNativeWindow.WindowInfo, major_version, minor_version, forward_compatible ? GraphicsContextFlags.ForwardCompatible : GraphicsContextFlags.Default);
|
||||
MakeDefaultCurrent();
|
||||
|
||||
//this is important for reasons unknown
|
||||
this.GraphicsContext.LoadAll();
|
||||
|
||||
//misc initialization
|
||||
CreateRenderStates();
|
||||
PurgeStateCache();
|
||||
}
|
||||
|
||||
public void BeginScene()
|
||||
{
|
||||
//seems not to be needed...
|
||||
}
|
||||
|
||||
public void EndScene()
|
||||
{
|
||||
//seems not to be needed...
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
//TODO - a lot of analysis here
|
||||
OffscreenNativeWindow.Dispose(); OffscreenNativeWindow = null;
|
||||
GraphicsContext.Dispose(); GraphicsContext = null;
|
||||
}
|
||||
|
||||
public void Clear(ClearBufferMask mask)
|
||||
{
|
||||
GL.Clear((global::OpenTK.Graphics.OpenGL.ClearBufferMask)mask);
|
||||
}
|
||||
public void SetClearColor(sd.Color color)
|
||||
{
|
||||
GL.ClearColor(color);
|
||||
}
|
||||
|
||||
public IGraphicsControl Internal_CreateGraphicsControl()
|
||||
{
|
||||
var glc = new GLControlWrapper_Vulkan(this);
|
||||
glc.CreateControl();
|
||||
|
||||
//now the control's context will be current. annoying! fix it.
|
||||
MakeDefaultCurrent();
|
||||
|
||||
|
||||
return glc;
|
||||
}
|
||||
|
||||
public int GenTexture() { return GL.GenTexture(); }
|
||||
public void FreeTexture(Texture2d tex)
|
||||
{
|
||||
GL.DeleteTexture((int)tex.Opaque);
|
||||
}
|
||||
|
||||
public Shader CreateFragmentShader(bool cg, string source, string entry, bool required)
|
||||
{
|
||||
return CreateShader(cg, ShaderType.FragmentShader, source, entry, required);
|
||||
}
|
||||
public Shader CreateVertexShader(bool cg, string source, string entry, bool required)
|
||||
{
|
||||
return CreateShader(cg, ShaderType.VertexShader, source, entry, required);
|
||||
}
|
||||
|
||||
public IBlendState CreateBlendState(BlendingFactorSrc colorSource, BlendEquationMode colorEquation, BlendingFactorDest colorDest,
|
||||
BlendingFactorSrc alphaSource, BlendEquationMode alphaEquation, BlendingFactorDest alphaDest)
|
||||
{
|
||||
return new CacheBlendState(true, colorSource, colorEquation, colorDest, alphaSource, alphaEquation, alphaDest);
|
||||
}
|
||||
|
||||
public void SetBlendState(IBlendState rsBlend)
|
||||
{
|
||||
var mybs = rsBlend as CacheBlendState;
|
||||
if (mybs.Enabled)
|
||||
{
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendEquationSeparate(mybs.colorEquation, mybs.alphaEquation);
|
||||
GL.BlendFuncSeparate(mybs.colorSource, mybs.colorDest, mybs.alphaSource, mybs.alphaDest);
|
||||
}
|
||||
else GL.Disable(EnableCap.Blend);
|
||||
if (rsBlend == _rsBlendNoneOpaque)
|
||||
{
|
||||
//make sure constant color is set correctly
|
||||
GL.BlendColor(new Color4(255, 255, 255, 255));
|
||||
}
|
||||
}
|
||||
|
||||
public IBlendState BlendNoneCopy { get { return _rsBlendNoneVerbatim; } }
|
||||
public IBlendState BlendNoneOpaque { get { return _rsBlendNoneOpaque; } }
|
||||
public IBlendState BlendNormal { get { return _rsBlendNormal; } }
|
||||
|
||||
class ShaderWrapper
|
||||
{
|
||||
public int sid;
|
||||
public Dictionary<string, string> MapCodeToNative;
|
||||
public Dictionary<string, string> MapNativeToCode;
|
||||
}
|
||||
|
||||
class PipelineWrapper
|
||||
{
|
||||
public int pid;
|
||||
public Shader FragmentShader, VertexShader;
|
||||
public List<int> SamplerLocs;
|
||||
}
|
||||
|
||||
public Pipeline CreatePipeline(VertexLayout vertexLayout, Shader vertexShader, Shader fragmentShader, bool required, string memo)
|
||||
{
|
||||
//if the shaders arent available, the pipeline isn't either
|
||||
if (!vertexShader.Available || !fragmentShader.Available)
|
||||
{
|
||||
string errors = string.Format("Vertex Shader:\r\n {0} \r\n-------\r\nFragment Shader:\r\n{1}", vertexShader.Errors, fragmentShader.Errors);
|
||||
if (required)
|
||||
throw new InvalidOperationException("Couldn't build required GL pipeline:\r\n" + errors);
|
||||
var pipeline = new Pipeline(this, null, false, null, null, null);
|
||||
pipeline.Errors = errors;
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
|
||||
var vsw = vertexShader.Opaque as ShaderWrapper;
|
||||
var fsw = fragmentShader.Opaque as ShaderWrapper;
|
||||
var sws = new[] { vsw,fsw };
|
||||
|
||||
bool mapVariables = vsw.MapCodeToNative != null || fsw.MapCodeToNative != null;
|
||||
|
||||
ErrorCode errcode;
|
||||
int pid = GL.CreateProgram();
|
||||
GL.AttachShader(pid, vsw.sid);
|
||||
errcode = GL.GetError();
|
||||
GL.AttachShader(pid, fsw.sid);
|
||||
errcode = GL.GetError();
|
||||
|
||||
//NOT BEING USED NOW: USING SEMANTICS INSTEAD
|
||||
////bind the attribute locations from the vertex layout
|
||||
////as we go, look for attribute mappings (CGC will happily reorder and rename our attribute mappings)
|
||||
////what's more it will _RESIZE_ them but this seems benign..somehow..
|
||||
////WELLLLLLL we wish we could do that by names
|
||||
////but the shaders dont seem to be adequate quality (oddly named attributes.. texCoord vs texCoord1). need to use semantics instead.
|
||||
//foreach (var kvp in vertexLayout.Items)
|
||||
//{
|
||||
// string name = kvp.Value.Name;
|
||||
// //if (mapVariables)
|
||||
// //{
|
||||
// // foreach (var sw in sws)
|
||||
// // {
|
||||
// // if (sw.MapNativeToCode.ContainsKey(name))
|
||||
// // {
|
||||
// // name = sw.MapNativeToCode[name];
|
||||
// // break;
|
||||
// // }
|
||||
// // }
|
||||
// //}
|
||||
|
||||
// if(mapVariables) {
|
||||
// ////proxy for came-from-cgc
|
||||
// //switch (kvp.Value.Usage)
|
||||
// //{
|
||||
// // case AttributeUsage.Position:
|
||||
// //}
|
||||
// }
|
||||
|
||||
// //GL.BindAttribLocation(pid, kvp.Key, name);
|
||||
//}
|
||||
|
||||
|
||||
GL.LinkProgram(pid);
|
||||
errcode = GL.GetError();
|
||||
|
||||
string resultLog = GL.GetProgramInfoLog(pid);
|
||||
|
||||
if (errcode != ErrorCode.NoError)
|
||||
if (required)
|
||||
throw new InvalidOperationException("Error creating pipeline (error returned from glLinkProgram): " + errcode + "\r\n\r\n" + resultLog);
|
||||
else success = false;
|
||||
|
||||
int linkStatus;
|
||||
GL.GetProgram(pid, GetProgramParameterName.LinkStatus, out linkStatus);
|
||||
if (linkStatus == 0)
|
||||
if (required)
|
||||
throw new InvalidOperationException("Error creating pipeline (link status false returned from glLinkProgram): " + "\r\n\r\n" + resultLog);
|
||||
else success = false;
|
||||
|
||||
//need to work on validation. apparently there are some weird caveats to glValidate which make it complicated and possibly excuses (barely) the intel drivers' dysfunctional operation
|
||||
//"A sampler points to a texture unit used by fixed function with an incompatible target"
|
||||
//
|
||||
//info:
|
||||
//http://www.opengl.org/sdk/docs/man/xhtml/glValidateProgram.xml
|
||||
//This function mimics the validation operation that OpenGL implementations must perform when rendering commands are issued while programmable shaders are part of current state.
|
||||
//glValidateProgram checks to see whether the executables contained in program can execute given the current OpenGL state
|
||||
//This function is typically useful only during application development.
|
||||
//
|
||||
//So, this is no big deal. we shouldnt be calling validate right now anyway.
|
||||
//conclusion: glValidate is very complicated and is of virtually no use unless your draw calls are returning errors and you want to know why
|
||||
//GL.ValidateProgram(pid);
|
||||
//errcode = GL.GetError();
|
||||
//resultLog = GL.GetProgramInfoLog(pid);
|
||||
//if (errcode != ErrorCode.NoError)
|
||||
// throw new InvalidOperationException("Error creating pipeline (error returned from glValidateProgram): " + errcode + "\r\n\r\n" + resultLog);
|
||||
//int validateStatus;
|
||||
//GL.GetProgram(pid, GetProgramParameterName.ValidateStatus, out validateStatus);
|
||||
//if (validateStatus == 0)
|
||||
// throw new InvalidOperationException("Error creating pipeline (validateStatus status false returned from glValidateProgram): " + "\r\n\r\n" + resultLog);
|
||||
|
||||
//set the program to active, in case we need to set sampler uniforms on it
|
||||
GL.UseProgram(pid);
|
||||
|
||||
//get all the attributes (not needed)
|
||||
List<AttributeInfo> attributes = new List<AttributeInfo>();
|
||||
int nAttributes;
|
||||
GL.GetProgram(pid, GetProgramParameterName.ActiveAttributes, out nAttributes);
|
||||
for (int i = 0; i < nAttributes; i++)
|
||||
{
|
||||
int size, length;
|
||||
string name = new System.Text.StringBuilder(1024).ToString();
|
||||
ActiveAttribType type;
|
||||
GL.GetActiveAttrib(pid, i, 1024, out length, out size, out type, out name);
|
||||
attributes.Add(new AttributeInfo() { Handle = new IntPtr(i), Name = name });
|
||||
}
|
||||
|
||||
//get all the uniforms
|
||||
List<UniformInfo> uniforms = new List<UniformInfo>();
|
||||
int nUniforms;
|
||||
GL.GetProgram(pid,GetProgramParameterName.ActiveUniforms,out nUniforms);
|
||||
List<int> samplers = new List<int>();
|
||||
|
||||
for (int i = 0; i < nUniforms; i++)
|
||||
{
|
||||
int size, length;
|
||||
ActiveUniformType type;
|
||||
string name = new System.Text.StringBuilder(1024).ToString().ToString();
|
||||
GL.GetActiveUniform(pid, i, 1024, out length, out size, out type, out name);
|
||||
errcode = GL.GetError();
|
||||
int loc = GL.GetUniformLocation(pid, name);
|
||||
|
||||
//translate name if appropriate
|
||||
//not sure how effective this approach will be, due to confusion of vertex and fragment uniforms
|
||||
if (mapVariables)
|
||||
{
|
||||
if (vsw.MapCodeToNative.ContainsKey(name)) name = vsw.MapCodeToNative[name];
|
||||
if (fsw.MapCodeToNative.ContainsKey(name)) name = fsw.MapCodeToNative[name];
|
||||
}
|
||||
|
||||
var ui = new UniformInfo();
|
||||
ui.Name = name;
|
||||
ui.Opaque = loc;
|
||||
|
||||
if (type == ActiveUniformType.Sampler2D)
|
||||
{
|
||||
ui.IsSampler = true;
|
||||
ui.SamplerIndex = samplers.Count;
|
||||
ui.Opaque = loc | (samplers.Count << 24);
|
||||
samplers.Add(loc);
|
||||
}
|
||||
|
||||
uniforms.Add(ui);
|
||||
}
|
||||
|
||||
//deactivate the program, so we dont accidentally use it
|
||||
GL.UseProgram(0);
|
||||
|
||||
if (!vertexShader.Available) success = false;
|
||||
if (!fragmentShader.Available) success = false;
|
||||
|
||||
var pw = new PipelineWrapper() { pid = pid, VertexShader = vertexShader, FragmentShader = fragmentShader, SamplerLocs = samplers };
|
||||
|
||||
return new Pipeline(this, pw, success, vertexLayout, uniforms, memo);
|
||||
}
|
||||
|
||||
public void FreePipeline(Pipeline pipeline)
|
||||
{
|
||||
var pw = pipeline.Opaque as PipelineWrapper;
|
||||
|
||||
//unavailable pipelines will have no opaque
|
||||
if (pw == null)
|
||||
return;
|
||||
|
||||
GL.DeleteProgram(pw.pid);
|
||||
|
||||
pw.FragmentShader.Release();
|
||||
pw.VertexShader.Release();
|
||||
}
|
||||
|
||||
public void Internal_FreeShader(Shader shader)
|
||||
{
|
||||
var sw = shader.Opaque as ShaderWrapper;
|
||||
GL.DeleteShader(sw.sid);
|
||||
}
|
||||
|
||||
public void BindPipeline(Pipeline pipeline)
|
||||
{
|
||||
_CurrPipeline = pipeline;
|
||||
|
||||
if (pipeline == null)
|
||||
{
|
||||
sStatePendingVertexLayout = null;
|
||||
GL.UseProgram(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pipeline.Available) throw new InvalidOperationException("Attempt to bind unavailable pipeline");
|
||||
sStatePendingVertexLayout = pipeline.VertexLayout;
|
||||
|
||||
var pw = pipeline.Opaque as PipelineWrapper;
|
||||
GL.UseProgram(pw.pid);
|
||||
|
||||
//this is dumb and confusing, but we have to bind physical sampler numbers to sampler variables.
|
||||
for (int i = 0; i < pw.SamplerLocs.Count; i++)
|
||||
{
|
||||
GL.Uniform1(pw.SamplerLocs[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public VertexLayout CreateVertexLayout() { return new VertexLayout(this, null); }
|
||||
|
||||
private void BindTexture2d(Texture2d tex)
|
||||
{
|
||||
GL.BindTexture(TextureTarget.Texture2D, (int)tex.Opaque);
|
||||
}
|
||||
|
||||
public void SetTextureWrapMode(Texture2d tex, bool clamp)
|
||||
{
|
||||
BindTexture2d(tex);
|
||||
int mode;
|
||||
if (clamp)
|
||||
{
|
||||
mode = (int)global::OpenTK.Graphics.OpenGL.TextureWrapMode.ClampToEdge;
|
||||
}
|
||||
else
|
||||
mode = (int)global::OpenTK.Graphics.OpenGL.TextureWrapMode.Repeat;
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, mode);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, mode);
|
||||
}
|
||||
|
||||
public unsafe void BindArrayData(void* pData)
|
||||
{
|
||||
MyBindArrayData(sStatePendingVertexLayout, pData);
|
||||
}
|
||||
|
||||
public void DrawArrays(PrimitiveType mode, int first, int count)
|
||||
{
|
||||
GL.DrawArrays((global::OpenTK.Graphics.OpenGL.PrimitiveType)mode, first, count);
|
||||
}
|
||||
|
||||
public void SetPipelineUniform(PipelineUniform uniform, bool value)
|
||||
{
|
||||
GL.Uniform1((int)uniform.Sole.Opaque, value ? 1 : 0);
|
||||
}
|
||||
|
||||
public unsafe void SetPipelineUniformMatrix(PipelineUniform uniform, Matrix4 mat, bool transpose)
|
||||
{
|
||||
//GL.UniformMatrix4((int)uniform.Opaque, 1, transpose, (float*)&mat);
|
||||
GL.Uniform4((int)uniform.Sole.Opaque + 0, 1, (float*)&mat.Row0);
|
||||
GL.Uniform4((int)uniform.Sole.Opaque + 1, 1, (float*)&mat.Row1);
|
||||
GL.Uniform4((int)uniform.Sole.Opaque + 2, 1, (float*)&mat.Row2);
|
||||
GL.Uniform4((int)uniform.Sole.Opaque + 3, 1, (float*)&mat.Row3);
|
||||
}
|
||||
|
||||
public unsafe void SetPipelineUniformMatrix(PipelineUniform uniform, ref Matrix4 mat, bool transpose)
|
||||
{
|
||||
fixed(Matrix4* pMat = &mat)
|
||||
GL.UniformMatrix4((int)uniform.Sole.Opaque, 1, transpose, (float*)pMat);
|
||||
}
|
||||
|
||||
public void SetPipelineUniform(PipelineUniform uniform, Vector4 value)
|
||||
{
|
||||
GL.Uniform4((int)uniform.Sole.Opaque, value.X, value.Y, value.Z, value.W);
|
||||
}
|
||||
|
||||
public void SetPipelineUniform(PipelineUniform uniform, Vector2 value)
|
||||
{
|
||||
GL.Uniform2((int)uniform.Sole.Opaque, value.X, value.Y);
|
||||
}
|
||||
|
||||
public void SetPipelineUniform(PipelineUniform uniform, float value)
|
||||
{
|
||||
if (uniform.Owner == null) return; //uniform was optimized out
|
||||
GL.Uniform1((int)uniform.Sole.Opaque, value);
|
||||
}
|
||||
|
||||
public unsafe void SetPipelineUniform(PipelineUniform uniform, Vector4[] values)
|
||||
{
|
||||
fixed (Vector4* pValues = &values[0])
|
||||
GL.Uniform4((int)uniform.Sole.Opaque, values.Length, (float*)pValues);
|
||||
}
|
||||
|
||||
public void SetPipelineUniformSampler(PipelineUniform uniform, Texture2d tex)
|
||||
{
|
||||
int n = ((int)uniform.Sole.Opaque)>>24;
|
||||
|
||||
//set the sampler index into the uniform first
|
||||
if (sActiveTexture != n)
|
||||
{
|
||||
sActiveTexture = n;
|
||||
var selectedUnit = (TextureUnit)((int)TextureUnit.Texture0 + n);
|
||||
GL.ActiveTexture(selectedUnit);
|
||||
}
|
||||
|
||||
//now bind the texture
|
||||
GL.BindTexture(TextureTarget.Texture2D, (int)tex.Opaque);
|
||||
}
|
||||
|
||||
public void TexParameter2d(Texture2d tex, TextureParameterName pname, int param)
|
||||
{
|
||||
BindTexture2d(tex);
|
||||
GL.TexParameter(TextureTarget.Texture2D, (global::OpenTK.Graphics.OpenGL.TextureParameterName)pname, param);
|
||||
}
|
||||
|
||||
public Texture2d LoadTexture(sd.Bitmap bitmap)
|
||||
{
|
||||
using (var bmp = new BitmapBuffer(bitmap, new BitmapLoadOptions()))
|
||||
return (this as IGL).LoadTexture(bmp);
|
||||
}
|
||||
|
||||
public Texture2d LoadTexture(Stream stream)
|
||||
{
|
||||
using(var bmp = new BitmapBuffer(stream,new BitmapLoadOptions()))
|
||||
return (this as IGL).LoadTexture(bmp);
|
||||
}
|
||||
|
||||
public Texture2d CreateTexture(int width, int height)
|
||||
{
|
||||
int id = GenTexture();
|
||||
return new Texture2d(this, id, width, height);
|
||||
}
|
||||
|
||||
public Texture2d WrapGLTexture2d(IntPtr glTexId, int width, int height)
|
||||
{
|
||||
return new Texture2d(this as IGL, glTexId.ToInt32(), width, height);
|
||||
}
|
||||
|
||||
public void LoadTextureData(Texture2d tex, BitmapBuffer bmp)
|
||||
{
|
||||
sdi.BitmapData bmp_data = bmp.LockBits();
|
||||
try
|
||||
{
|
||||
GL.BindTexture(TextureTarget.Texture2D, (int)tex.Opaque);
|
||||
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, bmp.Width, bmp.Height, PixelFormat.Bgra, PixelType.UnsignedByte, bmp_data.Scan0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bmp.UnlockBits(bmp_data);
|
||||
}
|
||||
}
|
||||
|
||||
public void FreeRenderTarget(RenderTarget rt)
|
||||
{
|
||||
rt.Texture2d.Dispose();
|
||||
GL.Ext.DeleteFramebuffer((int)rt.Opaque);
|
||||
}
|
||||
|
||||
public unsafe RenderTarget CreateRenderTarget(int w, int h)
|
||||
{
|
||||
//create a texture for it
|
||||
int texid = GenTexture();
|
||||
Texture2d tex = new Texture2d(this, texid, w, h);
|
||||
GL.BindTexture(TextureTarget.Texture2D,texid);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba8, w, h, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
tex.SetMagFilter(TextureMagFilter.Nearest);
|
||||
tex.SetMinFilter(TextureMinFilter.Nearest);
|
||||
|
||||
//create the FBO
|
||||
int fbid = GL.Ext.GenFramebuffer();
|
||||
GL.Ext.BindFramebuffer(FramebufferTarget.Framebuffer, fbid);
|
||||
|
||||
//bind the tex to the FBO
|
||||
GL.Ext.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, texid, 0);
|
||||
|
||||
//do something, I guess say which colorbuffers are used by the framebuffer
|
||||
DrawBuffersEnum* buffers = stackalloc DrawBuffersEnum[1];
|
||||
buffers[0] = DrawBuffersEnum.ColorAttachment0;
|
||||
GL.DrawBuffers(1, buffers);
|
||||
|
||||
if (GL.Ext.CheckFramebufferStatus(FramebufferTarget.Framebuffer) != FramebufferErrorCode.FramebufferComplete)
|
||||
throw new InvalidOperationException("Error creating framebuffer (at CheckFramebufferStatus)");
|
||||
|
||||
//since we're done configuring unbind this framebuffer, to return to the default
|
||||
GL.Ext.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
|
||||
return new RenderTarget(this, fbid, tex);
|
||||
}
|
||||
|
||||
public void BindRenderTarget(RenderTarget rt)
|
||||
{
|
||||
_CurrRenderTarget = rt;
|
||||
if(rt == null)
|
||||
GL.Ext.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||
else
|
||||
GL.Ext.BindFramebuffer(FramebufferTarget.Framebuffer, (int)rt.Opaque);
|
||||
}
|
||||
|
||||
public Texture2d LoadTexture(BitmapBuffer bmp)
|
||||
{
|
||||
Texture2d ret = null;
|
||||
int id = GenTexture();
|
||||
try
|
||||
{
|
||||
ret = new Texture2d(this, id, bmp.Width, bmp.Height);
|
||||
GL.BindTexture(TextureTarget.Texture2D, id);
|
||||
//picking a color order that matches doesnt seem to help, any. maybe my driver is accelerating it, or maybe it isnt a big deal. but its something to study on another day
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmp.Width, bmp.Height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
(this as IGL).LoadTextureData(ret, bmp);
|
||||
}
|
||||
catch
|
||||
{
|
||||
GL.DeleteTexture(id);
|
||||
throw;
|
||||
}
|
||||
|
||||
//set default filtering.. its safest to do this always
|
||||
ret.SetFilterNearest();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public unsafe BitmapBuffer ResolveTexture2d(Texture2d tex)
|
||||
{
|
||||
//note - this is dangerous since it changes the bound texture. could we save it?
|
||||
BindTexture2d(tex);
|
||||
var bb = new BitmapBuffer(tex.IntWidth, tex.IntHeight);
|
||||
var bmpdata = bb.LockBits();
|
||||
GL.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Bgra, PixelType.UnsignedByte, bmpdata.Scan0);
|
||||
var err = GL.GetError();
|
||||
bb.UnlockBits(bmpdata);
|
||||
return bb;
|
||||
}
|
||||
|
||||
public Texture2d LoadTexture(string path)
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
return (this as IGL).LoadTexture(fs);
|
||||
}
|
||||
|
||||
public Matrix4 CreateGuiProjectionMatrix(int w, int h)
|
||||
{
|
||||
return CreateGuiProjectionMatrix(new sd.Size(w, h));
|
||||
}
|
||||
|
||||
public Matrix4 CreateGuiViewMatrix(int w, int h, bool autoflip)
|
||||
{
|
||||
return CreateGuiViewMatrix(new sd.Size(w, h), autoflip);
|
||||
}
|
||||
|
||||
public Matrix4 CreateGuiProjectionMatrix(sd.Size dims)
|
||||
{
|
||||
Matrix4 ret = Matrix4.Identity;
|
||||
ret.M11 = 2.0f / (float)dims.Width;
|
||||
ret.M22 = 2.0f / (float)dims.Height;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Matrix4 CreateGuiViewMatrix(sd.Size dims, bool autoflip)
|
||||
{
|
||||
Matrix4 ret = Matrix4.Identity;
|
||||
ret.M22 = -1.0f;
|
||||
ret.M41 = -(float)dims.Width * 0.5f;
|
||||
ret.M42 = (float)dims.Height * 0.5f;
|
||||
if (autoflip)
|
||||
{
|
||||
if (_CurrRenderTarget == null) { }
|
||||
else
|
||||
{
|
||||
//flip as long as we're not a final render target
|
||||
ret.M22 = 1.0f;
|
||||
ret.M42 *= -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void SetViewport(int x, int y, int width, int height)
|
||||
{
|
||||
GL.Viewport(x, y, width, height);
|
||||
GL.Scissor(x, y, width, height); //hack for mupen[rice]+intel: at least the rice plugin leaves the scissor rectangle scrambled, and we're trying to run it in the main graphics context for intel
|
||||
//BUT ALSO: new specifications.. viewport+scissor make sense together
|
||||
}
|
||||
|
||||
public void SetViewport(int width, int height)
|
||||
{
|
||||
SetViewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
public void SetViewport(sd.Size size)
|
||||
{
|
||||
SetViewport(size.Width, size.Height);
|
||||
}
|
||||
|
||||
public void SetViewport(swf.Control control)
|
||||
{
|
||||
var r = control.ClientRectangle;
|
||||
SetViewport(r.Left, r.Top, r.Width, r.Height);
|
||||
}
|
||||
|
||||
//------------------
|
||||
|
||||
INativeWindow OffscreenNativeWindow;
|
||||
IGraphicsContext GraphicsContext;
|
||||
|
||||
//---------------
|
||||
//my utility methods
|
||||
|
||||
GLControl CastControl(swf.Control swfControl)
|
||||
{
|
||||
GLControl glc = swfControl as GLControl;
|
||||
if (glc == null)
|
||||
throw new ArgumentException("Argument isn't a control created by the IGL interface", "glControl");
|
||||
return glc;
|
||||
}
|
||||
|
||||
Shader CreateShader(bool cg, ShaderType type, string source, string entry, bool required)
|
||||
{
|
||||
var sw = new ShaderWrapper();
|
||||
if (cg)
|
||||
{
|
||||
var cgc = new CGC();
|
||||
var results = cgc.Run(source, entry, type == ShaderType.FragmentShader ? "glslf" : "glslv", false);
|
||||
|
||||
if (!results.Succeeded)
|
||||
{
|
||||
Console.WriteLine("CGC failed");
|
||||
Console.WriteLine(results.Errors);
|
||||
return new Shader(this, null, false);
|
||||
}
|
||||
|
||||
source = results.Code;
|
||||
sw.MapCodeToNative = results.MapCodeToNative;
|
||||
sw.MapNativeToCode = results.MapNativeToCode;
|
||||
}
|
||||
|
||||
int sid = GL.CreateShader(type);
|
||||
bool ok = CompileShaderSimple(sid, source, required);
|
||||
if(!ok)
|
||||
{
|
||||
GL.DeleteShader(sid);
|
||||
sid = 0;
|
||||
}
|
||||
|
||||
sw.sid = sid;
|
||||
|
||||
return new Shader(this, sw, ok);
|
||||
}
|
||||
|
||||
bool CompileShaderSimple(int sid, string source, bool required)
|
||||
{
|
||||
bool success = true;
|
||||
ErrorCode errcode;
|
||||
|
||||
errcode = GL.GetError();
|
||||
if (errcode != ErrorCode.NoError)
|
||||
if (required)
|
||||
throw new InvalidOperationException("Error compiling shader (from previous operation) " + errcode);
|
||||
else success = false;
|
||||
|
||||
GL.ShaderSource(sid, source);
|
||||
|
||||
errcode = GL.GetError();
|
||||
if (errcode != ErrorCode.NoError)
|
||||
if (required)
|
||||
throw new InvalidOperationException("Error compiling shader (ShaderSource) " + errcode);
|
||||
else success = false;
|
||||
|
||||
GL.CompileShader(sid);
|
||||
errcode = GL.GetError();
|
||||
|
||||
string resultLog = GL.GetShaderInfoLog(sid);
|
||||
|
||||
if (errcode != ErrorCode.NoError)
|
||||
{
|
||||
string message = "Error compiling shader (CompileShader) " + errcode + "\r\n\r\n" + resultLog;
|
||||
if (required)
|
||||
throw new InvalidOperationException(message);
|
||||
else
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
int n;
|
||||
GL.GetShader(sid, ShaderParameter.CompileStatus, out n);
|
||||
|
||||
if (n == 0)
|
||||
if (required)
|
||||
throw new InvalidOperationException("Error compiling shader (CompileShader )" + "\r\n\r\n" + resultLog);
|
||||
else success = false;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void UnbindVertexAttributes()
|
||||
{
|
||||
//HAMNUTS:
|
||||
//its not clear how many bindings we'll have to disable before we can enable the ones we need..
|
||||
//so lets just disable the ones we remember we have bound
|
||||
var currBindings = sVertexAttribEnables;
|
||||
foreach (var index in currBindings)
|
||||
GL.DisableVertexAttribArray(index);
|
||||
currBindings.Clear();
|
||||
}
|
||||
|
||||
unsafe void MyBindArrayData(VertexLayout layout, void* pData)
|
||||
{
|
||||
UnbindVertexAttributes();
|
||||
|
||||
//HAMNUTS (continued)
|
||||
var currBindings = sVertexAttribEnables;
|
||||
sStateCurrentVertexLayout = sStatePendingVertexLayout;
|
||||
|
||||
if (layout == null) return;
|
||||
|
||||
//disable all the client states.. a lot of overhead right now, to be sure
|
||||
GL.DisableClientState(ArrayCap.VertexArray);
|
||||
GL.DisableClientState(ArrayCap.ColorArray);
|
||||
for(int i=0;i<8;i++)
|
||||
GL.DisableVertexAttribArray(i);
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
GL.ClientActiveTexture(TextureUnit.Texture0 + i);
|
||||
GL.DisableClientState(ArrayCap.TextureCoordArray);
|
||||
}
|
||||
GL.ClientActiveTexture(TextureUnit.Texture0);
|
||||
|
||||
foreach (var kvp in layout.Items)
|
||||
{
|
||||
if(_CurrPipeline.Memo == "gui")
|
||||
{
|
||||
GL.VertexAttribPointer(kvp.Key, kvp.Value.Components, (VertexAttribPointerType)kvp.Value.AttribType, kvp.Value.Normalized, kvp.Value.Stride, new IntPtr(pData) + kvp.Value.Offset);
|
||||
GL.EnableVertexAttribArray(kvp.Key);
|
||||
currBindings.Add(kvp.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var pw = _CurrPipeline.Opaque as PipelineWrapper;
|
||||
|
||||
//comment SNACKPANTS
|
||||
switch (kvp.Value.Usage)
|
||||
{
|
||||
case AttributeUsage.Position:
|
||||
GL.EnableClientState(ArrayCap.VertexArray);
|
||||
GL.VertexPointer(kvp.Value.Components,VertexPointerType.Float,kvp.Value.Stride,new IntPtr(pData) + kvp.Value.Offset);
|
||||
break;
|
||||
case AttributeUsage.Texcoord0:
|
||||
GL.ClientActiveTexture(TextureUnit.Texture0);
|
||||
GL.EnableClientState(ArrayCap.TextureCoordArray);
|
||||
GL.TexCoordPointer(kvp.Value.Components, TexCoordPointerType.Float, kvp.Value.Stride, new IntPtr(pData) + kvp.Value.Offset);
|
||||
break;
|
||||
case AttributeUsage.Texcoord1:
|
||||
GL.ClientActiveTexture(TextureUnit.Texture1);
|
||||
GL.EnableClientState(ArrayCap.TextureCoordArray);
|
||||
GL.TexCoordPointer(kvp.Value.Components, TexCoordPointerType.Float, kvp.Value.Stride, new IntPtr(pData) + kvp.Value.Offset);
|
||||
GL.ClientActiveTexture(TextureUnit.Texture0);
|
||||
break;
|
||||
case AttributeUsage.Color0:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void MakeDefaultCurrent()
|
||||
{
|
||||
MakeContextCurrent(this.GraphicsContext,OffscreenNativeWindow.WindowInfo);
|
||||
}
|
||||
|
||||
internal void MakeContextCurrent(IGraphicsContext context, global::OpenTK.Platform.IWindowInfo windowInfo)
|
||||
{
|
||||
context.MakeCurrent(windowInfo);
|
||||
PurgeStateCache();
|
||||
}
|
||||
|
||||
void CreateRenderStates()
|
||||
{
|
||||
_rsBlendNoneVerbatim = new CacheBlendState(
|
||||
false,
|
||||
BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero,
|
||||
BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
|
||||
|
||||
_rsBlendNoneOpaque = new CacheBlendState(
|
||||
false,
|
||||
BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero,
|
||||
BlendingFactorSrc.ConstantAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
|
||||
|
||||
_rsBlendNormal = new CacheBlendState(
|
||||
true,
|
||||
BlendingFactorSrc.SrcAlpha, BlendEquationMode.FuncAdd, BlendingFactorDest.OneMinusSrcAlpha,
|
||||
BlendingFactorSrc.One, BlendEquationMode.FuncAdd, BlendingFactorDest.Zero);
|
||||
}
|
||||
|
||||
CacheBlendState _rsBlendNoneVerbatim, _rsBlendNoneOpaque, _rsBlendNormal;
|
||||
|
||||
//state caches
|
||||
int sActiveTexture;
|
||||
VertexLayout sStateCurrentVertexLayout;
|
||||
VertexLayout sStatePendingVertexLayout;
|
||||
HashSet<int> sVertexAttribEnables = new HashSet<int>();
|
||||
void PurgeStateCache()
|
||||
{
|
||||
sStateCurrentVertexLayout = null;
|
||||
sStatePendingVertexLayout = null;
|
||||
sVertexAttribEnables.Clear();
|
||||
sActiveTexture = -1;
|
||||
}
|
||||
|
||||
} //class IGL_Vulkan
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
|
||||
namespace BizHawk.Bizware.BizwareGL.Drivers.Vulkan
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles RetroArch's GLSL shader pass format
|
||||
/// This isnt implemented in BizwareGL abstract layer because it relies too much on GLSL peculiarities
|
||||
/// </summary>
|
||||
public class RetroShader : IDisposable
|
||||
{
|
||||
public RetroShader(IGL owner, string source, bool debug = false)
|
||||
{
|
||||
Owner = owner as IGL_TK;
|
||||
|
||||
VertexLayout = owner.CreateVertexLayout();
|
||||
VertexLayout.DefineVertexAttribute("VertexCoord", 0, 4, VertexAttribPointerType.Float, AttributeUsage.Unspecified, false, 40, 0); //VertexCoord
|
||||
VertexLayout.DefineVertexAttribute("ColorShit", 1, 4, VertexAttribPointerType.Float, AttributeUsage.Unspecified, false, 40, 16); //COLOR
|
||||
VertexLayout.DefineVertexAttribute("TexCoord", 2, 2, VertexAttribPointerType.Float, AttributeUsage.Unspecified, false, 40, 32); //TexCoord (is this vec2 or vec4? the glsl converted from cg had vec4 but the cg had vec2...)
|
||||
VertexLayout.Close();
|
||||
|
||||
string vsSource = "#define VERTEX\r\n" + source;
|
||||
string psSource = "#define FRAGMENT\r\n" + source;
|
||||
var vs = Owner.CreateVertexShader(vsSource, debug);
|
||||
var ps = Owner.CreateFragmentShader(psSource, debug);
|
||||
Pipeline = Owner.CreatePipeline(VertexLayout, vs, ps, debug);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Pipeline.Dispose();
|
||||
}
|
||||
|
||||
public void Bind()
|
||||
{
|
||||
//lame...
|
||||
Owner.BindPipeline(Pipeline);
|
||||
}
|
||||
|
||||
public unsafe void Run(Texture2d tex, Size InputSize, Size OutputSize, bool flip)
|
||||
{
|
||||
//ack! make sure to set the pipeline before setting
|
||||
Bind();
|
||||
|
||||
Pipeline["InputSize"].Set(new Vector2(InputSize.Width,InputSize.Height));
|
||||
Pipeline["TextureSize"].Set(new Vector2(InputSize.Width, InputSize.Height));
|
||||
Pipeline["OutputSize"].Set(new Vector2(OutputSize.Width, OutputSize.Height));
|
||||
Pipeline["FrameCount"].Set(0); //todo
|
||||
Pipeline["FrameDirection"].Set(1); //todo
|
||||
|
||||
var Projection = Owner.CreateGuiProjectionMatrix(OutputSize);
|
||||
var Modelview = Owner.CreateGuiViewMatrix(OutputSize);
|
||||
Pipeline["MVPMatrix"].Set(Modelview * Projection, false);
|
||||
|
||||
Owner.SetTextureWrapMode(tex, true);
|
||||
|
||||
Pipeline["Texture"].Set(tex);
|
||||
Owner.SetViewport(OutputSize);
|
||||
|
||||
int w = OutputSize.Width;
|
||||
int h = OutputSize.Height;
|
||||
float v0,v1;
|
||||
if (flip) { v0 = 1; v1 = 0; }
|
||||
else { v0 = 0; v1 = 1; }
|
||||
float* pData = stackalloc float[10*4];
|
||||
int i=0;
|
||||
pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; pData[i++] = 1; //topleft vert
|
||||
pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; //junk
|
||||
pData[i++] = 0; pData[i++] = v0; //texcoord
|
||||
pData[i++] = w; pData[i++] = 0; pData[i++] = 0; pData[i++] = 1; //topright vert
|
||||
pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; //junk
|
||||
pData[i++] = 1; pData[i++] = v0; //texcoord
|
||||
pData[i++] = 0; pData[i++] = h; pData[i++] = 0; pData[i++] = 1; //bottomleft vert
|
||||
pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; //junk
|
||||
pData[i++] = 0; pData[i++] = v1; //texcoord
|
||||
pData[i++] = w; pData[i++] = h; pData[i++] = 0; pData[i++] = 1; //bottomright vert
|
||||
pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; pData[i++] = 0; //junk
|
||||
pData[i++] = 1; pData[i++] = v1; //texcoord
|
||||
|
||||
Owner.SetBlendState(Owner.BlendNoneCopy);
|
||||
Owner.BindArrayData(pData);
|
||||
Owner.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
|
||||
}
|
||||
|
||||
|
||||
public IGL_TK Owner { get; private set; }
|
||||
|
||||
VertexLayout VertexLayout;
|
||||
public Pipeline Pipeline;
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@
|
|||
<CodeAnalysisIgnoreBuiltInRules>false</CodeAnalysisIgnoreBuiltInRules>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="OpenTK, Version=1.1.0.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\References\OpenTK.dll</HintPath>
|
||||
</Reference>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
cd "$(dirname "$0")/.." && msbuild BizHawk.sln
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
cd "$(dirname "$0")/.." && msbuild /p:Configuration=Release BizHawk.sln
|
Binary file not shown.
12
subwcrev.sh
12
subwcrev.sh
|
@ -1,12 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd "`dirname "$0"`"
|
||||
|
||||
if test -d .git ; then
|
||||
REV="`git svn info | grep Revision: | cut -d' ' -f2`"
|
||||
else
|
||||
REV="`svn info | grep Revision: | cut -d' ' -f2`"
|
||||
fi
|
||||
|
||||
sed -e 's/\$WCREV\$/'$REV'/g' "$1/Properties/svnrev_template" > "$1/Properties/svnrev.cs.tmp"
|
||||
cmp -s "$1/Properties/svnrev.cs.tmp" "$1/Properties/svnrev.cs" || cp "$1/Properties/svnrev.cs.tmp" "$1/Properties/svnrev.cs"
|
Loading…
Reference in New Issue