Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
+276
View File
@@ -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,
),
),
),
),
),
);
}
}