Initial commit
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import 'window_chrome.dart';
|
||||
|
||||
final _windowControlsBinding = _WindowControlsBinding();
|
||||
|
||||
class WindowControls extends StatefulWidget {
|
||||
final Brightness? brightness;
|
||||
|
||||
const WindowControls({super.key, this.brightness});
|
||||
|
||||
|
||||
const WindowControls.forShellOverlay({super.key}) : brightness = null;
|
||||
|
||||
|
||||
const WindowControls.forDarkSurface({super.key})
|
||||
: brightness = Brightness.dark;
|
||||
|
||||
@override
|
||||
State<WindowControls> createState() => _WindowControlsState();
|
||||
}
|
||||
|
||||
class _WindowControlsState extends State<WindowControls> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
unawaited(_windowControlsBinding.ensureInitialized());
|
||||
}
|
||||
|
||||
Future<void> _toggleMaximize() async {
|
||||
if (_windowControlsBinding.isMaximized.value) {
|
||||
await windowManager.unmaximize();
|
||||
return;
|
||||
}
|
||||
await windowManager.maximize();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!Platform.isWindows && !Platform.isMacOS) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final resolvedBrightness =
|
||||
widget.brightness ?? Theme.of(context).brightness;
|
||||
final isLight = resolvedBrightness == Brightness.light;
|
||||
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: _windowControlsBinding.isMaximized,
|
||||
builder: (context, isMaximized, _) {
|
||||
final backgroundColor = isLight
|
||||
? Colors.black.withValues(alpha: 0.14)
|
||||
: const Color(0xFF08090c).withValues(alpha: 0.72);
|
||||
final iconColor = isLight
|
||||
? Colors.black.withValues(alpha: 0.65)
|
||||
: Colors.white.withValues(alpha: 0.85);
|
||||
final defaultHoverColor = isLight
|
||||
? Colors.black.withValues(alpha: 0.10)
|
||||
: Colors.white.withValues(alpha: 0.15);
|
||||
const containerHeight = WindowChrome.controlsHeight;
|
||||
const containerRadius = 20.0;
|
||||
const buttonWidth = WindowChrome.buttonWidth;
|
||||
const iconSize = 16.0;
|
||||
|
||||
return Container(
|
||||
height: containerHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(containerRadius),
|
||||
boxShadow: isLight
|
||||
? null
|
||||
: const [
|
||||
BoxShadow(
|
||||
color: Color(0x2E000000),
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_ControlButton(
|
||||
icon: Icons.remove_rounded,
|
||||
tooltip: '最小化',
|
||||
iconColor: iconColor,
|
||||
defaultHoverColor: defaultHoverColor,
|
||||
width: buttonWidth,
|
||||
height: containerHeight,
|
||||
iconSize: iconSize,
|
||||
borderRadius: containerRadius,
|
||||
onTap: windowManager.minimize,
|
||||
),
|
||||
_ControlButton(
|
||||
icon: isMaximized
|
||||
? Icons.filter_none_rounded
|
||||
: Icons.crop_square_rounded,
|
||||
tooltip: isMaximized ? '还原' : '最大化',
|
||||
iconColor: iconColor,
|
||||
defaultHoverColor: defaultHoverColor,
|
||||
width: buttonWidth,
|
||||
height: containerHeight,
|
||||
iconSize: iconSize,
|
||||
borderRadius: containerRadius,
|
||||
onTap: _toggleMaximize,
|
||||
),
|
||||
_ControlButton(
|
||||
icon: Icons.close_rounded,
|
||||
tooltip: '关闭',
|
||||
iconColor: iconColor,
|
||||
defaultHoverColor: defaultHoverColor,
|
||||
hoverColor: const Color(0xFFE81123),
|
||||
pressedColor: const Color(0xFFC50F1F),
|
||||
width: buttonWidth,
|
||||
height: containerHeight,
|
||||
iconSize: iconSize,
|
||||
borderRadius: containerRadius,
|
||||
onTap: windowManager.close,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WindowControlsBinding with WindowListener {
|
||||
final ValueNotifier<bool> isMaximized = ValueNotifier(false);
|
||||
|
||||
Future<void> ensureInitialized() async {
|
||||
if (!windowManager.listeners.contains(this)) {
|
||||
windowManager.addListener(this);
|
||||
}
|
||||
try {
|
||||
isMaximized.value = await windowManager.isMaximized();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMaximize() {
|
||||
isMaximized.value = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowUnmaximize() {
|
||||
isMaximized.value = false;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if (windowManager.listeners.contains(this)) {
|
||||
windowManager.removeListener(this);
|
||||
}
|
||||
isMaximized.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
class _ControlButton extends StatefulWidget {
|
||||
final IconData icon;
|
||||
final String tooltip;
|
||||
final Color iconColor;
|
||||
final Color defaultHoverColor;
|
||||
final Color? hoverColor;
|
||||
final Color? pressedColor;
|
||||
final double width;
|
||||
final double height;
|
||||
final double iconSize;
|
||||
final double borderRadius;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _ControlButton({
|
||||
required this.icon,
|
||||
required this.tooltip,
|
||||
required this.iconColor,
|
||||
required this.defaultHoverColor,
|
||||
this.hoverColor,
|
||||
this.pressedColor,
|
||||
this.width = 36,
|
||||
this.height = 30,
|
||||
this.iconSize = 16,
|
||||
this.borderRadius = 20,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_ControlButton> createState() => _ControlButtonState();
|
||||
}
|
||||
|
||||
class _ControlButtonState extends State<_ControlButton> {
|
||||
bool _hovering = false;
|
||||
bool _pressing = false;
|
||||
bool _focused = false;
|
||||
late final FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = FocusNode();
|
||||
_focusNode.addListener(_onFocusChange);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(_onFocusChange);
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onFocusChange() {
|
||||
setState(() => _focused = _focusNode.hasFocus);
|
||||
}
|
||||
|
||||
bool get _active => _hovering || _focused;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hoverBg = widget.hoverColor ?? widget.defaultHoverColor;
|
||||
final pressedBg =
|
||||
widget.pressedColor ??
|
||||
widget.defaultHoverColor.withValues(
|
||||
alpha: widget.defaultHoverColor.a * 1.8,
|
||||
);
|
||||
|
||||
final Color bg;
|
||||
if (_pressing) {
|
||||
bg = widget.hoverColor != null
|
||||
? (widget.pressedColor ?? hoverBg)
|
||||
: pressedBg;
|
||||
} else if (_active) {
|
||||
bg = hoverBg;
|
||||
} else {
|
||||
bg = Colors.transparent;
|
||||
}
|
||||
|
||||
final bool useBrightIcon =
|
||||
(_active || _pressing) && widget.hoverColor != null;
|
||||
|
||||
return Tooltip(
|
||||
message: widget.tooltip,
|
||||
child: Focus(
|
||||
focusNode: _focusNode,
|
||||
child: MouseRegion(
|
||||
onEnter: (_) => setState(() => _hovering = true),
|
||||
onExit: (_) => setState(() {
|
||||
_hovering = false;
|
||||
_pressing = false;
|
||||
}),
|
||||
child: GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
onTapDown: (_) => setState(() => _pressing = true),
|
||||
onTapUp: (_) => setState(() => _pressing = false),
|
||||
onTapCancel: () => setState(() => _pressing = false),
|
||||
child: Container(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
widget.icon,
|
||||
size: widget.iconSize,
|
||||
color: useBrightIcon ? Colors.white : widget.iconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user