mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-10-30 22:38:30 +08:00
frontend: Improve spin box UX
This commit is contained in:
@@ -24,6 +24,10 @@ if(NOT TARGET OBS::bpm)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/bpm" bpm)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::qt-obs-spinbox)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/qt/obs-spinbox" obs-spinbox)
|
||||
endif()
|
||||
|
||||
add_executable(obs-studio)
|
||||
add_executable(OBS::studio ALIAS obs-studio)
|
||||
|
||||
@@ -38,6 +42,7 @@ target_link_libraries(
|
||||
OBS::frontend-api
|
||||
OBS::json11
|
||||
OBS::bpm
|
||||
OBS::qt-obs-spinbox
|
||||
)
|
||||
|
||||
include(cmake/ui-components.cmake)
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
|
||||
--padding_base_border: calc(var(--padding_base) + 1px);
|
||||
|
||||
--spinbox_min_width: calc(var(--input_height) * 2.5);
|
||||
--spinbox_button_height: calc(var(--input_height_half) - 1px);
|
||||
|
||||
--volume_slider: calc(calc(4px + var(--font_base_value)) / 4);
|
||||
@@ -1307,9 +1308,12 @@ QDoubleSpinBox {
|
||||
border: var(--input_border_width) solid var(--input_bg);
|
||||
border-radius: var(--border_radius);
|
||||
padding: var(--input_padding) var(--input_text_padding);
|
||||
padding-right: var(--padding_base);
|
||||
|
||||
height: var(--input_height);
|
||||
min-width: var(--spinbox_width);
|
||||
max-height: var(--input_height);
|
||||
min-width: var(--spinbox_min_width);
|
||||
}
|
||||
|
||||
QSpinBox:hover,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "OBSBasicTransform.hpp"
|
||||
|
||||
#include <DoubleSpinBox.hpp>
|
||||
#include <widgets/OBSBasic.hpp>
|
||||
|
||||
#include "moc_OBSBasicTransform.cpp"
|
||||
|
||||
@@ -489,7 +489,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="snapDistance">
|
||||
<widget class="OBS::DoubleSpinBox" name="snapDistance">
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
@@ -1947,7 +1947,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="multitrackVideoMaximumAggregateBitrate">
|
||||
<widget class="OBS::SpinBox" name="multitrackVideoMaximumAggregateBitrate">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -1996,7 +1996,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="multitrackVideoMaximumVideoTracks">
|
||||
<widget class="OBS::SpinBox" name="multitrackVideoMaximumVideoTracks">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -2157,7 +2157,7 @@
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_35" stretch="0">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="whipSimulcastTotalLayers">
|
||||
<widget class="OBS::SpinBox" name="whipSimulcastTotalLayers">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
@@ -2507,7 +2507,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="simpleOutputVBitrate">
|
||||
<widget class="OBS::SpinBox" name="simpleOutputVBitrate">
|
||||
<property name="minimum">
|
||||
<number>200</number>
|
||||
</property>
|
||||
@@ -3060,7 +3060,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="simpleRBSecMax">
|
||||
<widget class="OBS::SpinBox" name="simpleRBSecMax">
|
||||
<property name="suffix">
|
||||
<string notr="true"> sec</string>
|
||||
</property>
|
||||
@@ -3083,7 +3083,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="simpleRBMegsMax">
|
||||
<widget class="OBS::SpinBox" name="simpleRBMegsMax">
|
||||
<property name="suffix">
|
||||
<string notr="true"> MB</string>
|
||||
</property>
|
||||
@@ -4146,7 +4146,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QSpinBox" name="advOutSplitFileTime">
|
||||
<widget class="OBS::SpinBox" name="advOutSplitFileTime">
|
||||
<property name="suffix">
|
||||
<string> min</string>
|
||||
</property>
|
||||
@@ -4169,7 +4169,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QSpinBox" name="advOutSplitFileSize">
|
||||
<widget class="OBS::SpinBox" name="advOutSplitFileSize">
|
||||
<property name="suffix">
|
||||
<string> MB</string>
|
||||
</property>
|
||||
@@ -4520,7 +4520,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QSpinBox" name="advOutFFVBitrate">
|
||||
<widget class="OBS::SpinBox" name="advOutFFVBitrate">
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
@@ -4540,7 +4540,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QSpinBox" name="advOutFFVGOPSize">
|
||||
<widget class="OBS::SpinBox" name="advOutFFVGOPSize">
|
||||
<property name="maximum">
|
||||
<number>1000000000</number>
|
||||
</property>
|
||||
@@ -4610,7 +4610,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QSpinBox" name="advOutFFABitrate">
|
||||
<widget class="OBS::SpinBox" name="advOutFFABitrate">
|
||||
<property name="minimum">
|
||||
<number>32</number>
|
||||
</property>
|
||||
@@ -5794,7 +5794,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="advRBSecMax">
|
||||
<widget class="OBS::SpinBox" name="advRBSecMax">
|
||||
<property name="suffix">
|
||||
<string notr="true"> s</string>
|
||||
</property>
|
||||
@@ -5810,7 +5810,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="advRBMegsMax">
|
||||
<widget class="OBS::SpinBox" name="advRBMegsMax">
|
||||
<property name="suffix">
|
||||
<string> MB</string>
|
||||
</property>
|
||||
@@ -6660,7 +6660,7 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignmentFlag::AlignTop">
|
||||
<widget class="QSpinBox" name="fpsInteger">
|
||||
<widget class="OBS::SpinBox" name="fpsInteger">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
@@ -6702,7 +6702,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="fpsNumerator">
|
||||
<widget class="OBS::SpinBox" name="fpsNumerator">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
@@ -6722,7 +6722,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="fpsDenominator">
|
||||
<widget class="OBS::SpinBox" name="fpsDenominator">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
@@ -8118,7 +8118,7 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="sdrWhiteLevel">
|
||||
<widget class="OBS::SpinBox" name="sdrWhiteLevel">
|
||||
<property name="suffix">
|
||||
<string notr="true"> nits</string>
|
||||
</property>
|
||||
@@ -8147,7 +8147,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="hdrNominalPeakLevel">
|
||||
<widget class="OBS::SpinBox" name="hdrNominalPeakLevel">
|
||||
<property name="suffix">
|
||||
<string notr="true"> nits</string>
|
||||
</property>
|
||||
@@ -8367,7 +8367,7 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="streamDelaySec">
|
||||
<widget class="OBS::SpinBox" name="streamDelaySec">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -8477,7 +8477,7 @@
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="reconnectRetryDelay">
|
||||
<widget class="OBS::SpinBox" name="reconnectRetryDelay">
|
||||
<property name="suffix">
|
||||
<string> s</string>
|
||||
</property>
|
||||
@@ -8506,7 +8506,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="reconnectMaxRetries">
|
||||
<widget class="OBS::SpinBox" name="reconnectMaxRetries">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
@@ -8797,9 +8797,19 @@
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>UrlPushButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>components/UrlPushButton.hpp</header>
|
||||
<class>OBS::SpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>SpinBox.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>OBS::DoubleSpinBox</class>
|
||||
<extends>QDoubleSpinBox</extends>
|
||||
<header>DoubleSpinBox.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>AbsoluteSlider</class>
|
||||
<extends>QSlider</extends>
|
||||
<header>components/AbsoluteSlider.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>OBSHotkeyEdit</class>
|
||||
@@ -8807,9 +8817,9 @@
|
||||
<header>settings/OBSHotkeyEdit.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>AbsoluteSlider</class>
|
||||
<extends>QSlider</extends>
|
||||
<header>components/AbsoluteSlider.hpp</header>
|
||||
<class>UrlPushButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>components/UrlPushButton.hpp</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QDoubleSpinBox" name="sizeX">
|
||||
<widget class="OBS::DoubleSpinBox" name="sizeX">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
@@ -169,7 +169,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="positionX">
|
||||
<widget class="OBS::DoubleSpinBox" name="positionX">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
@@ -200,7 +200,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QDoubleSpinBox" name="rotation">
|
||||
<widget class="OBS::DoubleSpinBox" name="rotation">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -295,7 +295,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="positionY">
|
||||
<widget class="OBS::DoubleSpinBox" name="positionY">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
@@ -326,7 +326,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QDoubleSpinBox" name="sizeY">
|
||||
<widget class="OBS::DoubleSpinBox" name="sizeY">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
@@ -586,7 +586,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="boundsWidth">
|
||||
<widget class="OBS::DoubleSpinBox" name="boundsWidth">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
@@ -620,7 +620,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="boundsHeight">
|
||||
<widget class="OBS::DoubleSpinBox" name="boundsHeight">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
@@ -729,7 +729,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<widget class="QSpinBox" name="cropBottom">
|
||||
<widget class="OBS::SpinBox" name="cropBottom">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -773,7 +773,7 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QSpinBox" name="cropRight">
|
||||
<widget class="OBS::SpinBox" name="cropRight">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -804,7 +804,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="cropLeft">
|
||||
<widget class="OBS::SpinBox" name="cropLeft">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -845,7 +845,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="cropTop">
|
||||
<widget class="OBS::SpinBox" name="cropTop">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -928,6 +928,18 @@
|
||||
<tabstop>cropTop</tabstop>
|
||||
<tabstop>cropBottom</tabstop>
|
||||
</tabstops>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>OBS::SpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>SpinBox.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>OBS::DoubleSpinBox</class>
|
||||
<extends>QDoubleSpinBox</extends>
|
||||
<header>DoubleSpinBox.hpp</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
||||
@@ -28,6 +28,10 @@ if(NOT TARGET OBS::qt-icon-label)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/qt/icon-label" "${CMAKE_BINARY_DIR}/shared/qt/icon-label")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET OBS::qt-obs-spinbox)
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/shared/qt/obs-spinbox" "${CMAKE_BINARY_DIR}/shared/qt/obs-spinbox")
|
||||
endif()
|
||||
|
||||
add_library(properties-view INTERFACE)
|
||||
add_library(OBS::properties-view ALIAS properties-view)
|
||||
|
||||
@@ -51,6 +55,7 @@ target_link_libraries(
|
||||
OBS::libobs
|
||||
OBS::qt-wrappers
|
||||
OBS::qt-plain-text-edit
|
||||
OBS::qt-obs-spinbox
|
||||
OBS::qt-vertical-scroll-area
|
||||
OBS::qt-slider-ignorewheel
|
||||
OBS::qt-icon-label
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
#include <QObject>
|
||||
#include <QDesktopServices>
|
||||
#include <QUuid>
|
||||
|
||||
#include "DoubleSpinBox.hpp"
|
||||
#include "SpinBox.hpp"
|
||||
#include "double-slider.hpp"
|
||||
#include "spinbox-ignorewheel.hpp"
|
||||
#include "moc_properties-view.cpp"
|
||||
@@ -434,7 +437,7 @@ void OBSPropertiesView::AddInt(obs_property_t *prop, QFormLayout *layout, QLabel
|
||||
|
||||
const char *name = obs_property_name(prop);
|
||||
int val = (int)obs_data_get_int(settings, name);
|
||||
QSpinBox *spin = new SpinBoxIgnoreScroll();
|
||||
OBS::SpinBox *spin = new OBS::SpinBox();
|
||||
|
||||
spin->setEnabled(obs_property_enabled(prop));
|
||||
|
||||
@@ -482,7 +485,7 @@ void OBSPropertiesView::AddFloat(obs_property_t *prop, QFormLayout *layout, QLab
|
||||
|
||||
const char *name = obs_property_name(prop);
|
||||
double val = obs_data_get_double(settings, name);
|
||||
QDoubleSpinBox *spin = new QDoubleSpinBox();
|
||||
OBS::DoubleSpinBox *spin = new OBS::DoubleSpinBox();
|
||||
|
||||
if (!obs_property_enabled(prop)) {
|
||||
spin->setEnabled(false);
|
||||
@@ -517,8 +520,8 @@ void OBSPropertiesView::AddFloat(obs_property_t *prop, QFormLayout *layout, QLab
|
||||
slider->setOrientation(Qt::Horizontal);
|
||||
subLayout->addWidget(slider);
|
||||
|
||||
connect(slider, &DoubleSlider::doubleValChanged, spin, &QDoubleSpinBox::setValue);
|
||||
connect(spin, &QDoubleSpinBox::valueChanged, slider, &DoubleSlider::setDoubleVal);
|
||||
connect(slider, &DoubleSlider::doubleValChanged, spin, &OBS::DoubleSpinBox::setValue);
|
||||
connect(spin, &OBS::DoubleSpinBox::valueChanged, slider, &DoubleSlider::setDoubleVal);
|
||||
}
|
||||
|
||||
connect(spin, &QDoubleSpinBox::valueChanged, info, &WidgetInfo::ControlChanged);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2026 by Taylor Giampaolo <warchamp7@obsproject.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 "AutoSelectLineEdit.hpp"
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
||||
namespace OBS {
|
||||
void AutoSelectLineEdit::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
selectAll();
|
||||
event->accept();
|
||||
}
|
||||
} // namespace OBS
|
||||
@@ -0,0 +1,38 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2026 by Taylor Giampaolo <warchamp7@obsproject.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 <QLineEdit>
|
||||
|
||||
namespace OBS {
|
||||
class DoubleSpinBox;
|
||||
class SpinBox;
|
||||
|
||||
class AutoSelectLineEdit : private QLineEdit {
|
||||
Q_OBJECT
|
||||
|
||||
friend class DoubleSpinBox;
|
||||
friend class SpinBox;
|
||||
|
||||
private:
|
||||
explicit AutoSelectLineEdit(QWidget *parent) : QLineEdit(parent) {};
|
||||
|
||||
protected:
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
};
|
||||
} // namespace OBS
|
||||
@@ -0,0 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.28...3.30)
|
||||
|
||||
find_package(Qt6 REQUIRED Core Widgets)
|
||||
|
||||
add_library(qt-obs-spinbox INTERFACE)
|
||||
add_library(OBS::qt-obs-spinbox ALIAS qt-obs-spinbox)
|
||||
|
||||
target_sources(
|
||||
qt-obs-spinbox
|
||||
INTERFACE AutoSelectLineEdit.cpp AutoSelectLineEdit.hpp DoubleSpinBox.cpp DoubleSpinBox.hpp SpinBox.cpp SpinBox.hpp
|
||||
)
|
||||
target_include_directories(qt-obs-spinbox INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
target_link_libraries(qt-obs-spinbox INTERFACE Qt::Core Qt::Widgets)
|
||||
@@ -0,0 +1,245 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2026 by Taylor Giampaolo <warchamp7@obsproject.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 "DoubleSpinBox.hpp"
|
||||
|
||||
#include "AutoSelectLineEdit.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QLocale>
|
||||
#include <QMouseEvent>
|
||||
#include <QStyle>
|
||||
#include <QStyleOptionSpinBox>
|
||||
#include <QTimer>
|
||||
|
||||
namespace {
|
||||
QString trimAffixes(const QString &fullText, const QString &prefix, const QString &suffix)
|
||||
{
|
||||
if (fullText.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString text = fullText;
|
||||
|
||||
if (!prefix.isEmpty() && text.startsWith(prefix)) {
|
||||
text.remove(0, prefix.length());
|
||||
}
|
||||
|
||||
if (!suffix.isEmpty() && text.endsWith(suffix)) {
|
||||
text.chop(suffix.length());
|
||||
}
|
||||
|
||||
return text.trimmed();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace OBS {
|
||||
DoubleSpinBox::DoubleSpinBox(QWidget *parent) : QDoubleSpinBox(parent)
|
||||
{
|
||||
setKeyboardTracking(false);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
auto lineEdit = new AutoSelectLineEdit(this);
|
||||
setLineEdit(lineEdit);
|
||||
|
||||
connect(lineEdit, &QLineEdit::cursorPositionChanged, this, &DoubleSpinBox::clampCursorPosition);
|
||||
}
|
||||
|
||||
DoubleSpinBox::~DoubleSpinBox() {}
|
||||
|
||||
QValidator::State DoubleSpinBox::validate(QString &text, int &) const
|
||||
{
|
||||
QString stripped = trimAffixes(text, prefix(), suffix());
|
||||
|
||||
QString decPoint = locale().decimalPoint();
|
||||
if (stripped.isEmpty() || stripped == "-" || stripped == decPoint || stripped == "-" + decPoint) {
|
||||
return QValidator::Intermediate;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
double val = locale().toDouble(stripped, &ok);
|
||||
|
||||
if (!ok) {
|
||||
return QValidator::Invalid;
|
||||
}
|
||||
|
||||
QString resultText = prefix() + textFromValue(val) + suffix();
|
||||
if (text != resultText) {
|
||||
return QValidator::Intermediate;
|
||||
}
|
||||
|
||||
if (val < minimum() || val > maximum()) {
|
||||
return QValidator::Intermediate;
|
||||
}
|
||||
|
||||
return QValidator::Acceptable;
|
||||
}
|
||||
|
||||
void DoubleSpinBox::fixup(QString &input) const
|
||||
{
|
||||
QString stripped = trimAffixes(input, prefix(), suffix());
|
||||
|
||||
bool ok;
|
||||
double val = locale().toDouble(stripped, &ok);
|
||||
|
||||
if (ok) {
|
||||
val = qBound(minimum(), val, maximum());
|
||||
input = prefix() + textFromValue(val) + suffix();
|
||||
} else {
|
||||
// Fallback to base class behavior if parsing fails
|
||||
QDoubleSpinBox::fixup(input);
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleSpinBox::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QStyleOptionSpinBox opt;
|
||||
initStyleOption(&opt);
|
||||
|
||||
if (style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxUp, this).contains(event->pos())) {
|
||||
QDoubleSpinBox::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxDown, this).contains(event->pos())) {
|
||||
QDoubleSpinBox::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse button was pressed and is not over the spinbox buttons, forward event to the lineEdit.
|
||||
QRect lineEditRect = lineEdit()->geometry();
|
||||
mouseDragYOffset = ((double)height() / 2) - event->position().y();
|
||||
|
||||
if (!lineEditRect.contains(event->pos())) {
|
||||
QPointF localPos = event->pos() - lineEditRect.topLeft();
|
||||
QMouseEvent forwardEvent(event->type(), localPos, event->globalPosition(), event->button(),
|
||||
event->buttons(), event->modifiers());
|
||||
|
||||
QCoreApplication::sendEvent(lineEdit(), &forwardEvent);
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QDoubleSpinBox::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void DoubleSpinBox::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!(event->buttons() & Qt::LeftButton)) {
|
||||
QDoubleSpinBox::mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
QStyleOptionSpinBox opt;
|
||||
initStyleOption(&opt);
|
||||
|
||||
if (style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxUp, this).contains(event->pos())) {
|
||||
QDoubleSpinBox::mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxDown, this).contains(event->pos())) {
|
||||
QDoubleSpinBox::mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse button is down and not over the spinbox buttons, forward move event to the lineEdit.
|
||||
// This allows events within the spinbox but outside the actual lineEdit widget to work.
|
||||
QPointF relativePos = lineEdit()->mapFromGlobal(event->globalPosition());
|
||||
relativePos.ry() -= mouseDragYOffset;
|
||||
|
||||
QMouseEvent forwardEvent(event->type(), relativePos, event->globalPosition(), event->button(), event->buttons(),
|
||||
event->modifiers());
|
||||
QCoreApplication::sendEvent(lineEdit(), &forwardEvent);
|
||||
}
|
||||
|
||||
void DoubleSpinBox::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
bool isShiftHeld = QGuiApplication::keyboardModifiers() & Qt::ShiftModifier;
|
||||
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Up:
|
||||
event->accept();
|
||||
if (stepEnabled() & QAbstractSpinBox::StepUpEnabled) {
|
||||
stepBy(isShiftHeld ? 10 : 1);
|
||||
}
|
||||
return;
|
||||
case Qt::Key_Down:
|
||||
event->accept();
|
||||
if (stepEnabled() & QAbstractSpinBox::StepDownEnabled) {
|
||||
stepBy(isShiftHeld ? -10 : -1);
|
||||
}
|
||||
return;
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
event->accept();
|
||||
interpretText();
|
||||
return;
|
||||
}
|
||||
|
||||
QDoubleSpinBox::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void DoubleSpinBox::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
if (!hasFocus()) {
|
||||
event->ignore();
|
||||
} else {
|
||||
QDoubleSpinBox::wheelEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleSpinBox::clampCursorPosition()
|
||||
{
|
||||
QSignalBlocker block(lineEdit());
|
||||
|
||||
if (suffix().isEmpty() && prefix().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cursorPosition = lineEdit()->cursorPosition();
|
||||
bool hasSelectedText = !lineEdit()->selectedText().isEmpty();
|
||||
|
||||
if (!suffix().isEmpty()) {
|
||||
int suffixIndex = lineEdit()->text().length() - suffix().length();
|
||||
if (cursorPosition > suffixIndex) {
|
||||
int cursorOvershoot = lineEdit()->selectionEnd() - suffixIndex;
|
||||
if (hasSelectedText && cursorOvershoot > 0) {
|
||||
lineEdit()->setSelection(lineEdit()->selectionStart(),
|
||||
lineEdit()->selectionLength() - cursorOvershoot);
|
||||
} else {
|
||||
lineEdit()->setCursorPosition(suffixIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prefix().isEmpty()) {
|
||||
int prefixIndex = prefix().length();
|
||||
if (cursorPosition < prefixIndex) {
|
||||
int cursorUndershoot = prefixIndex - lineEdit()->selectionStart();
|
||||
if (hasSelectedText && cursorUndershoot > 0) {
|
||||
lineEdit()->setSelection(lineEdit()->selectionEnd(),
|
||||
-lineEdit()->selectionLength() + cursorUndershoot);
|
||||
} else {
|
||||
lineEdit()->setCursorPosition(prefixIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace OBS
|
||||
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2026 by Taylor Giampaolo <warchamp7@obsproject.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 <QDoubleSpinBox>
|
||||
|
||||
namespace OBS {
|
||||
class DoubleSpinBox : public QDoubleSpinBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DoubleSpinBox(QWidget *parent = nullptr);
|
||||
~DoubleSpinBox();
|
||||
|
||||
virtual QValidator::State validate(QString &text, int &pos) const override;
|
||||
virtual void fixup(QString &input) const override;
|
||||
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
virtual void keyPressEvent(QKeyEvent *event) override;
|
||||
virtual void wheelEvent(QWheelEvent *event) override;
|
||||
|
||||
private:
|
||||
double mouseDragYOffset = 0.0;
|
||||
|
||||
private slots:
|
||||
void clampCursorPosition();
|
||||
};
|
||||
} // namespace OBS
|
||||
@@ -0,0 +1,243 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2026 by Taylor Giampaolo <warchamp7@obsproject.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 "SpinBox.hpp"
|
||||
|
||||
#include "AutoSelectLineEdit.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QLocale>
|
||||
#include <QMouseEvent>
|
||||
#include <QStyle>
|
||||
#include <QStyleOptionSpinBox>
|
||||
#include <QTimer>
|
||||
|
||||
namespace {
|
||||
QString trimAffixes(const QString &fullText, const QString &prefix, const QString &suffix)
|
||||
{
|
||||
if (fullText.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString text = fullText;
|
||||
|
||||
if (!prefix.isEmpty() && text.startsWith(prefix)) {
|
||||
text.remove(0, prefix.length());
|
||||
}
|
||||
|
||||
if (!suffix.isEmpty() && text.endsWith(suffix)) {
|
||||
text.chop(suffix.length());
|
||||
}
|
||||
|
||||
return text.trimmed();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace OBS {
|
||||
SpinBox::SpinBox(QWidget *parent) : QSpinBox(parent)
|
||||
{
|
||||
setKeyboardTracking(false);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
auto lineEdit = new AutoSelectLineEdit(this);
|
||||
setLineEdit(lineEdit);
|
||||
|
||||
connect(lineEdit, &QLineEdit::cursorPositionChanged, this, &SpinBox::clampCursorPosition);
|
||||
}
|
||||
|
||||
SpinBox::~SpinBox() {}
|
||||
|
||||
QValidator::State SpinBox::validate(QString &text, int &) const
|
||||
{
|
||||
QString stripped = trimAffixes(text, prefix(), suffix());
|
||||
|
||||
if (stripped.isEmpty() || stripped == "-") {
|
||||
return QValidator::Intermediate;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
int val = locale().toInt(stripped, &ok);
|
||||
|
||||
if (!ok) {
|
||||
return QValidator::Invalid;
|
||||
}
|
||||
|
||||
QString resultText = prefix() + textFromValue(val) + suffix();
|
||||
if (text != resultText) {
|
||||
return QValidator::Intermediate;
|
||||
}
|
||||
|
||||
if (val < minimum() || val > maximum()) {
|
||||
return QValidator::Intermediate;
|
||||
}
|
||||
|
||||
return QValidator::Acceptable;
|
||||
}
|
||||
|
||||
void SpinBox::fixup(QString &input) const
|
||||
{
|
||||
QString stripped = trimAffixes(input, prefix(), suffix());
|
||||
|
||||
bool ok;
|
||||
int val = locale().toInt(stripped, &ok);
|
||||
|
||||
if (ok) {
|
||||
val = qBound(minimum(), val, maximum());
|
||||
input = prefix() + textFromValue(val) + suffix();
|
||||
} else {
|
||||
// Fallback to base class behavior if parsing fails
|
||||
QSpinBox::fixup(input);
|
||||
}
|
||||
}
|
||||
|
||||
void SpinBox::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QStyleOptionSpinBox opt;
|
||||
initStyleOption(&opt);
|
||||
|
||||
if (style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxUp, this).contains(event->pos())) {
|
||||
QSpinBox::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxDown, this).contains(event->pos())) {
|
||||
QSpinBox::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse button was pressed and is not over the spinbox buttons, forward event to the lineEdit.
|
||||
QRect lineEditRect = lineEdit()->geometry();
|
||||
mouseDragYOffset = ((double)height() / 2) - event->position().y();
|
||||
|
||||
if (!lineEditRect.contains(event->pos())) {
|
||||
QPointF localPos = event->pos() - lineEditRect.topLeft();
|
||||
QMouseEvent forwardEvent(event->type(), localPos, event->globalPosition(), event->button(),
|
||||
event->buttons(), event->modifiers());
|
||||
|
||||
QCoreApplication::sendEvent(lineEdit(), &forwardEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
QSpinBox::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void SpinBox::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!(event->buttons() & Qt::LeftButton)) {
|
||||
QSpinBox::mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
QStyleOptionSpinBox opt;
|
||||
initStyleOption(&opt);
|
||||
|
||||
if (style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxUp, this).contains(event->pos())) {
|
||||
QSpinBox::mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (style()->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxDown, this).contains(event->pos())) {
|
||||
QSpinBox::mouseMoveEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse button is down and not over the spinbox buttons, forward move event to the lineEdit.
|
||||
// This allows events within the spinbox but outside the actual lineEdit widget to work.
|
||||
QPointF relativePos = lineEdit()->mapFromGlobal(event->globalPosition());
|
||||
relativePos.ry() -= mouseDragYOffset;
|
||||
|
||||
QMouseEvent forwardEvent(event->type(), relativePos, event->globalPosition(), event->button(), event->buttons(),
|
||||
event->modifiers());
|
||||
QCoreApplication::sendEvent(lineEdit(), &forwardEvent);
|
||||
}
|
||||
|
||||
void SpinBox::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
bool isShiftHeld = QGuiApplication::keyboardModifiers() & Qt::ShiftModifier;
|
||||
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Up:
|
||||
event->accept();
|
||||
if (stepEnabled() & QAbstractSpinBox::StepUpEnabled) {
|
||||
stepBy(isShiftHeld ? 10 : 1);
|
||||
}
|
||||
return;
|
||||
case Qt::Key_Down:
|
||||
event->accept();
|
||||
if (stepEnabled() & QAbstractSpinBox::StepDownEnabled) {
|
||||
stepBy(isShiftHeld ? -10 : -1);
|
||||
}
|
||||
return;
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
event->accept();
|
||||
interpretText();
|
||||
return;
|
||||
}
|
||||
|
||||
QSpinBox::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void SpinBox::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
if (!hasFocus()) {
|
||||
event->ignore();
|
||||
} else {
|
||||
QSpinBox::wheelEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void SpinBox::clampCursorPosition()
|
||||
{
|
||||
QSignalBlocker block(lineEdit());
|
||||
|
||||
if (suffix().isEmpty() && prefix().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cursorPosition = lineEdit()->cursorPosition();
|
||||
bool hasSelectedText = !lineEdit()->selectedText().isEmpty();
|
||||
|
||||
if (!suffix().isEmpty()) {
|
||||
int suffixIndex = lineEdit()->text().length() - suffix().length();
|
||||
if (cursorPosition > suffixIndex) {
|
||||
int cursorOvershoot = lineEdit()->selectionEnd() - suffixIndex;
|
||||
if (hasSelectedText && cursorOvershoot > 0) {
|
||||
lineEdit()->setSelection(lineEdit()->selectionStart(),
|
||||
lineEdit()->selectionLength() - cursorOvershoot);
|
||||
} else {
|
||||
lineEdit()->setCursorPosition(suffixIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prefix().isEmpty()) {
|
||||
int prefixIndex = prefix().length();
|
||||
if (cursorPosition < prefixIndex) {
|
||||
int cursorUndershoot = prefixIndex - lineEdit()->selectionStart();
|
||||
if (hasSelectedText && cursorUndershoot > 0) {
|
||||
lineEdit()->setSelection(lineEdit()->selectionEnd(),
|
||||
-lineEdit()->selectionLength() + cursorUndershoot);
|
||||
} else {
|
||||
lineEdit()->setCursorPosition(prefixIndex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace OBS
|
||||
@@ -0,0 +1,45 @@
|
||||
/******************************************************************************
|
||||
Copyright (C) 2026 by Taylor Giampaolo <warchamp7@obsproject.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 <QSpinBox>
|
||||
|
||||
namespace OBS {
|
||||
class SpinBox : public QSpinBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SpinBox(QWidget *parent = nullptr);
|
||||
~SpinBox();
|
||||
|
||||
virtual QValidator::State validate(QString &text, int &pos) const override;
|
||||
virtual void fixup(QString &input) const override;
|
||||
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
virtual void keyPressEvent(QKeyEvent *event) override;
|
||||
virtual void wheelEvent(QWheelEvent *event) override;
|
||||
|
||||
private:
|
||||
double mouseDragYOffset = 0.0;
|
||||
|
||||
private slots:
|
||||
void clampCursorPosition();
|
||||
};
|
||||
} // namespace OBS
|
||||
Reference in New Issue
Block a user