@@ -195,8 +195,15 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer<CanvasNode> {
195195 body: body,
196196 );
197197 } else {
198+ final Color ? background = retrieveBackgroundColor (context, node);
199+ // Under a TapThroughScope, a canvas without a background takes no taps
200+ // on its empty parts, so they reach its dialog's barrier.
201+ final bool transparent =
202+ (background == null || background.a == 0 ) &&
203+ TapThroughScope .of (context);
198204 scaffold = Material (
199- color: retrieveBackgroundColor (context, node),
205+ type: transparent ? MaterialType .transparency : MaterialType .canvas,
206+ color: transparent ? null : background,
200207 child: body,
201208 );
202209 }
@@ -377,6 +384,9 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer<CanvasNode> {
377384 settings: settings,
378385 builder: (context) => CanvasViewport (
379386 node: node,
387+ followsScreen:
388+ node.scaleMode == ScaleMode .responsive &&
389+ hasResponsiveLayout (node, getNode),
380390 child: switch (node.scaleMode) {
381391 ScaleMode .responsive => _wrapInScaffoldForResponsive (
382392 context: context,
@@ -393,6 +403,20 @@ class PassiveCanvasTransformer extends NodeWidgetTransformer<CanvasNode> {
393403 );
394404 }
395405
406+ /// Whether [node] or a node under it is a responsive layout (a
407+ /// [VarianceNode] whose variants map to breakpoints), in any variant.
408+ static bool hasResponsiveLayout (BaseNode node, GetNode getNode) {
409+ if (node is VarianceNode ) {
410+ if (node.isResponsive) return true ;
411+ return node.allVariantsChildren.any (
412+ (id) => hasResponsiveLayout (getNode (id), getNode),
413+ );
414+ }
415+ return node.childrenOrEmpty.any (
416+ (id) => hasResponsiveLayout (getNode (id), getNode),
417+ );
418+ }
419+
396420 static Color ? retrieveBackgroundColor (BuildContext context, CanvasNode node) {
397421 final fills = < PaintModel > [
398422 for (final fill in node.fills)
@@ -439,6 +463,13 @@ enum CanvasSizing {
439463 /// The canvas keeps the size it was designed at, within the space it is
440464 /// given. Used where the host only centers the layout and lets it choose
441465 /// its size, such as a dialog.
466+ ///
467+ /// A responsive canvas with a responsive layout in it (see
468+ /// [PassiveCanvasTransformer.hasResponsiveLayout] ) fills the space
469+ /// instead, as with [fill] : its variant follows the screen, and each
470+ /// variant places itself in it (a full-screen sheet on a phone, a
471+ /// centered card on a desktop). Give such a canvas no background so the
472+ /// barrier shows around what its layout draws.
442473 design,
443474}
444475
@@ -462,6 +493,22 @@ class CanvasSizingScope extends InheritedWidget {
462493 sizing != oldWidget.sizing;
463494}
464495
496+ /// Makes the empty parts of the layout below let taps through to what is
497+ /// behind it: the parts where neither a node's fills, strokes or content
498+ /// paint nor a reactive node is (a transparent canvas, an empty frame). Set
499+ /// by `CodelesslyDialogWidget` , so a tap beside a full-screen dialog's
500+ /// layout reaches the barrier and closes a dismissible dialog.
501+ class TapThroughScope extends InheritedWidget {
502+ const TapThroughScope ({super .key, required super .child});
503+
504+ /// Whether a [TapThroughScope] is above [context] .
505+ static bool of (BuildContext context) =>
506+ context.getInheritedWidgetOfExactType <TapThroughScope >() != null ;
507+
508+ @override
509+ bool updateShouldNotify (TapThroughScope oldWidget) => false ;
510+ }
511+
465512/// The width the canvas above is laid out at, which picks the variant a
466513/// responsive [VarianceNode] shows (see [VarianceNode.variantForWidth] ), the
467514/// same width the editor's design canvas has.
@@ -495,9 +542,19 @@ class CanvasWidthScope extends InheritedWidget {
495542/// on an unbounded axis it gets its design size (see [CanvasSizing.fill] ).
496543class CanvasViewport extends StatelessWidget {
497544 final CanvasNode node;
545+
546+ /// Whether the canvas fills the space under [CanvasSizing.design] too: a
547+ /// responsive canvas that holds a responsive layout.
548+ final bool followsScreen;
549+
498550 final Widget child;
499551
500- const CanvasViewport ({super .key, required this .node, required this .child});
552+ const CanvasViewport ({
553+ super .key,
554+ required this .node,
555+ this .followsScreen = false ,
556+ required this .child,
557+ });
501558
502559 /// The canvas box for [constraints] under [sizing] , per axis. A null
503560 /// height leaves the height to the canvas content (see
@@ -540,7 +597,9 @@ class CanvasViewport extends StatelessWidget {
540597
541598 @override
542599 Widget build (BuildContext context) {
543- final CanvasSizing sizing = CanvasSizingScope .of (context);
600+ final CanvasSizing sizing = followsScreen
601+ ? CanvasSizing .fill
602+ : CanvasSizingScope .of (context);
544603 return LayoutBuilder (
545604 builder: (context, constraints) {
546605 final box = resolve (node, constraints, sizing: sizing);
0 commit comments