Skip to content

Commit

Permalink
feat: improve pixel snapping (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
knopp authored Nov 14, 2023
1 parent 53d58b7 commit 20abc35
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 37 deletions.
10 changes: 6 additions & 4 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import 'package:pixel_snap/widgets.dart';
import 'widgets.dart';

void main() {
runApp(const MainApp());
Widget app = const MainApp();
if (!kIsWeb && kDebugMode) {
app = PixelSnapDebugBar(child: app);
}
runApp(app);
}

class MinimalApp extends StatelessWidget {
Expand All @@ -23,7 +27,7 @@ class MinimalApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
final res = WidgetsApp(
return WidgetsApp(
color: Colors.blue,
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) {
return PageRouteBuilder<T>(
Expand All @@ -37,8 +41,6 @@ class MinimalApp extends StatelessWidget {
),
),
);
return res;
// return PixelSnapDebugBar(child: res);
}
}

Expand Down
73 changes: 53 additions & 20 deletions example/lib/popover_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,27 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' show Colors;
import 'package:flutter/widgets.dart';
import 'package:headless/headless.dart';
import 'package:pixel_snap/pixel_snap.dart';

class SamplePopoverDelegate extends BasePopoverDelegate {
SamplePopoverDelegate({
required super.attachments,
}) : super(
calloutSize: 10,
popoverDistance: 12,
calloutAnimationDuration: const Duration(milliseconds: 150),
);

late PixelSnap _pixelSnap;

@override
double get calloutSize => _pixelSnap(10);

@override
double get popoverDistance => _pixelSnap(12);

@override
EdgeInsets getScreenInsets(EdgeInsets safeAreaInsets) {
// Enforce minimum insets
const min = EdgeInsets.all(20);
final min = EdgeInsets.all(_pixelSnap(20));
return EdgeInsets.fromLTRB(
math.max(min.left, safeAreaInsets.left),
math.max(min.top, safeAreaInsets.top),
Expand All @@ -28,6 +35,12 @@ class SamplePopoverDelegate extends BasePopoverDelegate {
);
}

@override
Widget buildScaffold(BuildContext context, Widget child, Animation<double> animation) {
_pixelSnap = PixelSnap.of(context);
return super.buildScaffold(context, child, animation);
}

@override
Widget buildPopover(
BuildContext context,
Expand All @@ -44,6 +57,7 @@ class SamplePopoverDelegate extends BasePopoverDelegate {
child: RepaintBoundary(
child: CustomPaint(
painter: _PopoverPainter(
pixelSnap: PixelSnap.of(context),
geometry: geometry,
getCalloutHeightFactor: calloutHeightFactor,
),
Expand Down Expand Up @@ -179,21 +193,29 @@ class _PopoverPainter extends CustomPainter {
_PopoverPainter({
required this.geometry,
required this.getCalloutHeightFactor,
required this.pixelSnap,
}) : super(repaint: geometry);

final ValueListenable<PopoverGeometry?> geometry;
final double Function(bool calloutVisible) getCalloutHeightFactor;
final PixelSnap pixelSnap;

@override
void paint(Canvas canvas, Size size) {
final geometry = this.geometry.value;
if (geometry == null) {
return;
}

late double heightFactor;

final path = _makePopoverPath(
size,
geometry,
getCalloutHeightFactor,
(visible) {
heightFactor = getCalloutHeightFactor(visible);
return heightFactor;
},
);

// BlurStyle.outer doesn't seem to work with HTML renderer
Expand All @@ -219,13 +241,22 @@ class _PopoverPainter extends CustomPainter {
canvas.restore();
}

canvas.drawPath(
path,
Paint()
..strokeWidth = 1
..color = Colors.grey.shade400
..style = PaintingStyle.stroke,
final innerPath = _makePopoverPath(
size,
geometry,
(_) => heightFactor,
rectAdjustment: EdgeInsets.all(pixelSnap(-1)),
radiusAdjustment: pixelSnap(-1),
calloutAdjustment: pixelSnap(-1),
);

final fillPath = Path()
..fillType = PathFillType.evenOdd
..addPath(path, Offset.zero)
..addPath(innerPath, Offset.zero)
..close();

canvas.drawPath(fillPath, Paint()..color = Colors.grey.shade400);
}

@override
Expand All @@ -249,15 +280,15 @@ extension on PopoverEdge {
}
}

Path _makePopoverPath(
Size size,
PopoverGeometry geometry,
double Function(bool calloutVisible) getCalloutHeightFactor,
) {
final rect = geometry.popover;
Path _makePopoverPath(Size size, PopoverGeometry geometry,
double Function(bool calloutVisible) getCalloutHeightFactor,
{EdgeInsets rectAdjustment = EdgeInsets.zero,
double radiusAdjustment = 0,
double calloutAdjustment = 0}) {
final rect = rectAdjustment.inflateRect(geometry.popover);
final path = Path();
path.addCupertinoRRect(
RRect.fromRectAndRadius(rect, const Radius.circular(10)),
RRect.fromRectAndRadius(rect, Radius.circular(10 + radiusAdjustment)),
lineCallback: (path, from, to, edge) {
if (edge != geometry.attachment.getCalloutEdge()?.asCupertinoEdge) {
path.lineTo(to.dx, to.dy);
Expand Down Expand Up @@ -308,7 +339,7 @@ Path _makePopoverPath(
mainAxisSign = 1.0;
}

final bool calloutVisible = crossAxisDistance == geometry.requestedDistance &&
final bool calloutVisible = (crossAxisDistance - geometry.requestedDistance).abs() < 0.001 &&
mainAxisPositionMin <= attachmentPosition &&
mainAxisPositionMax >= attachmentPosition;

Expand All @@ -322,8 +353,10 @@ Path _makePopoverPath(

attachmentPosition = attachmentPosition.clamp(mainAxisPositionMin, mainAxisPositionMax);

final control1 = mainAxisSign * (calloutMainSizeHalf / 2.0);
final control2 = mainAxisSign * (calloutMainSizeHalf / 3.0);
final control1 =
mainAxisSign * (calloutMainSizeHalf / 2.0) - mainAxisSign * calloutAdjustment;
final control2 =
mainAxisSign * (calloutMainSizeHalf / 3.0) + mainAxisSign * calloutAdjustment;

final calloutMainAxisStart = attachmentPosition - mainAxisSign * calloutMainSizeHalf;
final calloutMainAxisEnd = attachmentPosition + mainAxisSign * calloutMainSizeHalf;
Expand Down
18 changes: 12 additions & 6 deletions example/lib/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class SampleButton extends StatelessWidget {
ButtonState state,
Widget? child,
) {
final ps = PixelSnap.of(context);
final borderColor = switch (state) {
ButtonState(enabled: false) => Colors.blue.shade200,
ButtonState(pressed: true) => Colors.blue.shade400,
Expand Down Expand Up @@ -124,7 +125,7 @@ class SampleButton extends StatelessWidget {
decoration: ShapeDecoration(
// border: Border.fromBorderSide(
// // borderRadius: BorderRadius.circular(6),
// BorderSide(color: borderColor.withOpacity(0.5), width: 3),
// BorderSide(color: borderColor, width: 1),
// ),
// borderRadius: const BorderRadius.all(Radius.circular(6.0)),
// boxShadow: [
Expand All @@ -136,8 +137,8 @@ class SampleButton extends StatelessWidget {
// ],
shape: CupertinoRectangleBorder(
// borderRadius: BorderRadius.circular(6),
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
side: BorderSide(color: borderColor, width: 1),
borderRadius: const BorderRadius.all(Radius.circular(6.0)).pixelSnap(ps),
side: BorderSide(color: borderColor, width: 1).pixelSnap(ps),
),
shadows: [
BoxShadow(
Expand All @@ -155,9 +156,14 @@ class SampleButton extends StatelessWidget {
],
),
),
child: DefaultTextStyle.merge(
style: TextStyle(color: textColor),
child: child!,
child: Container(
child: DefaultTextStyle.merge(
style: TextStyle(
height: 1.17,
color: textColor,
),
child: child!,
),
),
),
);
Expand Down
6 changes: 5 additions & 1 deletion lib/src/popover.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ abstract class PopoverDelegate {
///
/// When showing popover this method will be called before [computePosition] and
/// then also the next frame.
Widget buildScaffold(BuildContext context, Widget child, Animation<double> animation);
Widget buildScaffold(
BuildContext context,
Widget child,
Animation<double> animation,
);

Widget buildVeil(
BuildContext context,
Expand Down
7 changes: 3 additions & 4 deletions lib/src/popover_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import 'popover_geometry.dart';
abstract class BasePopoverDelegate extends PopoverDelegate {
BasePopoverDelegate({
required this.attachments,
required this.calloutSize,
required this.popoverDistance,
required this.calloutAnimationDuration,
});

Expand All @@ -18,11 +16,11 @@ abstract class BasePopoverDelegate extends PopoverDelegate {
final List<PopoverAttachment> attachments;

/// Height of the popover call-out.
final double calloutSize;
double get calloutSize;

/// Requested distance between the popover and anchor. This may not be honored
/// if popover needs to be repositioned to fit on screen.
final double popoverDistance;
double get popoverDistance;

/// Returns the insets that used when positioning popover on screen.
EdgeInsets getScreenInsets(EdgeInsets safeAreaInsets) {
Expand Down Expand Up @@ -65,6 +63,7 @@ abstract class BasePopoverDelegate extends PopoverDelegate {
//

@override
@mustCallSuper
Widget buildScaffold(
BuildContext context,
Widget child,
Expand Down
10 changes: 8 additions & 2 deletions test/popover_delegate_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ import 'test_util.dart';
class TestDelegate extends BasePopoverDelegate {
TestDelegate({
required super.attachments,
required super.calloutSize,
required super.popoverDistance,
required this.calloutSize,
required this.popoverDistance,
required super.calloutAnimationDuration,
});

@override
final double calloutSize;

@override
final double popoverDistance;

ValueNotifier<PopoverGeometry?>? _geometry;

PopoverGeometry? get lastGeometry => _geometry?.value;
Expand Down

0 comments on commit 20abc35

Please sign in to comment.