mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-04 15:49:49 +00:00
311 lines
8.9 KiB
Dart
311 lines
8.9 KiB
Dart
import 'package:flutter/foundation.dart' show TargetPlatform, defaultTargetPlatform;
|
|
import 'package:flutter/material.dart' show Colors;
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:window_manager/window_manager.dart';
|
|
|
|
import '../../../core/window/app_window_chrome.dart';
|
|
import '../../theme/app_design_tokens.dart';
|
|
import '../../theme/app_palette.dart';
|
|
|
|
class DesktopWindowHeader extends StatelessWidget {
|
|
const DesktopWindowHeader({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (!AppWindowChrome.usesCustomChrome) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return SizedBox(
|
|
height: AppDesignTokens.windowHeaderHeight,
|
|
child: const Row(
|
|
children: [
|
|
Expanded(child: DragToMoveArea(child: SizedBox.expand())),
|
|
WindowCaptionControls(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class WindowCaptionControls extends StatefulWidget {
|
|
const WindowCaptionControls({super.key});
|
|
|
|
@override
|
|
State<WindowCaptionControls> createState() => _WindowCaptionControlsState();
|
|
}
|
|
|
|
class _WindowCaptionControlsState extends State<WindowCaptionControls> with WindowListener {
|
|
static const double _captionGlyphSize = 20;
|
|
bool _isMaximized = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
windowManager.addListener(this);
|
|
_refreshState();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
windowManager.removeListener(this);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void onWindowMaximize() => _refreshState();
|
|
|
|
@override
|
|
void onWindowUnmaximize() => _refreshState();
|
|
|
|
Future<void> _refreshState() async {
|
|
final value = await windowManager.isMaximized();
|
|
if (mounted) {
|
|
setState(() => _isMaximized = value);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (!AppWindowChrome.usesCustomChrome) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
final palette = AppPalette.of(context);
|
|
final textColor = palette.textSecondary;
|
|
final isLinux = defaultTargetPlatform == TargetPlatform.linux;
|
|
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_CaptionControlButton(
|
|
icon: const _MinimizeGlyph(),
|
|
foreground: textColor,
|
|
size: isLinux ? 28 : 30,
|
|
radius: isLinux ? 999 : 4,
|
|
background: isLinux ? const Color(0x0DFFFFFF) : Colors.transparent,
|
|
hoverBackground: isLinux ? const Color(0x1AE95420) : palette.captionButtonHover,
|
|
hoverForeground: textColor,
|
|
onPressed: windowManager.minimize,
|
|
),
|
|
SizedBox(width: isLinux ? 8 : 4),
|
|
_CaptionControlButton(
|
|
icon: _isMaximized ? const _RestoreGlyph() : const _MaximizeGlyph(),
|
|
foreground: textColor,
|
|
size: isLinux ? 28 : 30,
|
|
radius: isLinux ? 999 : 4,
|
|
background: isLinux ? const Color(0x0DFFFFFF) : Colors.transparent,
|
|
hoverBackground: isLinux ? const Color(0x1AE95420) : palette.captionButtonHover,
|
|
hoverForeground: textColor,
|
|
onPressed: _isMaximized ? windowManager.unmaximize : windowManager.maximize,
|
|
),
|
|
SizedBox(width: isLinux ? 8 : 4),
|
|
_CaptionControlButton(
|
|
icon: const _CloseGlyph(),
|
|
foreground: isLinux ? const Color(0xFFE95420) : textColor,
|
|
background: isLinux ? const Color(0x33E95420) : Colors.transparent,
|
|
size: isLinux ? 28 : 30,
|
|
radius: isLinux ? 999 : 4,
|
|
hoverBackground: isLinux ? const Color(0xFFE95420) : const Color(0xFFE81123),
|
|
hoverForeground: const Color(0xFFFFFFFF),
|
|
onPressed: windowManager.close,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CaptionControlButton extends StatefulWidget {
|
|
const _CaptionControlButton({
|
|
required this.icon,
|
|
required this.foreground,
|
|
required this.onPressed,
|
|
required this.size,
|
|
required this.radius,
|
|
required this.background,
|
|
this.hoverBackground,
|
|
this.hoverForeground,
|
|
});
|
|
|
|
final Widget icon;
|
|
final Color foreground;
|
|
final VoidCallback onPressed;
|
|
final double size;
|
|
final double radius;
|
|
final Color background;
|
|
final Color? hoverBackground;
|
|
final Color? hoverForeground;
|
|
|
|
@override
|
|
State<_CaptionControlButton> createState() => _CaptionControlButtonState();
|
|
}
|
|
|
|
class _CaptionControlButtonState extends State<_CaptionControlButton> {
|
|
bool _hovered = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final palette = AppPalette.of(context);
|
|
final background = _hovered ? (widget.hoverBackground ?? palette.captionButtonHover) : widget.background;
|
|
final foreground = _hovered ? (widget.hoverForeground ?? widget.foreground) : widget.foreground;
|
|
|
|
return MouseRegion(
|
|
onEnter: (_) => setState(() => _hovered = true),
|
|
onExit: (_) => setState(() => _hovered = false),
|
|
cursor: SystemMouseCursors.click,
|
|
child: GestureDetector(
|
|
onTap: widget.onPressed,
|
|
child: Container(
|
|
width: widget.size,
|
|
height: widget.size,
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(color: background, borderRadius: BorderRadius.circular(widget.radius)),
|
|
child: IconTheme(
|
|
data: IconThemeData(color: foreground),
|
|
child: Center(child: widget.icon),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MinimizeGlyph extends StatelessWidget {
|
|
const _MinimizeGlyph();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final color = IconTheme.of(context).color ?? Colors.white;
|
|
final size = _WindowCaptionControlsState._captionGlyphSize;
|
|
return SizedBox(
|
|
width: size,
|
|
height: size,
|
|
child: Center(
|
|
child: Container(
|
|
width: size * 0.72,
|
|
height: size <= 12 ? 1.2 : 1.6,
|
|
decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(999)),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MaximizeGlyph extends StatelessWidget {
|
|
const _MaximizeGlyph();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final color = IconTheme.of(context).color ?? Colors.white;
|
|
final size = _WindowCaptionControlsState._captionGlyphSize;
|
|
final boxSize = size * 0.62;
|
|
return SizedBox(
|
|
width: size,
|
|
height: size,
|
|
child: Center(
|
|
child: Container(
|
|
width: boxSize,
|
|
height: boxSize,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: color, width: size <= 12 ? 0.9 : 0.95),
|
|
borderRadius: BorderRadius.circular(1),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RestoreGlyph extends StatelessWidget {
|
|
const _RestoreGlyph();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final color = IconTheme.of(context).color ?? Colors.white;
|
|
final size = _WindowCaptionControlsState._captionGlyphSize;
|
|
final boxSize = size * 0.56;
|
|
final topBoxLeft = size * 0.40;
|
|
final topBoxTop = size * 0.16;
|
|
final bottomBoxLeft = size * 0.16;
|
|
final bottomBoxTop = size * 0.40;
|
|
final borderWidth = size <= 12 ? 1.0 : 1.15;
|
|
return SizedBox(
|
|
width: size,
|
|
height: size,
|
|
child: Stack(
|
|
children: [
|
|
Positioned(
|
|
left: topBoxLeft,
|
|
top: topBoxTop,
|
|
child: Container(
|
|
width: boxSize,
|
|
height: boxSize,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: color, width: borderWidth),
|
|
borderRadius: BorderRadius.circular(1),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
left: bottomBoxLeft,
|
|
top: bottomBoxTop,
|
|
child: Container(
|
|
width: boxSize,
|
|
height: boxSize,
|
|
decoration: BoxDecoration(
|
|
color: Colors.transparent,
|
|
border: Border.all(color: color, width: borderWidth),
|
|
borderRadius: BorderRadius.circular(1),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CloseGlyph extends StatelessWidget {
|
|
const _CloseGlyph();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final color = IconTheme.of(context).color ?? Colors.white;
|
|
return SizedBox(
|
|
width: _WindowCaptionControlsState._captionGlyphSize,
|
|
height: _WindowCaptionControlsState._captionGlyphSize,
|
|
child: CustomPaint(painter: _CrossPainter(color)),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _CrossPainter extends CustomPainter {
|
|
const _CrossPainter(this.color);
|
|
|
|
final Color color;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final paint = Paint()
|
|
..color = color
|
|
..strokeWidth = size.width <= 12 ? 1.2 : 1.8
|
|
..strokeCap = StrokeCap.round;
|
|
canvas.drawLine(
|
|
Offset(size.width * 0.28, size.height * 0.28),
|
|
Offset(size.width * 0.72, size.height * 0.72),
|
|
paint,
|
|
);
|
|
canvas.drawLine(
|
|
Offset(size.width * 0.72, size.height * 0.28),
|
|
Offset(size.width * 0.28, size.height * 0.72),
|
|
paint,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant _CrossPainter oldDelegate) {
|
|
return oldDelegate.color != color;
|
|
}
|
|
}
|