Created
May 14, 2020 05:32
-
-
Save nicholasspencer/59713521cce97f53ebdccae0bb7bacc7 to your computer and use it in GitHub Desktop.
Tutorial widget knockout system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Tutorial extends StatefulWidget { | |
final Widget child; | |
final bool show; | |
const Tutorial({ | |
@required this.child, | |
this.show = false, | |
Key key, | |
}) : super(key: key); | |
static TutorialState of(BuildContext context) => | |
context.findAncestorStateOfType<TutorialState>(); | |
@override | |
TutorialState createState() => TutorialState(); | |
} | |
class TutorialState extends State<Tutorial> { | |
Set<RRect> rects = {}; | |
@override | |
Widget build(BuildContext context) { | |
return widget.show != true | |
? widget.child | |
: Stack( | |
children: <Widget>[ | |
widget.child, | |
Positioned.fill( | |
child: ClipPath( | |
clipper: TutorialClipper( | |
rects: rects, | |
), | |
child: Material( | |
color: Colors.black87, | |
), | |
), | |
), | |
], | |
); | |
} | |
void addRRect(RRect rect) { | |
rects.add(rect); | |
} | |
void removeRRect(RRect rect) { | |
rects.remove(rect); | |
} | |
} | |
class TutorialHighlight extends StatefulWidget { | |
final Widget child; | |
final EdgeInsets padding; | |
final Radius radius; | |
const TutorialHighlight({ | |
Key key, | |
this.child, | |
this.padding = EdgeInsets.zero, | |
this.radius = Radius.zero, | |
}) : super(key: key); | |
@override | |
_TutorialHighlightState createState() => _TutorialHighlightState(); | |
} | |
class _TutorialHighlightState extends State<TutorialHighlight> { | |
RRect rect; | |
@override | |
void didUpdateWidget(TutorialHighlight oldWidget) { | |
bubbleRect(); | |
super.didUpdateWidget(oldWidget); | |
} | |
@override | |
Widget build(BuildContext context) => Container(child: widget.child); | |
void bubbleRect() { | |
final tutorial = Tutorial.of(context); | |
final tutorialBox = tutorial?.context?.findRenderObject() as RenderBox; | |
final box = context?.findRenderObject() as RenderBox; | |
if ((box?.hasSize == false ?? false) || | |
(tutorialBox?.hasSize == false ?? false)) { | |
return; | |
} | |
if (this.rect != null) { | |
tutorial.removeRRect(this.rect); | |
this.rect = null; | |
} | |
final position = box.localToGlobal(Offset.zero, ancestor: tutorialBox); | |
final size = box.size; | |
final rect = RRect.fromLTRBR( | |
position.dx + (-widget.padding.left), | |
position.dy + (-widget.padding.top), | |
position.dx + size.width + widget.padding.right, | |
position.dy + size.height + widget.padding.bottom, | |
widget.radius, | |
); | |
this.rect = rect; | |
tutorial.addRRect(rect); | |
} | |
} | |
class TutorialClipper extends CustomClipper<Path> { | |
final Set<RRect> rects; | |
const TutorialClipper({ | |
@required this.rects, | |
}); | |
@override | |
Path getClip(Size size) { | |
final path = Path(); | |
for (var rrect in rects) { | |
path.addRRect(rrect); | |
} | |
path.addRect(new Rect.fromLTWH(0.0, 0.0, size.width, size.height)); | |
path.fillType = PathFillType.evenOdd; | |
return path; | |
} | |
@override | |
bool shouldReclip(CustomClipper<Path> oldClipper) => this != oldClipper; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment