Added control interface
This commit is contained in:
@@ -171,7 +171,8 @@ jobs:
|
|||||||
run: Compress-Archive -Path dist/VideoShader/* -DestinationPath dist/VideoShader.zip -Force
|
run: Compress-Archive -Path dist/VideoShader/* -DestinationPath dist/VideoShader.zip -Force
|
||||||
|
|
||||||
- name: Upload Runtime Package
|
- name: Upload Runtime Package
|
||||||
uses: actions/upload-artifact@v4
|
# Gitea/GHES-compatible runners do not support the v4 artifact backend yet.
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: VideoShader-windows-release
|
name: VideoShader-windows-release
|
||||||
path: dist/VideoShader.zip
|
path: dist/VideoShader.zip
|
||||||
|
|||||||
@@ -249,9 +249,7 @@ If neither variable is set, the workflow falls back to the repo-local defaults u
|
|||||||
- Audio.
|
- Audio.
|
||||||
- Improve text rendering.
|
- Improve text rendering.
|
||||||
- Genlock.
|
- Genlock.
|
||||||
- Don't hardfail on shader fail
|
- Find a better UI library for react.
|
||||||
- Find a better UI library.
|
|
||||||
- Logs.
|
- Logs.
|
||||||
- Continue source cleanup/refactoring. Pass 1 done
|
- Continue source cleanup/refactoring. Pass 1 done
|
||||||
- Display the control URL in the Windows app, ideally clickable, without rendering it on the video output.
|
|
||||||
- Support a separate sound shader `.slang` file in shader packages.
|
- Support a separate sound shader `.slang` file in shader packages.
|
||||||
|
|||||||
@@ -46,6 +46,10 @@
|
|||||||
#include "resource.h"
|
#include "resource.h"
|
||||||
#include "OpenGLComposite.h"
|
#include "OpenGLComposite.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#ifndef WGL_CONTEXT_MAJOR_VERSION_ARB
|
#ifndef WGL_CONTEXT_MAJOR_VERSION_ARB
|
||||||
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
||||||
#endif
|
#endif
|
||||||
@@ -65,6 +69,140 @@
|
|||||||
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
|
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
|
||||||
typedef HGLRC (WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hShareContext, const int* attribList);
|
typedef HGLRC (WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hdc, HGLRC hShareContext, const int* attribList);
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const int kStatusStripHeight = 92;
|
||||||
|
const int kStatusPadding = 8;
|
||||||
|
const int kStatusLabelWidth = 58;
|
||||||
|
const int kStatusButtonWidth = 86;
|
||||||
|
const int kStatusRowHeight = 24;
|
||||||
|
const int kStatusGap = 6;
|
||||||
|
const UINT kCreateStatusStripMessage = WM_APP + 1;
|
||||||
|
|
||||||
|
enum StatusControlId
|
||||||
|
{
|
||||||
|
kControlUrlEditId = 2001,
|
||||||
|
kDocsUrlEditId = 2002,
|
||||||
|
kOscAddressEditId = 2003,
|
||||||
|
kOpenControlButtonId = 2004,
|
||||||
|
kOpenDocsButtonId = 2005
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StatusStripControls
|
||||||
|
{
|
||||||
|
HWND panel = NULL;
|
||||||
|
HWND controlLabel = NULL;
|
||||||
|
HWND controlUrl = NULL;
|
||||||
|
HWND openControl = NULL;
|
||||||
|
HWND docsLabel = NULL;
|
||||||
|
HWND docsUrl = NULL;
|
||||||
|
HWND openDocs = NULL;
|
||||||
|
HWND oscLabel = NULL;
|
||||||
|
HWND oscAddress = NULL;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool StatusStripCreated(const StatusStripControls& controls)
|
||||||
|
{
|
||||||
|
return controls.panel != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
HWND CreateStatusChild(HWND parent, const char* className, const char* text, DWORD style, int controlId)
|
||||||
|
{
|
||||||
|
return CreateWindowExA(
|
||||||
|
0,
|
||||||
|
className,
|
||||||
|
text,
|
||||||
|
WS_CHILD | WS_VISIBLE | style,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
parent,
|
||||||
|
reinterpret_cast<HMENU>(static_cast<INT_PTR>(controlId)),
|
||||||
|
reinterpret_cast<HINSTANCE>(GetWindowLongPtr(parent, GWLP_HINSTANCE)),
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateStatusStrip(HWND hWnd, StatusStripControls& controls)
|
||||||
|
{
|
||||||
|
controls.panel = CreateStatusChild(hWnd, "STATIC", "", SS_NOTIFY, 0);
|
||||||
|
controls.controlLabel = CreateStatusChild(hWnd, "STATIC", "Control", SS_LEFT, 0);
|
||||||
|
controls.controlUrl = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY, kControlUrlEditId);
|
||||||
|
controls.openControl = CreateStatusChild(hWnd, "BUTTON", "Open", BS_PUSHBUTTON, kOpenControlButtonId);
|
||||||
|
controls.docsLabel = CreateStatusChild(hWnd, "STATIC", "Docs", SS_LEFT, 0);
|
||||||
|
controls.docsUrl = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY, kDocsUrlEditId);
|
||||||
|
controls.openDocs = CreateStatusChild(hWnd, "BUTTON", "Open", BS_PUSHBUTTON, kOpenDocsButtonId);
|
||||||
|
controls.oscLabel = CreateStatusChild(hWnd, "STATIC", "OSC", SS_LEFT, 0);
|
||||||
|
controls.oscAddress = CreateStatusChild(hWnd, "EDIT", "", ES_AUTOHSCROLL | ES_READONLY, kOscAddressEditId);
|
||||||
|
|
||||||
|
HFONT guiFont = reinterpret_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
|
||||||
|
HWND children[] = {
|
||||||
|
controls.controlLabel,
|
||||||
|
controls.controlUrl,
|
||||||
|
controls.openControl,
|
||||||
|
controls.docsLabel,
|
||||||
|
controls.docsUrl,
|
||||||
|
controls.openDocs,
|
||||||
|
controls.oscLabel,
|
||||||
|
controls.oscAddress
|
||||||
|
};
|
||||||
|
for (HWND child : children)
|
||||||
|
{
|
||||||
|
if (child)
|
||||||
|
SendMessage(child, WM_SETFONT, reinterpret_cast<WPARAM>(guiFont), TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWindowTextA(controls.controlUrl, "Starting control server...");
|
||||||
|
SetWindowTextA(controls.docsUrl, "Starting API docs...");
|
||||||
|
SetWindowTextA(controls.oscAddress, "Starting OSC listener...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutStatusStrip(HWND hWnd, const StatusStripControls& controls)
|
||||||
|
{
|
||||||
|
RECT clientRect = {};
|
||||||
|
if (!GetClientRect(hWnd, &clientRect) || !controls.panel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int clientWidth = static_cast<int>(clientRect.right - clientRect.left);
|
||||||
|
const int clientHeight = static_cast<int>(clientRect.bottom - clientRect.top);
|
||||||
|
const int panelTop = std::max(0, clientHeight - kStatusStripHeight);
|
||||||
|
MoveWindow(controls.panel, 0, panelTop, clientWidth, kStatusStripHeight, TRUE);
|
||||||
|
|
||||||
|
const int rowX = kStatusPadding;
|
||||||
|
const int editX = rowX + kStatusLabelWidth + kStatusGap;
|
||||||
|
const int buttonX = std::max(editX, clientWidth - kStatusPadding - kStatusButtonWidth);
|
||||||
|
const int editWidth = std::max(80, buttonX - editX - kStatusGap);
|
||||||
|
const int oscWidth = std::max(80, clientWidth - editX - kStatusPadding);
|
||||||
|
const int row1 = panelTop + kStatusPadding;
|
||||||
|
const int row2 = row1 + kStatusRowHeight + kStatusGap;
|
||||||
|
const int row3 = row2 + kStatusRowHeight + kStatusGap;
|
||||||
|
|
||||||
|
MoveWindow(controls.controlLabel, rowX, row1 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
|
||||||
|
MoveWindow(controls.controlUrl, editX, row1, editWidth, kStatusRowHeight, TRUE);
|
||||||
|
MoveWindow(controls.openControl, buttonX, row1, kStatusButtonWidth, kStatusRowHeight, TRUE);
|
||||||
|
MoveWindow(controls.docsLabel, rowX, row2 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
|
||||||
|
MoveWindow(controls.docsUrl, editX, row2, editWidth, kStatusRowHeight, TRUE);
|
||||||
|
MoveWindow(controls.openDocs, buttonX, row2, kStatusButtonWidth, kStatusRowHeight, TRUE);
|
||||||
|
MoveWindow(controls.oscLabel, rowX, row3 + 3, kStatusLabelWidth, kStatusRowHeight, TRUE);
|
||||||
|
MoveWindow(controls.oscAddress, editX, row3, oscWidth, kStatusRowHeight, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateStatusStrip(const StatusStripControls& controls, const OpenGLComposite& composite)
|
||||||
|
{
|
||||||
|
if (!StatusStripCreated(controls))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SetWindowTextA(controls.controlUrl, composite.GetControlUrl().c_str());
|
||||||
|
SetWindowTextA(controls.docsUrl, composite.GetDocsUrl().c_str());
|
||||||
|
SetWindowTextA(controls.oscAddress, composite.GetOscAddress().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenUrl(const char* url)
|
||||||
|
{
|
||||||
|
ShellExecuteA(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ShowUnhandledExceptionMessage(const char* prefix)
|
void ShowUnhandledExceptionMessage(const char* prefix)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -203,6 +341,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
static HDC hDC = NULL; // Private GDI Device context
|
static HDC hDC = NULL; // Private GDI Device context
|
||||||
static OpenGLComposite* pOpenGLComposite = NULL;
|
static OpenGLComposite* pOpenGLComposite = NULL;
|
||||||
static bool sInteractiveResize = false;
|
static bool sInteractiveResize = false;
|
||||||
|
static StatusStripControls sStatusStrip;
|
||||||
|
|
||||||
switch (message)
|
switch (message)
|
||||||
{
|
{
|
||||||
@@ -251,7 +390,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
{
|
{
|
||||||
wglMakeCurrent( NULL, NULL );
|
wglMakeCurrent( NULL, NULL );
|
||||||
if (pOpenGLComposite->Start())
|
if (pOpenGLComposite->Start())
|
||||||
|
{
|
||||||
|
PostMessage(hWnd, kCreateStatusStripMessage, 0, 0);
|
||||||
break; // success
|
break; // success
|
||||||
|
}
|
||||||
MessageBoxA(NULL, "The OpenGL/DeckLink runtime initialized, but playout failed to start. See the previous DeckLink start message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
|
MessageBoxA(NULL, "The OpenGL/DeckLink runtime initialized, but playout failed to start. See the previous DeckLink start message for the failing call.", "Startup failed", MB_OK | MB_ICONERROR);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -273,6 +415,18 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case kCreateStatusStripMessage:
|
||||||
|
if (pOpenGLComposite)
|
||||||
|
{
|
||||||
|
if (!StatusStripCreated(sStatusStrip))
|
||||||
|
CreateStatusStrip(hWnd, sStatusStrip);
|
||||||
|
|
||||||
|
UpdateStatusStrip(sStatusStrip, *pOpenGLComposite);
|
||||||
|
LayoutStatusStrip(hWnd, sStatusStrip);
|
||||||
|
InvalidateRect(hWnd, NULL, FALSE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case WM_DESTROY:
|
case WM_DESTROY:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -313,6 +467,8 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
case WM_SIZE:
|
case WM_SIZE:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (StatusStripCreated(sStatusStrip))
|
||||||
|
LayoutStatusStrip(hWnd, sStatusStrip);
|
||||||
if (pOpenGLComposite)
|
if (pOpenGLComposite)
|
||||||
pOpenGLComposite->resizeGL(LOWORD(lParam), HIWORD(lParam));
|
pOpenGLComposite->resizeGL(LOWORD(lParam), HIWORD(lParam));
|
||||||
}
|
}
|
||||||
@@ -361,6 +517,28 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case WM_COMMAND:
|
||||||
|
switch (LOWORD(wParam))
|
||||||
|
{
|
||||||
|
case kOpenControlButtonId:
|
||||||
|
if (pOpenGLComposite)
|
||||||
|
{
|
||||||
|
std::string url = pOpenGLComposite->GetControlUrl();
|
||||||
|
OpenUrl(url.c_str());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case kOpenDocsButtonId:
|
||||||
|
if (pOpenGLComposite)
|
||||||
|
{
|
||||||
|
std::string url = pOpenGLComposite->GetDocsUrl();
|
||||||
|
OpenUrl(url.c_str());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return (DefWindowProc(hWnd, message, wParam, lParam));
|
return (DefWindowProc(hWnd, message, wParam, lParam));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ public:
|
|||||||
bool ResetLayerParameters(const std::string& layerId, std::string& error);
|
bool ResetLayerParameters(const std::string& layerId, std::string& error);
|
||||||
bool SaveStackPreset(const std::string& presetName, std::string& error);
|
bool SaveStackPreset(const std::string& presetName, std::string& error);
|
||||||
bool LoadStackPreset(const std::string& presetName, std::string& error);
|
bool LoadStackPreset(const std::string& presetName, std::string& error);
|
||||||
|
unsigned short GetControlServerPort() const;
|
||||||
|
unsigned short GetOscPort() const;
|
||||||
|
std::string GetControlUrl() const;
|
||||||
|
std::string GetDocsUrl() const;
|
||||||
|
std::string GetOscAddress() const;
|
||||||
|
|
||||||
void resizeGL(WORD width, WORD height);
|
void resizeGL(WORD width, WORD height);
|
||||||
void paintGL();
|
void paintGL();
|
||||||
|
|||||||
@@ -5,6 +5,31 @@ std::string OpenGLComposite::GetRuntimeStateJson() const
|
|||||||
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
|
return mRuntimeHost ? mRuntimeHost->BuildStateJson() : "{}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned short OpenGLComposite::GetControlServerPort() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost ? mRuntimeHost->GetServerPort() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short OpenGLComposite::GetOscPort() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost ? mRuntimeHost->GetOscPort() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OpenGLComposite::GetControlUrl() const
|
||||||
|
{
|
||||||
|
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OpenGLComposite::GetDocsUrl() const
|
||||||
|
{
|
||||||
|
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/docs";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OpenGLComposite::GetOscAddress() const
|
||||||
|
{
|
||||||
|
return "udp://127.0.0.1:" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
|
||||||
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
||||||
{
|
{
|
||||||
if (!mRuntimeHost->AddLayer(shaderId, error))
|
if (!mRuntimeHost->AddLayer(shaderId, error))
|
||||||
|
|||||||
Reference in New Issue
Block a user