Skip to content

Commit 8b0c2e7

Browse files
committed
Add HoverFloatEffect
1 parent 13d8a03 commit 8b0c2e7

File tree

2 files changed

+90
-2
lines changed

2 files changed

+90
-2
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter/foundation.dart';
3+
4+
/// A widget that applies a floating hover effect to its child.
5+
///
6+
/// When the mouse hovers over this widget, it creates a subtle 3D-like effect
7+
/// by translating and scaling the child based on mouse position.
8+
///
9+
/// This effect is only applied on desktop platforms (Windows, macOS, Linux) and web.
10+
/// On mobile platforms, the child widget is returned without any hover effects.
11+
class HoverFloatEffect extends StatefulWidget {
12+
/// The widget to apply the hover effect to.
13+
final Widget child;
14+
15+
/// The scale factor applied to the child when hovering.
16+
/// A value of 1.0 means no scaling, 1.05 means 5% larger.
17+
/// Defaults to 1.05 for a subtle zoom effect.
18+
final double zoomScale;
19+
20+
/// The translation distance in pixels for the hover effect.
21+
/// Controls how far the widget moves from its center position.
22+
/// Defaults to 20 pixels for a subtle floating effect.
23+
final double translationDistance;
24+
25+
const HoverFloatEffect({
26+
super.key,
27+
required this.child,
28+
this.zoomScale = 1.05,
29+
this.translationDistance = 20,
30+
});
31+
32+
@override
33+
State<HoverFloatEffect> createState() => _HoverFloatEffectState();
34+
}
35+
36+
class _HoverFloatEffectState extends State<HoverFloatEffect> {
37+
Offset _offset = Offset.zero;
38+
39+
/// Returns true if the current platform supports mouse hover effects.
40+
bool get _supportsHover =>
41+
kIsWeb ||
42+
defaultTargetPlatform == TargetPlatform.windows ||
43+
defaultTargetPlatform == TargetPlatform.macOS ||
44+
defaultTargetPlatform == TargetPlatform.linux;
45+
46+
@override
47+
Widget build(BuildContext context) {
48+
// On mobile platforms, just return the child without hover effects
49+
if (!_supportsHover) {
50+
return widget.child;
51+
}
52+
53+
final size = MediaQuery.of(context).size;
54+
return MouseRegion(
55+
onHover: (event) {
56+
// Calculate normalized position (-0.5 to 0.5) and apply translation distance
57+
final dx =
58+
(event.position.dx / size.width - 0.5) * widget.translationDistance;
59+
final dy =
60+
(event.position.dy / size.height - 0.5) *
61+
widget.translationDistance;
62+
setState(() => _offset = Offset(dx, dy));
63+
},
64+
onExit: (_) => setState(() => _offset = Offset.zero),
65+
child: TweenAnimationBuilder(
66+
tween: Tween<Offset>(begin: Offset.zero, end: _offset),
67+
duration: const Duration(milliseconds: 200),
68+
curve: Curves.easeOut,
69+
builder: (context, offset, child) {
70+
return Transform.translate(
71+
offset: offset,
72+
child: Transform.scale(scale: widget.zoomScale, child: child),
73+
);
74+
},
75+
child: widget.child,
76+
),
77+
);
78+
}
79+
}

packages/flyer_chat_reactions/lib/src/widgets/reactions_dialog.dart

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:pull_down_button/pull_down_button.dart'
99
show PullDownMenuEntry, PullDownMenu;
1010

1111
import '../models/default_data.dart';
12+
import '../utils/hover_float_effect.dart';
1213
import '../utils/typedef.dart';
1314

1415
//// Theme values for [ReactionsDialogWidget].
@@ -35,6 +36,7 @@ class ReactionsDialogWidget extends StatefulWidget {
3536
this.reactionsPickerReactedBackgroundColor,
3637
this.reactionTapAnimationDuration,
3738
this.reactionPickerFadeLeftAnimationDuration,
39+
this.activateHoverFloatEffect = true,
3840
});
3941

4042
/// The message widget to be displayed in the dialog
@@ -75,6 +77,9 @@ class ReactionsDialogWidget extends StatefulWidget {
7577
/// Animation duration to display the reactions row
7678
final Duration? reactionPickerFadeLeftAnimationDuration;
7779

80+
/// Whether to activate the hover float effect
81+
final bool activateHoverFloatEffect;
82+
7883
@override
7984
State<ReactionsDialogWidget> createState() => _ReactionsDialogWidgetState();
8085
}
@@ -97,15 +102,17 @@ class _ReactionsDialogWidgetState extends State<ReactionsDialogWidget> {
97102
return BackdropFilter(
98103
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
99104
child: Padding(
100-
padding: const EdgeInsets.only(right: 20.0, left: 20.0),
105+
padding: const EdgeInsets.only(right: 32.0, left: 32.0),
101106
child: Column(
102107
mainAxisSize: MainAxisSize.max,
103108
crossAxisAlignment: widget.widgetAlignment,
104109
mainAxisAlignment: MainAxisAlignment.center,
105110
children: [
106111
buildReactionsPicker(context, theme),
107112
const SizedBox(height: 10),
108-
widget.messageWidget,
113+
widget.activateHoverFloatEffect
114+
? HoverFloatEffect(child: widget.messageWidget)
115+
: widget.messageWidget,
109116
if (widget.menuItems != null && widget.menuItems!.isNotEmpty) ...[
110117
const SizedBox(height: 10),
111118
PullDownMenu(items: widget.menuItems!),
@@ -239,6 +246,7 @@ void showReactionsDialog(
239246
Duration? reactionTapAnimationDuration,
240247
Duration? reactionPickerFadeLeftAnimationDuration,
241248
Widget? moreReactionsWidget,
249+
bool activateHoverFloatEffect = true,
242250
}) {
243251
final providers = ChatProviders.from(context);
244252

@@ -278,6 +286,7 @@ void showReactionsDialog(
278286
reactionPickerFadeLeftAnimationDuration:
279287
reactionPickerFadeLeftAnimationDuration,
280288
moreReactionsWidget: moreReactionsWidget,
289+
activateHoverFloatEffect: activateHoverFloatEffect,
281290
),
282291
),
283292
);

0 commit comments

Comments
 (0)