diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index d0c80793549f0495b78c29dc12f46cfad6ea7c7b..b66e98f2df915bf7f608bae815284ff10a6e9504 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -222,6 +222,7 @@ set(libobs_libobs_HEADERS obs-ui.h obs-properties.h obs-data.h + obs-interaction.h obs-module.h obs-scene.h obs-source.h diff --git a/libobs/obs-interaction.h b/libobs/obs-interaction.h new file mode 100644 index 0000000000000000000000000000000000000000..d213df2167bed7e21c651eae1fe1acf40e12ea01 --- /dev/null +++ b/libobs/obs-interaction.h @@ -0,0 +1,56 @@ +/****************************************************************************** + 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 "util/c99defs.h" + +enum obs_interaction_flags { + INTERACT_NONE = 0, + INTERACT_CAPS_KEY = 1, + INTERACT_SHIFT_KEY = 1 << 1, + INTERACT_CONTROL_KEY = 1 << 2, + INTERACT_ALT_KEY = 1 << 3, + INTERACT_MOUSE_LEFT = 1 << 4, + INTERACT_MOUSE_MIDDLE = 1 << 5, + INTERACT_MOUSE_RIGHT = 1 << 6, + INTERACT_COMMAND_KEY = 1 << 7, + INTERACT_NUMLOCK_KEY = 1 << 8, + INTERACT_IS_KEY_PAD = 1 << 9, + INTERACT_IS_LEFT = 1 << 10, + INTERACT_IS_RIGHT = 1 << 11 +}; + +enum obs_mouse_button_type { + MOUSE_LEFT, + MOUSE_MIDDLE, + MOUSE_RIGHT +}; + +struct obs_mouse_event { + uint32_t modifiers; + int32_t x; + int32_t y; +}; + +struct obs_key_event { + uint32_t modifiers; + char *text; + uint32_t native_modifiers; + uint32_t native_scancode; + uint32_t native_vkey; +}; diff --git a/libobs/obs-source.c b/libobs/obs-source.c index eb1f61d423f12fb67d45b03515d22629a0f643a1..6b0015b8773a72976f99081664ea5bbf3ff10322 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -389,6 +389,76 @@ void obs_source_update(obs_source_t source, obs_data_t settings) } } +void obs_source_send_mouse_click(obs_source_t source, + const struct obs_mouse_event *event, + int32_t type, bool mouse_up, + uint32_t click_count) +{ + if (!source) + return; + + if (source->info.output_flags & OBS_SOURCE_INTERACTION) { + if (source->info.mouse_click) { + source->info.mouse_click(source->context.data, + event, type, mouse_up, click_count); + } + } +} + +void obs_source_send_mouse_move(obs_source_t source, + const struct obs_mouse_event *event, bool mouse_leave) +{ + if (!source) + return; + + if (source->info.output_flags & OBS_SOURCE_INTERACTION) { + if (source->info.mouse_move) { + source->info.mouse_move(source->context.data, + event, mouse_leave); + } + } +} + +void obs_source_send_mouse_wheel(obs_source_t source, + const struct obs_mouse_event *event, int x_delta, int y_delta) +{ + if (!source) + return; + + if (source->info.output_flags & OBS_SOURCE_INTERACTION) { + if (source->info.mouse_wheel) { + source->info.mouse_wheel(source->context.data, + event, x_delta, y_delta); + } + } +} + +void obs_source_send_focus(obs_source_t source, bool focus) +{ + if (!source) + return; + + if (source->info.output_flags & OBS_SOURCE_INTERACTION) { + if (source->info.focus) { + source->info.focus(source->context.data, focus); + } + } +} + +void obs_source_send_key_click(obs_source_t source, + const struct obs_key_event *event, bool key_up) +{ + if (!source) + return; + + if (source->info.output_flags & OBS_SOURCE_INTERACTION) { + if (source->info.key_click) { + source->info.key_click(source->context.data, event, + key_up); + } + } +} + static void activate_source(obs_source_t source) { if (source->context.data && source->info.activate) diff --git a/libobs/obs-source.h b/libobs/obs-source.h index 20636a7b0c1b2cf62fc65fa97020429c3fc21ebe..9bdccd37571b316c295d2387297b745d95f76351 100644 --- a/libobs/obs-source.h +++ b/libobs/obs-source.h @@ -88,6 +88,14 @@ enum obs_source_type { */ #define OBS_SOURCE_COLOR_MATRIX (1<<4) +/** + * Source supports interaction. + * + * When this is used, the source will receive interaction events + * if they provide the necessary callbacks in the source definition structure. + */ +#define OBS_SOURCE_INTERACTION (1<<5) + /** @} */ typedef void (*obs_source_enum_proc_t)(obs_source_t parent, obs_source_t child, @@ -283,6 +291,60 @@ struct obs_source_info { * @param settings Settings */ void (*load)(void *data, obs_data_t settings); + + /** + * Called when interacting with a source and a mouse-down or mouse-up + * occurs. + * + * @param data Source data + * @param event Mouse event properties + * @param type Mouse button pushed + * @param mouse_up Mouse event type (true if mouse-up) + * @param click_count Mouse click count (1 for single click, etc.) + */ + void (*mouse_click)(void *data, + const struct obs_mouse_event *event, + int32_t type, bool mouse_up, uint32_t click_count); + /** + * Called when interacting with a source and a mouse-move occurs. + * + * @param data Source data + * @param event Mouse event properties + * @param mouse_leave Mouse leave state (true if mouse left source) + */ + void (*mouse_move)(void *data, + const struct obs_mouse_event *event, bool mouse_leave); + + /** + * Called when interacting with a source and a mouse-wheel occurs. + * + * @param data Source data + * @param event Mouse event properties + * @param x_delta Movement delta in the horizontal direction + * @param y_delta Movement delta in the vertical direction + */ + void (*mouse_wheel)(void *data, + const struct obs_mouse_event *event, int x_delta, + int y_delta); + /** + * Called when interacting with a source and gain focus/lost focus event + * occurs. + * + * @param data Source data + * @param focus Focus state (true if focus gained) + */ + void (*focus)(void *data, bool focus); + + /** + * Called when interacting with a source and a key-up or key-down + * occurs. + * + * @param data Source data + * @param event Key event properties + * @param focus Key event type (true if mouse-up) + */ + void (*key_click)(void *data, const struct obs_key_event *event, + bool key_up); }; EXPORT void obs_register_source_s(const struct obs_source_info *info, diff --git a/libobs/obs.h b/libobs/obs.h index e54f5a059405e4a622d8bb5c20b8586d84ce5390..8bbe8b2e5cb60f63c2fc40027856965e3035a7af 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -33,6 +33,7 @@ #include "obs-data.h" #include "obs-ui.h" #include "obs-properties.h" +#include "obs-interaction.h" struct matrix4; @@ -751,6 +752,27 @@ EXPORT void obs_source_add_child(obs_source_t parent, obs_source_t child); */ EXPORT void obs_source_remove_child(obs_source_t parent, obs_source_t child); +/** Sends a mouse down/up event to a source */ +EXPORT void obs_source_send_mouse_click(obs_source_t source, + const struct obs_mouse_event *event, + int32_t type, bool mouse_up, + uint32_t click_count); + +/** Sends a mouse move event to a source. */ +EXPORT void obs_source_send_mouse_move(obs_source_t source, + const struct obs_mouse_event *event, bool mouse_leave); + +/** Sends a mouse wheel event to a source */ +EXPORT void obs_source_send_mouse_wheel(obs_source_t source, + const struct obs_mouse_event *event, int x_delta, int y_delta); + +/** Sends a got-focus or lost-focus event to a source */ +EXPORT void obs_source_send_focus(obs_source_t source, bool focus); + +/** Sends a key up/down event to a source */ +EXPORT void obs_source_send_key_click(obs_source_t source, + const struct obs_key_event *event, bool key_up); + /** Begins transition frame. Sets all transitioning volume values to 0.0f. */ EXPORT void obs_transition_begin_frame(obs_source_t transition); diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index 84a14a844ed3e367e1cea652f3458f1c72213597..9cac34198e82bf79f882b3a0eaafd6b49c618142 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -74,6 +74,7 @@ set(obs_SOURCES obs-app.cpp window-basic-main.cpp window-basic-settings.cpp + window-basic-interaction.cpp window-basic-properties.cpp window-basic-source-select.cpp window-license-agreement.cpp @@ -92,6 +93,7 @@ set(obs_HEADERS window-main.hpp window-basic-main.hpp window-basic-settings.hpp + window-basic-interaction.hpp window-basic-properties.hpp window-basic-source-select.hpp window-license-agreement.hpp @@ -114,6 +116,7 @@ set(obs_UI forms/OBSBasicTransform.ui forms/OBSBasicSettings.ui forms/OBSBasicSourceSelect.ui + forms/OBSBasicInteraction.ui forms/OBSBasicProperties.ui) set(obs_QRC diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index d6b620e645057f423d14cbd0d345e56c1eea1aac..3377dbf9842d1abc8ec18997112d8510a45e5b2f 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -95,6 +95,9 @@ Basic.PropertiesWindow.AutoSelectFormat="%1 (unsupported; autoselect: %2)" Basic.PropertiesWindow.SelectColor="Select color" Basic.PropertiesWindow.SelectFont="Select font" +# interaction window +Basic.InteractionWindow="Interacting with '%1'" + # status bar Basic.StatusBar.Reconnecting="Disconnected, reconnecting (attempt %1)" Basic.StatusBar.ReconnectSuccessful="Reconnection successful" diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui index bb5434d56dec20e300cc75c81564db34d647855f..19a529d71641372babc7a2b9f1eb16241ce1646b 100644 --- a/obs/forms/OBSBasic.ui +++ b/obs/forms/OBSBasic.ui @@ -791,6 +791,11 @@ <string>Basic.MainMenu.Help.CheckForUpdates</string> </property> </action> + <action name="actionInteract"> + <property name="text"> + <string>Interact</string> + </property> + </action> </widget> <customwidgets> <customwidget> diff --git a/obs/forms/OBSBasicInteraction.ui b/obs/forms/OBSBasicInteraction.ui new file mode 100644 index 0000000000000000000000000000000000000000..65d313bc93f5ba10ad9621dd2123daea759a9c30 --- /dev/null +++ b/obs/forms/OBSBasicInteraction.ui @@ -0,0 +1,48 @@ +<?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> diff --git a/obs/window-basic-interaction.cpp b/obs/window-basic-interaction.cpp new file mode 100644 index 0000000000000000000000000000000000000000..15460b0cc7d06cc0e8cd406239bde17e000e4396 --- /dev/null +++ b/obs/window-basic-interaction.cpp @@ -0,0 +1,391 @@ +/****************************************************************************** + 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); +} diff --git a/obs/window-basic-interaction.hpp b/obs/window-basic-interaction.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e2633b49b9ea290e90dd4f4b42c0d342a669f70d --- /dev/null +++ b/obs/window-basic-interaction.hpp @@ -0,0 +1,90 @@ +/****************************************************************************** + 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; +}; diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index bf516015b6270ca680a0a8c367cec5540aee3d4a..705e3badf1142a0357047d9fcc4c1c663946c4c8 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -596,6 +596,9 @@ OBSBasic::~OBSBasic() delete cpuUsageTimer; os_cpu_usage_info_destroy(cpuUsageInfo); + if (interaction) + delete interaction; + if (properties) delete properties; @@ -624,12 +627,16 @@ OBSScene OBSBasic::GetCurrentScene() return item ? item->data(Qt::UserRole).value<OBSScene>() : nullptr; } -OBSSceneItem OBSBasic::GetCurrentSceneItem() +OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) { - QListWidgetItem *item = ui->sources->currentItem(); return item ? item->data(Qt::UserRole).value<OBSSceneItem>() : nullptr; } +OBSSceneItem OBSBasic::GetCurrentSceneItem() +{ + return GetSceneItem(ui->sources->currentItem()); +} + void OBSBasic::UpdateSources(OBSScene scene) { ui->sources->clear(); @@ -663,6 +670,16 @@ void OBSBasic::InsertSceneItem(obs_sceneitem_t item) 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) { if (properties) @@ -1606,6 +1623,10 @@ void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) if (addSourceMenu) popup.addSeparator(); + OBSSceneItem sceneItem = GetSceneItem(item); + obs_source_t source = obs_sceneitem_get_source(sceneItem); + QAction *action; + popup.addAction(QTStr("Rename"), this, SLOT(EditSceneItemName())); popup.addAction(QTStr("Remove"), this, @@ -1615,6 +1636,13 @@ void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) popup.addMenu(ui->orderMenu); popup.addMenu(ui->transformMenu); 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, SLOT(on_actionSourceProperties_triggered())); } @@ -1699,6 +1727,15 @@ void OBSBasic::on_actionRemoveSource_triggered() 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() { OBSSceneItem item = GetCurrentSceneItem(); diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index 516c179d3c9e109357a86ef0b57cd630a5b25785..72795183e5d49d659523472c951198b255a24d2f 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -25,6 +25,7 @@ #include <vector> #include <memory> #include "window-main.hpp" +#include "window-basic-interaction.hpp" #include "window-basic-properties.hpp" #include "window-basic-transform.hpp" @@ -57,6 +58,7 @@ private: bool loaded = false; + QPointer<OBSBasicInteraction> interaction; QPointer<OBSBasicProperties> properties; QPointer<OBSBasicTransform> transformWindow; @@ -120,6 +122,7 @@ private: void InitPrimitives(); + OBSSceneItem GetSceneItem(QListWidgetItem *item); OBSSceneItem GetCurrentSceneItem(); bool QueryRemoveSource(obs_source_t source); @@ -140,6 +143,7 @@ private: void TempStreamOutput(const char *url, const char *key, int vBitrate, int aBitrate); + void CreateInteractionWindow(obs_source_t source); void CreatePropertiesWindow(obs_source_t source); public slots: @@ -259,6 +263,7 @@ private slots: void on_sources_customContextMenuRequested(const QPoint &pos); void on_actionAddSource_triggered(); void on_actionRemoveSource_triggered(); + void on_actionInteract_triggered(); void on_actionSourceProperties_triggered(); void on_actionSourceUp_triggered(); void on_actionSourceDown_triggered();