40
40
#include < QBitmap>
41
41
#include < QTableView>
42
42
#include < QStyledItemDelegate>
43
+ #include < QVariantAnimation>
43
44
#include < DSpinBox>
44
45
#include < DTreeView>
45
46
#include < DIconButton>
@@ -122,6 +123,57 @@ inline static bool isTheClassObject(QObject *object)
122
123
#endif
123
124
}
124
125
126
+ ChameleonMovementAnimation::ChameleonMovementAnimation (QWidget *targetWidget)
127
+ : QVariantAnimation(targetWidget)
128
+ {
129
+ setDuration (150 );
130
+
131
+ connect (this , &QVariantAnimation::valueChanged, targetWidget, [this ] (const QVariant &value) {
132
+ if (!isRuning ())
133
+ return ;
134
+
135
+ const auto rect = value.toRect ();
136
+ Q_ASSERT (!m_currentRect.isEmpty ());
137
+ this ->targetWidget ()->update (m_currentRect.united (rect));
138
+ m_currentRect = rect;
139
+ });
140
+ connect (this , &QVariantAnimation::finished, targetWidget, [this ] {
141
+ Q_ASSERT (m_currentRect == m_targetRect);
142
+ // 确保动画结束后有一帧的刷新,因为在菜单的动画过程中会修改菜单文字的 opacity
143
+ // 对opacity的修改会根据是否处于动画状态进行判断,因此要确保动画结束后刷新它
144
+ this ->targetWidget ()->update (m_currentRect);
145
+ });
146
+ }
147
+
148
+ QWidget *ChameleonMovementAnimation::targetWidget () const
149
+ {
150
+ return qobject_cast<QWidget*>(parent ());
151
+ }
152
+
153
+ void ChameleonMovementAnimation::setTargetRect (const QRect &rect)
154
+ {
155
+ if (m_targetRect == rect)
156
+ return ;
157
+
158
+ m_lastTargetRect = m_targetRect;
159
+ m_targetRect = rect;
160
+
161
+ if (m_currentRect.isEmpty ())
162
+ m_currentRect = m_lastTargetRect;
163
+
164
+ // 当目标绘制区域改变时,说明当前正在进行的动画过期了,应该重新开始动画
165
+ stop ();
166
+ setStartValue (m_currentRect);
167
+ setEndValue (rect);
168
+
169
+ if (!m_currentRect.isEmpty ()) {
170
+ start ();
171
+ } else {
172
+ // 这种情况说明不需要进行动画,往往发生在首次显示,这时候应该直接绘制到目标区域
173
+ m_currentRect = rect;
174
+ }
175
+ }
176
+
125
177
ChameleonStyle::ChameleonStyle ()
126
178
: DStyle()
127
179
{
@@ -2757,7 +2809,7 @@ bool ChameleonStyle::drawMenuBarItem(const QStyleOptionMenuItem *option, QRect &
2757
2809
return true ;
2758
2810
}
2759
2811
2760
- void ChameleonStyle::drawMenuItemBackground (const QStyleOption *option, QPainter *painter, QStyleOptionMenuItem::MenuItemType type) const
2812
+ ChameleonMovementAnimation * ChameleonStyle::drawMenuItemBackground (const QStyleOption *option, QPainter *painter, QStyleOptionMenuItem::MenuItemType type) const
2761
2813
{
2762
2814
QBrush color;
2763
2815
bool selected = (option->state & QStyle::State_Enabled) && option->state & QStyle::State_Selected;
@@ -2766,50 +2818,11 @@ void ChameleonStyle::drawMenuItemBackground(const QStyleOption *option, QPainter
2766
2818
painter->setPen (Qt::NoPen);
2767
2819
painter->setBrush (getColor (option, QPalette::Highlight));
2768
2820
painter->drawRect (option->rect );
2769
- return ;
2821
+ return nullptr ;
2770
2822
}
2771
2823
2772
- // 清理旧的阴影
2773
- if (option->styleObject ) {
2774
- const QRect shadow = option->styleObject ->property (" _d_menu_shadow_rect" ).toRect ();
2775
- const QRect shadow_base = option->styleObject ->property (" _d_menu_shadow_base_rect" ).toRect ();
2776
-
2777
- // 如果当前菜单项时已选中的,并且shadow_base不等于当前区域,此时应当清理阴影区域
2778
- // 如果当前要绘制的item是触发阴影绘制的那一项,那么,此时应当清空阴影区域
2779
- if ((selected && shadow_base != option->rect )
2780
- || (!selected && shadow_base == option->rect )
2781
- || (!selected && shadow_base.width () != option->rect .width ())) {
2782
- // 清空阴影区域
2783
- option->styleObject ->setProperty (" _d_menu_shadow_rect" , QVariant ());
2784
- option->styleObject ->setProperty (" _d_menu_shadow_base_rect" , QVariant ());
2785
-
2786
- // 确保阴影区域能重绘
2787
- if (QWidget *w = qobject_cast<QWidget*>(option->styleObject )) {
2788
- w->update (shadow);
2789
- }
2790
- }
2791
- }
2792
-
2793
2824
if (selected) {
2794
- color = option->palette .highlight ();
2795
-
2796
- // draw shadow
2797
- if (type == QStyleOptionMenuItem::Normal) {
2798
- if (option->styleObject ) {
2799
- QRect shadow (0 , 0 , option->rect .width (), 7 );
2800
- shadow.moveTop (option->rect .bottom () + 1 );
2801
- option->styleObject ->setProperty (" _d_menu_shadow_rect" , shadow);
2802
- option->styleObject ->setProperty (" _d_menu_shadow_base_rect" , option->rect );
2803
-
2804
- // 确保阴影区域能重绘
2805
- if (QWidget *w = qobject_cast<QWidget*>(option->styleObject )) {
2806
- w->update (shadow);
2807
- }
2808
- }
2809
- }
2810
-
2811
- painter->fillRect (option->rect , color);
2812
- } else {
2825
+ } else do {
2813
2826
color = option->palette .window ().color ();
2814
2827
2815
2828
if (color.color ().isValid () && color.color ().alpha () != 0 ) {
@@ -2846,35 +2859,51 @@ void ChameleonStyle::drawMenuItemBackground(const QStyleOption *option, QPainter
2846
2859
}
2847
2860
2848
2861
if (!option->styleObject )
2849
- return ;
2862
+ break ;
2850
2863
2851
- // 为上一个item绘制阴影
2852
- const QRect shadow = option->styleObject ->property (" _d_menu_shadow_rect" ).toRect ();
2864
+ } while (false );
2853
2865
2854
- // 判断阴影rect是否在自己的区域
2855
- if (!option->rect .contains (shadow.center ()))
2856
- return ;
2866
+ { // 无论如何都尝试绘制,因为可能有动画存在
2867
+ color = option->palette .highlight ();
2857
2868
2858
- static QColor shadow_color;
2859
- static QPixmap shadow_pixmap;
2869
+ QWidget *animationTargetWidget = qobject_cast<QWidget*>(option->styleObject );
2870
+ if (!option->styleObject )
2871
+ animationTargetWidget = dynamic_cast <QWidget*>(painter->device ());
2860
2872
2861
- if (shadow_color != option->palette .color (QPalette::Active, QPalette::Highlight)) {
2862
- shadow_color = option->palette .color (QPalette::Active, QPalette::Highlight);
2863
- QImage image (" :/chameleonstyle/menu_shadow.svg" );
2864
- QPainter pa (&image);
2865
- pa.setCompositionMode (QPainter::CompositionMode_SourceIn);
2866
- pa.fillRect (image.rect (), shadow_color);
2867
- shadow_pixmap = QPixmap::fromImage (image);
2868
- }
2873
+ ChameleonMovementAnimation *animation = nullptr ;
2869
2874
2870
- if (!shadow_pixmap.isNull ()) {
2871
- if (QMenu *menu = qobject_cast<QMenu *>(option->styleObject )) {
2872
- if (!menu->geometry ().contains (QCursor::pos ()))
2873
- return ;
2875
+ if (animationTargetWidget) {
2876
+ animation = animationTargetWidget->findChild <ChameleonMovementAnimation*>(" _d_menu_select_animation" ,
2877
+ Qt::FindDirectChildrenOnly);
2878
+ if (!animation) {
2879
+ animation = new ChameleonMovementAnimation (animationTargetWidget);
2880
+ animation->setObjectName (" _d_menu_select_animation" );
2874
2881
}
2875
- painter->drawPixmap (shadow, shadow_pixmap);
2882
+
2883
+ if (selected)
2884
+ animation->setTargetRect (option->rect );
2885
+ }
2886
+
2887
+ if (animation && animation->isRuning ()) {
2888
+ auto opacity = painter->opacity ();
2889
+ // 一些状态为 disable 的 menu item 在绘制时会修改不透明度,这里暂时改回1.0。
2890
+ painter->setOpacity (1.0 );
2891
+ painter->setBrush (color);
2892
+ painter->setPen (Qt::NoPen);
2893
+ QRect rect = animation->currentRect ().adjusted (6 , 0 , -6 , 0 );
2894
+ painter->drawRoundedRect (rect, 6 , 6 );
2895
+ painter->setOpacity (opacity);
2896
+
2897
+ return animation;
2898
+ } else if (selected) {
2899
+ painter->setBrush (color);
2900
+ painter->setPen (Qt::NoPen);
2901
+ QRect rect = option->rect .adjusted (6 , 0 , -6 , 0 );
2902
+ painter->drawRoundedRect (rect, 6 , 6 );
2876
2903
}
2877
2904
}
2905
+
2906
+ return nullptr ;
2878
2907
}
2879
2908
2880
2909
void ChameleonStyle::drawMenuItemRedPoint (const QStyleOptionMenuItem *option, QPainter *painter, const QWidget *widget) const
@@ -2955,7 +2984,7 @@ bool ChameleonStyle::drawMenuItem(const QStyleOptionMenuItem *option, QPainter *
2955
2984
bool sunken = menuItem->state & State_Sunken;
2956
2985
2957
2986
// 绘制背景
2958
- drawMenuItemBackground (option, painter, menuItem->menuItemType );
2987
+ auto animation = drawMenuItemBackground (option, painter, menuItem->menuItemType );
2959
2988
2960
2989
// 绘制分段
2961
2990
if (menuItem->menuItemType == QStyleOptionMenuItem::Separator) {
@@ -2972,6 +3001,13 @@ bool ChameleonStyle::drawMenuItem(const QStyleOptionMenuItem *option, QPainter *
2972
3001
return true ;
2973
3002
}
2974
3003
3004
+ const bool useHighlightedText = selected && !animation;
3005
+ if (!useHighlightedText && selected) {
3006
+ // 在动画中时,selected item 的文字颜色不会使用 HighlightedText,当动画结束后会立即
3007
+ // 变为 HighlightedText,会显得比较突然,因此使用不透明度对文本等内容进行过渡
3008
+ painter->setOpacity (1.0 - animation->progress ());
3009
+ }
3010
+
2975
3011
// 绘制选择框
2976
3012
bool ignoreCheckMark = false ;
2977
3013
@@ -2990,7 +3026,7 @@ bool ChameleonStyle::drawMenuItem(const QStyleOptionMenuItem *option, QPainter *
2990
3026
checkRect.moveCenter (QPoint (checkRect.left () + smallIconSize / 2 , menuItem->rect .center ().y ()));
2991
3027
painter->setRenderHint (QPainter::Antialiasing);
2992
3028
2993
- if (selected )
3029
+ if (useHighlightedText )
2994
3030
painter->setPen (getColor (option, QPalette::HighlightedText));
2995
3031
else
2996
3032
painter->setPen (getColor (option, QPalette::BrightText));
@@ -3010,7 +3046,7 @@ bool ChameleonStyle::drawMenuItem(const QStyleOptionMenuItem *option, QPainter *
3010
3046
3011
3047
}
3012
3048
3013
- if (selected ) {
3049
+ if (useHighlightedText ) {
3014
3050
painter->setPen (getColor (option, QPalette::HighlightedText));
3015
3051
} else {
3016
3052
if ((option->state & QStyle::State_Enabled)) {
0 commit comments