1+ import 'dart:math' ;
2+
3+ import 'package:adaptive_theme/adaptive_theme.dart' ;
4+ import 'package:flutter/material.dart' ;
5+ import 'package:flutter/scheduler.dart' ;
6+ import 'package:flutter/services.dart' ;
7+ import 'package:flutter_box_transform/flutter_box_transform.dart' ;
18import 'package:hyper_effects_demo/stories/color_filter_scroll_transition.dart' ;
2- import 'package:hyper_effects_demo/stories/windows_settings_transition.dart' ;
39import 'package:hyper_effects_demo/stories/scroll_phase_transition.dart' ;
10+ import 'package:hyper_effects_demo/stories/scroll_wheel_blur.dart' ;
411import 'package:hyper_effects_demo/stories/scroll_wheel_transition.dart' ;
5- import 'package:flutter/material.dart' ;
6- import 'package:storybook_flutter/storybook_flutter.dart' ;
12+ import 'package:hyper_effects_demo/stories/windows_settings_transition.dart' ;
713
8- import 'stories/scroll_wheel_blur .dart' ;
14+ import 'story .dart' ;
915
1016void main () {
1117 runApp (const MyApp ());
@@ -16,40 +22,229 @@ class MyApp extends StatelessWidget {
1622
1723 @override
1824 Widget build (BuildContext context) {
19- return MaterialApp (
20- title: 'Hyper Effects Story Book' ,
21- debugShowCheckedModeBanner: false ,
22- home: Storybook (
23- initialStory: 'Scroll Phase Offset' ,
24- stories: [
25- Story (
26- name: 'Scroll Phase Offset' ,
27- description: 'Offsetting elements based on the phase of the scroll.' ,
28- builder: (context) => const ScrollPhaseTransition (),
29- ),
30- Story (
31- name: 'Scroll Phase Color Filter Transition' ,
32- description: 'Blending and changing image colors based on scroll phase.' ,
33- builder: (context) => const ColorFilterScrollTransition (),
25+ return AdaptiveTheme (
26+ light: ThemeData (
27+ useMaterial3: true ,
28+ brightness: Brightness .light,
29+ colorSchemeSeed: Colors .blue,
30+ inputDecorationTheme: InputDecorationTheme (
31+ isDense: true ,
32+ contentPadding:
33+ const EdgeInsets .symmetric (horizontal: 10 , vertical: 12 ),
34+ border: OutlineInputBorder (
35+ borderRadius: BorderRadius .circular (6 ),
3436 ),
35- Story (
36- name: 'Scroll Wheel Transition' ,
37- description: 'Warping elements to mimic a cylindrical effect.' ,
38- builder: (context) => const ScrollWheelTransition (),
39- ),
40- Story (
41- name: 'Scroll Phase Blur' ,
42- description:
43- 'A focus effect where elements outside the view are blurred' ,
44- builder: (context) => const ScrollWheelBlurTransition (),
37+ ),
38+ ),
39+ dark: ThemeData (
40+ useMaterial3: true ,
41+ brightness: Brightness .dark,
42+ colorSchemeSeed: Colors .blue,
43+ inputDecorationTheme: InputDecorationTheme (
44+ isDense: true ,
45+ contentPadding:
46+ const EdgeInsets .symmetric (horizontal: 10 , vertical: 12 ),
47+ border: OutlineInputBorder (
48+ borderRadius: BorderRadius .circular (6 ),
4549 ),
46- Story (
47- name: 'Pointer Transition' ,
48- description: 'Moves elements slightly with the pointer' ,
49- builder: (context) => const WindowsSettingsTransition (),
50+ ),
51+ ),
52+ initial: AdaptiveThemeMode .system,
53+ builder: (theme, darkTheme) => MaterialApp (
54+ title: 'Hyper Effects Storyboard' ,
55+ debugShowCheckedModeBanner: false ,
56+ theme: theme,
57+ darkTheme: darkTheme,
58+ home: const Storyboard (),
59+ ),
60+ );
61+ }
62+ }
63+
64+ class Storyboard extends StatefulWidget {
65+ const Storyboard ({super .key});
66+
67+ @override
68+ State <Storyboard > createState () => _StoryboardState ();
69+ }
70+
71+ class _StoryboardState extends State <Storyboard > {
72+ final List <Story > stories = [
73+ const Story (
74+ title: 'Scroll Phase Transition' ,
75+ child: ScrollPhaseTransition (),
76+ ),
77+ const Story (
78+ title: 'Scroll Wheel Blur Transition' ,
79+ child: ScrollWheelBlurTransition (),
80+ ),
81+ const Story (
82+ title: 'Scroll Wheel Transition' ,
83+ child: ScrollWheelTransition (),
84+ ),
85+ const Story (
86+ title: 'Windows Settings Effect' ,
87+ child: WindowsSettingsTransition (),
88+ ),
89+ const Story (
90+ title: 'Color Filter Scroll Transition' ,
91+ child: ColorFilterScrollTransition (),
92+ ),
93+ ];
94+ int ? selectedStory;
95+
96+ void onStorySelected (int index) {
97+ setState (() {
98+ selectedStory = index;
99+ });
100+ }
101+
102+ @override
103+ Widget build (BuildContext context) {
104+ return Scaffold (
105+ body: Row (
106+ children: [
107+ SizedBox (
108+ width: 300 ,
109+ child: ListView (
110+ children: [
111+ for (final Story story in stories)
112+ ListTile (
113+ title: Text (story.title),
114+ onTap: () => onStorySelected (stories.indexOf (story)),
115+ selected: stories.indexOf (story) == selectedStory,
116+ ),
117+ ],
118+ ),
50119 ),
120+ const VerticalDivider (width: 2 ),
121+ Expanded (
122+ flex: 3 ,
123+ child: ContentView (
124+ child: AnimatedSwitcher (
125+ duration: const Duration (milliseconds: 300 ),
126+ child: selectedStory != null
127+ ? stories[selectedStory! ].child
128+ : const Center (
129+ child: Text ('Select a story to view.' ),
130+ ),
131+ ),
132+ )),
51133 ],
52134 ),
53135 );
54136 }
55137}
138+
139+ class ContentView extends StatefulWidget {
140+ final Widget child;
141+
142+ const ContentView ({super .key, required this .child});
143+
144+ @override
145+ State <ContentView > createState () => _ContentViewState ();
146+ }
147+
148+ class _ContentViewState extends State <ContentView > with WidgetsBindingObserver {
149+ final GlobalKey _key = GlobalKey ();
150+
151+ final TransformableBoxController controller = TransformableBoxController (
152+ resizeModeResolver: () {
153+ final pressedKeys = WidgetsBinding .instance.keyboard.logicalKeysPressed;
154+
155+ final isShiftPressed =
156+ pressedKeys.contains (LogicalKeyboardKey .shiftLeft) ||
157+ pressedKeys.contains (LogicalKeyboardKey .shiftRight);
158+
159+ if (isShiftPressed) {
160+ return ResizeMode .symmetricScale;
161+ } else {
162+ return ResizeMode .symmetric;
163+ }
164+ },
165+ allowFlippingWhileResizing: false ,
166+ );
167+
168+ @override
169+ void initState () {
170+ super .initState ();
171+
172+ controller
173+ .setConstraints (const BoxConstraints (minHeight: 200 , minWidth: 200 ));
174+
175+ SchedulerBinding .instance.addPostFrameCallback ((_) {
176+ controller.setClampingRect (getArea (), notify: false );
177+ controller.setRect (controller.clampingRect);
178+ if (mounted) setState (() {});
179+ });
180+
181+ WidgetsBinding .instance.addObserver (this );
182+ }
183+
184+ @override
185+ void didChangeMetrics () {
186+ super .didChangeMetrics ();
187+
188+ controller.setClampingRect (getArea ());
189+ controller.setRect (getRect (), recalculate: true );
190+
191+ if (mounted) setState (() {});
192+ }
193+
194+ @override
195+ void didUpdateWidget (covariant ContentView oldWidget) {
196+ super .didUpdateWidget (oldWidget);
197+
198+ controller.setRect (getRect (), recalculate: true );
199+ }
200+
201+ Rect getArea () {
202+ final RenderBox renderBox =
203+ _key.currentContext? .findRenderObject () as RenderBox ;
204+ final size = renderBox.size;
205+ return Rect .fromLTWH (0 , 0 , size.width, size.height).deflate (16 );
206+ }
207+
208+ Rect getRect () {
209+ Rect rect = controller.rect;
210+ rect = Rect .fromCenter (
211+ center: controller.clampingRect.center,
212+ width: min (controller.clampingRect.width, rect.width),
213+ height: min (controller.clampingRect.height, rect.height),
214+ );
215+ return rect;
216+ }
217+
218+ @override
219+ void dispose () {
220+ WidgetsBinding .instance.removeObserver (this );
221+ controller.dispose ();
222+ super .dispose ();
223+ }
224+
225+ @override
226+ Widget build (BuildContext context) {
227+ return Stack (
228+ key: _key,
229+ children: [
230+ TransformableBox (
231+ controller: controller,
232+ draggable: false ,
233+ allowContentFlipping: false ,
234+ contentBuilder: (BuildContext context, Rect rect, Flip flip) {
235+ return DecoratedBox (
236+ decoration: BoxDecoration (
237+ border: Border .all (
238+ color: Theme .of (context).colorScheme.primary,
239+ width: 2 ,
240+ strokeAlign: BorderSide .strokeAlignOutside,
241+ ),
242+ ),
243+ child: Center (child: widget.child),
244+ );
245+ },
246+ ),
247+ ],
248+ );
249+ }
250+ }
0 commit comments