From amarok-devel Mon Aug 09 00:26:03 2010 From: Thomas =?iso-8859-1?q?L=FCbking?= Date: Mon, 09 Aug 2010 00:26:03 +0000 To: amarok-devel Subject: [UI] [Patch] Volume "dial"... Message-Id: <201008090226.03741.thomas.luebking () web ! de> X-MARC-Message: https://marc.info/?l=amarok-devel&m=128132696515001 MIME-Version: 1 Content-Type: multipart/mixed; boundary="--Boundary-00=_bs0XMYAV+TUxReD" --Boundary-00=_bs0XMYAV+TUxReD Content-Type: Text/Plain; charset="us-ascii" Content-Transfer-Encoding: 7bit I've rethought a couple of things (even after the other thread) Of course there (imo) good reasons for such approach, but i'll explain them later - first you should try out (best w/o reading the code - i promise, it contains very few trojans ;-) and check for usability. a) "do i get it" b) "can i handle it" Otherwise it's visually a "bit rough on the corners", ie. could need two more icons to finegrain/stress state representation and maybe some nice animations =) also i don't like the invocation of un/mute strings (since they can be horribly long as verbs in some langs) ... and the tooltip on value changes (using the dial!) sometimes works and sometimes not .. due to the wonders of tooltip land =\ ok: that's enough info for the moment. Cheers Thomas ps: patch size defers from default_theme_clean.svg update, sorry :( --Boundary-00=_bs0XMYAV+TUxReD Content-Type: text/x-patch; charset="UTF-8"; name="volume_usability.diff" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="volume_usability.diff" diff --git a/src/images/default-theme-clean.svg b/src/images/default-theme-clean.svg index 6bad334..80ada8d 100644 --- a/src/images/default-theme-clean.svg +++ b/src/images/default-theme-clean.svg @@ -30,18 +30,20 @@ objecttolerance="10" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1.4142136" - inkscape:cx="2035.649" - inkscape:cy="223.19126" + inkscape:zoom="0.7071068" + inkscape:cx="2335.6462" + inkscape:cy="215.32329" inkscape:document-units="px" - inkscape:current-layer="PLAYpause" + inkscape:current-layer="AutoSlide" showgrid="true" inkscape:window-width="1204" inkscape:window-height="922" - inkscape:window-x="180" - inkscape:window-y="154" + inkscape:window-x="69" + inkscape:window-y="63" inkscape:window-maximized="0" - inkscape:snap-global="true"> + inkscape:snap-global="true" + showguides="true" + inkscape:guide-bbox="true"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -11416,7 +11782,7 @@ + style="fill:url(#linearGradient4222);fill-opacity:1;stroke:#000000;stroke-width:1;stroke-opacity:0.62745097999999999;stroke-miterlimit:4;stroke-dasharray:none" /> progress_slider_played_left + + + + + + + + + + + + + + + + diff --git a/src/widgets/VolumeDial.cpp b/src/widgets/VolumeDial.cpp index 4447921..4507d88 100644 --- a/src/widgets/VolumeDial.cpp +++ b/src/widgets/VolumeDial.cpp @@ -38,6 +38,8 @@ VolumeDial::VolumeDial( QWidget *parent ) : QDial( parent ) { m_anim.step = 0; m_anim.timer = 0; + m_autoSlide.timer = 0; + m_softUp.timer = 0; setMouseTracking( true ); connect ( this, SIGNAL( valueChanged(int) ), SLOT( valueChangedSlot(int) ) ); @@ -110,14 +112,81 @@ static bool onRing( const QRect &r, const QPoint &p ) return sqrt(dx*dx + dy*dy) > r.width()/4; } +static int clamp( int v, int min, int max ) +{ + if (v > max) return max; + if (v < min) return min; + return v; +} + void VolumeDial::mouseMoveEvent( QMouseEvent *me ) { if ( me->buttons() == Qt::NoButton ) setCursor( onRing( rect(), me->pos() ) ? Qt::PointingHandCursor : Qt::ArrowCursor ); - else if ( m_isClick ) + else if ( m_isClick || m_autoSlide.timer ) + { me->accept(); + if ( !m_autoSlide.timer ) + { + QPoint dragDist = me->pos() - m_autoSlide.startPos; + dragDist.rx() = qAbs( dragDist.x() ); + dragDist.ry() = qAbs( dragDist.y() ); + + if ( dragDist.x() > 7 && dragDist.x() > 3*dragDist.y()/2 ) + { + m_autoSlide.direction = Qt::Horizontal; + m_autoSlide.timer = startTimer( 80 ); + } + else if ( dragDist.y() > 7 && dragDist.y() > 3*dragDist.x()/2 ) + { + m_autoSlide.direction = Qt::Vertical; + m_autoSlide.timer = startTimer( 80 ); + } + if ( m_autoSlide.timer ) + update(); // changed - redisplay the dial + } + if ( m_autoSlide.timer ) + { + int exc; + if ( m_autoSlide.direction == Qt::Horizontal ) + { + const int max = 2*width()/3; + exc = clamp( me->pos().x() - width()/2, -max, max ); + m_autoSlide.delta = 2*exc*exc/width(); // e^2/(w/2) + } + else + { + const int max = 2*height()/3; + exc = clamp( me->pos().y() - height()/2, -max, max ); + m_autoSlide.delta = -2*exc*exc/height(); // e^2/(h/2) + } + if ( !m_autoSlide.delta ) + { + if ( m_autoSlide.direction == Qt::Horizontal ) + setValue( value() + me->pos().x() - m_autoSlide.lastPos.x() ); + else + setValue( value() - me->pos().y() + m_autoSlide.lastPos.y() ); + } + else if ( exc < 0 ) + m_autoSlide.delta = -m_autoSlide.delta; // ^2 kills sign... + } + } + else if ( m_softUp.timer ) + { + QPoint d = m_softUp.startPos - me->pos(); + if ( d.manhattanLength() > 6 ) + { // directly jump to the position and stop fading in due to user request + killTimer( m_softUp.timer ); + m_softUp.timer = m_softUp.delta = 0; + m_formerValue = m_softUp.target; + blockSignals( false ); + setValue( m_softUp.target ); + blockSignals( true ); + } + } else - QDial::mouseMoveEvent( me ); + QDial::mouseMoveEvent( me ); // still support radial movement + m_autoSlide.lastPos = me->pos(); } void VolumeDial::mousePressEvent( QMouseEvent *me ) @@ -128,20 +197,39 @@ void VolumeDial::mousePressEvent( QMouseEvent *me ) return; } + // check whether the outer dial was hit for radial usage m_isClick = !onRing( rect(), me->pos() ); + // for value changes caused by mouseevent we'll only let our adjusted value changes be emitted + // see ::sliderChange() + m_formerValue = value(); + blockSignals( true ); + if ( m_isClick ) + { + if ( m_autoSlide.timer ) // should not happen, but ensure to prevent multiple infinite timers on lousy event dispatchers .. not naming any OS here ;-P + { + killTimer( m_autoSlide.timer ); + m_autoSlide.timer = 0; + } + m_autoSlide.startPos = m_autoSlide.lastPos = me->pos(); + m_autoSlide.delta = 0; update(); // hide the ring + } else { setCursor( Qt::PointingHandCursor ); // hint dragging - QDial::mousePressEvent( me ); // this will directly jump to the proper position + setSliderDown( true ); + m_softUp.startPos = me->pos(); + m_softUp.delta = 1; + if ( m_softUp.timer ) + { + killTimer( m_softUp.timer ); + m_softUp.timer = 0; + } + m_autoSlide.direction = (Qt::Orientation)-1; + QDial::mouseMoveEvent( me ); // this will cause or guarded sliderChange() } - - // for value changes caused by mouseevent we'll only let our adjusted value changes be emitted - // see ::sliderChange() - m_formerValue = value(); - blockSignals( true ); } void VolumeDial::mouseReleaseEvent( QMouseEvent *me ) @@ -153,7 +241,19 @@ void VolumeDial::mouseReleaseEvent( QMouseEvent *me ) setCursor( Qt::ArrowCursor ); setSliderDown( false ); - if ( m_isClick ) + m_softUp.delta = 0; // cleanup + if ( m_softUp.timer ) + { + killTimer( m_softUp.timer ); + m_softUp.timer = 0; + } + else if ( m_autoSlide.timer ) // ELSE cleanup + { + killTimer( m_autoSlide.timer ); + m_autoSlide.timer = 0; + m_autoSlide.delta = 0; + } + else if ( m_isClick ) // ELSE! { m_isClick = !onRing( rect(), me->pos() ); if ( m_isClick ) @@ -161,20 +261,44 @@ void VolumeDial::mouseReleaseEvent( QMouseEvent *me ) } m_isClick = false; + update(); // show other icon again } void VolumeDial::paintEvent( QPaintEvent * ) { QPainter p( this ); - int icon = m_muted ? 0 : 3; - if ( icon && value() < 66 ) - icon = value() < 33 ? 1 : 2; + int icon; + if ( m_autoSlide.timer || m_isClick ) + icon = 4; + else + { + icon = m_muted ? 0 : 3; + if ( icon && value() < 66 ) + icon = value() < 33 ? 1 : 2; + } p.drawPixmap( 0,0, m_icon[ icon ] ); - if ( !m_isClick ) + if ( m_autoSlide.timer || !m_isClick ) { p.setPen( QPen( m_sliderGradient, 3, Qt::SolidLine, Qt::RoundCap ) ); p.setRenderHint( QPainter::Antialiasing ); p.drawArc( rect().adjusted(4,4,-4,-4), -110*16, - value()*320*16 / (maximum() - minimum()) ); + if ( m_autoSlide.timer ) + { + p.setPen( Qt::black ); // NOT GOOD - this should be more flexible... (but the def. icons are white inside) + p.setFont( font() ); + p.drawText( rect().adjusted( 0, QFontMetrics(font()).xHeight()/2, 0, 0 ), Qt::AlignCenter, QString("%1%").arg(value()) ); + } + } + else if ( m_isClick ) + { + p.setPen( Qt::black ); // NOT GOOD - this should be more flexible... (but the def. icons are white inside) + QString text = m_muted ? i18n( "Unmute" ) : i18n( "Mute" ); + QFont fnt; + fnt.setBold(true); + if ( fnt.pointSize() != -1 ) // asian fonts are all crap :-( + fnt.setPointSize( 2*fnt.pointSize()*width() / ( 3*QFontMetrics(fnt).boundingRect( text ).width() ) ); + p.setFont( fnt ); + p.drawText( rect().adjusted( 0, QFontMetrics(fnt).xHeight()/2, 0, 0 ), Qt::AlignCenter, text ); } p.end(); } @@ -193,6 +317,12 @@ void VolumeDial::resizeEvent( QResizeEvent *re ) if( re->size() != re->oldSize() ) { + QFont fnt; + fnt.setBold(true); + if ( fnt.pointSize() != -1 ) // asian fonts are all crap :-( + fnt.setPointSize( 2*fnt.pointSize()*width() / ( 3*QFontMetrics(fnt).boundingRect( "333%" ).width() ) ); + setFont( fnt ); + renderIcons(); m_sliderGradient = QPixmap( size() ); updateSliderGradient(); @@ -206,9 +336,10 @@ void VolumeDial::renderIcons() m_icon[1] = The::svgHandler()->renderSvg( "Volume_low", width(), height(), "Volume_low", true ); m_icon[2] = The::svgHandler()->renderSvg( "Volume_mid", width(), height(), "Volume_mid", true ); m_icon[3] = The::svgHandler()->renderSvg( "Volume", width(), height(), "Volume", true ); - if( layoutDirection() == Qt::RightToLeft ) + m_icon[4] = The::svgHandler()->renderSvg( "AutoSlide", width(), height(), "AutoSlide", true ); + if ( layoutDirection() == Qt::RightToLeft ) { - for ( int i = 0; i < 4; ++i ) + for ( int i = 0; i < 5; ++i ) m_icon[i] = QPixmap::fromImage( m_icon[i].toImage().mirrored( true, false ) ); } } @@ -232,22 +363,49 @@ void VolumeDial::stopFade() void VolumeDial::timerEvent( QTimerEvent *te ) { - if ( te->timerId() != m_anim.timer ) + if ( te->timerId() == m_autoSlide.timer ) + { + int nv = clamp( value() + m_autoSlide.delta, minimum(), maximum() ); + if ( nv != value() ) + setValue( nv ); return; - if ( underMouse() ) // fade in + } + + if ( te->timerId() == m_softUp.timer ) { - m_anim.step += 2; - if ( m_anim.step > 5 ) - stopFade(); + int nv = clamp( value() + m_softUp.delta, minimum(), m_softUp.target ); + if ( nv != value() ) + { + blockSignals( false ); + setValue( nv ); + blockSignals( true ); + m_softUp.delta *= 1.6; + } + else // done + { + killTimer( m_softUp.timer ); + m_softUp.timer = m_softUp.delta = 0; + } + return; } - else // fade out + + if ( te->timerId() == m_anim.timer ) { - --m_anim.step; - if ( m_anim.step < 1 ) - stopFade(); + if ( underMouse() ) // fade in + { + m_anim.step += 2; + if ( m_anim.step > 5 ) + stopFade(); + } + else // fade out + { + --m_anim.step; + if ( m_anim.step < 1 ) + stopFade(); + } + updateSliderGradient(); + repaint(); } - updateSliderGradient(); - repaint(); } void VolumeDial::updateSliderGradient() @@ -312,9 +470,37 @@ QSize VolumeDial::sizeHint() const void VolumeDial::sliderChange( SliderChange change ) { + if ( m_autoSlide.timer ) + { + blockSignals( false ); + emit sliderMoved( value() ); + emit valueChanged( value() ); + blockSignals( true ); + return; + } + if ( change == SliderValueChange && isSliderDown() && signalsBlocked() ) { int d = value() - m_formerValue; + + if ( m_softUp.delta && !m_softUp.timer ) + { + if ( d > 10 ) + { + m_softUp.target = value(); + setValue( m_formerValue ); + m_softUp.timer = startTimer( 50 ); + } + else + { + blockSignals( false ); + emit sliderMoved( value() ); + emit valueChanged( value() ); + blockSignals( true ); + QDial::sliderChange(change); + } + return; // this is an initial volUp attempt - no real action at all + } if ( d && d < 33 && d > -33 ) // don't allow real "jumps" > 1/3 { if ( d > 5 ) // ease movement @@ -329,6 +515,8 @@ void VolumeDial::sliderChange( SliderChange change ) } if ( d ) setValue( m_formerValue ); + const QPoint tooltipPosition = mapToGlobal( rect().translated( 7, -22 ).bottomLeft() ); + QToolTip::showText( tooltipPosition, toolTip() ); } QDial::sliderChange(change); } diff --git a/src/widgets/VolumeDial.h b/src/widgets/VolumeDial.h index f706cf2..2b4473a 100644 --- a/src/widgets/VolumeDial.h +++ b/src/widgets/VolumeDial.h @@ -70,7 +70,7 @@ private slots: void valueChangedSlot( int ); private: - QPixmap m_icon[4]; + QPixmap m_icon[5]; QPixmap m_sliderGradient; int m_formerValue; QList m_wheelProxies; @@ -79,6 +79,17 @@ private: int step; int timer; } m_anim; + struct + { + Qt::Orientation direction; + QPoint startPos, lastPos; + int timer, delta; + } m_autoSlide; + struct + { + QPoint startPos; + int timer, delta, target; + } m_softUp; bool m_isClick, m_isDown, m_muted; QColor m_highlightColor; }; --Boundary-00=_bs0XMYAV+TUxReD Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ Amarok-devel mailing list Amarok-devel@kde.org https://mail.kde.org/mailman/listinfo/amarok-devel --Boundary-00=_bs0XMYAV+TUxReD--