mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: platform specific title bar buttons
This commit is contained in:
parent
00f527ea5e
commit
62677209a2
@ -1,8 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:titlebar_buttons/titlebar_buttons.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
class PageWindowTitleBar extends StatefulHookWidget
|
class PageWindowTitleBar extends StatefulHookWidget
|
||||||
implements PreferredSizeWidget {
|
implements PreferredSizeWidget {
|
||||||
@ -49,18 +52,6 @@ class PageWindowTitleBar extends StatefulHookWidget
|
|||||||
class _PageWindowTitleBarState extends State<PageWindowTitleBar> {
|
class _PageWindowTitleBarState extends State<PageWindowTitleBar> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isMaximized = useState<bool?>(null);
|
|
||||||
|
|
||||||
maximizeOrRestore() async {
|
|
||||||
if (await windowManager.isMaximized()) {
|
|
||||||
await windowManager.unmaximize();
|
|
||||||
isMaximized.value = false;
|
|
||||||
} else {
|
|
||||||
await windowManager.maximize();
|
|
||||||
isMaximized.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onHorizontalDragStart: (details) {
|
onHorizontalDragStart: (details) {
|
||||||
if (kIsDesktop) {
|
if (kIsDesktop) {
|
||||||
@ -77,24 +68,7 @@ class _PageWindowTitleBarState extends State<PageWindowTitleBar> {
|
|||||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||||
actions: [
|
actions: [
|
||||||
...?widget.actions,
|
...?widget.actions,
|
||||||
if (kIsDesktop && !kIsMacOS) ...[
|
const WindowTitleBarButtons(),
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.minimize),
|
|
||||||
onPressed: () => windowManager.minimize(),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
isMaximized.value ?? false
|
|
||||||
? Icons.fullscreen_exit
|
|
||||||
: Icons.fullscreen,
|
|
||||||
),
|
|
||||||
onPressed: maximizeOrRestore,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
onPressed: () => windowManager.close(),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
backgroundColor: widget.backgroundColor,
|
backgroundColor: widget.backgroundColor,
|
||||||
foregroundColor: widget.foregroundColor,
|
foregroundColor: widget.foregroundColor,
|
||||||
@ -110,3 +84,489 @@ class _PageWindowTitleBarState extends State<PageWindowTitleBar> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WindowTitleBarButtons extends HookWidget {
|
||||||
|
const WindowTitleBarButtons({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isMaximized = useState<bool?>(null);
|
||||||
|
const type = ThemeType.auto;
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (kIsDesktop) {
|
||||||
|
windowManager.isMaximized().then((value) {
|
||||||
|
isMaximized.value = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!kIsDesktop || kIsMacOS) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kIsWindows) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final colors = WindowButtonColors(
|
||||||
|
normal: Colors.transparent,
|
||||||
|
iconNormal: theme.colorScheme.onBackground,
|
||||||
|
mouseOver: theme.colorScheme.onBackground.withOpacity(0.1),
|
||||||
|
mouseDown: theme.colorScheme.onBackground.withOpacity(0.2),
|
||||||
|
iconMouseOver: theme.colorScheme.onBackground,
|
||||||
|
iconMouseDown: theme.colorScheme.onBackground,
|
||||||
|
);
|
||||||
|
|
||||||
|
final closeColors = WindowButtonColors(
|
||||||
|
normal: Colors.transparent,
|
||||||
|
iconNormal: theme.colorScheme.onBackground,
|
||||||
|
mouseOver: Colors.red,
|
||||||
|
mouseDown: Colors.red[800]!,
|
||||||
|
iconMouseOver: Colors.white,
|
||||||
|
iconMouseDown: Colors.black,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 25),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MinimizeWindowButton(
|
||||||
|
onPressed: windowManager.minimize,
|
||||||
|
colors: colors,
|
||||||
|
),
|
||||||
|
if (isMaximized.value != true)
|
||||||
|
MaximizeWindowButton(
|
||||||
|
colors: colors,
|
||||||
|
onPressed: () {
|
||||||
|
windowManager.maximize();
|
||||||
|
isMaximized.value = true;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else
|
||||||
|
RestoreWindowButton(
|
||||||
|
colors: colors,
|
||||||
|
onPressed: () {
|
||||||
|
windowManager.unmaximize();
|
||||||
|
isMaximized.value = false;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CloseWindowButton(
|
||||||
|
colors: closeColors,
|
||||||
|
onPressed: windowManager.close,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 20, left: 10),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
DecoratedMinimizeButton(
|
||||||
|
type: type,
|
||||||
|
onPressed: windowManager.minimize,
|
||||||
|
),
|
||||||
|
DecoratedMaximizeButton(
|
||||||
|
type: type,
|
||||||
|
onPressed: () async {
|
||||||
|
if (await windowManager.isMaximized()) {
|
||||||
|
await windowManager.unmaximize();
|
||||||
|
isMaximized.value = false;
|
||||||
|
} else {
|
||||||
|
await windowManager.maximize();
|
||||||
|
isMaximized.value = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DecoratedCloseButton(
|
||||||
|
type: type,
|
||||||
|
onPressed: windowManager.close,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef WindowButtonIconBuilder = Widget Function(
|
||||||
|
WindowButtonContext buttonContext);
|
||||||
|
typedef WindowButtonBuilder = Widget Function(
|
||||||
|
WindowButtonContext buttonContext, Widget icon);
|
||||||
|
|
||||||
|
class WindowButtonContext {
|
||||||
|
BuildContext context;
|
||||||
|
MouseState mouseState;
|
||||||
|
Color? backgroundColor;
|
||||||
|
Color iconColor;
|
||||||
|
WindowButtonContext(
|
||||||
|
{required this.context,
|
||||||
|
required this.mouseState,
|
||||||
|
this.backgroundColor,
|
||||||
|
required this.iconColor});
|
||||||
|
}
|
||||||
|
|
||||||
|
class WindowButtonColors {
|
||||||
|
late Color normal;
|
||||||
|
late Color mouseOver;
|
||||||
|
late Color mouseDown;
|
||||||
|
late Color iconNormal;
|
||||||
|
late Color iconMouseOver;
|
||||||
|
late Color iconMouseDown;
|
||||||
|
WindowButtonColors(
|
||||||
|
{Color? normal,
|
||||||
|
Color? mouseOver,
|
||||||
|
Color? mouseDown,
|
||||||
|
Color? iconNormal,
|
||||||
|
Color? iconMouseOver,
|
||||||
|
Color? iconMouseDown}) {
|
||||||
|
this.normal = normal ?? _defaultButtonColors.normal;
|
||||||
|
this.mouseOver = mouseOver ?? _defaultButtonColors.mouseOver;
|
||||||
|
this.mouseDown = mouseDown ?? _defaultButtonColors.mouseDown;
|
||||||
|
this.iconNormal = iconNormal ?? _defaultButtonColors.iconNormal;
|
||||||
|
this.iconMouseOver = iconMouseOver ?? _defaultButtonColors.iconMouseOver;
|
||||||
|
this.iconMouseDown = iconMouseDown ?? _defaultButtonColors.iconMouseDown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final _defaultButtonColors = WindowButtonColors(
|
||||||
|
normal: Colors.transparent,
|
||||||
|
iconNormal: const Color(0xFF805306),
|
||||||
|
mouseOver: const Color(0xFF404040),
|
||||||
|
mouseDown: const Color(0xFF202020),
|
||||||
|
iconMouseOver: const Color(0xFFFFFFFF),
|
||||||
|
iconMouseDown: const Color(0xFFF0F0F0),
|
||||||
|
);
|
||||||
|
|
||||||
|
class WindowButton extends StatelessWidget {
|
||||||
|
final WindowButtonBuilder? builder;
|
||||||
|
final WindowButtonIconBuilder? iconBuilder;
|
||||||
|
late final WindowButtonColors colors;
|
||||||
|
final bool animate;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
WindowButton(
|
||||||
|
{Key? key,
|
||||||
|
WindowButtonColors? colors,
|
||||||
|
this.builder,
|
||||||
|
@required this.iconBuilder,
|
||||||
|
this.padding,
|
||||||
|
this.onPressed,
|
||||||
|
this.animate = false})
|
||||||
|
: super(key: key) {
|
||||||
|
this.colors = colors ?? _defaultButtonColors;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getBackgroundColor(MouseState mouseState) {
|
||||||
|
if (mouseState.isMouseDown) return colors.mouseDown;
|
||||||
|
if (mouseState.isMouseOver) return colors.mouseOver;
|
||||||
|
return colors.normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getIconColor(MouseState mouseState) {
|
||||||
|
if (mouseState.isMouseDown) return colors.iconMouseDown;
|
||||||
|
if (mouseState.isMouseOver) return colors.iconMouseOver;
|
||||||
|
return colors.iconNormal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return Container();
|
||||||
|
} else {
|
||||||
|
// Don't show button on macOS
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MouseStateBuilder(
|
||||||
|
builder: (context, mouseState) {
|
||||||
|
WindowButtonContext buttonContext = WindowButtonContext(
|
||||||
|
mouseState: mouseState,
|
||||||
|
context: context,
|
||||||
|
backgroundColor: getBackgroundColor(mouseState),
|
||||||
|
iconColor: getIconColor(mouseState));
|
||||||
|
|
||||||
|
var icon =
|
||||||
|
(iconBuilder != null) ? iconBuilder!(buttonContext) : Container();
|
||||||
|
|
||||||
|
var fadeOutColor =
|
||||||
|
getBackgroundColor(MouseState()..isMouseOver = true).withOpacity(0);
|
||||||
|
var padding = this.padding ?? const EdgeInsets.all(10);
|
||||||
|
var animationMs =
|
||||||
|
mouseState.isMouseOver ? (animate ? 100 : 0) : (animate ? 200 : 0);
|
||||||
|
Widget iconWithPadding = Padding(padding: padding, child: icon);
|
||||||
|
iconWithPadding = AnimatedContainer(
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
duration: Duration(milliseconds: animationMs),
|
||||||
|
color: buttonContext.backgroundColor ?? fadeOutColor,
|
||||||
|
child: iconWithPadding);
|
||||||
|
var button =
|
||||||
|
(builder != null) ? builder!(buttonContext, icon) : iconWithPadding;
|
||||||
|
return SizedBox(
|
||||||
|
width: 45,
|
||||||
|
height: 32,
|
||||||
|
child: button,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onPressed: () {
|
||||||
|
if (onPressed != null) onPressed!();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MinimizeWindowButton extends WindowButton {
|
||||||
|
MinimizeWindowButton(
|
||||||
|
{Key? key,
|
||||||
|
WindowButtonColors? colors,
|
||||||
|
VoidCallback? onPressed,
|
||||||
|
bool? animate})
|
||||||
|
: super(
|
||||||
|
key: key,
|
||||||
|
colors: colors,
|
||||||
|
animate: animate ?? false,
|
||||||
|
iconBuilder: (buttonContext) =>
|
||||||
|
MinimizeIcon(color: buttonContext.iconColor),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MaximizeWindowButton extends WindowButton {
|
||||||
|
MaximizeWindowButton(
|
||||||
|
{Key? key,
|
||||||
|
WindowButtonColors? colors,
|
||||||
|
VoidCallback? onPressed,
|
||||||
|
bool? animate})
|
||||||
|
: super(
|
||||||
|
key: key,
|
||||||
|
colors: colors,
|
||||||
|
animate: animate ?? false,
|
||||||
|
iconBuilder: (buttonContext) =>
|
||||||
|
MaximizeIcon(color: buttonContext.iconColor),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestoreWindowButton extends WindowButton {
|
||||||
|
RestoreWindowButton(
|
||||||
|
{Key? key,
|
||||||
|
WindowButtonColors? colors,
|
||||||
|
VoidCallback? onPressed,
|
||||||
|
bool? animate})
|
||||||
|
: super(
|
||||||
|
key: key,
|
||||||
|
colors: colors,
|
||||||
|
animate: animate ?? false,
|
||||||
|
iconBuilder: (buttonContext) =>
|
||||||
|
RestoreIcon(color: buttonContext.iconColor),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _defaultCloseButtonColors = WindowButtonColors(
|
||||||
|
mouseOver: const Color(0xFFD32F2F),
|
||||||
|
mouseDown: const Color(0xFFB71C1C),
|
||||||
|
iconNormal: const Color(0xFF805306),
|
||||||
|
iconMouseOver: const Color(0xFFFFFFFF));
|
||||||
|
|
||||||
|
class CloseWindowButton extends WindowButton {
|
||||||
|
CloseWindowButton(
|
||||||
|
{Key? key,
|
||||||
|
WindowButtonColors? colors,
|
||||||
|
VoidCallback? onPressed,
|
||||||
|
bool? animate})
|
||||||
|
: super(
|
||||||
|
key: key,
|
||||||
|
colors: colors ?? _defaultCloseButtonColors,
|
||||||
|
animate: animate ?? false,
|
||||||
|
iconBuilder: (buttonContext) =>
|
||||||
|
CloseIcon(color: buttonContext.iconColor),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switched to CustomPaint icons by https://github.com/esDotDev
|
||||||
|
|
||||||
|
/// Close
|
||||||
|
class CloseIcon extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const CloseIcon({Key? key, required this.color}) : super(key: key);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Stack(children: [
|
||||||
|
// Use rotated containers instead of a painter because it renders slightly crisper than a painter for some reason.
|
||||||
|
Transform.rotate(
|
||||||
|
angle: pi * .25,
|
||||||
|
child:
|
||||||
|
Center(child: Container(width: 14, height: 1, color: color))),
|
||||||
|
Transform.rotate(
|
||||||
|
angle: pi * -.25,
|
||||||
|
child:
|
||||||
|
Center(child: Container(width: 14, height: 1, color: color))),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximize
|
||||||
|
class MaximizeIcon extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const MaximizeIcon({Key? key, required this.color}) : super(key: key);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _AlignedPaint(_MaximizePainter(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MaximizePainter extends _IconPainter {
|
||||||
|
_MaximizePainter(Color color) : super(color);
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
Paint p = getPaint(color);
|
||||||
|
canvas.drawRect(Rect.fromLTRB(0, 0, size.width - 1, size.height - 1), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restore
|
||||||
|
class RestoreIcon extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const RestoreIcon({
|
||||||
|
Key? key,
|
||||||
|
required this.color,
|
||||||
|
}) : super(key: key);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _AlignedPaint(_RestorePainter(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RestorePainter extends _IconPainter {
|
||||||
|
_RestorePainter(Color color) : super(color);
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
Paint p = getPaint(color);
|
||||||
|
canvas.drawRect(Rect.fromLTRB(0, 2, size.width - 2, size.height), p);
|
||||||
|
canvas.drawLine(const Offset(2, 2), const Offset(2, 0), p);
|
||||||
|
canvas.drawLine(const Offset(2, 0), Offset(size.width, 0), p);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(size.width, 0), Offset(size.width, size.height - 2), p);
|
||||||
|
canvas.drawLine(Offset(size.width, size.height - 2),
|
||||||
|
Offset(size.width - 2, size.height - 2), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Minimize
|
||||||
|
class MinimizeIcon extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
const MinimizeIcon({Key? key, required this.color}) : super(key: key);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => _AlignedPaint(_MinimizePainter(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MinimizePainter extends _IconPainter {
|
||||||
|
_MinimizePainter(Color color) : super(color);
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
Paint p = getPaint(color);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(0, size.height / 2), Offset(size.width, size.height / 2), p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helpers
|
||||||
|
abstract class _IconPainter extends CustomPainter {
|
||||||
|
_IconPainter(this.color);
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AlignedPaint extends StatelessWidget {
|
||||||
|
const _AlignedPaint(this.painter, {Key? key}) : super(key: key);
|
||||||
|
final CustomPainter painter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: CustomPaint(size: const Size(10, 10), painter: painter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Paint getPaint(Color color, [bool isAntiAlias = false]) => Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..isAntiAlias = isAntiAlias
|
||||||
|
..strokeWidth = 1;
|
||||||
|
|
||||||
|
typedef MouseStateBuilderCB = Widget Function(
|
||||||
|
BuildContext context, MouseState mouseState);
|
||||||
|
|
||||||
|
class MouseState {
|
||||||
|
bool isMouseOver = false;
|
||||||
|
bool isMouseDown = false;
|
||||||
|
MouseState();
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "isMouseDown: $isMouseDown - isMouseOver: $isMouseOver";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T? _ambiguate<T>(T? value) => value;
|
||||||
|
|
||||||
|
class MouseStateBuilder extends StatefulWidget {
|
||||||
|
final MouseStateBuilderCB builder;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
const MouseStateBuilder({Key? key, required this.builder, this.onPressed})
|
||||||
|
: super(key: key);
|
||||||
|
@override
|
||||||
|
_MouseStateBuilderState createState() => _MouseStateBuilderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MouseStateBuilderState extends State<MouseStateBuilder> {
|
||||||
|
late MouseState _mouseState;
|
||||||
|
_MouseStateBuilderState() {
|
||||||
|
_mouseState = MouseState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (event) {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseOver = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onExit: (event) {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseOver = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
onTapDown: (_) {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseDown = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTapCancel: () {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseDown = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_mouseState.isMouseDown = false;
|
||||||
|
_mouseState.isMouseOver = false;
|
||||||
|
});
|
||||||
|
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback((_) {
|
||||||
|
if (widget.onPressed != null) {
|
||||||
|
widget.onPressed!();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTapUp: (_) {},
|
||||||
|
child: widget.builder(context, _mouseState)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
24
pubspec.lock
24
pubspec.lock
@ -330,6 +330,14 @@ packages:
|
|||||||
url: "https://github.com/ThexXTURBOXx/catcher"
|
url: "https://github.com/ThexXTURBOXx/catcher"
|
||||||
source: git
|
source: git
|
||||||
version: "0.7.1"
|
version: "0.7.1"
|
||||||
|
change_case:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: change_case
|
||||||
|
sha256: f4e08feaa845e75e4f5ad2b0e15f24813d7ea6c27e7b78252f0c17f752cf1157
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -782,6 +790,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
gsettings:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gsettings
|
||||||
|
sha256: fe90d719e09a6f36607021047e642068a0c98839d9633db00b91633420ae8b0d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.7"
|
||||||
hive:
|
hive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1564,6 +1580,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
titlebar_buttons:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: titlebar_buttons
|
||||||
|
sha256: babf62b48b80f290b9ef8b0135df7d9e9bebcb9c27e8380a53df9bb72f3fb03c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
tuple:
|
tuple:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -69,6 +69,7 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/rinukkusu/spotify-dart
|
url: https://github.com/rinukkusu/spotify-dart
|
||||||
ref: 9e8ef4556942d42d74874de5491253156e7e6f43
|
ref: 9e8ef4556942d42d74874de5491253156e7e6f43
|
||||||
|
titlebar_buttons: ^1.0.0
|
||||||
tuple: ^2.0.1
|
tuple: ^2.0.1
|
||||||
url_launcher: ^6.1.7
|
url_launcher: ^6.1.7
|
||||||
uuid: ^3.0.7
|
uuid: ^3.0.7
|
||||||
|
Loading…
Reference in New Issue
Block a user