Files
Reboot-Launcher/gui/lib/src/messenger/abstract/overlay.dart
Alessandro Autiero e3b8d7d182 Release 9.2.0
2024-07-06 18:43:52 +02:00

171 lines
5.2 KiB
Dart

import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/rendering.dart';
import 'package:reboot_launcher/src/page/implementation/home_page.dart';
import 'package:reboot_launcher/src/page/pages.dart';
typedef WidgetBuilder = Widget Function(BuildContext, void Function());
class OverlayTarget extends StatefulWidget {
final Widget child;
const OverlayTarget({super.key, required this.child});
@override
State<OverlayTarget> createState() => OverlayTargetState();
OverlayTargetState of(BuildContext context) => context.findAncestorStateOfType<OverlayTargetState>()!;
}
class OverlayTargetState extends State<OverlayTarget> {
@override
Widget build(BuildContext context) => widget.child;
void showOverlay({
required String text,
required WidgetBuilder actionBuilder,
Offset offset = Offset.zero,
bool ignoreTargetPointers = true,
AttachMode attachMode = AttachMode.start
}) {
final renderBox = context.findRenderObject() as RenderBox;
final position = renderBox.localToGlobal(Offset.zero);
final color = FluentTheme.of(context).acrylicBackgroundColor;
late OverlayEntry entry;
entry = OverlayEntry(
builder: (context) => Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
child: _AbsorbPointer(
exclusion: ignoreTargetPointers ? null : renderBox
)
),
Positioned(
left: position.dx - (attachMode != AttachMode.start ? renderBox.size.width : 0) + offset.dx,
top: position.dy + (renderBox.size.height / 2) + offset.dy,
child: CustomPaint(
painter: _CallOutShape(color, attachMode != AttachMode.start),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(text),
const SizedBox(height: 12.0),
actionBuilder(context, () => entry.remove())
],
),
),
),
),
],
)
);
appOverlayKey.currentState?.insert(entry);
}
}
enum AttachMode {
start,
middle,
end;
}
// Harder than one would think
class _CallOutShape extends CustomPainter {
final Color color;
final bool end;
_CallOutShape(this.color, this.end);
@override
void paint(Canvas canvas, Size size) {
final fillPaint = Paint()
..color = color
..style = PaintingStyle.fill;
final borderPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 0.25
..color = Colors.white;
final path = Path();
path.moveTo(10, 0);
if(!end) {
path.lineTo(12.5, 0);
path.lineTo(20, -12.5);
path.lineTo(27.5, 0);
}else {
path.lineTo(size.width - 27.5, 0);
path.lineTo(size.width - 20, -12.5);
path.lineTo(size.width - 12.5, 0);
}
path.lineTo(size.width - 10, 0);
path.arcToPoint(Offset(size.width, 10), radius: Radius.circular(10));
path.lineTo(size.width, size.height - 10);
path.arcToPoint(Offset(size.width - 10, size.height), radius: Radius.circular(10));
path.lineTo(10, size.height);
path.arcToPoint(Offset(0, size.height - 10), radius: Radius.circular(10));
path.lineTo(0, 10);
path.arcToPoint(Offset(10, 0), radius: Radius.circular(10));
path.close();
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, borderPaint);
canvas.drawShadow(path, color, 1, true);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
class _AbsorbPointer extends SingleChildRenderObjectWidget {
final RenderBox? exclusion;
const _AbsorbPointer({
required this.exclusion
});
@override
_RenderAbsorbPointer createRenderObject(BuildContext context) => _RenderAbsorbPointer(
exclusion: exclusion
);
}
class _RenderAbsorbPointer extends RenderProxyBox {
final RenderBox? exclusion;
_RenderAbsorbPointer({
required this.exclusion,
RenderBox? child
}) : super(child);
@override
bool hitTest(BoxHitTestResult result, { required Offset position }) {
final exclusion = this.exclusion;
if(exclusion == null) {
return size.contains(position);
}
// 32 is the height of the title bar (need this offset as the overlay area doesn't include it)
// Not an optimal solution but it works (calculating it is kind of complicated)
position = Offset(position.dx, position.dy + HomePage.kTitleBarHeight);
final exclusionPosition = exclusion.localToGlobal(Offset.zero);
final exclusionSize = Rect.fromLTRB(
exclusionPosition.dx,
exclusionPosition.dy,
exclusionPosition.dx + exclusion.size.width,
exclusionPosition.dy + exclusion.size.height
);
return !exclusionSize.contains(position);
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) => super.visitChildrenForSemantics(visitor);
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isBlockingUserActions = true;
}
}