Files
Mike-Solar c0dcd33de0 R8 phase 1: extract app/engine shared utilities into shared/include/oakutil
Move the pure-header utilities shared by app/ and engine/ out of
engine/common/ into a new shared/include/oakutil/ layer (define, lerp,
decibel, digit, range, crashpadutils, autoscroll, qtutils, filefunctions
declarations, and a trimmed xmlutils exposing a CancelAtom-free void*
overload). engine/common/ keeps forwarding headers so internal include
paths are unchanged; app/ now includes oakutil/* directly.

engine/node/project.h gains an explicit NodeGroup forward declaration
previously obtained transitively through the old xmlutils.h.
2026-07-27 17:14:56 +08:00

351 lines
7.9 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 3 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 "sliderladder.h"
#include <QApplication>
#include <QDateTime>
#include <QDebug>
#include <QScreen>
#include <QtMath>
#include <QVBoxLayout>
#ifdef Q_OS_MAC
#include <ApplicationServices/ApplicationServices.h>
#endif
#include "oakutil/lerp.h"
#include "oakutil/qtutils.h"
#include "common/configwrapper.h"
namespace olive
{
SliderLadder::SliderLadder(double drag_multiplier, int nb_outer_values,
QString width_hint, QWidget *parent)
: QFrame(parent, Qt::Popup)
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
setFrameShape(QFrame::Box);
setLineWidth(1);
if (!OAK_CONFIG("UseSliderLadders").toBool()) {
nb_outer_values = 0;
}
for (int i = nb_outer_values - 1; i >= 0; i--) {
elements_.append(new SliderLadderElement(
qPow(10, i + 1) * drag_multiplier, width_hint));
}
// Create center entry
SliderLadderElement *start_element =
new SliderLadderElement(drag_multiplier, width_hint);
active_element_ = elements_.size();
start_element->set_highlighted(true);
elements_.append(start_element);
for (int i = 0; i < nb_outer_values; i++) {
elements_.append(new SliderLadderElement(
qPow(10, -i - 1) * drag_multiplier, width_hint));
}
foreach (SliderLadderElement *e, elements_) {
layout->addWidget(e);
}
if (elements_.size() == 1) {
elements_.first()->set_multiplier_visible(false);
}
drag_timer_.setInterval(10);
connect(&drag_timer_, &QTimer::timeout, this, &SliderLadder::timer_update);
screen_ = nullptr;
foreach (QScreen *screen, qApp->screens()) {
if (screen->geometry().contains(QCursor::pos())) {
screen_ = screen;
break;
}
}
if (using_ladders()) {
drag_start_x_ = -1;
wrap_count_ = 0;
} else {
#if defined(Q_OS_MAC)
CGAssociateMouseAndMouseCursorPosition(false);
CGDisplayHideCursor(kCGDirectMainDisplay);
CGGetLastMouseDelta(nullptr, nullptr);
#else
drag_start_x_ = QCursor::pos().x();
drag_start_y_ = QCursor::pos().y();
static_cast<QGuiApplication *>(QApplication::instance())
->setOverrideCursor(Qt::BlankCursor);
#endif
}
}
SliderLadder::~SliderLadder()
{
if (using_ladders()) {
if (wrap_count_ != 0) {
// If wrapped, restore cursor to ladder
QCursor::setPos(pos() + rect().center());
}
} else {
#if defined(Q_OS_MAC)
CGAssociateMouseAndMouseCursorPosition(true);
CGDisplayShowCursor(kCGDirectMainDisplay);
#else
static_cast<QGuiApplication *>(QApplication::instance())
->restoreOverrideCursor();
#endif
}
}
void SliderLadder::set_value(const QString &s)
{
foreach (SliderLadderElement *e, elements_) {
e->set_value(s);
}
}
void SliderLadder::start_listening_to_mouse_input()
{
QMetaObject::invokeMethod(&drag_timer_, "start", Qt::QueuedConnection);
}
void SliderLadder::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event)
this->close();
}
void SliderLadder::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event)
drag_timer_.stop();
emit released();
QFrame::closeEvent(event);
}
void SliderLadder::timer_update()
{
int ladder_left = this->x();
int ladder_right = this->x() + this->width() - 1;
int now_pos = QCursor::pos().x();
if (using_ladders()) {
bool is_under_mouse = (now_pos >= ladder_left &&
now_pos <= ladder_right && wrap_count_ == 0);
if (drag_start_x_ != -1 &&
(is_under_mouse ||
(drag_start_x_ < ladder_left && now_pos > ladder_right) ||
(drag_start_x_ > ladder_right && now_pos < ladder_left))) {
// We're ending a drag, try to return the value back to its beginning
int anchor;
if (drag_start_x_ < ladder_left) {
anchor = ladder_left;
} else {
anchor = ladder_right;
}
int makeup_value = anchor - drag_start_x_;
emit dragged_by_value(makeup_value,
elements_.at(active_element_)->get_multiplier());
drag_start_x_ = -1;
}
if (is_under_mouse) {
// Determine which element is currently active
for (int i = 0; i < elements_.size(); i++) {
if (elements_.at(i)->underMouse()) {
if (i != active_element_) {
elements_.at(active_element_)->set_highlighted(false);
active_element_ = i;
elements_.at(active_element_)->set_highlighted(true);
}
break;
}
}
} else {
if (drag_start_x_ == -1) {
// Drag is a new leave from the ladder, calculate origin
if (now_pos < ladder_left) {
drag_start_x_ = ladder_left;
} else {
drag_start_x_ = ladder_right;
}
}
emit dragged_by_value(now_pos - drag_start_x_,
elements_.at(active_element_)->get_multiplier());
// Determine if cursor is at desktop edge, if so wrap around to other side
if (screen_) {
int left = screen_->geometry().left();
int right = screen_->geometry().right();
int width = right - left;
if (now_pos <= left || now_pos >= right) {
int orig_now_pos = now_pos;
int orig_wrap = wrap_count_;
if (now_pos <= left) {
wrap_count_--;
now_pos += width;
} else {
wrap_count_++;
now_pos -= width;
}
QPoint p(now_pos, QCursor::pos().y());
QCursor::setPos(p);
if (QCursor::pos() != p) {
wrap_count_ = orig_wrap;
now_pos = orig_now_pos;
}
}
}
drag_start_x_ = now_pos;
}
} else {
int32_t x_mvmt, y_mvmt;
// Keep cursor in the same position
#if defined(Q_OS_MAC)
CGGetLastMouseDelta(&x_mvmt, &y_mvmt);
#else
QPoint current_pos = QCursor::pos();
x_mvmt = current_pos.x() - drag_start_x_;
y_mvmt = current_pos.y() - drag_start_y_;
QCursor::setPos(QPoint(drag_start_x_, drag_start_y_));
#endif
if (x_mvmt || y_mvmt) {
double multiplier = 1.0;
if (qApp->keyboardModifiers() & Qt::ControlModifier) {
multiplier *= 0.01;
}
if (qApp->keyboardModifiers() & Qt::ShiftModifier) {
multiplier *= 100.0;
}
emit dragged_by_value(x_mvmt + y_mvmt, multiplier);
}
}
}
bool SliderLadder::using_ladders() const
{
return elements_.size() > 1;
}
SliderLadderElement::SliderLadderElement(const double &multiplier,
QString width_hint, QWidget *parent)
: QWidget(parent)
, multiplier_(multiplier)
, highlighted_(false)
, multiplier_visible_(true)
{
QVBoxLayout *layout = new QVBoxLayout(this);
label_ = new QLabel();
label_->setAlignment(Qt::AlignCenter);
label_->setFixedWidth(
QtUtils::q_font_metrics_width(label_->fontMetrics(), width_hint));
layout->addWidget(label_);
QPalette p = palette();
QColor highlight_color = palette().text().color();
highlight_color.setAlpha(64);
p.setColor(QPalette::Highlight, highlight_color);
setPalette(p);
setAutoFillBackground(true);
update_label();
}
void SliderLadderElement::set_highlighted(bool e)
{
highlighted_ = e;
if (highlighted_) {
setBackgroundRole(QPalette::Highlight);
} else {
setBackgroundRole(QPalette::Window);
}
update_label();
}
void SliderLadderElement::set_value(const QString &value)
{
value_ = value;
update_label();
}
void SliderLadderElement::set_multiplier_visible(bool e)
{
multiplier_visible_ = e;
update_label();
}
void SliderLadderElement::update_label()
{
if (multiplier_visible_) {
QString val_text;
if (highlighted_) {
val_text = value_;
}
label_->setText(QStringLiteral("%1\n%2").arg(
QString::number(multiplier_), val_text));
} else {
label_->setText(value_);
}
}
}