Skip to content
Snippets Groups Projects
Commit 42211347 authored by John Bradley's avatar John Bradley
Browse files

Add interaction gui

parent 3f993e40
No related branches found
No related tags found
No related merge requests found
...@@ -55,6 +55,7 @@ set(obs_SOURCES ...@@ -55,6 +55,7 @@ set(obs_SOURCES
obs-app.cpp obs-app.cpp
window-basic-main.cpp window-basic-main.cpp
window-basic-settings.cpp window-basic-settings.cpp
window-basic-interaction.cpp
window-basic-properties.cpp window-basic-properties.cpp
window-basic-source-select.cpp window-basic-source-select.cpp
window-license-agreement.cpp window-license-agreement.cpp
...@@ -73,6 +74,7 @@ set(obs_HEADERS ...@@ -73,6 +74,7 @@ set(obs_HEADERS
window-main.hpp window-main.hpp
window-basic-main.hpp window-basic-main.hpp
window-basic-settings.hpp window-basic-settings.hpp
window-basic-interaction.hpp
window-basic-properties.hpp window-basic-properties.hpp
window-basic-source-select.hpp window-basic-source-select.hpp
window-license-agreement.hpp window-license-agreement.hpp
...@@ -95,6 +97,7 @@ set(obs_UI ...@@ -95,6 +97,7 @@ set(obs_UI
forms/OBSBasicTransform.ui forms/OBSBasicTransform.ui
forms/OBSBasicSettings.ui forms/OBSBasicSettings.ui
forms/OBSBasicSourceSelect.ui forms/OBSBasicSourceSelect.ui
forms/OBSBasicInteraction.ui
forms/OBSBasicProperties.ui) forms/OBSBasicProperties.ui)
set(obs_QRC set(obs_QRC
......
...@@ -95,6 +95,9 @@ Basic.PropertiesWindow.AutoSelectFormat="%1 (unsupported; autoselect: %2)" ...@@ -95,6 +95,9 @@ Basic.PropertiesWindow.AutoSelectFormat="%1 (unsupported; autoselect: %2)"
Basic.PropertiesWindow.SelectColor="Select color" Basic.PropertiesWindow.SelectColor="Select color"
Basic.PropertiesWindow.SelectFont="Select font" Basic.PropertiesWindow.SelectFont="Select font"
# interaction window
Basic.InteractionWindow="Interacting with '%1'"
# status bar # status bar
Basic.StatusBar.Reconnecting="Disconnected, reconnecting (attempt %1)" Basic.StatusBar.Reconnecting="Disconnected, reconnecting (attempt %1)"
Basic.StatusBar.ReconnectSuccessful="Reconnection successful" Basic.StatusBar.ReconnectSuccessful="Reconnection successful"
......
...@@ -791,6 +791,11 @@ ...@@ -791,6 +791,11 @@
<string>Basic.MainMenu.Help.CheckForUpdates</string> <string>Basic.MainMenu.Help.CheckForUpdates</string>
</property> </property>
</action> </action>
<action name="actionInteract">
<property name="text">
<string>Interact</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OBSBasicInteraction</class>
<widget class="QDialog" name="OBSBasicInteraction">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>664</width>
<height>562</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="OBSQTDisplay" name="preview" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>OBSQTDisplay</class>
<extends>QWidget</extends>
<header>qt-display.hpp</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "obs-app.hpp"
#include "window-basic-interaction.hpp"
#include "window-basic-main.hpp"
#include "qt-wrappers.hpp"
#include "display-helpers.hpp"
#include <QCloseEvent>
#include <QScreen>
#include <QWindow>
using namespace std;
OBSBasicInteraction::OBSBasicInteraction(QWidget *parent, OBSSource source_)
: QDialog (parent),
main (qobject_cast<OBSBasic*>(parent)),
resizeTimer (0),
ui (new Ui::OBSBasicInteraction),
source (source_),
removedSignal (obs_source_get_signal_handler(source), "remove",
OBSBasicInteraction::SourceRemoved, this)
{
int cx = (int)config_get_int(App()->GlobalConfig(), "InteractionWindow",
"cx");
int cy = (int)config_get_int(App()->GlobalConfig(), "InteractionWindow",
"cy");
ui->setupUi(this);
ui->preview->setMouseTracking(true);
ui->preview->setFocusPolicy(Qt::StrongFocus);
ui->preview->installEventFilter(BuildEventFilter());
if (cx > 400 && cy > 400)
resize(cx, cy);
OBSData settings = obs_source_get_settings(source);
obs_data_release(settings);
connect(windowHandle(), &QWindow::screenChanged, [this]() {
if (resizeTimer)
killTimer(resizeTimer);
resizeTimer = startTimer(100);
});
const char *name = obs_source_get_name(source);
setWindowTitle(QTStr("Basic.InteractionWindow").arg(QT_UTF8(name)));
}
OBSEventFilter *OBSBasicInteraction::BuildEventFilter()
{
return new OBSEventFilter(
[this](QObject *obj, QEvent *event)
{
UNUSED_PARAMETER(obj);
switch(event->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
return this->HandleMouseClickEvent(
static_cast<QMouseEvent *>(event));
case QEvent::MouseMove:
case QEvent::Enter:
case QEvent::Leave:
return this->HandleMouseMoveEvent(
static_cast<QMouseEvent *>(event));
case QEvent::Wheel:
return this->HandleMouseWheelEvent(
static_cast<QWheelEvent *>(event));
case QEvent::FocusIn:
case QEvent::FocusOut:
return this->HandleFocusEvent(
static_cast<QFocusEvent *>(event));
case QEvent::KeyPress:
case QEvent::KeyRelease:
return this->HandleKeyEvent(
static_cast<QKeyEvent *>(event));
default:
return false;
}
});
}
void OBSBasicInteraction::SourceRemoved(void *data, calldata_t params)
{
QMetaObject::invokeMethod(static_cast<OBSBasicInteraction*>(data),
"close");
UNUSED_PARAMETER(params);
}
void OBSBasicInteraction::DrawPreview(void *data, uint32_t cx, uint32_t cy)
{
OBSBasicInteraction *window = static_cast<OBSBasicInteraction*>(data);
if (!window->source)
return;
uint32_t sourceCX = max(obs_source_get_width(window->source), 1u);
uint32_t sourceCY = max(obs_source_get_height(window->source), 1u);
int x, y;
int newCX, newCY;
float scale;
GetScaleAndCenterPos(sourceCX, sourceCY, cx, cy, x, y, scale);
newCX = int(scale * float(sourceCX));
newCY = int(scale * float(sourceCY));
gs_viewport_push();
gs_projection_push();
gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY),
-100.0f, 100.0f);
gs_set_viewport(x, y, newCX, newCY);
obs_source_video_render(window->source);
gs_projection_pop();
gs_viewport_pop();
}
void OBSBasicInteraction::OnInteractionResized()
{
if (resizeTimer)
killTimer(resizeTimer);
resizeTimer = startTimer(100);
}
void OBSBasicInteraction::resizeEvent(QResizeEvent *event)
{
if (isVisible()) {
if (resizeTimer)
killTimer(resizeTimer);
resizeTimer = startTimer(100);
}
UNUSED_PARAMETER(event);
}
void OBSBasicInteraction::timerEvent(QTimerEvent *event)
{
if (event->timerId() == resizeTimer) {
killTimer(resizeTimer);
resizeTimer = 0;
QSize size = GetPixelSize(ui->preview);
obs_display_resize(display, size.width(), size.height());
}
}
void OBSBasicInteraction::closeEvent(QCloseEvent *event)
{
QDialog::closeEvent(event);
if (!event->isAccepted())
return;
// remove draw callback and release display in case our drawable
// surfaces go away before the destructor gets called
obs_display_remove_draw_callback(display,
OBSBasicInteraction::DrawPreview, this);
display = nullptr;
config_set_int(App()->GlobalConfig(), "InteractionWindow", "cx",
width());
config_set_int(App()->GlobalConfig(), "InteractionWindow", "cy",
height());
}
static int TranslateQtKeyboardEventModifiers(QInputEvent *event, bool mouseEvent) {
int obsModifiers = INTERACT_NONE;
if (event->modifiers().testFlag(Qt::ShiftModifier))
obsModifiers |= INTERACT_SHIFT_KEY;
if (event->modifiers().testFlag(Qt::AltModifier))
obsModifiers |= INTERACT_ALT_KEY;
#ifdef APPLE
// Mac: Meta = Control, Control = Command
if (event->modifiers().testFlag(Qt::ControlModifier))
obsModifiers |= INTERACT_COMMAND_KEY;
if (event->modifiers().testFlag(Qt::MetaModifier))
obsModifiers |= INTERACT_CONTROL_KEY;
#else
// Handle windows key? Can a browser even trap that key?
if (event->modifiers().testFlag(Qt::ControlModifier))
obsModifiers |= INTERACT_CONTROL_KEY;
#endif
if (!mouseEvent) {
if (event->modifiers().testFlag(Qt::KeypadModifier))
obsModifiers |= INTERACT_IS_KEY_PAD;
}
return obsModifiers;
}
static int TranslateQtMouseEventModifiers(
QMouseEvent *event)
{
int modifiers = TranslateQtKeyboardEventModifiers(event, true);
if (event->buttons().testFlag(Qt::LeftButton))
modifiers |= INTERACT_MOUSE_LEFT;
if (event->buttons().testFlag(Qt::MiddleButton))
modifiers |= INTERACT_MOUSE_MIDDLE;
if (event->buttons().testFlag(Qt::RightButton))
modifiers |= INTERACT_MOUSE_RIGHT;
return modifiers;
}
bool OBSBasicInteraction::GetSourceRelativeXY(
int mouseX, int mouseY, int &relX, int &relY)
{
QSize size = GetPixelSize(ui->preview);
uint32_t sourceCX = max(obs_source_get_width(source), 1u);
uint32_t sourceCY = max(obs_source_get_height(source), 1u);
int x, y;
float scale;
GetScaleAndCenterPos(sourceCX, sourceCY, size.width(), size.height(),
x, y, scale);
if (x > 0) {
relX = int(float(mouseX - x) / scale);
relY = int(float(mouseY / scale));
} else {
relX = int(float(mouseX / scale));
relY = int(float(mouseY - y) / scale);
}
// Confirm mouse is inside the source
if (relX < 0 || relX > int(sourceCX))
return false;
if (relY < 0 || relY > int(sourceCY))
return false;
return true;
}
bool OBSBasicInteraction::HandleMouseClickEvent(
QMouseEvent *event)
{
bool mouseUp = event->type() == QEvent::MouseButtonRelease;
int clickCount = 1;
if (event->type() == QEvent::MouseButtonDblClick)
clickCount = 2;
struct obs_mouse_event mouseEvent = {};
mouseEvent.modifiers = TranslateQtMouseEventModifiers(event);
int32_t button = 0;
switch (event->button()) {
case Qt::LeftButton:
button = MOUSE_LEFT;
break;
case Qt::MiddleButton:
button = MOUSE_MIDDLE;
break;
case Qt::RightButton:
button = MOUSE_RIGHT;
break;
default:
blog(LOG_WARNING, "unknown button type %d",
event->button());
return false;
}
// Why doesn't this work?
//if (event->flags().testFlag(Qt::MouseEventCreatedDoubleClick))
// clickCount = 2;
bool insideSource = GetSourceRelativeXY(event->x(), event->y(),
mouseEvent.x, mouseEvent.y);
if (mouseUp || insideSource)
obs_source_send_mouse_click(source, &mouseEvent, button,
mouseUp, clickCount);
return true;
}
bool OBSBasicInteraction::HandleMouseMoveEvent(QMouseEvent *event)
{
struct obs_mouse_event mouseEvent = {};
bool mouseLeave = event->type() == QEvent::Leave;
if (!mouseLeave) {
mouseEvent.modifiers = TranslateQtMouseEventModifiers(event);
mouseLeave = !GetSourceRelativeXY(event->x(), event->y(),
mouseEvent.x, mouseEvent.y);
}
obs_source_send_mouse_move(source, &mouseEvent, mouseLeave);
return true;
}
bool OBSBasicInteraction::HandleMouseWheelEvent(QWheelEvent *event)
{
struct obs_mouse_event mouseEvent = {};
mouseEvent.modifiers = TranslateQtKeyboardEventModifiers(event, true);
int xDelta = 0;
int yDelta = 0;
if (!event->pixelDelta().isNull()) {
if (event->orientation() == Qt::Horizontal)
xDelta = event->pixelDelta().x();
else
yDelta = event->pixelDelta().y();
} else {
if (event->orientation() == Qt::Horizontal)
xDelta = event->delta();
else
yDelta = event->delta();
}
obs_source_send_mouse_wheel(source, &mouseEvent, xDelta, yDelta);
return true;
}
bool OBSBasicInteraction::HandleFocusEvent(QFocusEvent *event)
{
bool focus = event->type() == QEvent::FocusIn;
obs_source_send_focus(source, focus);
return true;
}
bool OBSBasicInteraction::HandleKeyEvent(QKeyEvent *event)
{
struct obs_key_event keyEvent;
QByteArray text = event->text().toUtf8();
keyEvent.modifiers = TranslateQtKeyboardEventModifiers(event, false);
keyEvent.text = text.data();
keyEvent.native_modifiers = event->nativeModifiers();
keyEvent.native_scancode = event->nativeScanCode();
keyEvent.native_vkey = event->nativeVirtualKey();
bool keyUp = event->type() == QEvent::KeyRelease;
obs_source_send_key_click(source, &keyEvent, keyUp);
return true;
}
void OBSBasicInteraction::Init()
{
gs_init_data init_data = {};
show();
QSize previewSize = GetPixelSize(ui->preview);
init_data.cx = uint32_t(previewSize.width());
init_data.cy = uint32_t(previewSize.height());
init_data.format = GS_RGBA;
QTToGSWindow(ui->preview->winId(), init_data.window);
display = obs_display_create(&init_data);
if (display)
obs_display_add_draw_callback(display,
OBSBasicInteraction::DrawPreview, this);
}
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
#include <QDialog>
#include <memory>
#include <functional>
#include <obs.hpp>
#include "properties-view.hpp"
class OBSBasic;
#include "ui_OBSBasicInteraction.h"
class OBSEventFilter;
class OBSBasicInteraction : public QDialog {
Q_OBJECT
private:
OBSBasic *main;
int resizeTimer;
std::unique_ptr<Ui::OBSBasicInteraction> ui;
OBSSource source;
OBSDisplay display;
OBSSignal removedSignal;
static void SourceRemoved(void *data, calldata_t params);
static void DrawPreview(void *data, uint32_t cx, uint32_t cy);
bool GetSourceRelativeXY(int mouseX, int mouseY, int &x, int &y);
bool HandleMouseClickEvent(QMouseEvent *event);
bool HandleMouseMoveEvent(QMouseEvent *event);
bool HandleMouseWheelEvent(QWheelEvent *event);
bool HandleFocusEvent(QFocusEvent *event);
bool HandleKeyEvent(QKeyEvent *event);
OBSEventFilter *BuildEventFilter();
private slots:
void OnInteractionResized();
public:
OBSBasicInteraction(QWidget *parent, OBSSource source_);
void Init();
protected:
virtual void resizeEvent(QResizeEvent *event) override;
virtual void timerEvent(QTimerEvent *event) override;
virtual void closeEvent(QCloseEvent *event) override;
};
typedef std::function<bool(QObject *, QEvent *)> EventFilterFunc;
class OBSEventFilter : public QObject
{
Q_OBJECT
public:
OBSEventFilter(EventFilterFunc filter_)
: filter(filter_)
{}
protected:
bool eventFilter(QObject *obj, QEvent *event)
{
return filter(obj, event);
}
private:
EventFilterFunc filter;
};
...@@ -596,6 +596,9 @@ OBSBasic::~OBSBasic() ...@@ -596,6 +596,9 @@ OBSBasic::~OBSBasic()
delete cpuUsageTimer; delete cpuUsageTimer;
os_cpu_usage_info_destroy(cpuUsageInfo); os_cpu_usage_info_destroy(cpuUsageInfo);
if (interaction)
delete interaction;
if (properties) if (properties)
delete properties; delete properties;
...@@ -667,6 +670,16 @@ void OBSBasic::InsertSceneItem(obs_sceneitem_t item) ...@@ -667,6 +670,16 @@ void OBSBasic::InsertSceneItem(obs_sceneitem_t item)
CreatePropertiesWindow(source); CreatePropertiesWindow(source);
} }
void OBSBasic::CreateInteractionWindow(obs_source_t source)
{
if (interaction)
interaction->close();
interaction = new OBSBasicInteraction(this, source);
interaction->Init();
interaction->setAttribute(Qt::WA_DeleteOnClose, true);
}
void OBSBasic::CreatePropertiesWindow(obs_source_t source) void OBSBasic::CreatePropertiesWindow(obs_source_t source)
{ {
if (properties) if (properties)
...@@ -1610,6 +1623,10 @@ void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) ...@@ -1610,6 +1623,10 @@ void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
if (addSourceMenu) if (addSourceMenu)
popup.addSeparator(); popup.addSeparator();
OBSSceneItem sceneItem = GetSceneItem(item);
obs_source_t source = obs_sceneitem_get_source(sceneItem);
QAction *action;
popup.addAction(QTStr("Rename"), this, popup.addAction(QTStr("Rename"), this,
SLOT(EditSceneItemName())); SLOT(EditSceneItemName()));
popup.addAction(QTStr("Remove"), this, popup.addAction(QTStr("Remove"), this,
...@@ -1619,6 +1636,13 @@ void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) ...@@ -1619,6 +1636,13 @@ void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
popup.addMenu(ui->orderMenu); popup.addMenu(ui->orderMenu);
popup.addMenu(ui->transformMenu); popup.addMenu(ui->transformMenu);
popup.addSeparator(); popup.addSeparator();
action = popup.addAction(QTStr("Interact"), this,
SLOT(on_actionInteract_triggered()));
action->setEnabled(obs_source_get_output_flags(source) &
OBS_SOURCE_INTERACTION);
popup.addAction(QTStr("Properties"), this, popup.addAction(QTStr("Properties"), this,
SLOT(on_actionSourceProperties_triggered())); SLOT(on_actionSourceProperties_triggered()));
} }
...@@ -1703,6 +1727,15 @@ void OBSBasic::on_actionRemoveSource_triggered() ...@@ -1703,6 +1727,15 @@ void OBSBasic::on_actionRemoveSource_triggered()
obs_sceneitem_remove(item); obs_sceneitem_remove(item);
} }
void OBSBasic::on_actionInteract_triggered()
{
OBSSceneItem item = GetCurrentSceneItem();
OBSSource source = obs_sceneitem_get_source(item);
if (source)
CreateInteractionWindow(source);
}
void OBSBasic::on_actionSourceProperties_triggered() void OBSBasic::on_actionSourceProperties_triggered()
{ {
OBSSceneItem item = GetCurrentSceneItem(); OBSSceneItem item = GetCurrentSceneItem();
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include "window-main.hpp" #include "window-main.hpp"
#include "window-basic-interaction.hpp"
#include "window-basic-properties.hpp" #include "window-basic-properties.hpp"
#include "window-basic-transform.hpp" #include "window-basic-transform.hpp"
...@@ -57,6 +58,7 @@ private: ...@@ -57,6 +58,7 @@ private:
bool loaded = false; bool loaded = false;
QPointer<OBSBasicInteraction> interaction;
QPointer<OBSBasicProperties> properties; QPointer<OBSBasicProperties> properties;
QPointer<OBSBasicTransform> transformWindow; QPointer<OBSBasicTransform> transformWindow;
...@@ -141,6 +143,7 @@ private: ...@@ -141,6 +143,7 @@ private:
void TempStreamOutput(const char *url, const char *key, void TempStreamOutput(const char *url, const char *key,
int vBitrate, int aBitrate); int vBitrate, int aBitrate);
void CreateInteractionWindow(obs_source_t source);
void CreatePropertiesWindow(obs_source_t source); void CreatePropertiesWindow(obs_source_t source);
public slots: public slots:
...@@ -260,6 +263,7 @@ private slots: ...@@ -260,6 +263,7 @@ private slots:
void on_sources_customContextMenuRequested(const QPoint &pos); void on_sources_customContextMenuRequested(const QPoint &pos);
void on_actionAddSource_triggered(); void on_actionAddSource_triggered();
void on_actionRemoveSource_triggered(); void on_actionRemoveSource_triggered();
void on_actionInteract_triggered();
void on_actionSourceProperties_triggered(); void on_actionSourceProperties_triggered();
void on_actionSourceUp_triggered(); void on_actionSourceUp_triggered();
void on_actionSourceDown_triggered(); void on_actionSourceDown_triggered();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment