diff --git a/AsuroImgui.sln b/AsuroImgui.sln index 0939443..608564f 100644 --- a/AsuroImgui.sln +++ b/AsuroImgui.sln @@ -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 diff --git a/AsuroTool/ApplicationData.cpp b/AsuroTool/ApplicationData.cpp new file mode 100644 index 0000000..a19c1cf --- /dev/null +++ b/AsuroTool/ApplicationData.cpp @@ -0,0 +1,9 @@ +#include "ApplicationData.h" + +AudioDevice::~AudioDevice() +{ + if (device) + { + //device->Release(); + } +} diff --git a/AsuroTool/ApplicationData.h b/AsuroTool/ApplicationData.h new file mode 100644 index 0000000..3d42430 --- /dev/null +++ b/AsuroTool/ApplicationData.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + +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 playbackDevices = {}; + std::vector recordingDevices = {}; +}; diff --git a/AsuroTool/AsuroTool.cpp b/AsuroTool/AsuroTool.cpp new file mode 100644 index 0000000..6ed6ae2 --- /dev/null +++ b/AsuroTool/AsuroTool.cpp @@ -0,0 +1,217 @@ +//Disables console window +#if !_DEBUG +#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") +#endif + +#include + +#include +#include + +#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(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(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& 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); +} diff --git a/AsuroTool/AsuroTool.h b/AsuroTool/AsuroTool.h new file mode 100644 index 0000000..31990b5 --- /dev/null +++ b/AsuroTool/AsuroTool.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#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& deviceList, const char* title); +void drawCircle(float radius, ImU32 color); diff --git a/AsuroTool/AsuroTool.rc b/AsuroTool/AsuroTool.rc new file mode 100644 index 0000000..1088a64 --- /dev/null +++ b/AsuroTool/AsuroTool.rc @@ -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 + diff --git a/AsuroTool/AsuroTool.vcxproj b/AsuroTool/AsuroTool.vcxproj new file mode 100644 index 0000000..3bccf41 --- /dev/null +++ b/AsuroTool/AsuroTool.vcxproj @@ -0,0 +1,202 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {cc10396f-b66e-4240-844f-bedcdd94e88e} + AsuroTool + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + E:\Code\AsuroImgui\ImguiBase;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + false + E:\Code\AsuroImgui\ImguiBase;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + true + E:\Code\AsuroImgui\ImguiBase;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + false + E:\Code\AsuroImgui\ImguiBase;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + {bb8a1ca3-7660-49e9-baf1-a99f733f7db6} + + + + + false + true + false + true + false + true + false + true + Document + + + false + true + false + true + false + true + false + true + Document + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AsuroTool/AsuroTool.vcxproj.filters b/AsuroTool/AsuroTool.vcxproj.filters new file mode 100644 index 0000000..0ff0079 --- /dev/null +++ b/AsuroTool/AsuroTool.vcxproj.filters @@ -0,0 +1,84 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + Resource Files + + + + + Resource Files + + + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + \ No newline at end of file diff --git a/AsuroTool/AudioApi.cpp b/AsuroTool/AudioApi.cpp new file mode 100644 index 0000000..fe44616 --- /dev/null +++ b/AsuroTool/AudioApi.cpp @@ -0,0 +1,172 @@ + +#include +#include +#include + +#include +#include + +#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& 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; +} \ No newline at end of file diff --git a/AsuroTool/AudioApi.h b/AsuroTool/AudioApi.h new file mode 100644 index 0000000..e61c70b --- /dev/null +++ b/AsuroTool/AudioApi.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#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& deviceList, EDataFlow deviceType); +void reloadDeviceLists(ApplicationData* appData); +float getVolume(IAudioEndpointVolume* volumeInterface); +float getMeterValue(IAudioMeterInformation* meterInterface); diff --git a/AsuroTool/Montserrat-Regular.ttf b/AsuroTool/Montserrat-Regular.ttf new file mode 100644 index 0000000..1cd0259 Binary files /dev/null and b/AsuroTool/Montserrat-Regular.ttf differ diff --git a/AsuroTool/PolicyConfig.h b/AsuroTool/PolicyConfig.h new file mode 100644 index 0000000..ff6b2ac --- /dev/null +++ b/AsuroTool/PolicyConfig.h @@ -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 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 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 +}; + +// ---------------------------------------------------------------------------- diff --git a/AsuroTool/Util.cpp b/AsuroTool/Util.cpp new file mode 100644 index 0000000..a4a35b8 --- /dev/null +++ b/AsuroTool/Util.cpp @@ -0,0 +1,31 @@ +#include + +#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; +} diff --git a/AsuroTool/Util.h b/AsuroTool/Util.h new file mode 100644 index 0000000..1ec1d7d --- /dev/null +++ b/AsuroTool/Util.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +bool isError(const HRESULT result, const std::stringstream message); +bool isError(const HRESULT result, const char* message); +std::string utf8Encode(const std::wstring& wstr); diff --git a/AsuroTool/kaiju.ico b/AsuroTool/kaiju.ico new file mode 100644 index 0000000..7e688ea Binary files /dev/null and b/AsuroTool/kaiju.ico differ diff --git a/AsuroTool/remixicon.ttf b/AsuroTool/remixicon.ttf new file mode 100644 index 0000000..c461f40 Binary files /dev/null and b/AsuroTool/remixicon.ttf differ diff --git a/AsuroTool/resource.h b/AsuroTool/resource.h new file mode 100644 index 0000000..7e9ba23 --- /dev/null +++ b/AsuroTool/resource.h @@ -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 diff --git a/ImguiBase/ImguiBase.cpp b/ImguiBase/ImguiBase.cpp index 66f09a4..90a7d8d 100644 --- a/ImguiBase/ImguiBase.cpp +++ b/ImguiBase/ImguiBase.cpp @@ -5,7 +5,9 @@ #include #define GLFW_INCLUDE_NONE #define GLFW_INCLUDE_VULKAN +#define GLFW_EXPOSE_NATIVE_WIN32 #include +#include #include 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); +} \ No newline at end of file diff --git a/ImguiBase/ImguiBase.h b/ImguiBase/ImguiBase.h index 1d2baa5..35b57b3 100644 --- a/ImguiBase/ImguiBase.h +++ b/ImguiBase/ImguiBase.h @@ -1,10 +1,16 @@ #pragma once +#include +#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*)); \ No newline at end of file +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); diff --git a/ImguiNodes/ImguiNodes.cpp b/ImguiNodes/ImguiNodes.cpp index 20fd04d..1b9a0d4 100644 --- a/ImguiNodes/ImguiNodes.cpp +++ b/ImguiNodes/ImguiNodes.cpp @@ -10,15 +10,15 @@ int main() { - CustomDrawData customDrawData{}; + ApplicationData applicationData{}; - startImgui(&customDrawData, init, draw); + startImgui(&applicationData, init, draw, "Node Test", 1280, 720); } /// /// Setup before first draw. /// -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() /// /// Draw main application content. /// -void draw(DrawData& drawData, void* customDataVoid) +void draw(DrawData& drawData, void* customData) { - CustomDrawData* customData = static_cast(customDataVoid); - - bool isDraggingLine = false; - ImVec2 lineStartPos = ImVec2{}; + ApplicationData* applicationData = static_cast(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(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(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) } /// -/// Draws a small circle in the same row. +/// Creates an invisible button that does nothing, but can be used for drag/drop operations. /// -/// Returns the position of the circle. -/// True if an event matching the dragType has occurred. -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; - } +/// +/// Draws a small circle at the current cursor position. +/// +/// Returns the center position of the circle. +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; } /// @@ -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); } /// /// Removes any attached connections before erasing this NodeElement from a vector. /// /// Continued iterator -std::vector::iterator cleanEraseElement(std::vector& elements, std::vector::iterator toErase, CustomDrawData* data) +std::vector::iterator cleanEraseElement(std::vector& elements, std::vector::iterator toErase, ApplicationData* data) { auto connectionIterator = data->connections.begin(); while (connectionIterator != data->connections.end()) @@ -253,17 +252,46 @@ std::vector::iterator cleanEraseElement(std::vector& 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(); - this->triggers = std::vector(); + 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 }); } diff --git a/ImguiNodes/ImguiNodes.h b/ImguiNodes/ImguiNodes.h index 183be98..87b654c 100644 --- a/ImguiNodes/ImguiNodes.h +++ b/ImguiNodes/ImguiNodes.h @@ -2,39 +2,41 @@ #include #include -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 alerts; - std::vector triggers; + std::string title = {}; + std::vector alerts = {}; + std::vector 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 nodes; - std::vector connections; + std::vector nodes = {}; + std::vector 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::iterator cleanEraseElement(std::vector& vector, std::vector::iterator toErase, CustomDrawData* data); \ No newline at end of file +bool invisibleInlineButton(const char* title, const char* id); +std::vector::iterator cleanEraseElement(std::vector& vector, std::vector::iterator toErase, ApplicationData* data); +void cleanEreaseNodeElements(NodeWindow& node, ApplicationData* data);