diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 95737ed3afe..d098bf26660 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -375,7 +375,7 @@ lmms--gui--TrackContentWidget { /* gear button in tracks */ -lmms--gui--TrackOperationsWidget > QPushButton { +lmms--gui--TrackOperationsWidget QPushButton { max-height: 26px; max-width: 26px; min-height: 26px; @@ -384,7 +384,7 @@ lmms--gui--TrackOperationsWidget > QPushButton { border: none; } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator { image: url("resources:trackop.png"); subcontrol-origin: padding; subcontrol-position: center; @@ -392,12 +392,12 @@ lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { top: 1px; } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:hover { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:hover { image: url("resources:trackop_h.png"); } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:pressed, -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:checked { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:pressed, +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:checked { image: url("resources:trackop_c.png"); position: relative; top: 2px; diff --git a/data/themes/default/style.css b/data/themes/default/style.css index e2dd369f952..83933df199a 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -411,7 +411,7 @@ lmms--gui--TrackContentWidget { /* gear button in tracks */ -lmms--gui--TrackOperationsWidget > QPushButton { +lmms--gui--TrackOperationsWidget QPushButton { max-height: 26px; max-width: 26px; min-height: 26px; @@ -420,7 +420,7 @@ lmms--gui--TrackOperationsWidget > QPushButton { border: none; } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator { image: url("resources:trackop.png"); subcontrol-origin: padding; subcontrol-position: center; @@ -428,8 +428,8 @@ lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator { top: 1px; } -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:pressed, -lmms--gui--TrackOperationsWidget > QPushButton::menu-indicator:checked { +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:pressed, +lmms--gui--TrackOperationsWidget QPushButton::menu-indicator:checked { image: url("resources:trackop.png"); position: relative; top: 2px; diff --git a/include/PixmapButton.h b/include/PixmapButton.h index 734bd11ae5c..a0b4141deba 100644 --- a/include/PixmapButton.h +++ b/include/PixmapButton.h @@ -45,6 +45,7 @@ class LMMS_EXPORT PixmapButton : public AutomatableButton void setInactiveGraphic( const QPixmap & _pm, bool _update = true ); QSize sizeHint() const override; + QSize minimumSizeHint() const override; signals: void doubleClicked(); diff --git a/include/TrackGrip.h b/include/TrackGrip.h new file mode 100644 index 00000000000..aa19222a6ff --- /dev/null +++ b/include/TrackGrip.h @@ -0,0 +1,68 @@ +/* + * TrackGrip.h - Grip that can be used to move tracks + * + * Copyright (c) 2024- Michael Gregorius + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef LMMS_GUI_TRACK_GRIP_H +#define LMMS_GUI_TRACK_GRIP_H + +#include + + +class QPixmap; + +namespace lmms +{ + +class Track; + +namespace gui +{ + +class TrackGrip : public QWidget +{ + Q_OBJECT +public: + TrackGrip(Track* track, QWidget* parent = 0); + ~TrackGrip() override = default; + +signals: + void grabbed(); + void released(); + +protected: + void mousePressEvent(QMouseEvent*) override; + void mouseReleaseEvent(QMouseEvent*) override; + void paintEvent(QPaintEvent*) override; + +private: + Track* m_track = nullptr; + bool m_isGrabbed = false; + static QPixmap* s_grabbedPixmap; + static QPixmap* s_releasedPixmap; +}; + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_TRACK_GRIP_H diff --git a/include/TrackOperationsWidget.h b/include/TrackOperationsWidget.h index 4dbb5353c5f..8417298b423 100644 --- a/include/TrackOperationsWidget.h +++ b/include/TrackOperationsWidget.h @@ -33,6 +33,7 @@ namespace lmms::gui { class PixmapButton; +class TrackGrip; class TrackView; class TrackOperationsWidget : public QWidget @@ -42,6 +43,7 @@ class TrackOperationsWidget : public QWidget TrackOperationsWidget( TrackView * parent ); ~TrackOperationsWidget() override = default; + TrackGrip* getTrackGrip() const { return m_trackGrip; } protected: void mousePressEvent( QMouseEvent * me ) override; @@ -65,6 +67,7 @@ private slots: private: TrackView * m_trackView; + TrackGrip* m_trackGrip; QPushButton * m_trackOps; PixmapButton * m_muteBtn; PixmapButton * m_soloBtn; diff --git a/include/TrackView.h b/include/TrackView.h index b2654202b9a..c13ba1e870e 100644 --- a/include/TrackView.h +++ b/include/TrackView.h @@ -171,7 +171,8 @@ public slots: private slots: void createClipView( lmms::Clip * clip ); void muteChanged(); - + void onTrackGripGrabbed(); + void onTrackGripReleased(); } ; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4195ec58c1a..fe4a2c462b2 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -94,6 +94,7 @@ SET(LMMS_SRCS gui/tracks/TrackLabelButton.cpp gui/tracks/TrackOperationsWidget.cpp gui/tracks/TrackRenameLineEdit.cpp + gui/tracks/TrackGrip.cpp gui/tracks/TrackView.cpp gui/widgets/AutomatableButton.cpp diff --git a/src/gui/tracks/TrackGrip.cpp b/src/gui/tracks/TrackGrip.cpp new file mode 100644 index 00000000000..6133c6e962d --- /dev/null +++ b/src/gui/tracks/TrackGrip.cpp @@ -0,0 +1,106 @@ +/* + * TrackGrip.cpp - Grip that can be used to move tracks + * + * Copyright (c) 2024- Michael Gregorius + * + * This file is part of LMMS - https://lmms.io + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "TrackGrip.h" + +#include "embed.h" +#include "Track.h" + +#include +#include +#include + + +namespace lmms::gui +{ + +QPixmap* TrackGrip::s_grabbedPixmap = nullptr; +QPixmap* TrackGrip::s_releasedPixmap = nullptr; + +constexpr int c_margin = 2; + +TrackGrip::TrackGrip(Track* track, QWidget* parent) : + QWidget(parent), + m_track(track) +{ + if (!s_grabbedPixmap) + { + s_grabbedPixmap = new QPixmap(embed::getIconPixmap("track_op_grip_c")); + } + + if (!s_releasedPixmap) + { + s_releasedPixmap = new QPixmap(embed::getIconPixmap("track_op_grip")); + } + + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + + setCursor(Qt::OpenHandCursor); + + setFixedWidth(std::max(s_grabbedPixmap->width(), s_releasedPixmap->width()) + 2 * c_margin); +} + +void TrackGrip::mousePressEvent(QMouseEvent* m) +{ + m->accept(); + + m_isGrabbed = true; + setCursor(Qt::ClosedHandCursor); + + emit grabbed(); + + update(); +} + +void TrackGrip::mouseReleaseEvent(QMouseEvent* m) +{ + m->accept(); + + m_isGrabbed = false; + setCursor(Qt::OpenHandCursor); + + emit released(); + + update(); +} + +void TrackGrip::paintEvent(QPaintEvent*) +{ + QPainter p(this); + + // Check if the color of the track should be used for the background + const auto color = m_track->color(); + const auto muted = m_track->getMutedModel()->value(); + + if (color.has_value() && !muted) + { + p.fillRect(rect(), color.value()); + } + + // Paint the pixmap + auto r = rect().marginsRemoved(QMargins(c_margin, c_margin, c_margin, c_margin)); + p.drawTiledPixmap(r, m_isGrabbed ? *s_grabbedPixmap : *s_releasedPixmap); +} + +} // namespace lmms::gui diff --git a/src/gui/tracks/TrackOperationsWidget.cpp b/src/gui/tracks/TrackOperationsWidget.cpp index 6cb74e2c5b7..6aca6628242 100644 --- a/src/gui/tracks/TrackOperationsWidget.cpp +++ b/src/gui/tracks/TrackOperationsWidget.cpp @@ -24,6 +24,7 @@ #include "TrackOperationsWidget.h" +#include #include #include #include @@ -44,6 +45,7 @@ #include "StringPairDrag.h" #include "Track.h" #include "TrackContainerView.h" +#include "TrackGrip.h" #include "TrackView.h" namespace lmms::gui @@ -68,41 +70,86 @@ TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) : setObjectName( "automationEnabled" ); + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->setAlignment(Qt::AlignTop); - m_trackOps = new QPushButton( this ); - m_trackOps->move( 12, 1 ); + m_trackGrip = new TrackGrip(m_trackView->getTrack(), this); + layout->addWidget(m_trackGrip); + + // This widget holds the gear icon and the mute and solo + // buttons in a layout. + auto operationsWidget = new QWidget(this); + auto operationsLayout = new QHBoxLayout(operationsWidget); + operationsLayout->setContentsMargins(0, 0, 0, 0); + operationsLayout->setSpacing(0); + + m_trackOps = new QPushButton(operationsWidget); m_trackOps->setFocusPolicy( Qt::NoFocus ); m_trackOps->setMenu( toMenu ); m_trackOps->setToolTip(tr("Actions")); + // This helper lambda wraps a PixmapButton in a QWidget. This is necessary due to some strange effect where the + // PixmapButtons are resized to a size that's larger than their minimum/fixed size when the method "show" is called + // in "TrackContainerView::realignTracks". Specifically, with the default theme the buttons are resized from + // (16, 14) to (26, 26). This then makes them behave not as expected in layouts. + // The resizing is not done for QWidgets. Therefore we wrap the PixmapButton in a QWidget which is set to a + // fixed size that will be able to show the active and inactive pixmap. We can then use the QWidget in layouts + // without any disturbances. + // + // The resizing only seems to affect the track view hierarchy and is triggered by Qt's internal mechanisms. + // For example the buttons in the mixer view do not seem to be affected. + // If you want to debug this simply override "PixmapButton::resizeEvent" and trigger a break point in there. + auto buildPixmapButtonWrappedInWidget = [](QWidget* parent, const QString& toolTip, + std::string_view activeGraphic, std::string_view inactiveGraphic, PixmapButton*& pixmapButton) + { + const auto activePixmap = embed::getIconPixmap(activeGraphic); + const auto inactivePixmap = embed::getIconPixmap(inactiveGraphic); + + auto necessarySize = activePixmap.size().expandedTo(inactivePixmap.size()); + + auto wrapperWidget = new QWidget(parent); + wrapperWidget->setFixedSize(necessarySize); + + auto button = new PixmapButton(wrapperWidget, toolTip); + button->setCheckable(true); + button->setActiveGraphic(activePixmap); + button->setInactiveGraphic(inactivePixmap); + button->setToolTip(toolTip); + + pixmapButton = button; + + return wrapperWidget; + }; - m_muteBtn = new PixmapButton( this, tr( "Mute" ) ); - m_muteBtn->setActiveGraphic( embed::getIconPixmap( "led_off" ) ); - m_muteBtn->setInactiveGraphic( embed::getIconPixmap( "led_green" ) ); - m_muteBtn->setCheckable( true ); + auto muteWidget = buildPixmapButtonWrappedInWidget(operationsWidget, tr("Mute"), "led_off", "led_green", m_muteBtn); + auto soloWidget = buildPixmapButtonWrappedInWidget(operationsWidget, tr("Solo"), "led_red", "led_off", m_soloBtn); - m_soloBtn = new PixmapButton( this, tr( "Solo" ) ); - m_soloBtn->setActiveGraphic( embed::getIconPixmap( "led_red" ) ); - m_soloBtn->setInactiveGraphic( embed::getIconPixmap( "led_off" ) ); - m_soloBtn->setCheckable( true ); + operationsLayout->addWidget(m_trackOps, Qt::AlignCenter); + operationsLayout->addSpacing(5); if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { - m_muteBtn->move( 46, 0 ); - m_soloBtn->move( 46, 16 ); + auto vlayout = new QVBoxLayout(); + vlayout->setContentsMargins(0, 0, 0, 0); + vlayout->setSpacing(0); + vlayout->addStretch(1); + vlayout->addWidget(muteWidget); + vlayout->addWidget(soloWidget); + vlayout->addStretch(1); + operationsLayout->addLayout(vlayout); } else { - m_muteBtn->move( 46, 8 ); - m_soloBtn->move( 62, 8 ); + operationsLayout->addWidget(muteWidget, Qt::AlignCenter); + operationsLayout->addWidget(soloWidget, Qt::AlignCenter); } - m_muteBtn->show(); - m_muteBtn->setToolTip(tr("Mute")); + operationsLayout->addStretch(1); - m_soloBtn->show(); - m_soloBtn->setToolTip(tr("Solo")); + layout->addWidget(operationsWidget, 0, Qt::AlignTop); connect( this, SIGNAL(trackRemovalScheduled(lmms::gui::TrackView*)), m_trackView->trackContainerView(), @@ -154,31 +201,17 @@ void TrackOperationsWidget::mousePressEvent( QMouseEvent * me ) -/*! \brief Repaint the trackOperationsWidget +/*! + * \brief Repaint the trackOperationsWidget * - * If we're not moving, and in the Pattern Editor, then turn - * automation on or off depending on its previous state and show - * ourselves. - * - * Otherwise, hide ourselves. - * - * \todo Flesh this out a bit - is it correct? - * \param pe The paint event to respond to + * Only things that's done for now is to paint the background + * with the brush of the window from the palette. */ -void TrackOperationsWidget::paintEvent( QPaintEvent * pe ) +void TrackOperationsWidget::paintEvent(QPaintEvent*) { QPainter p( this ); p.fillRect(rect(), palette().brush(QPalette::Window)); - - if (m_trackView->getTrack()->color().has_value() && !m_trackView->getTrack()->getMutedModel()->value()) - { - QRect coloredRect( 0, 0, 10, m_trackView->getTrack()->getHeight() ); - - p.fillRect(coloredRect, m_trackView->getTrack()->color().value()); - } - - p.drawPixmap(2, 2, embed::getIconPixmap(m_trackView->isMovingTrack() ? "track_op_grip_c" : "track_op_grip")); } diff --git a/src/gui/tracks/TrackView.cpp b/src/gui/tracks/TrackView.cpp index e2323602154..ecd397975f1 100644 --- a/src/gui/tracks/TrackView.cpp +++ b/src/gui/tracks/TrackView.cpp @@ -41,6 +41,7 @@ #include "PixmapButton.h" #include "StringPairDrag.h" #include "Track.h" +#include "TrackGrip.h" #include "TrackContainerView.h" #include "ClipView.h" @@ -102,6 +103,10 @@ TrackView::TrackView( Track * track, TrackContainerView * tcv ) : connect( &m_track->m_soloModel, SIGNAL(dataChanged()), m_track, SLOT(toggleSolo()), Qt::DirectConnection ); + + auto trackGrip = m_trackOperationsWidget.getTrackGrip(); + connect(trackGrip, &TrackGrip::grabbed, this, &TrackView::onTrackGripGrabbed); + connect(trackGrip, &TrackGrip::released, this, &TrackView::onTrackGripReleased); // create views for already existing clips for (const auto& clip : m_track->m_clips) @@ -284,22 +289,6 @@ void TrackView::mousePressEvent( QMouseEvent * me ) QCursor c( Qt::SizeVerCursor); QApplication::setOverrideCursor( c ); } - else - { - if( me->x()>10 ) // 10 = The width of the grip + 2 pixels to the left and right. - { - QWidget::mousePressEvent( me ); - return; - } - - m_action = Action::Move; - - QCursor c( Qt::SizeVerCursor ); - QApplication::setOverrideCursor( c ); - // update because in move-mode, all elements in - // track-op-widgets are hidden as a visual feedback - m_trackOperationsWidget.update(); - } me->accept(); } @@ -451,6 +440,16 @@ void TrackView::muteChanged() } +void TrackView::onTrackGripGrabbed() +{ + m_action = Action::Move; +} + +void TrackView::onTrackGripReleased() +{ + m_action = Action::None; +} + void TrackView::setIndicatorMute(FadeButton* indicator, bool muted) diff --git a/src/gui/widgets/PixmapButton.cpp b/src/gui/widgets/PixmapButton.cpp index 069acad567c..5f2e01b3d24 100644 --- a/src/gui/widgets/PixmapButton.cpp +++ b/src/gui/widgets/PixmapButton.cpp @@ -124,16 +124,13 @@ void PixmapButton::setInactiveGraphic( const QPixmap & _pm, bool _update ) QSize PixmapButton::sizeHint() const { - if (isActive()) - { - return m_activePixmap.size(); - } - else - { - return m_inactivePixmap.size(); - } + return minimumSizeHint(); } +QSize PixmapButton::minimumSizeHint() const +{ + return m_activePixmap.size().expandedTo(m_inactivePixmap.size()); +} bool PixmapButton::isActive() const {