This commit is contained in:
2022-07-14 20:03:21 +02:00
parent a616970ce2
commit ff996e8e55
21 changed files with 1310 additions and 157 deletions

View File

@@ -7,6 +7,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImguiBase", "ImguiBase\Imgu
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImguiNodes", "ImguiNodes\ImguiNodes.vcxproj", "{BE4E5CFB-C93F-41D7-B474-721AD43D51A3}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AsuroTool", "AsuroTool\AsuroTool.vcxproj", "{CC10396F-B66E-4240-844F-BEDCDD94E88E}"
ProjectSection(ProjectDependencies) = postProject
{BB8A1CA3-7660-49E9-BAF1-A99F733F7DB6} = {BB8A1CA3-7660-49E9-BAF1-A99F733F7DB6}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -31,6 +36,14 @@ Global
{BE4E5CFB-C93F-41D7-B474-721AD43D51A3}.Release|x64.Build.0 = Release|x64
{BE4E5CFB-C93F-41D7-B474-721AD43D51A3}.Release|x86.ActiveCfg = Release|Win32
{BE4E5CFB-C93F-41D7-B474-721AD43D51A3}.Release|x86.Build.0 = Release|Win32
{CC10396F-B66E-4240-844F-BEDCDD94E88E}.Debug|x64.ActiveCfg = Debug|x64
{CC10396F-B66E-4240-844F-BEDCDD94E88E}.Debug|x64.Build.0 = Debug|x64
{CC10396F-B66E-4240-844F-BEDCDD94E88E}.Debug|x86.ActiveCfg = Debug|Win32
{CC10396F-B66E-4240-844F-BEDCDD94E88E}.Debug|x86.Build.0 = Debug|Win32
{CC10396F-B66E-4240-844F-BEDCDD94E88E}.Release|x64.ActiveCfg = Release|x64
{CC10396F-B66E-4240-844F-BEDCDD94E88E}.Release|x64.Build.0 = Release|x64
{CC10396F-B66E-4240-844F-BEDCDD94E88E}.Release|x86.ActiveCfg = Release|Win32
{CC10396F-B66E-4240-844F-BEDCDD94E88E}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -0,0 +1,9 @@
#include "ApplicationData.h"
AudioDevice::~AudioDevice()
{
if (device)
{
//device->Release();
}
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <string>
#include <vector>
class AudioDevice {
public:
IMMDevice* device = nullptr;
IAudioEndpointVolume* volumeInterface = nullptr;
IAudioMeterInformation* meterInterface = nullptr;
std::wstring id = {};
std::string name = {};
unsigned long state = {};
bool isDefaultConsole = {};
bool isDefaultMedia = {};
bool isDefaultCommunication = {};
~AudioDevice();
};
class ApplicationSettings {
public:
bool showDisabledDevices = false;
bool fitWindowHeight = true;
};
class ApplicationData {
public:
ApplicationSettings settings = {};
std::vector<AudioDevice> playbackDevices = {};
std::vector<AudioDevice> recordingDevices = {};
};

217
AsuroTool/AsuroTool.cpp Normal file
View File

@@ -0,0 +1,217 @@
//Disables console window
#if !_DEBUG
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#endif
#include <windows.h>
#include <iostream>
#include <sstream>
#include "Util.h"
#include "AudioApi.h"
#include "resource.h"
#include "AsuroTool.h"
int main()
{
ApplicationData applicationData{};
startImgui(&applicationData, init, draw, "Asuro's Tool", 600, 400);
}
void init(DrawData& drawData, void* customData)
{
ApplicationData* appData = static_cast<ApplicationData*>(customData);
// Load text font
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 18.0f);
// Load icon font
static const ImWchar icons_ranges[] = { 0xEA01, 0xF2DF, 0 };
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.PixelSnapH = true;
io.Fonts->AddFontFromFileTTF("remixicon.ttf", 14.0f, &icons_config, icons_ranges);
// Set up audio device api
HRESULT initResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
isError(initResult, "Failed to initialize COM: ");
reloadDeviceLists(appData);
// Set window icon
LPWSTR iconId = MAKEINTRESOURCE(IDI_ICON1);
HANDLE iconLarge = LoadImageW(GetModuleHandle(NULL), iconId, IMAGE_ICON, 64, 64, 0);
HANDLE iconSmall = LoadImageW(GetModuleHandle(NULL), iconId, IMAGE_ICON, 32, 32, 0);
SendMessage(drawData.window_handle, WM_SETICON, ICON_BIG, (LPARAM)iconLarge);
SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL, (LPARAM)iconSmall);
SendMessage(drawData.window_handle, WM_SETICON, ICON_SMALL2, (LPARAM)iconSmall);
}
void draw(DrawData& drawData, void* customData)
{
ApplicationData* appData = static_cast<ApplicationData*>(customData);
float customYCursor = 0;
customYCursor += menuBar(appData).y;
ImVec2 containingSize = ImGui::GetMainViewport()->Size;
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(containingSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData->playbackDevices, " \xEE\xB8\x84 Playback").y;
customYCursor += 5.;
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(containingSize.x, 0));
customYCursor += audioDeviceWindow(appData, appData->recordingDevices, " \xEE\xBD\x8F Recording").y;
if (appData->settings.fitWindowHeight)
{
drawData.window_size.y = customYCursor;
}
}
ImVec2 menuBar(ApplicationData* appData)
{
ImVec2 size{};
if (ImGui::BeginMainMenuBar())
{
if (ImGui::BeginMenu("Settings"))
{
ImGui::Checkbox("Show Disabled Devices", &appData->settings.showDisabledDevices);
ImGui::Checkbox("Fit Window Height", &appData->settings.fitWindowHeight);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Debug"))
{
if (ImGui::Button("Manual Refresh"))
{
reloadDeviceLists(appData);
}
ImGui::EndMenu();
}
size = ImGui::GetWindowSize();
ImGui::EndMainMenuBar();
}
return size;
}
bool customButton(const char* id_start, const char* id_end, const char* title, bool visible)
{
std::string buttonId(id_start);
buttonId.append(id_end);
bool result = false;
if (visible)
{
ImGui::PushID(buttonId.c_str());
result = ImGui::SmallButton(title);
ImGui::PopID();
}
else
{
ImGui::InvisibleButton(buttonId.c_str(), ImGui::CalcTextSize(title));
}
return result;
}
ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& deviceList, const char* title)
{
if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize))
{
if (ImGui::BeginTable("DeviceTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_NoSavedSettings))
{
ImGui::TableSetupColumn("Devices", ImGuiTableColumnFlags_WidthStretch, 3.);
ImGui::TableSetupColumn("Volume", ImGuiTableColumnFlags_WidthStretch, 1.);
ImGui::TableSetupColumn("Defaults", ImGuiTableColumnFlags_WidthFixed, 55.);
ImGui::TableHeadersRow();
for (auto& dev : deviceList)
{
std::string deviceIdUtf8 = utf8Encode(dev.id);
if (dev.state == DEVICE_STATE_ACTIVE)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1., 1., 1., 1.));
}
else if (!appData->settings.showDisabledDevices)
{
continue;
}
else
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.7, .7, .7, 1.));
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text(dev.name.c_str());
ImGui::TableNextColumn();
if (dev.state == DEVICE_STATE_ACTIVE)
{
float volume = log10f(getMeterValue(dev.meterInterface) * 9. + 1.);
auto drawList = ImGui::GetWindowDrawList();
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
ImVec2 space = ImGui::GetContentRegionAvail();
float lineY = cursorPos.y + ImGui::GetTextLineHeight() / 2.;
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + space.x, lineY), IM_COL32(120, 120, 120, 255), 2.);
drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + space.x * volume, lineY), IM_COL32(200, 200, 255, 255), 3.);
}
ImGui::TableNextColumn();
if (dev.state == DEVICE_STATE_ACTIVE)
{
if (dev.isDefaultConsole)
{
drawCircle(5, IM_COL32(50, 50, 222, 255));
}
if (customButton("bn_d_", deviceIdUtf8.c_str(), "\xEE\xBE\x82", !dev.isDefaultConsole))
{
setDefaultAudioDevice(appData, dev.id.c_str(), ERole::eConsole);
}
ImGui::SameLine();
if (dev.isDefaultCommunication)
{
drawCircle(5, IM_COL32(222, 50, 50, 255));
}
if (customButton("bn_c_", deviceIdUtf8.c_str(), "\xEE\xBF\xA9", !dev.isDefaultCommunication))
{
setDefaultAudioDevice(appData, dev.id.c_str(), ERole::eCommunications);
}
}
ImGui::PopStyleColor();
}
ImGui::EndTable();
}
}
ImVec2 size = ImGui::GetWindowSize();
ImGui::End();
return size;
}
void drawCircle(float radius, ImU32 color)
{
ImGui::Dummy(ImVec2(0, 0));
ImGui::SameLine();
auto drawList = ImGui::GetWindowDrawList();
ImVec2 cursorPos = ImGui::GetCursorPos();
ImVec2 windowPos = ImGui::GetWindowPos();
drawList->AddCircleFilled(ImVec2(cursorPos.x + windowPos.x, cursorPos.y + windowPos.y + ImGui::GetTextLineHeight() / 2.), radius, color);
}

12
AsuroTool/AsuroTool.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <vector>
#include "ImguiBase.h"
#include "ApplicationData.h"
void init(DrawData& drawData, void* customData);
void draw(DrawData& drawData, void* customData);
ImVec2 menuBar(ApplicationData* appData);
ImVec2 audioDeviceWindow(ApplicationData* appData, std::vector<AudioDevice>& deviceList, const char* title);
void drawCircle(float radius, ImU32 color);

81
AsuroTool/AsuroTool.rc Normal file
View File

@@ -0,0 +1,81 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// English (United Kingdom) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
#pragma code_page(1252)
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "kaiju.ico"
#endif // English (United Kingdom) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

202
AsuroTool/AsuroTool.vcxproj Normal file
View File

@@ -0,0 +1,202 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{cc10396f-b66e-4240-844f-bedcdd94e88e}</ProjectGuid>
<RootNamespace>AsuroTool</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>E:\Code\AsuroImgui\ImguiBase;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>E:\Code\AsuroImgui\ImguiBase;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>E:\Code\AsuroImgui\ImguiBase;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>E:\Code\AsuroImgui\ImguiBase;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="ApplicationData.cpp" />
<ClCompile Include="AsuroTool.cpp" />
<ClCompile Include="Util.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ApplicationData.h" />
<ClInclude Include="AsuroTool.h" />
<ClCompile Include="AudioApi.cpp" />
<ClInclude Include="AudioApi.h" />
<ClInclude Include="PolicyConfig.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="Util.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ImguiBase\ImguiBase.vcxproj">
<Project>{bb8a1ca3-7660-49e9-baf1-a99f733f7db6}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="Montserrat-Regular.ttf">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
<FileType>Document</FileType>
</CopyFileToFolders>
<CopyFileToFolders Include="remixicon.ttf">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</DeploymentContent>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</ExcludedFromBuild>
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</DeploymentContent>
<FileType>Document</FileType>
</CopyFileToFolders>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="AsuroTool.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="d:\nextcloud\dragon collection\profile pics\icon2.ico" />
<Image Include="d:\nextcloud\dragon collection\profile pics\icon3.ico" />
<Image Include="D:\Nextcloud\Dragon Collection\Profile Pics\kaiju.ico" />
<Image Include="kaiju.ico" />
<Image Include="kaiju32.png" />
<Image Include="kaiju64.png" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AsuroTool.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AudioApi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ApplicationData.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AsuroTool.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Util.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PolicyConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AudioApi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ApplicationData.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="Montserrat-Regular.ttf">
<Filter>Resource Files</Filter>
</CopyFileToFolders>
<CopyFileToFolders Include="remixicon.ttf">
<Filter>Resource Files</Filter>
</CopyFileToFolders>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="AsuroTool.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Image Include="d:\nextcloud\dragon collection\profile pics\icon2.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="kaiju64.png">
<Filter>Resource Files</Filter>
</Image>
<Image Include="d:\nextcloud\dragon collection\profile pics\icon3.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="D:\Nextcloud\Dragon Collection\Profile Pics\kaiju.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="kaiju32.png">
<Filter>Resource Files</Filter>
</Image>
<Image Include="kaiju.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>

172
AsuroTool/AudioApi.cpp Normal file
View File

@@ -0,0 +1,172 @@
#include <windows.h>
#include <functiondiscoverykeys.h>
#include <endpointvolume.h>
#include <vector>
#include <algorithm>
#include "ImguiBase.h"
#include "Util.h"
#include "AudioApi.h"
#include "PolicyConfig.h"
HRESULT getDeviceProperty(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData)
{
PropVariantInit(outData);
HRESULT nameResult = propertyStore->GetValue(propertyKey, outData);
return nameResult;
}
HRESULT getDevicePropertyString(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData, const wchar_t*& outString, const wchar_t* defaultStr)
{
HRESULT result = getDeviceProperty(propertyStore, propertyKey, outData);
outString = outData->vt != VT_LPWSTR ? defaultStr : outData->pwszVal;
return result;
}
void setDefaultAudioDevice(ApplicationData* appData, const wchar_t* deviceId, ERole role)
{
IPolicyConfigVista* pPolicyConfig;
HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigVistaClient), NULL, CLSCTX_ALL, __uuidof(IPolicyConfigVista), (LPVOID*)&pPolicyConfig);
if (!isError(hr, "Failed to set default audio device: "))
{
hr = pPolicyConfig->SetDefaultEndpoint(deviceId, role);
pPolicyConfig->Release();
reloadDeviceLists(appData);
}
}
void loadAudioDevices(std::vector<AudioDevice>& deviceList, EDataFlow deviceType)
{
deviceList.clear();
HRESULT err;
IMMDeviceEnumerator* deviceEnumerator = NULL;
IMMDeviceCollection* deviceCollection = NULL;
err = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator));
if (isError(err, "Failed to set up audio device enumerator: ")) return;
err = deviceEnumerator->EnumAudioEndpoints(deviceType, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &deviceCollection);
if (isError(err, "Failed to enumerate audio devices: ")) return;
UINT deviceCount;
err = deviceCollection->GetCount(&deviceCount);
if (isError(err, "Failed to count audio devices: ")) return;
IMMDevice* defaultConsoleDevice = NULL;
LPWSTR defaultConsoleId = nullptr;
err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eConsole, &defaultConsoleDevice);
if (!FAILED(err))
{
defaultConsoleDevice->GetId(&defaultConsoleId);
}
IMMDevice* defaultMediaOutput = NULL;
LPWSTR defaultMediaId = nullptr;
err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eMultimedia, &defaultMediaOutput);
if (!FAILED(err))
{
defaultMediaOutput->GetId(&defaultMediaId);
}
IMMDevice* defaultCommunicationOutput = NULL;
LPWSTR defaultCommunicationId = nullptr;
err = deviceEnumerator->GetDefaultAudioEndpoint(deviceType, ERole::eCommunications, &defaultCommunicationOutput);
if (!FAILED(err))
{
defaultCommunicationOutput->GetId(&defaultCommunicationId);
}
for (UINT i = 0; i < deviceCount; i += 1)
{
IMMDevice* device;
err = deviceCollection->Item(i, &device);
isError(err, std::stringstream("Failed to get device ") << i << ": ");
LPWSTR deviceId;
err = device->GetId(&deviceId);
isError(err, std::stringstream("Failed to get device id ") << i << ": ");
IPropertyStore* propertyStore;
err = device->OpenPropertyStore(STGM_READ, &propertyStore);
isError(err, std::stringstream("Failed to open device ") << i << "prop store: ");
PROPVARIANT deviceNameProp;
const wchar_t* deviceName;
err = getDevicePropertyString(propertyStore, PKEY_Device_FriendlyName, &deviceNameProp, deviceName);
isError(err, std::stringstream("Failed to read name of device ") << i << ": ");
DWORD deviceState;
err = device->GetState(&deviceState);
isError(err, std::stringstream("Failed to reat state of device ") << i << ": ");
IAudioEndpointVolume* volumeInterface;
err = device->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&volumeInterface);
isError(err, "Failed to get audio endpoint volume interface: ");
IAudioMeterInformation* meterInterface;
err = device->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID*)&meterInterface);
isError(err, "Failed to get audio meter interface: ");
deviceList.push_back({
device,
volumeInterface,
meterInterface,
std::wstring(deviceId),
utf8Encode(deviceName),
deviceState,
utf8Encode(defaultConsoleId) == utf8Encode(deviceId),
utf8Encode(defaultMediaId) == utf8Encode(deviceId),
utf8Encode(defaultCommunicationId) == utf8Encode(deviceId),
});
// Free stuff
if (propertyStore)
{
propertyStore->Release();
}
CoTaskMemFree(deviceId);
}
if (deviceEnumerator)
{
deviceEnumerator->Release();
}
if (deviceCollection)
{
deviceCollection->Release();
}
std::sort(deviceList.begin(), deviceList.end(), [](AudioDevice& a, AudioDevice& b) { return a.state < b.state; });
}
void reloadDeviceLists(ApplicationData* appData)
{
loadAudioDevices(appData->playbackDevices, EDataFlow::eRender);
loadAudioDevices(appData->recordingDevices, EDataFlow::eCapture);
}
float getVolume(IAudioEndpointVolume* volumeInterface)
{
float volume;
if (FAILED(volumeInterface->GetChannelVolumeLevel(0, &volume)))
{
volume = 0.;
}
return volume;
}
float getMeterValue(IAudioMeterInformation* meterInterface)
{
float volume;
if (FAILED(meterInterface->GetPeakValue(&volume)))
{
volume = 0.;
}
return volume;
}

13
AsuroTool/AudioApi.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <mmdeviceapi.h>
#include "ApplicationData.h"
HRESULT getDeviceProperty(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData);
HRESULT getDevicePropertyString(IPropertyStore* propertyStore, const PROPERTYKEY propertyKey, PROPVARIANT* outData, const wchar_t*& outString, const wchar_t* defaultStr = L"Unknown");
void setDefaultAudioDevice(ApplicationData* appData, const wchar_t* deviceId, ERole role);
void loadAudioDevices(std::vector<AudioDevice>& deviceList, EDataFlow deviceType);
void reloadDeviceLists(ApplicationData* appData);
float getVolume(IAudioEndpointVolume* volumeInterface);
float getMeterValue(IAudioMeterInformation* meterInterface);

Binary file not shown.

201
AsuroTool/PolicyConfig.h Normal file
View File

@@ -0,0 +1,201 @@
/* The MIT License (MIT)
Copyright (c) 2015 DefSound
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// ----------------------------------------------------------------------------
// PolicyConfig.h
// Undocumented COM-interface IPolicyConfig.
// Use for set default audio render endpoint
// @author EreTIk
// ----------------------------------------------------------------------------
#pragma once
#include "mmreg.h"
interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigClient
// {870af99c-171d-4f9e-af0d-e63df40c2bc9}
//
// interface IPolicyConfig
// {f8679f50-850a-41cf-9c72-430f290290c8}
//
// Query interface:
// CComPtr<IPolicyConfig> PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient));
//
// @compatible: Windows 7 and Later
// ----------------------------------------------------------------------------
interface IPolicyConfig : public IUnknown
{
public:
virtual HRESULT GetMixFormat(
PCWSTR,
WAVEFORMATEX **
);
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **
);
virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
PCWSTR
);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *
);
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR,
INT,
PINT64,
PINT64
);
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR,
PINT64
);
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
PCWSTR,
struct DeviceShareMode *
);
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
PCWSTR,
struct DeviceShareMode *
);
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *
);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *
);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
__in PCWSTR wszDeviceId,
__in ERole eRole
);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR,
INT
);
};
interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620") IPolicyConfigVista;
class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862") CPolicyConfigVistaClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigVistaClient
// {294935CE-F637-4E7C-A41B-AB255460B862}
//
// interface IPolicyConfigVista
// {568b9108-44bf-40b4-9006-86afe5b5a620}
//
// Query interface:
// CComPtr<IPolicyConfigVista> PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient));
//
// @compatible: Windows Vista and Later
// ----------------------------------------------------------------------------
interface IPolicyConfigVista : public IUnknown
{
public:
virtual HRESULT GetMixFormat(
PCWSTR,
WAVEFORMATEX **
); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
PCWSTR,
INT,
WAVEFORMATEX **
);
virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
PCWSTR,
WAVEFORMATEX *,
WAVEFORMATEX *
);
virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
PCWSTR,
INT,
PINT64,
PINT64
); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
PCWSTR,
PINT64
); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetShareMode(
PCWSTR,
struct DeviceShareMode *
); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE SetShareMode(
PCWSTR,
struct DeviceShareMode *
); // not available on Windows 7, use method from IPolicyConfig
virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *
);
virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
PCWSTR,
const PROPERTYKEY &,
PROPVARIANT *
);
virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
__in PCWSTR wszDeviceId,
__in ERole eRole
);
virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
PCWSTR,
INT
); // not available on Windows 7, use method from IPolicyConfig
};
// ----------------------------------------------------------------------------

31
AsuroTool/Util.cpp Normal file
View File

@@ -0,0 +1,31 @@
#include <iostream>
#include "Util.h"
bool isError(const HRESULT result, const std::stringstream message)
{
if (FAILED(result))
{
std::cout << message.str() << std::hex << result << std::endl;
return true;
}
return false;
}
bool isError(const HRESULT result, const char* message)
{
return isError(result, std::stringstream(message));
}
std::string utf8Encode(const std::wstring& wstr)
{
if (wstr.empty())
{
return std::string();
}
int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
std::string resultString(sizeNeeded, 0);
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &resultString[0], sizeNeeded, NULL, NULL);
return resultString;
}

8
AsuroTool/Util.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <windows.h>
#include <sstream>
bool isError(const HRESULT result, const std::stringstream message);
bool isError(const HRESULT result, const char* message);
std::string utf8Encode(const std::wstring& wstr);

BIN
AsuroTool/kaiju.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

BIN
AsuroTool/remixicon.ttf Normal file

Binary file not shown.

16
AsuroTool/resource.h Normal file
View File

@@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by AsuroTool.rc
//
#define IDI_ICON1 109
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 112
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

View File

@@ -5,7 +5,9 @@
#include <iostream>
#define GLFW_INCLUDE_NONE
#define GLFW_INCLUDE_VULKAN
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <vulkan/vulkan.h>
static VkAllocationCallbacks* g_Allocator = NULL;
@@ -329,7 +331,7 @@ static void glfw_error_callback(int error, const char* description)
fprintf(stderr, "Glfw Error %d: %s\n", error, description);
}
int startImgui(void* customState, void (*const initFunc)(void), void (*const drawFunc)(DrawData&, void*))
int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void (*const drawFunc)(DrawData&, void*), const char* title, int windowWidth, int windowHeight)
{
// Setup GLFW window
glfwSetErrorCallback(glfw_error_callback);
@@ -337,7 +339,7 @@ int startImgui(void* customState, void (*const initFunc)(void), void (*const dra
return 1;
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+Vulkan example", NULL, NULL);
GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, title, NULL, NULL);
// Setup Vulkan
if (!glfwVulkanSupported())
@@ -404,7 +406,12 @@ int startImgui(void* customState, void (*const initFunc)(void), void (*const dra
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
//IM_ASSERT(font != NULL);
initFunc();
// Our state
DrawData drawData{};
drawData.window_handle = glfwGetWin32Window(window);
drawData.window_size = getWindowSize(window);
initFunc(drawData, customState);
// Upload Fonts
{
@@ -436,9 +443,6 @@ int startImgui(void* customState, void (*const initFunc)(void), void (*const dra
ImGui_ImplVulkan_DestroyFontUploadObjects();
}
// Our state
DrawData drawData{};
// Main loop
while (!glfwWindowShouldClose(window))
{
@@ -468,8 +472,16 @@ int startImgui(void* customState, void (*const initFunc)(void), void (*const dra
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImVec2 prevWindowSize = getWindowSize(window);
drawData.window_size = prevWindowSize;
drawFunc(drawData, customState);
if (drawData.window_size.x != prevWindowSize.x || drawData.window_size.y != prevWindowSize.y)
{
glfwSetWindowSize(window, drawData.window_size.x, drawData.window_size.y);
}
// Rendering
ImGui::Render();
ImDrawData* draw_data = ImGui::GetDrawData();
@@ -500,3 +512,11 @@ int startImgui(void* customState, void (*const initFunc)(void), void (*const dra
return 0;
}
ImVec2 getWindowSize(GLFWwindow* window)
{
int window_width;
int window_height;
glfwGetWindowSize(window, &window_width, &window_height);
return ImVec2(window_width, window_height);
}

View File

@@ -1,10 +1,16 @@
#pragma once
#include <Windows.h>
#include "imgui_impl_glfw.h"
#include "imgui/headers/imgui.h"
class DrawData {
public:
ImVec4 clear_color;
HWND window_handle = nullptr;
ImVec4 clear_color = {};
ImVec2 window_size = {};
};
int startImgui(void* customState, void (*const initFunc)(void), void (* const drawFunc)(DrawData&, void*));
int startImgui(void* customState, void (*const initFunc)(DrawData&, void*), void (* const drawFunc)(DrawData&, void*), const char* title, int windowWidth, int windowHeight);
ImVec2 getWindowSize(GLFWwindow* window);

View File

@@ -10,15 +10,15 @@
int main()
{
CustomDrawData customDrawData{};
ApplicationData applicationData{};
startImgui(&customDrawData, init, draw);
startImgui(&applicationData, init, draw, "Node Test", 1280, 720);
}
/// <summary>
/// Setup before first draw.
/// </summary>
void init()
void init(DrawData& drawData, void* customData)
{
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("Montserrat-Regular.ttf", 16.0f);
@@ -27,118 +27,120 @@ void init()
/// <summary>
/// Draw main application content.
/// </summary>
void draw(DrawData& drawData, void* customDataVoid)
void draw(DrawData& drawData, void* customData)
{
CustomDrawData* customData = static_cast<CustomDrawData*>(customDataVoid);
bool isDraggingLine = false;
ImVec2 lineStartPos = ImVec2{};
ApplicationData* applicationData = static_cast<ApplicationData*>(customData);
// Top menu bar
ImGui::BeginMainMenuBar();
if (ImGui::Button("Add Node"))
{
std::string name{ "Node " };
name.append(std::to_string(customData->nodes.size() + 1));
customData->nodes.push_back(NodeWindow(name));
name.append(std::to_string(applicationData->nodes.size() + 1));
applicationData->nodes.push_back(NodeWindow(name));
}
ImGui::EndMainMenuBar();
// Draw all node windows
auto nodeIterator = customData->nodes.begin();
while (nodeIterator != customData->nodes.end())
bool isDraggingLine = false;
ImVec2 lineStartPos = ImVec2{};
auto nodeIterator = applicationData->nodes.begin();
while (nodeIterator != applicationData->nodes.end())
{
NodeWindow& node = *nodeIterator;
bool nodeOpen;
ImGui::Begin(node.title.c_str(), &nodeOpen, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize);
if (!nodeOpen)
{
auto triggerIterator = node.triggers.begin();
while (triggerIterator != node.triggers.end())
if (!nodeOpen)
{
triggerIterator = cleanEraseElement(node.triggers, triggerIterator, customData);
cleanEreaseNodeElements(*nodeIterator, applicationData);
nodeIterator = applicationData->nodes.erase(nodeIterator);
ImGui::End();
break; // TODO: contine creates bug with closing too many windows???
}
auto alertIterator = node.alerts.begin();
while (alertIterator != node.alerts.end())
{
alertIterator = cleanEraseElement(node.alerts, alertIterator, customData);
}
nodeIterator = customData->nodes.erase(nodeIterator);
ImGui::End();
break; // TODO: contine will create problems with closing too many windows?
}
// Expand clip rect to allow circles on edges
ImVec2 contentMin = ImGui::GetWindowPos();
ImVec2 contentMax = ImVec2{ contentMin.x + ImGui::GetWindowWidth(), contentMin.y + ImGui::GetWindowHeight() };
ImGui::PushClipRect(ImVec2{ contentMin.x - 15, contentMin.y }, ImVec2{ contentMax.x + 15, contentMax.y }, false);
auto alertIterator = node.alerts.begin();
while (alertIterator != node.alerts.end())
{
if (drawCircle(0, CircleDragType::Source, alertIterator._Ptr, &alertIterator->position))
{
isDraggingLine = true;
lineStartPos = alertIterator->position;
ImGui::Text(alertIterator->name.c_str());
ConnectionPayload payload{ alertIterator->id };
ImGui::SetDragDropPayload("NODE_CONNECTION", (void*)&payload, sizeof(ConnectionPayload));
ImGui::EndDragDropSource();
}
ImGui::Text(alertIterator->name.c_str());
if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
{
if (inlineButton("x", alertIterator._Ptr))
if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
{
alertIterator = cleanEraseElement(node.alerts, alertIterator, customData);
continue;
if (inlineButton("x", alertIterator._Ptr))
{
alertIterator = cleanEraseElement(node.alerts, alertIterator, applicationData);
continue;
}
}
}
alertIterator++;
}
auto triggerIterator = node.triggers.begin();
while (triggerIterator != node.triggers.end())
{
if (drawCircle(-ImGui::GetStyle().WindowPadding.x, CircleDragType::Target, triggerIterator._Ptr, &triggerIterator->position))
{
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("NODE_CONNECTION"))
else
{
ConnectionPayload* payloadData = static_cast<ConnectionPayload*>(payload->Data);
customData->connections.push_back(NodeConnection { payloadData->sourceID, triggerIterator->id });
invisibleInlineButton("x", "x");
}
ImGui::EndDragDropTarget();
}
ImGui::Text(triggerIterator->name.c_str());
if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
{
if (inlineButton("x", triggerIterator._Ptr))
ImGui::SameLine();
drawCircle(&alertIterator->position);
float lineHeight = ImGui::GetTextLineHeight();
ImVec2 dragAreaSize = ImVec2{ lineHeight, lineHeight };
invisibleDragArea(alertIterator._Ptr, dragAreaSize);
if (ImGui::BeginDragDropSource())
{
triggerIterator = cleanEraseElement(node.triggers, triggerIterator, customData);
continue;
isDraggingLine = true;
lineStartPos = alertIterator->position;
ImGui::Text(alertIterator->name.c_str());
ConnectionPayload payload{ alertIterator->id };
ImGui::SetDragDropPayload("NODE_CONNECTION", (void*)&payload, sizeof(ConnectionPayload));
ImGui::EndDragDropSource();
}
alertIterator++;
}
triggerIterator++;
}
if (ImGui::Button("New Alert"))
{
node.alerts.push_back(NodeElement("Today, 18:00"));
}
if (ImGui::Button("New Trigger"))
{
node.triggers.push_back(NodeElement("Doot"));
}
auto triggerIterator = node.triggers.begin();
while (triggerIterator != node.triggers.end())
{
drawCircle(&triggerIterator->position);
ImGui::PopClipRect();
float lineHeight = ImGui::GetTextLineHeight();
ImVec2 dragAreaSize = ImVec2{ lineHeight, lineHeight };
invisibleDragArea(triggerIterator._Ptr, dragAreaSize);
if (ImGui::BeginDragDropTarget())
{
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("NODE_CONNECTION"))
{
ConnectionPayload* payloadData = static_cast<ConnectionPayload*>(payload->Data);
applicationData->addConnection(payloadData->sourceID, triggerIterator->id);
}
ImGui::EndDragDropTarget();
}
ImGui::SameLine();
ImGui::Text(triggerIterator->name.c_str());
if (ImGui::IsWindowHovered() || ImGui::IsWindowFocused())
{
if (inlineButton("x", triggerIterator._Ptr))
{
triggerIterator = cleanEraseElement(node.triggers, triggerIterator, applicationData);
continue;
}
}
triggerIterator++;
}
if (ImGui::Button("New Alert"))
{
node.alerts.push_back(NodeElement("Today, 18:00"));
}
if (ImGui::Button("New Trigger"))
{
node.triggers.push_back(NodeElement("Doot"));
}
}
ImGui::End();
nodeIterator++;
}
@@ -151,11 +153,11 @@ void draw(DrawData& drawData, void* customDataVoid)
// Draw connected lines in background
ImDrawList* bgDrawList = ImGui::GetBackgroundDrawList();
for (NodeConnection& nodeConnection : customData->connections)
for (NodeConnection& nodeConnection : applicationData->connections)
{
ImVec2 sourcePos = ImVec2{ 0, 0 };
ImVec2 targetPos = ImVec2{ 0, 0 };
for (auto node : customData->nodes)
for (auto node : applicationData->nodes)
{
for (auto alert : node.alerts)
{
@@ -177,42 +179,35 @@ void draw(DrawData& drawData, void* customDataVoid)
}
/// <summary>
/// Draws a small circle in the same row.
/// Creates an invisible button that does nothing, but can be used for drag/drop operations.
/// </summary>
/// <param name="outCirclePos">Returns the position of the circle.</param>
/// <returns>True if an event matching the dragType has occurred.</returns>
bool drawCircle(float xOffset, CircleDragType dragType, const void* id, ImVec2* outCirclePos)
void invisibleDragArea(const void* id, ImVec2& size)
{
auto cursor = ImGui::GetCursorScreenPos();
ImDrawList* drawList = ImGui::GetWindowDrawList();
float circleRadius = 5;
float yCircleOffset = ImGui::GetTextLineHeight() / 2.;
ImVec2 circlePos = ImVec2{ cursor.x + xOffset, cursor.y + yCircleOffset };
drawList->AddCircleFilled(circlePos, 5, IM_COL32_WHITE);
ImVec2 originalPos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(xOffset);
ImGui::PushID(id);
ImGui::InvisibleButton("", ImVec2{ 15, 15 });
ImGui::InvisibleButton("", size);
ImGui::PopID();
ImGui::SetCursorPos(originalPos);
}
if (outCirclePos != nullptr)
{
*outCirclePos = circlePos;
}
/// <summary>
/// Draws a small circle at the current cursor position.
/// </summary>
/// <param name="outCircleCenter">Returns the center position of the circle.</param>
void drawCircle(ImVec2* outCircleCenter = nullptr)
{
float lineHeight = ImGui::GetTextLineHeight();
float circleRadius = (lineHeight / 2.) * 0.75;
float circleOffset = lineHeight / 2.;
if (dragType == CircleDragType::Source && ImGui::BeginDragDropSource())
ImVec2 cursor = ImGui::GetCursorScreenPos();
ImVec2 circlePos = ImVec2{ cursor.x + circleOffset, cursor.y + circleOffset };
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddCircleFilled(circlePos, circleRadius, IM_COL32_WHITE);
if (outCircleCenter != nullptr)
{
return true;
*outCircleCenter = circlePos;
}
if (dragType == CircleDragType::Target && ImGui::BeginDragDropTarget())
{
return true;
}
return false;
}
/// <summary>
@@ -222,23 +217,27 @@ bool inlineButton(const char* title, const void* id)
{
ImGui::SameLine();
ImGui::PushID(id);
if (ImGui::SmallButton(title))
{
ImGui::PopID();
return true;
}
else
{
ImGui::PopID();
return false;
}
bool result = ImGui::SmallButton(title);
ImGui::PopID();
return result;
}
bool invisibleInlineButton(const char* title, const char* id)
{
ImGui::SameLine();
ImVec2 padding = ImGui::GetStyle().FramePadding;
ImVec2 size = ImGui::CalcTextSize(title);
size.x += padding.x * 2.;
return ImGui::InvisibleButton(id, size);
}
/// <summary>
/// Removes any attached connections before erasing this NodeElement from a vector.
/// </summary>
/// <returns>Continued iterator</returns>
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& elements, std::vector<NodeElement>::iterator toErase, CustomDrawData* data)
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& elements, std::vector<NodeElement>::iterator toErase, ApplicationData* data)
{
auto connectionIterator = data->connections.begin();
while (connectionIterator != data->connections.end())
@@ -253,17 +252,46 @@ std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& e
return elements.erase(toErase);
}
NodeElement::NodeElement(std::string name)
void cleanEreaseNodeElements(NodeWindow& node, ApplicationData* data)
{
this->id = nodeIdCounter;
nodeIdCounter++;
this->name = name;
this->position = ImVec2{ 0, 0 };
auto triggerIterator = node.triggers.begin();
while (triggerIterator != node.triggers.end())
{
triggerIterator = cleanEraseElement(node.triggers, triggerIterator, data);
}
auto alertIterator = node.alerts.begin();
while (alertIterator != node.alerts.end())
{
alertIterator = cleanEraseElement(node.alerts, alertIterator, data);
}
}
NodeWindow::NodeWindow(std::string title)
NodeElement::NodeElement(std::string name) : name(name)
{
this->title = title;
this->alerts = std::vector<NodeElement>();
this->triggers = std::vector<NodeElement>();
this->id = globalNodeIdCounter;
globalNodeIdCounter++;
}
NodeWindow::NodeWindow(std::string title) : title(title)
{
}
void ApplicationData::addConnection(int sourceID, int targetID)
{
// Remove duplicate connections
auto connectionIterator = connections.begin();
while (connectionIterator != connections.end())
{
// Currently only remove on exact match, maybe add some other constraints later
if (connectionIterator->sourceID == sourceID && connectionIterator->targetID == targetID)
{
connectionIterator = connections.erase(connectionIterator);
continue;
}
connectionIterator++;
}
// Add new connection
connections.push_back(NodeConnection{ sourceID, targetID });
}

View File

@@ -2,39 +2,41 @@
#include <string>
#include <vector>
int nodeIdCounter = 0;
int globalNodeIdCounter = 0;
class NodeElement {
public:
int id;
std::string name;
ImVec2 position;
int id = -1;
std::string name = {};
ImVec2 position = {};
NodeElement(std::string name);
};
class NodeWindow {
public:
std::string title;
std::vector<NodeElement> alerts;
std::vector<NodeElement> triggers;
std::string title = {};
std::vector<NodeElement> alerts = {};
std::vector<NodeElement> triggers = {};
NodeWindow(std::string title);
};
typedef struct NodeConnection {
int sourceID;
int targetID;
struct NodeConnection {
int sourceID = -1;
int targetID = -1;
};
typedef struct ConnectionPayload {
int sourceID;
struct ConnectionPayload {
int sourceID = -1;
};
class CustomDrawData {
class ApplicationData {
public:
std::vector<NodeWindow> nodes;
std::vector<NodeConnection> connections;
std::vector<NodeWindow> nodes = {};
std::vector<NodeConnection> connections = {};
void addConnection(int sourceID, int targetID);
};
enum class CircleDragType
@@ -44,8 +46,12 @@ enum class CircleDragType
Target
};
void init();
void init(DrawData& drawData, void* customData);
void draw(DrawData& drawData, void* customDataVoid);
bool drawCircle(float xOffset, CircleDragType dragType, const void* id, ImVec2* outCirclePos = nullptr);
void invisibleDragArea(const void* id, ImVec2& size);
void drawCircle(ImVec2* outCircleCenter);
bool inlineButton(const char* title, const void* id);
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& vector, std::vector<NodeElement>::iterator toErase, CustomDrawData* data);
bool invisibleInlineButton(const char* title, const char* id);
std::vector<NodeElement>::iterator cleanEraseElement(std::vector<NodeElement>& vector, std::vector<NodeElement>::iterator toErase, ApplicationData* data);
void cleanEreaseNodeElements(NodeWindow& node, ApplicationData* data);