Change zoom in SongEditor to a Slider Zoom (#6664)

Co-authored-by: Dalton Messmer <33463986+messmerd@users.noreply.github.com>
Co-authored-by: Alex <allejok96@gmail.com>
This commit is contained in:
superpaik
2023-08-24 18:02:26 +02:00
committed by GitHub
parent da14de92fe
commit d6cf417a4d
8 changed files with 198 additions and 127 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -418,6 +418,25 @@ lmms--gui--AutomatableSlider::handle:vertical {
margin: -4px -12px -2px;
}
/* main horizontal sliders (zoom) */
lmms--gui--AutomatableSlider::groove:horizontal {
background: rgba(0,0,0, 128);
border: 1px inset rgba(100,100,100, 64);
border-radius: 2px;
height: 2px;
margin: 2px;
}
lmms--gui--AutomatableSlider::handle:horizontal {
background: none;
border-image: url(resources:horizontal_slider.png);
width: 10px;
height: 26px;
border-radius: 2px;
margin: -12px -2px;
}
/* about dialog */
QTabWidget, QTabWidget QWidget {
background: #5b6571;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -443,6 +443,25 @@ lmms--gui--AutomatableSlider::handle:vertical {
margin: -4px -12px -2px;
}
/* main horizontal sliders (zoom) */
lmms--gui--AutomatableSlider::groove:horizontal {
background: #040506;
border: none;
border-radius: 2px;
height: 2px;
margin: 2px;
}
lmms--gui--AutomatableSlider::handle:horizontal {
background: none;
border-image: url(resources:horizontal_slider.png);
width: 10px;
height: 26px;
border-radius: 2px;
margin: -12px -2px;
}
/* window that shows up when you add effects */
lmms--gui--EffectSelectDialog QScrollArea {

View File

@@ -26,6 +26,7 @@
#ifndef LMMS_GUI_SONG_EDITOR_H
#define LMMS_GUI_SONG_EDITOR_H
#include "AutomatableModel.h"
#include "Editor.h"
#include "TrackContainerView.h"
@@ -69,7 +70,6 @@ public:
void saveSettings( QDomDocument& doc, QDomElement& element ) override;
void loadSettings( const QDomElement& element ) override;
ComboBoxModel *zoomingModel() const;
ComboBoxModel *snappingModel() const;
float getSnapSize() const;
QString getSnapSizeString() const;
@@ -120,10 +120,12 @@ private:
bool allowRubberband() const override;
bool knifeMode() const override;
int calculatePixelsPerBar() const;
int calculateZoomSliderValue(int pixelsPerBar) const;
int trackIndexFromSelectionPoint(int yPos);
int indexOfTrackView(const TrackView* tv);
Song * m_song;
QScrollBar * m_leftRightScroll;
@@ -141,12 +143,10 @@ private:
PositionLine * m_positionLine;
ComboBoxModel* m_zoomingModel;
IntModel* m_zoomingModel;
ComboBoxModel* m_snappingModel;
bool m_proportionalSnap;
static const QVector<float> m_zoomLevels;
bool m_scrollBack;
bool m_smoothScroll;
@@ -158,14 +158,14 @@ private:
QPoint m_mousePos;
int m_rubberBandStartTrackview;
TimePos m_rubberbandStartTimePos;
int m_currentZoomingValue;
int m_rubberbandPixelsPerBar; //!< pixels per bar when selection starts
int m_trackHeadWidth;
bool m_selectRegion;
friend class SongEditorWindow;
signals:
void zoomingValueChanged( float );
void pixelsPerBarChanged(float);
} ;
@@ -213,7 +213,7 @@ private:
QAction* m_selectModeAction;
QAction* m_crtlAction;
ComboBox * m_zoomingComboBox;
AutomatableSlider * m_zoomingSlider;
ComboBox * m_snappingComboBox;
QLabel* m_snapSizeLabel;
@@ -221,7 +221,6 @@ private:
QAction* m_removeBarAction;
};
} // namespace gui
} // namespace lmms

View File

@@ -125,7 +125,7 @@ ClipView::ClipView( Clip * clip,
connect( m_clip, SIGNAL(lengthChanged()),
this, SLOT(updateLength()));
connect( getGUI()->songEditor()->m_editor->zoomingModel(), SIGNAL(dataChanged()), this, SLOT(updateLength()));
connect(getGUI()->songEditor()->m_editor, &SongEditor::pixelsPerBarChanged, this, &ClipView::updateLength);
connect( m_clip, SIGNAL(positionChanged()),
this, SLOT(updatePosition()));
connect( m_clip, SIGNAL(destroyedClip()), this, SLOT(close()));
@@ -314,10 +314,9 @@ void ClipView::updateLength()
}
else
{
setFixedWidth(
static_cast<int>( m_clip->length() * pixelsPerBar() /
TimePos::ticksPerBar() ) + 1 /*+
BORDER_WIDTH * 2-1*/ );
// this std::max function is needed for clips that do not start or end on the beat, otherwise, they "disappear" when zooming to min
// 3 is the minimun width needed to make a clip visible
setFixedWidth(std::max(static_cast<int>(m_clip->length() * pixelsPerBar() / TimePos::ticksPerBar() + 1), 3));
}
m_trackView->trackContainerView()->update();
}

View File

@@ -24,11 +24,14 @@
#include "SongEditor.h"
#include <cmath>
#include <QAction>
#include <QKeyEvent>
#include <QLabel>
#include <QMdiArea>
#include <QScrollBar>
#include <QSlider>
#include <QTimeLine>
#include "ActionGroup.h"
@@ -57,14 +60,24 @@
namespace lmms::gui
{
namespace
{
constexpr int MIN_PIXELS_PER_BAR = 2;
constexpr int MAX_PIXELS_PER_BAR = 400;
constexpr int ZOOM_STEPS = 200;
constexpr std::array SNAP_SIZES{8.f, 4.f, 2.f, 1.f, 1/2.f, 1/4.f, 1/8.f, 1/16.f};
constexpr std::array PROPORTIONAL_SNAP_SIZES{64.f, 32.f, 16.f, 8.f, 4.f, 2.f, 1.f, 1/2.f, 1/4.f, 1/8.f, 1/16.f, 1/32.f, 1/64.f};
}
const QVector<float> SongEditor::m_zoomLevels =
{ 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f };
SongEditor::SongEditor( Song * song ) :
TrackContainerView( song ),
m_song( song ),
m_zoomingModel(new ComboBoxModel()),
m_zoomingModel(new IntModel(calculateZoomSliderValue(DEFAULT_PIXELS_PER_BAR), 0, ZOOM_STEPS, nullptr, tr("Zoom"))),
m_snappingModel(new ComboBoxModel()),
m_proportionalSnap( false ),
m_scrollBack( false ),
@@ -75,7 +88,7 @@ SongEditor::SongEditor( Song * song ) :
m_mousePos(),
m_rubberBandStartTrackview(0),
m_rubberbandStartTimePos(0),
m_currentZoomingValue(m_zoomingModel->value()),
m_rubberbandPixelsPerBar(DEFAULT_PIXELS_PER_BAR),
m_trackHeadWidth(ConfigManager::inst()->value("ui", "compacttrackbuttons").toInt()==1
? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT
: DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH),
@@ -83,6 +96,7 @@ SongEditor::SongEditor( Song * song ) :
{
m_zoomingModel->setParent(this);
m_snappingModel->setParent(this);
m_timeLine = new TimeLineWidget( m_trackHeadWidth, 32,
pixelsPerBar(),
m_song->m_playPos[Song::Mode_PlaySong],
@@ -103,12 +117,15 @@ SongEditor::SongEditor( Song * song ) :
m_positionLine = new PositionLine(this);
static_cast<QVBoxLayout *>( layout() )->insertWidget( 1, m_timeLine );
connect( m_song, SIGNAL(playbackStateChanged()),
m_positionLine, SLOT(update()));
connect( this, SIGNAL(zoomingValueChanged(float)),
m_positionLine, SLOT(zoomChange(float)));
// When zoom changes, update position line
// But we must convert pixels per bar to a zoom factor where 1.0 is 100%
connect(this, &SongEditor::pixelsPerBarChanged, m_positionLine,
[this]() { m_positionLine->zoomChange(pixelsPerBar() / float(DEFAULT_PIXELS_PER_BAR)); });
// Ensure loop markers snap to same increments as clips. Zoom & proportional
// snap changes are handled in zoomingChanged() and toggleProportionalSnap()
connect(m_snappingModel, &ComboBoxModel::dataChanged,
@@ -171,8 +188,8 @@ SongEditor::SongEditor( Song * song ) :
SLOT(hideMasterVolumeFloat()));
m_mvsStatus = new TextFloat;
m_mvsStatus->setTitle( tr( "Master volume" ) );
m_mvsStatus->setPixmap( embed::getIconPixmap( "master_volume" ) );
m_mvsStatus->setTitle(tr("Master volume"));
m_mvsStatus->setPixmap(embed::getIconPixmap("master_volume"));
getGUI()->mainWindow()->addWidgetToToolBar( master_vol_lbl );
getGUI()->mainWindow()->addWidgetToToolBar( m_masterVolumeSlider );
@@ -240,33 +257,23 @@ SongEditor::SongEditor( Song * song ) :
connect(contentWidget()->verticalScrollBar(), SIGNAL(valueChanged(int)),this, SLOT(updateRubberband()));
connect(m_timeLine, SIGNAL(selectionFinished()), this, SLOT(stopSelectRegion()));
//zoom connects
connect(m_zoomingModel, SIGNAL(dataChanged()), this, SLOT(zoomingChanged()));
//Set up zooming model
for( float const & zoomLevel : m_zoomLevels )
// Set up snapping model
for (float bars : SNAP_SIZES)
{
m_zoomingModel->addItem( QString( "%1\%" ).arg( zoomLevel * 100 ) );
}
m_zoomingModel->setInitValue(
m_zoomingModel->findText( "100%" ) );
connect( m_zoomingModel, SIGNAL(dataChanged()),
this, SLOT(zoomingChanged()));
connect( m_zoomingModel, SIGNAL(dataChanged()),
m_positionLine, SLOT(update()));
//Set up snapping model, 2^i
for ( int i = 3; i >= -4; i-- )
{
if ( i > 0 )
if (bars > 1.0f)
{
m_snappingModel->addItem( QString( "%1 Bars").arg( 1 << i ) );
m_snappingModel->addItem(QString("%1 Bars").arg(bars));
}
else if ( i == 0 )
else if (bars == 1.0f)
{
m_snappingModel->addItem( "1 Bar" );
}
else
{
m_snappingModel->addItem( QString( "1/%1 Bar" ).arg( 1 << (-i) ) );
m_snappingModel->addItem(QString("1/%1 Bar").arg(1 / bars));
}
}
m_snappingModel->setInitValue( m_snappingModel->findText( "1 Bar" ) );
@@ -291,42 +298,40 @@ void SongEditor::loadSettings( const QDomElement& element )
/*! \brief Return grid size as number of bars */
float SongEditor::getSnapSize() const
{
// 1 Bar is the third value in the snapping dropdown
int val = -m_snappingModel->value() + 3;
float snapSize = SNAP_SIZES[m_snappingModel->value()];
// If proportional snap is on, we snap to finer values when zoomed in
if (m_proportionalSnap)
{
val = val - m_zoomingModel->value() + 3;
// Finds the closest available snap size
const float optimalSize = snapSize * DEFAULT_PIXELS_PER_BAR / pixelsPerBar();
return *std::min_element(PROPORTIONAL_SNAP_SIZES.begin(), PROPORTIONAL_SNAP_SIZES.end(), [optimalSize](float a, float b)
{
return std::abs(a - optimalSize) < std::abs(b - optimalSize);
});
}
val = std::max(val, -6); // -6 gives 1/64th bar snapping. Lower values cause crashing.
if ( val >= 0 ){
return 1 << val;
}
else {
return 1.0 / ( 1 << -val );
}
return snapSize;
}
QString SongEditor::getSnapSizeString() const
{
int val = -m_snappingModel->value() + 3;
val = val - m_zoomingModel->value() + 3;
val = std::max(val, -6); // -6 gives 1/64th bar snapping. Lower values cause crashing.
float bars = getSnapSize();
if ( val >= 0 ){
int bars = 1 << val;
if ( bars == 1 ) { return QString("1 Bar"); }
else
{
return QString( "%1 Bars" ).arg(bars);
}
if (bars < 1)
{
return QString(tr("1/%1 Bar")).arg(round(1 / bars));
}
else {
int div = ( 1 << -val );
return QString( "1/%1 Bar" ).arg(div);
else if (bars >= 2)
{
return QString(tr("%1 Bars")).arg(bars);
}
else
{
return QString("1 Bar");
}
}
@@ -367,7 +372,7 @@ void SongEditor::selectRegionFromPixels(int xStart, int xEnd)
//we save the position of scrollbars, mouse position and zooming level
m_origin = QPoint(xStart, 0);
m_scrollPos = QPoint(m_leftRightScroll->value(), contentWidget()->verticalScrollBar()->value());
m_currentZoomingValue = zoomingModel()->value();
m_rubberbandPixelsPerBar = pixelsPerBar();
//calculate the song position where the mouse was clicked
m_rubberbandStartTimePos = TimePos((xStart - m_trackHeadWidth)
@@ -399,10 +404,9 @@ void SongEditor::updateRubberband()
int originX = m_origin.x();
//take care of the zooming
if (m_currentZoomingValue != m_zoomingModel->value())
if (m_rubberbandPixelsPerBar != pixelsPerBar())
{
originX = m_trackHeadWidth + (originX - m_trackHeadWidth)
* m_zoomLevels[m_zoomingModel->value()] / m_zoomLevels[m_currentZoomingValue];
originX = m_trackHeadWidth + (originX - m_trackHeadWidth) * pixelsPerBar() / m_rubberbandPixelsPerBar;
}
//take care of the scrollbar position
@@ -524,9 +528,13 @@ void SongEditor::keyPressEvent( QKeyEvent * ke )
{
selectAllClips( false );
}
else if (ke->key() == Qt::Key_0 && ke->modifiers() & Qt::ControlModifier)
{
m_zoomingModel->reset();
}
else
{
QWidget::keyPressEvent( ke );
QWidget::keyPressEvent(ke);
}
}
@@ -535,35 +543,24 @@ void SongEditor::keyPressEvent( QKeyEvent * ke )
void SongEditor::wheelEvent( QWheelEvent * we )
{
if( we->modifiers() & Qt::ControlModifier )
if ((we->modifiers() & Qt::ControlModifier) && (position(we).x() > m_trackHeadWidth))
{
int z = m_zoomingModel->value();
if(we->angleDelta().y() > 0)
{
z++;
}
else if(we->angleDelta().y() < 0)
{
z--;
}
z = qBound( 0, z, m_zoomingModel->size() - 1 );
int x = position(we).x() - m_trackHeadWidth;
// bar based on the mouse x-position where the scroll wheel was used
int bar = x / pixelsPerBar();
// what would be the bar in the new zoom level on the very same mouse x
int newBar = x / DEFAULT_PIXELS_PER_BAR / m_zoomLevels[z];
// scroll so the bar "selected" by the mouse x doesn't move on the screen
// move zoom slider (pixelsPerBar will change automatically)
int step = we->modifiers() & Qt::ShiftModifier ? 1 : 5;
// when Alt is pressed, wheelEvent returns delta for x coordinate (mimics horizontal mouse wheel)
int direction = (we->angleDelta().y() + we->angleDelta().x()) > 0 ? 1 : -1;
m_zoomingModel->incValue(step * direction);
// scroll to zooming around cursor's bar
int newBar = static_cast<int>(x / pixelsPerBar());
m_leftRightScroll->setValue(m_leftRightScroll->value() + bar - newBar);
// update combobox with zooming-factor
m_zoomingModel->setValue( z );
// update timeline
m_song->m_playPos[Song::Mode_PlaySong].m_timeLine->
setPixelsPerBar( pixelsPerBar() );
m_song->m_playPos[Song::Mode_PlaySong].m_timeLine->setPixelsPerBar(pixelsPerBar());
// and make sure, all Clip's are resized and relocated
realignTracks();
}
@@ -612,7 +609,7 @@ void SongEditor::mousePressEvent(QMouseEvent *me)
//we save the position of scrollbars, mouse position and zooming level
m_scrollPos = QPoint(m_leftRightScroll->value(), contentWidget()->verticalScrollBar()->value());
m_origin = contentWidget()->mapFromParent(QPoint(me->pos().x(), me->pos().y()));
m_currentZoomingValue = zoomingModel()->value();
m_rubberbandPixelsPerBar = pixelsPerBar();
//paint the rubberband
rubberBand()->setEnabled(true);
@@ -655,12 +652,12 @@ void SongEditor::setMasterVolume( int new_val )
{
updateMasterVolumeFloat( new_val );
if( !m_mvsStatus->isVisible() && !m_song->m_loadingProject
if (!m_mvsStatus->isVisible() && !m_song->m_loadingProject
&& m_masterVolumeSlider->showStatus() )
{
m_mvsStatus->moveGlobal( m_masterVolumeSlider,
m_mvsStatus->moveGlobal(m_masterVolumeSlider,
QPoint( m_masterVolumeSlider->width() + 2, -2 ) );
m_mvsStatus->setVisibilityTimeOut( 1000 );
m_mvsStatus->setVisibilityTimeOut(1000);
}
Engine::audioEngine()->setMasterGain( new_val / 100.0f );
}
@@ -670,7 +667,7 @@ void SongEditor::setMasterVolume( int new_val )
void SongEditor::showMasterVolumeFloat( void )
{
m_mvsStatus->moveGlobal( m_masterVolumeSlider,
m_mvsStatus->moveGlobal(m_masterVolumeSlider,
QPoint( m_masterVolumeSlider->width() + 2, -2 ) );
m_mvsStatus->show();
updateMasterVolumeFloat( m_song->m_masterVolumeModel.value() );
@@ -681,7 +678,7 @@ void SongEditor::showMasterVolumeFloat( void )
void SongEditor::updateMasterVolumeFloat( int new_val )
{
m_mvsStatus->setText( tr( "Value: %1%" ).arg( new_val ) );
m_mvsStatus->setText(tr("Value: %1%").arg(new_val));
}
@@ -837,17 +834,50 @@ void SongEditor::updatePositionLine()
//! Convert zoom slider's value to bar width in pixels
int SongEditor::calculatePixelsPerBar() const
{
// What we need to raise 2 by to get MIN_PIXELS_PER_BAR and MAX_PIXELS_PER_BAR
static const double minExp = std::log2(MIN_PIXELS_PER_BAR);
static const double maxExp = std::log2(MAX_PIXELS_PER_BAR);
static const double stepsInv = 1 / static_cast<double>(ZOOM_STEPS) * (maxExp - minExp);
double exponent = m_zoomingModel->value() * stepsInv + minExp;
double ppb = std::exp2(exponent);
return static_cast<int>(std::round(ppb));
}
//! Convert bar width in pixels to zoom slider value
int SongEditor::calculateZoomSliderValue(int pixelsPerBar) const
{
// What we need to raise 2 by to get MIN_PIXELS_PER_BAR and MAX_PIXELS_PER_BAR
static const double minExp = std::log2(MIN_PIXELS_PER_BAR);
static const double maxExp = std::log2(MAX_PIXELS_PER_BAR);
double exponent = std::log2(pixelsPerBar);
double sliderValue = (exponent - minExp) / (maxExp - minExp) * ZOOM_STEPS;
return static_cast<int>(std::round(sliderValue));
}
void SongEditor::zoomingChanged()
{
setPixelsPerBar( m_zoomLevels[m_zoomingModel->value()] * DEFAULT_PIXELS_PER_BAR );
int ppb = calculatePixelsPerBar();
setPixelsPerBar(ppb);
m_song->m_playPos[Song::Mode_PlaySong].m_timeLine->
setPixelsPerBar( pixelsPerBar() );
m_song->m_playPos[Song::Mode_PlaySong].m_timeLine->setPixelsPerBar(ppb);
realignTracks();
updateRubberband();
m_timeLine->setSnapSize(getSnapSize());
emit zoomingValueChanged( m_zoomLevels[m_zoomingModel->value()] );
emit pixelsPerBarChanged(ppb);
}
@@ -899,14 +929,6 @@ int SongEditor::indexOfTrackView(const TrackView *tv)
ComboBoxModel *SongEditor::zoomingModel() const
{
return m_zoomingModel;
}
ComboBoxModel *SongEditor::snappingModel() const
{
return m_snappingModel;
@@ -991,16 +1013,19 @@ SongEditorWindow::SongEditorWindow(Song* song) :
auto zoom_lbl = new QLabel(m_toolBar);
zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) );
//Set up zooming-stuff
m_zoomingComboBox = new ComboBox( m_toolBar );
m_zoomingComboBox->setFixedSize( 80, ComboBox::DEFAULT_HEIGHT );
m_zoomingComboBox->move( 580, 4 );
m_zoomingComboBox->setModel(m_editor->m_zoomingModel);
m_zoomingComboBox->setToolTip(tr("Horizontal zooming"));
connect(m_editor->zoomingModel(), SIGNAL(dataChanged()), this, SLOT(updateSnapLabel()));
// Set slider zoom
m_zoomingSlider = new AutomatableSlider(m_toolBar, tr("Zoom"));
m_zoomingSlider->setModel(m_editor->m_zoomingModel);
m_zoomingSlider->setOrientation(Qt::Horizontal);
m_zoomingSlider->setPageStep(1);
m_zoomingSlider->setFocusPolicy(Qt::NoFocus);
m_zoomingSlider->setFixedSize(100, 26);
m_zoomingSlider->setToolTip(tr("Zoom"));
m_zoomingSlider->setContextMenuPolicy(Qt::NoContextMenu);
connect(m_editor->m_zoomingModel, SIGNAL(dataChanged()), this, SLOT(updateSnapLabel()));
zoomToolBar->addWidget( zoom_lbl );
zoomToolBar->addWidget( m_zoomingComboBox );
zoomToolBar->addWidget(m_zoomingSlider);
DropToolBar *snapToolBar = addDropToolBarToTop(tr("Snap controls"));
auto snap_lbl = new QLabel(m_toolBar);
@@ -1034,7 +1059,7 @@ SongEditorWindow::SongEditorWindow(Song* song) :
QSize SongEditorWindow::sizeHint() const
{
return {720, 300};
return {900, 300};
}
void SongEditorWindow::updateSnapLabel(){
@@ -1140,3 +1165,5 @@ void SongEditorWindow::adjustUiAfterProjectLoad()
} // namespace lmms::gui

View File

@@ -22,23 +22,29 @@
*
*/
#include "TimeLineWidget.h"
#include <cmath>
#include <QDomElement>
#include <QTimer>
#include <QMouseEvent>
#include <QPainter>
#include <QTimer>
#include <QToolBar>
#include "TimeLineWidget.h"
#include "embed.h"
#include "NStateButton.h"
#include "GuiApplication.h"
#include "NStateButton.h"
#include "TextFloat.h"
namespace lmms::gui
{
namespace
{
constexpr int MIN_BAR_LABEL_DISTANCE = 35;
}
QPixmap * TimeLineWidget::s_posMarkerPixmap = nullptr;
@@ -270,12 +276,14 @@ void TimeLineWidget::paintEvent( QPaintEvent * )
int const x = m_xOffset + s_posMarkerPixmap->width() / 2 -
( ( static_cast<int>( m_begin * m_ppb ) / TimePos::ticksPerBar() ) % static_cast<int>( m_ppb ) );
// Double the interval between bar numbers until they are far enough appart
int barLabelInterval = 1;
while (barLabelInterval * m_ppb < MIN_BAR_LABEL_DISTANCE) { barLabelInterval *= 2; }
for( int i = 0; x + i * m_ppb < width(); ++i )
{
++barNumber;
if( ( barNumber - 1 ) %
qMax( 1, qRound( 1.0f / 3.0f *
TimePos::ticksPerBar() / m_ppb ) ) == 0 )
if ((barNumber - 1) % barLabelInterval == 0)
{
const int cx = x + qRound( i * m_ppb );
p.setPen( barLineColor );