From dc4636338fcb6706a0bbb37f9f4c3ad3b985ce2c Mon Sep 17 00:00:00 2001 From: Marco Bavagnoli Date: Sat, 24 Feb 2024 13:35:22 +0100 Subject: [PATCH] Exception on dragging the child widget for version 3.1.6 #18 --- CHANGELOG.md | 5 + analysis_options.yaml | 5 + example/analysis_options.yaml | 5 + example/lib/main.dart | 455 +++++++++--------- example/lib/main_more.dart | 302 ++++++------ example/lib/submenu_card.dart | 13 +- example/pubspec.lock | 56 ++- example/pubspec.yaml | 4 +- lib/src/center_widget.dart | 11 +- lib/src/dinamyc_star_menu.dart | 6 +- lib/src/params/background_params.dart | 15 +- lib/src/params/boundary_background.dart | 24 +- lib/src/params/circle_shape_params.dart | 13 +- lib/src/params/grid_shape_params.dart | 9 +- lib/src/params/linear_shape_params.dart | 14 +- lib/src/params/star_menu_params.dart | 276 ++++++----- lib/src/star_item.dart | 66 +-- lib/src/star_menu.dart | 587 +++++++++++------------- lib/src/widget_params.dart | 12 +- lib/star_menu.dart | 2 +- pubspec.lock | 50 +- pubspec.yaml | 6 +- 22 files changed, 998 insertions(+), 938 deletions(-) create mode 100644 analysis_options.yaml create mode 100644 example/analysis_options.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index f3ee6da..10b591b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +##### [3.1.7] - 24 Feb 2024 +* fixed a bug when dragging outside the menu items +* code optimized and finally linted +* breaking change: removed unused `context` parameter from `StarMenuParameters.arc` factory + ##### [3.1.6] - 7 Feb 2024 * Breaking change bug fix: StarMenuParameters.dropdown factory has an unwanted shift on the X axis diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..dc6d44f --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:very_good_analysis/analysis_options.yaml + +linter: + rules: + public_member_api_docs: false \ No newline at end of file diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..b5cd897 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:very_good_analysis/analysis_options.yaml + +linter: + rules: + public_member_api_docs: false diff --git a/example/lib/main.dart b/example/lib/main.dart index e48ed8e..31c7b1f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,336 +2,351 @@ import 'package:flutter/material.dart'; import 'package:star_menu/star_menu.dart'; void main() { - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'StarMenu Demo', - home: MyHomePage(), + home: const MyHomePage(), + theme: ThemeData(useMaterial3: false), ); } } class MyHomePage extends StatelessWidget { - MyHomePage({Key? key}) : super(key: key); + const MyHomePage({super.key}); @override Widget build(BuildContext context) { - StarMenuController backgroundStarMenuController = StarMenuController(); - StarMenuController centerStarMenuController = StarMenuController(); - ValueNotifier sliderValue = ValueNotifier(0.5); - GlobalKey containerKey = GlobalKey(); + final backgroundStarMenuController = StarMenuController(); + final centerStarMenuController = StarMenuController(); + final sliderValue = ValueNotifier(0.5); + final containerKey = GlobalKey(); // entries for the dropdown menu - List upperMenuItems = [ - Text('menu entry 1'), - Text('menu entry 2'), - Text('menu entry 3'), - Text('menu entry 4'), - Text('menu entry 5'), - Text('menu entry 6'), + final upperMenuItems = [ + const Text('menu entry 1'), + const Text('menu entry 2'), + const Text('menu entry 3'), + const Text('menu entry 4'), + const Text('menu entry 5'), + const Text('menu entry 6'), ]; // other entries // Every items may have a sub-menu. // Here the sub-menus are added with [addStarMenu] extension - List otherEntries = [ - FloatingActionButton( + final otherEntries = [ + const FloatingActionButton( onPressed: null, backgroundColor: Colors.black, child: Icon(Icons.add_call), ).addStarMenu( - items: upperMenuItems, params: StarMenuParameters.dropdown(context)), - FloatingActionButton( + items: upperMenuItems, + params: StarMenuParameters.dropdown(context), + ), + const FloatingActionButton( onPressed: null, backgroundColor: Colors.indigo, child: Icon(Icons.adb), ).addStarMenu( - items: upperMenuItems, params: StarMenuParameters.dropdown(context)), - FloatingActionButton( + items: upperMenuItems, + params: StarMenuParameters.dropdown(context), + ), + const FloatingActionButton( onPressed: null, backgroundColor: Colors.purple, child: Icon(Icons.home), ).addStarMenu( - items: upperMenuItems, params: StarMenuParameters.dropdown(context)), - FloatingActionButton( + items: upperMenuItems, + params: StarMenuParameters.dropdown(context), + ), + const FloatingActionButton( onPressed: null, backgroundColor: Colors.blueGrey, child: Icon(Icons.delete), ).addStarMenu( - items: upperMenuItems, params: StarMenuParameters.dropdown(context)), - FloatingActionButton( + items: upperMenuItems, + params: StarMenuParameters.dropdown(context), + ), + const FloatingActionButton( onPressed: null, backgroundColor: Colors.deepPurple, child: Icon(Icons.get_app), ).addStarMenu( - items: upperMenuItems, params: StarMenuParameters.dropdown(context)), + items: upperMenuItems, + params: StarMenuParameters.dropdown(context), + ), ]; // bottom left menu entries - List chipsEntries = [ - Chip( - avatar: CircleAvatar(child: const Text('SM')), - label: const Text('of widgets'), + final chipsEntries = [ + const Chip( + avatar: CircleAvatar(child: Text('SM')), + label: Text('of widgets'), ), - Chip( - avatar: CircleAvatar(child: const Text('SM')), - label: const Text('any kind'), + const Chip( + avatar: CircleAvatar(child: Text('SM')), + label: Text('any kind'), ), - Chip( - avatar: CircleAvatar(child: const Text('SM')), - label: const Text('almost'), + const Chip( + avatar: CircleAvatar(child: Text('SM')), + label: Text('almost'), ), - Chip( - avatar: CircleAvatar(child: const Text('SM')), - label: const Text('can be'), + const Chip( + avatar: CircleAvatar(child: Text('SM')), + label: Text('can be'), ), - Chip( - avatar: CircleAvatar(child: const Text('SM')), - label: const Text('entries'), + const Chip( + avatar: CircleAvatar(child: Text('SM')), + label: Text('entries'), ), - Chip( - avatar: CircleAvatar(child: const Text('SM')), - label: const Text('The menu'), + const Chip( + avatar: CircleAvatar(child: Text('SM')), + label: Text('The menu'), ), ]; return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( - title: Text('StarMenu demo'), + title: const Text('StarMenu demo'), actions: [ // upper bar menu StarMenu( params: StarMenuParameters.dropdown(context).copyWith( - backgroundParams: BackgroundParams().copyWith( + backgroundParams: const BackgroundParams().copyWith( sigmaX: 3, sigmaY: 3, ), ), items: upperMenuItems, onItemTapped: (index, c) { - print('Item $index tapped'); + debugPrint('Item $index tapped'); c.closeMenu!(); }, - child: Padding( - padding: const EdgeInsets.all(18.0), + child: const Padding( + padding: EdgeInsets.all(18), child: Icon(Icons.menu), ), - ) + ), ], ), body: Padding( - padding: const EdgeInsets.all(8.0), - child: Stack( - children: [ - /// add a menu to the background - Container( - width: double.infinity, - height: double.infinity, - color: Colors.white, - ).addStarMenu( - items: upperMenuItems, - params: StarMenuParameters.dropdown(context).copyWith( - useTouchAsCenter: true, - ), - controller: backgroundStarMenuController, + padding: const EdgeInsets.all(8), + child: Stack( + children: [ + /// add a menu to the background + Container( + width: double.infinity, + height: double.infinity, + color: Colors.white, + ).addStarMenu( + items: upperMenuItems, + params: StarMenuParameters.dropdown(context).copyWith( + useTouchAsCenter: true, ), + controller: backgroundStarMenuController, + ), - Align( - alignment: Alignment.center, - child: Container( - width: 1, - height: double.maxFinite, - color: Colors.black45, - ), + Align( + child: Container( + width: 1, + height: double.maxFinite, + color: Colors.black45, ), - Align( - alignment: Alignment.center, - child: Container( - width: double.maxFinite, - height: 1, - color: Colors.black45, - ), + ), + Align( + child: Container( + width: double.maxFinite, + height: 1, + color: Colors.black45, ), + ), - // background - Align( - alignment: Alignment.topCenter, - child: Text( - 'Touch the background to open the menu ' - 'at the coordinates you touched', - textAlign: TextAlign.center, - textScaleFactor: 2, - ), + // background + const Align( + alignment: Alignment.topCenter, + child: Text( + 'Touch the background to open the menu ' + 'at the coordinates you touched', + textAlign: TextAlign.center, ), + ), - // center menu with default [StarMenuParameters] parameters - Align( - alignment: Alignment.center, - child: StarMenu( - params: StarMenuParameters(), - controller: centerStarMenuController, - items: otherEntries, - child: FloatingActionButton( - onPressed: null, - mini: true, - backgroundColor: Colors.blue, - child: Icon(Icons.add), - ), + // center menu with default [StarMenuParameters] parameters + Align( + child: StarMenu( + controller: centerStarMenuController, + items: otherEntries, + child: const FloatingActionButton( + onPressed: null, + mini: true, + backgroundColor: Colors.blue, + child: Icon(Icons.add), ), ), + ), - // center right menu. - Align( - alignment: Alignment.centerRight, - child: StarMenu( - params: StarMenuParameters.arc( - context, - ArcType.semiLeft, - radiusX: 100, - radiusY: 180, - ), - items: otherEntries, - child: FloatingActionButton( - onPressed: null, child: Icon(Icons.home_work_outlined)), + // center right menu. + Align( + alignment: Alignment.centerRight, + child: StarMenu( + params: StarMenuParameters.arc( + ArcType.semiLeft, + radiusX: 100, + radiusY: 180, + ), + items: otherEntries, + child: const FloatingActionButton( + onPressed: null, + child: Icon(Icons.home_work_outlined), ), ), + ), - // bottom right panel menu - Align( - alignment: Alignment.bottomRight, - child: StarMenu( - params: StarMenuParameters.panel(context, columns: 3) - .copyWith(centerOffset: Offset(-150, -150)), - items: [ - IconMenu(icon: Icons.skip_previous, text: 'previous'), - IconMenu(icon: Icons.play_arrow, text: 'play'), - IconMenu(icon: Icons.skip_next, text: 'next'), - IconMenu(icon: Icons.album, text: 'album'), - IconMenu(icon: Icons.alarm, text: 'alarm'), - IconMenu(icon: Icons.info_outline, text: 'info'), - SizedBox( - width: 180, - height: 20, - child: ValueListenableBuilder( - valueListenable: sliderValue, - builder: (_, v, __) { - return Slider( - value: v, - onChanged: (value) { - sliderValue.value = value; - }); - }), + // bottom right panel menu + Align( + alignment: Alignment.bottomRight, + child: StarMenu( + params: StarMenuParameters.panel( + context, + columns: 3, + ).copyWith(centerOffset: const Offset(-150, -150)), + items: [ + const IconMenu(icon: Icons.skip_previous, text: 'previous'), + const IconMenu(icon: Icons.play_arrow, text: 'play'), + const IconMenu(icon: Icons.skip_next, text: 'next'), + const IconMenu(icon: Icons.album, text: 'album'), + const IconMenu(icon: Icons.alarm, text: 'alarm'), + const IconMenu(icon: Icons.info_outline, text: 'info'), + SizedBox( + width: 180, + height: 20, + child: ValueListenableBuilder( + valueListenable: sliderValue, + builder: (_, v, __) { + return Slider( + value: v, + onChanged: (value) { + sliderValue.value = value; + }, + ); + }, ), - ], - child: FloatingActionButton( - onPressed: null, child: Icon(Icons.grid_view)), + ), + ], + child: const FloatingActionButton( + onPressed: null, + child: Icon(Icons.grid_view), ), ), + ), - // bottom left linear menu - Align( - alignment: Alignment.bottomLeft, - child: StarMenu( - params: StarMenuParameters( - shape: MenuShape.linear, - linearShapeParams: LinearShapeParams( - angle: 90, - alignment: LinearAlignment.left, - space: 15, - ), - animationCurve: Curves.easeOutCubic, - centerOffset: Offset(50, -50), - openDurationMs: 150, - ), - items: chipsEntries, - parentContext: containerKey.currentContext, - child: FloatingActionButton( - onPressed: null, - child: Icon(Icons.view_stream_rounded), + // bottom left linear menu + Align( + alignment: Alignment.bottomLeft, + child: StarMenu( + params: const StarMenuParameters( + shape: MenuShape.linear, + linearShapeParams: LinearShapeParams( + angle: 90, + alignment: LinearAlignment.left, + space: 15, ), + animationCurve: Curves.easeOutCubic, + centerOffset: Offset(50, -50), + openDurationMs: 150, + ), + items: chipsEntries, + parentContext: containerKey.currentContext, + child: const FloatingActionButton( + onPressed: null, + child: Icon(Icons.view_stream_rounded), ), ), + ), - // a generic Widget with its key defined. - // This key will be used to show the menu with an event - // like pressing a button - Align( - alignment: Alignment(0.0, -0.4), - child: Container( - key: containerKey, - width: 40, - height: 40, - decoration: BoxDecoration( - color: Colors.amber, - borderRadius: BorderRadius.all( - Radius.circular(40), - ), - border: Border.all( - width: 2, - color: Colors.black, - style: BorderStyle.solid, - ), + // a generic Widget with its key defined. + // This key will be used to show the menu with an event + // like pressing a button + Align( + alignment: const Alignment(0, -0.4), + child: Container( + key: containerKey, + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.amber, + borderRadius: const BorderRadius.all( + Radius.circular(40), + ), + border: Border.all( + width: 2, + color: Colors.black, + style: BorderStyle.solid, ), ), ), + ), - // open centered menu programmatically - Align( - alignment: Alignment.bottomCenter, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // programmatically open the menu using StartMenu controller - ElevatedButton( - onPressed: () => centerStarMenuController.openMenu!(), - child: Text( - 'Open centered menu\nprogrammatically\nwith controller', - textAlign: TextAlign.center, - ), + // open centered menu programmatically + Align( + alignment: Alignment.bottomCenter, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // programmatically open the menu using StartMenu controller + ElevatedButton( + onPressed: () => centerStarMenuController.openMenu!(), + child: const Text( + 'Open centered menu\nprogrammatically\nwith controller', + textAlign: TextAlign.center, ), - const SizedBox(height: 20), - // programmatically open the menu using - // a key of a widget - ElevatedButton( - onPressed: () { - StarMenuOverlay.displayStarMenu( - containerKey.currentContext!, - StarMenu( - params: StarMenuParameters(), - items: otherEntries, - parentContext: containerKey.currentContext, - ), - ); - }, - child: Text( - 'Open menu\nprogrammatically\nwith a Widget key', - textAlign: TextAlign.center, - ), + ), + const SizedBox(height: 20), + // programmatically open the menu using + // a key of a widget + ElevatedButton( + onPressed: () { + StarMenuOverlay.displayStarMenu( + containerKey.currentContext!, + StarMenu( + items: otherEntries, + parentContext: containerKey.currentContext, + ), + ); + }, + child: const Text( + 'Open menu\nprogrammatically\nwith a Widget key', + textAlign: TextAlign.center, ), - ], - ), + ), + ], ), - ], - )), + ), + ], + ), + ), ); } } class IconMenu extends StatelessWidget { - final IconData icon; - final String text; - const IconMenu({ - Key? key, required this.icon, required this.text, - }) : super(key: key); + super.key, + }); + + final IconData icon; + final String text; @override Widget build(BuildContext context) { diff --git a/example/lib/main_more.dart b/example/lib/main_more.dart index 19a3b35..f7b961a 100644 --- a/example/lib/main_more.dart +++ b/example/lib/main_more.dart @@ -1,31 +1,39 @@ import 'dart:math'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:star_menu/star_menu.dart'; import 'package:star_menu_example/submenu_card.dart'; void main() { - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override Widget build(BuildContext context) { return MaterialApp( title: 'StarMenu Demo', - home: MyHomePage(), + scrollBehavior: const MaterialScrollBehavior().copyWith( + // enable mouse dragging on desktop + dragDevices: PointerDeviceKind.values.toSet(), + ), + theme: ThemeData(useMaterial3: false), + home: const MyHomePage(), ); } } class MyHomePage extends StatefulWidget { - MyHomePage({Key? key}) : super(key: key); + const MyHomePage({super.key}); @override - _MyHomePageState createState() => _MyHomePageState(); + MyHomePageState createState() => MyHomePageState(); } -class _MyHomePageState extends State { +class MyHomePageState extends State { late List entries; late List subEntries; @@ -40,7 +48,7 @@ class _MyHomePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('StarMenu demo'), + title: const Text('StarMenu demo'), ), body: Stack( children: [ @@ -51,23 +59,22 @@ class _MyHomePageState extends State { child: SizedBox( height: 1000, child: Column( - mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox(height: 20), - Text('Load items at runtime'), + const SizedBox(height: 20), + const Text('Load items at runtime'), // LAZY MENU StarMenu( onStateChanged: (state) { - print('State changed: $state'); + debugPrint('State changed: $state'); }, - params: StarMenuParameters( + params: const StarMenuParameters( shape: MenuShape.linear, linearShapeParams: LinearShapeParams( - angle: 270, - space: 30, - alignment: LinearAlignment.center), + angle: 270, + space: 30, + ), ), onItemTapped: (index, controller) { @@ -78,17 +85,21 @@ class _MyHomePageState extends State { lazyItems: () async { return [ Container( - color: Color.fromARGB(255, Random().nextInt(255), - Random().nextInt(255), Random().nextInt(255)), - width: 60, + color: Color.fromARGB( + 255, + Random().nextInt(255), + Random().nextInt(255), + Random().nextInt(255), + ), + width: 100, height: 40, ), - Container( + SizedBox( width: 150, - height: 200, + height: 230, child: Card( elevation: 6, - margin: EdgeInsets.all(6), + margin: const EdgeInsets.all(6), child: ListView( children: [ 'the', @@ -103,8 +114,11 @@ class _MyHomePageState extends State { 'widgets', ].map((s) { return Card( - child: Text(s), - margin: EdgeInsets.all(10), + margin: const EdgeInsets.all(10), + child: Text( + s, + textAlign: TextAlign.center, + ), ); }).toList(), ), @@ -114,25 +128,25 @@ class _MyHomePageState extends State { }, child: FloatingActionButton( onPressed: () { - print('FloatingActionButton Menu1 tapped'); + debugPrint('FloatingActionButton Menu1 tapped'); }, - child: Icon(Icons.looks_one), + child: const Icon(Icons.looks_one), ), ), - SizedBox(height: 40), - Text('Colored and blurred background'), + const SizedBox(height: 40), + const Text('Colored and blurred background'), // LINEAR MENU StarMenu( params: StarMenuParameters( shape: MenuShape.linear, - openDurationMs: 400, onHoverScale: 1.3, - linearShapeParams: LinearShapeParams( - angle: 270, - space: 10, - alignment: LinearAlignment.center), + linearShapeParams: const LinearShapeParams( + angle: 270, + space: 10, + alignment: LinearAlignment.center, + ), boundaryBackground: BoundaryBackground(), backgroundParams: BackgroundParams( backgroundColor: Colors.blue.withOpacity(0.2), @@ -148,54 +162,61 @@ class _MyHomePageState extends State { items: entries, child: FloatingActionButton( onPressed: () { - print('FloatingActionButton Menu1 tapped'); + debugPrint('FloatingActionButton Menu1 tapped'); }, - child: Icon(Icons.looks_two), + child: const Icon(Icons.looks_two), ), ), - SizedBox(height: 40), - Text('Animated blur background'), + const SizedBox(height: 40), + const Text('Animated blur background'), // CIRCLE MENU // it's possible to use the extension addStarMenu() // with all Widgets FloatingActionButton( onPressed: () { - print('FloatingActionButton Menu2 tapped'); + debugPrint('FloatingActionButton Menu2 tapped'); }, backgroundColor: Colors.red, - child: Icon(Icons.looks_3), + child: const Icon(Icons.looks_3), ).addStarMenu( - items: entries, - params: StarMenuParameters( - backgroundParams: BackgroundParams( - animatedBlur: true, - sigmaX: 4.0, - sigmaY: 4.0, - backgroundColor: Colors.transparent), - circleShapeParams: CircleShapeParams(radiusY: 280), - openDurationMs: 1000, - rotateItemsAnimationAngle: 360, - ), onItemTapped: (index, controller) { - if (index == 7) controller.closeMenu!(); - }), + items: entries, + params: const StarMenuParameters( + backgroundParams: BackgroundParams( + animatedBlur: true, + sigmaX: 4, + sigmaY: 4, + backgroundColor: Colors.transparent, + ), + circleShapeParams: CircleShapeParams(radiusY: 280), + openDurationMs: 1000, + rotateItemsAnimationAngle: 360, + ), + onItemTapped: (index, controller) { + if (index == 7) controller.closeMenu!(); + }, + ), - SizedBox(height: 40), - Text('Animated color background'), + const SizedBox(height: 40), + const Text('Animated color background'), // GRID MENU StarMenu( params: StarMenuParameters( shape: MenuShape.grid, openDurationMs: 1200, - gridShapeParams: GridShapeParams( - columns: 3, columnsSpaceH: 6, columnsSpaceV: 6), + gridShapeParams: const GridShapeParams( + columns: 3, + columnsSpaceH: 6, + columnsSpaceV: 6, + ), backgroundParams: BackgroundParams( - sigmaX: 0, - sigmaY: 0, - animatedBackgroundColor: true, - backgroundColor: Colors.black.withOpacity(0.4)), + sigmaX: 0, + sigmaY: 0, + animatedBackgroundColor: true, + backgroundColor: Colors.black.withOpacity(0.4), + ), ), onItemTapped: (index, controller) { if (index == 7) controller.closeMenu!(); @@ -203,10 +224,10 @@ class _MyHomePageState extends State { items: entries, child: FloatingActionButton( onPressed: () { - print('FloatingActionButton Menu3 tapped'); + debugPrint('FloatingActionButton Menu3 tapped'); }, backgroundColor: Colors.black, - child: Icon(Icons.looks_4), + child: const Icon(Icons.looks_4), ), ), ], @@ -221,121 +242,140 @@ class _MyHomePageState extends State { // Build the list of menu entries List menuEntries() { - ValueNotifier sliderValue = ValueNotifier(0.5); + final sliderValue = ValueNotifier(0.5); return [ - SubMenuCard( + const SubMenuCard( width: 100, text: 'Linear, check whole menu boundaries', ).addStarMenu( - items: subEntries, - params: StarMenuParameters( - shape: MenuShape.linear, - linearShapeParams: LinearShapeParams( - angle: 120, - space: 15, - ), - checkMenuScreenBoundaries: true)), - SubMenuCard( + items: subEntries, + params: const StarMenuParameters( + shape: MenuShape.linear, + linearShapeParams: LinearShapeParams( + angle: 120, + space: 15, + ), + checkMenuScreenBoundaries: true, + ), + ), + const SubMenuCard( width: 70, text: 'Linear, centered items', ).addStarMenu( - items: subEntries, - params: StarMenuParameters( - shape: MenuShape.linear, - linearShapeParams: LinearShapeParams( - angle: 90, space: 15, alignment: LinearAlignment.center), - )), - SubMenuCard( + items: subEntries, + params: const StarMenuParameters( + shape: MenuShape.linear, + linearShapeParams: LinearShapeParams( + angle: 90, + space: 15, + alignment: LinearAlignment.center, + ), + ), + ), + const SubMenuCard( width: 70, text: 'Linear, check items boundaries', ).addStarMenu( - items: subEntries, - params: StarMenuParameters( - shape: MenuShape.linear, - linearShapeParams: LinearShapeParams( - angle: 60, - space: 15, - ), - checkItemsScreenBoundaries: true, - checkMenuScreenBoundaries: false)), - SubMenuCard( + items: subEntries, + params: const StarMenuParameters( + shape: MenuShape.linear, + linearShapeParams: LinearShapeParams( + angle: 60, + space: 15, + ), + checkItemsScreenBoundaries: true, + checkMenuScreenBoundaries: false, + ), + ), + const SubMenuCard( width: 70, text: 'Linear, left aligned', ).addStarMenu( - items: subEntries, - params: StarMenuParameters( - shape: MenuShape.linear, - linearShapeParams: LinearShapeParams( - angle: 90, space: 15, alignment: LinearAlignment.left), - )), - SubMenuCard( + items: subEntries, + params: const StarMenuParameters( + shape: MenuShape.linear, + linearShapeParams: LinearShapeParams( + angle: 90, + space: 15, + alignment: LinearAlignment.left, + ), + ), + ), + const SubMenuCard( width: 60, text: 'Centered circle', ).addStarMenu( - items: subEntries, - params: StarMenuParameters( - shape: MenuShape.circle, - useScreenCenter: true, - )), - SubMenuCard( + items: subEntries, + params: const StarMenuParameters( + shape: MenuShape.circle, + useScreenCenter: true, + ), + ), + const SubMenuCard( width: 70, text: 'Linear, right aligned', ).addStarMenu( - items: subEntries, - params: StarMenuParameters( - shape: MenuShape.linear, - linearShapeParams: LinearShapeParams( - angle: 90, space: 0, alignment: LinearAlignment.right), - )), + items: subEntries, + params: const StarMenuParameters( + shape: MenuShape.linear, + linearShapeParams: LinearShapeParams( + angle: 90, + space: 0, + alignment: LinearAlignment.right, + ), + ), + ), SizedBox( width: 180, height: 20, child: ValueListenableBuilder( - valueListenable: sliderValue, - builder: (_, v, __) { - return Slider( - value: v, - onChanged: (value) { - sliderValue.value = value; - }); - }), + valueListenable: sliderValue, + builder: (_, v, __) { + return Slider( + value: v, + onChanged: (value) { + sliderValue.value = value; + }, + ); + }, + ), ), - FloatingActionButton(child: Text('close'), onPressed: () {}) + FloatingActionButton(child: const Text('close'), onPressed: () {}), ]; } // Build the list of sub-menu entries List subMenuEntries() { return [ - Chip( + const Chip( avatar: CircleAvatar( - child: const Text('SM'), + child: Text('SM'), ), - label: const Text('of widgets'), + label: Text('of widgets'), ), - Chip( + const Chip( avatar: CircleAvatar( - child: const Text('SM'), + child: Text('SM'), ), - label: const Text('any kind'), + label: Text('any kind'), ), - Chip( + const Chip( avatar: CircleAvatar( - child: const Text('SM'), + child: Text('SM'), ), - label: const Text('almost'), + label: Text('almost'), ), - Chip( + const Chip( avatar: CircleAvatar( - child: const Text('SM'), + child: Text('SM'), ), - label: const Text('can be'), + label: Text('can be'), ), - Chip( + const Chip( avatar: CircleAvatar( - child: const Text('SM'), + child: Text('SM'), ), - label: const Text('The menu entries'), + label: Text('The menu entries'), ), ]; } diff --git a/example/lib/submenu_card.dart b/example/lib/submenu_card.dart index 37fbc43..a5564ce 100644 --- a/example/lib/submenu_card.dart +++ b/example/lib/submenu_card.dart @@ -1,22 +1,21 @@ import 'package:flutter/material.dart'; class SubMenuCard extends StatelessWidget { - final double width; - final String text; - const SubMenuCard({ - Key? key, required this.width, + super.key, this.text = '', - }) : super(key: key); + }); + final double width; + final String text; @override Widget build(BuildContext context) { return Card( elevation: 8, child: Padding( - padding: const EdgeInsets.all(8.0), - child: Container( + padding: const EdgeInsets.all(8), + child: SizedBox( width: width, child: Text(text, textAlign: TextAlign.center), ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 3755a80..d193b0f 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" fake_async: dependency: transitive description: @@ -67,38 +67,62 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" sky_engine: dependency: transitive description: flutter @@ -126,7 +150,7 @@ packages: path: ".." relative: true source: path - version: "3.1.5" + version: "3.1.7" stream_channel: dependency: transitive description: @@ -175,14 +199,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" - web: + vm_service: dependency: transitive description: - name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "13.0.0" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" + dart: ">=3.2.0 <4.0.0" flutter: ">=1.17.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0ea440a..75e524c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,7 +6,7 @@ homepage: www.marcobavagnoli.com version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: '>=3.2.0 <4.0.0' dependencies: flutter: @@ -15,7 +15,7 @@ dependencies: star_menu: path: ../ - cupertino_icons: ^1.0.2 + cupertino_icons: ^1.0.6 dev_dependencies: flutter_test: diff --git a/lib/src/center_widget.dart b/lib/src/center_widget.dart index 948ae82..8f02388 100644 --- a/lib/src/center_widget.dart +++ b/lib/src/center_widget.dart @@ -1,13 +1,12 @@ import 'package:flutter/widgets.dart'; -/// Center child widget using [center] position class CenteredWidget extends StatelessWidget { - final Widget child; - - CenteredWidget({ - key, + const CenteredWidget({ required this.child, - }) : super(key: key); + super.key, + }); + + final Widget child; @override Widget build(BuildContext context) { diff --git a/lib/src/dinamyc_star_menu.dart b/lib/src/dinamyc_star_menu.dart index 59ab533..96352bf 100644 --- a/lib/src/dinamyc_star_menu.dart +++ b/lib/src/dinamyc_star_menu.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'star_menu.dart'; +import 'package:star_menu/src/star_menu.dart'; // Creates an overlay to display the StarMenu and open it class StarMenuOverlay { @@ -9,7 +9,7 @@ class StarMenuOverlay { static OverlayEntry? _overlayEntry; // Build the StarMenu on an overlay - static displayStarMenu(BuildContext context, StarMenu starMenu) { + static void displayStarMenu(BuildContext context, StarMenu starMenu) { _sm = starMenu; // Retrieve the parent Overlay _overlayState = Overlay.of(context); @@ -32,7 +32,7 @@ class StarMenuOverlay { } } - static dispose() { + static void dispose() { _overlayEntry?.remove(); _overlayEntry = null; _sm = null; diff --git a/lib/src/params/background_params.dart b/lib/src/params/background_params.dart index 1c1213a..289e22f 100644 --- a/lib/src/params/background_params.dart +++ b/lib/src/params/background_params.dart @@ -3,6 +3,14 @@ import 'package:flutter/material.dart'; /// class to define background @immutable class BackgroundParams { + const BackgroundParams({ + this.animatedBlur = false, + this.sigmaX = 0.0, + this.sigmaY = 0.0, + this.animatedBackgroundColor = false, + this.backgroundColor = Colors.transparent, + }); + /// Animate background blur from 0.0 to sigma if true final bool animatedBlur; @@ -18,13 +26,6 @@ class BackgroundParams { /// Background color final Color backgroundColor; - const BackgroundParams( - {this.animatedBlur = false, - this.sigmaX = 0.0, - this.sigmaY = 0.0, - this.animatedBackgroundColor = false, - this.backgroundColor = Colors.transparent}); - BackgroundParams copyWith({ bool? animatedBlur, double? sigmaX, diff --git a/lib/src/params/boundary_background.dart b/lib/src/params/boundary_background.dart index c4fede8..d86edc6 100644 --- a/lib/src/params/boundary_background.dart +++ b/lib/src/params/boundary_background.dart @@ -3,6 +3,18 @@ import 'package:flutter/material.dart'; /// boundary background parameters @immutable class BoundaryBackground { + BoundaryBackground({ + this.color = Colors.white, + this.padding = const EdgeInsets.all(8), + Decoration? decoration, + this.blurSigmaX, + this.blurSigmaY, + }) : decoration = decoration ?? + BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: color, + ); + /// color of the boundary background final Color color; @@ -18,18 +30,6 @@ class BoundaryBackground { /// background blur sigmaY value final double? blurSigmaY; - BoundaryBackground({ - this.color = Colors.white, - this.padding = const EdgeInsets.all(8.0), - decoration, - this.blurSigmaX, - this.blurSigmaY, - }) : decoration = decoration ?? - BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: color, - ); - BoundaryBackground copyWith({ Color? color, EdgeInsets? padding, diff --git a/lib/src/params/circle_shape_params.dart b/lib/src/params/circle_shape_params.dart index a1b75ae..efe9170 100644 --- a/lib/src/params/circle_shape_params.dart +++ b/lib/src/params/circle_shape_params.dart @@ -3,6 +3,13 @@ import 'package:flutter/material.dart'; /// class to define circle shape params @immutable class CircleShapeParams { + const CircleShapeParams({ + this.radiusX = 100, + this.radiusY = 100, + this.startAngle = 0, + this.endAngle = 360, + }); + /// Horizontal radius final double radiusX; @@ -15,12 +22,6 @@ class CircleShapeParams { /// Ending angle for the 1st item. Anticlockwise with 0° on the right final double endAngle; - const CircleShapeParams( - {this.radiusX = 100, - this.radiusY = 100, - this.startAngle = 0, - this.endAngle = 360}); - CircleShapeParams copyWith({ double? radiusX, double? radiusY, diff --git a/lib/src/params/grid_shape_params.dart b/lib/src/params/grid_shape_params.dart index c973273..cec650e 100644 --- a/lib/src/params/grid_shape_params.dart +++ b/lib/src/params/grid_shape_params.dart @@ -3,6 +3,12 @@ import 'package:flutter/material.dart'; /// class to define grid shape params @immutable class GridShapeParams { + const GridShapeParams({ + this.columns = 3, + this.columnsSpaceH = 0, + this.columnsSpaceV = 0, + }); + /// Number of columns final int columns; @@ -12,9 +18,6 @@ class GridShapeParams { /// Vertical space between items final int columnsSpaceV; - const GridShapeParams( - {this.columns = 3, this.columnsSpaceH = 0, this.columnsSpaceV = 0}); - GridShapeParams copyWith({ int? columns, int? columnsSpaceH, diff --git a/lib/src/params/linear_shape_params.dart b/lib/src/params/linear_shape_params.dart index 274c86d..b797ff1 100644 --- a/lib/src/params/linear_shape_params.dart +++ b/lib/src/params/linear_shape_params.dart @@ -1,10 +1,13 @@ -import 'package:flutter/material.dart'; - enum LinearAlignment { left, center, right, top, bottom } /// class to define linear shape params -@immutable class LinearShapeParams { + const LinearShapeParams({ + this.angle = 90, + this.space = 0, + this.alignment = LinearAlignment.center, + }); + /// Degree angle. Anticlockwise with 0° on 3 o'clock final double angle; @@ -15,11 +18,6 @@ class LinearShapeParams { /// is vertical or horizontal final LinearAlignment alignment; - const LinearShapeParams( - {this.angle = 90, - this.space = 0, - this.alignment = LinearAlignment.center}); - LinearShapeParams copyWith({ double? angle, double? space, diff --git a/lib/src/params/star_menu_params.dart b/lib/src/params/star_menu_params.dart index f1f5319..edbcef0 100644 --- a/lib/src/params/star_menu_params.dart +++ b/lib/src/params/star_menu_params.dart @@ -1,15 +1,138 @@ -import 'package:flutter/material.dart'; +// ignore_for_file: avoid_redundant_argument_values -import '../star_menu.dart'; -import 'background_params.dart'; -import 'boundary_background.dart'; -import 'circle_shape_params.dart'; -import 'grid_shape_params.dart'; -import 'linear_shape_params.dart'; +import 'package:flutter/material.dart'; +import 'package:star_menu/src/params/background_params.dart'; +import 'package:star_menu/src/params/boundary_background.dart'; +import 'package:star_menu/src/params/circle_shape_params.dart'; +import 'package:star_menu/src/params/grid_shape_params.dart'; +import 'package:star_menu/src/params/linear_shape_params.dart'; +import 'package:star_menu/src/star_menu.dart'; /// class which is used to feed [StarMenu.params] @immutable class StarMenuParameters { + const StarMenuParameters({ + this.linearShapeParams = const LinearShapeParams(), + this.circleShapeParams = const CircleShapeParams(), + this.gridShapeParams = const GridShapeParams(), + this.backgroundParams = const BackgroundParams(), + this.shape = MenuShape.circle, + this.boundaryBackground, + this.useLongPress = false, + this.longPressDuration = const Duration(milliseconds: 500), + this.onHoverScale = 1.0, + this.openDurationMs = 400, + this.closeDurationMs = 150, + this.rotateItemsAnimationAngle = 0.0, + this.startItemScaleAnimation = 1.0, + this.centerOffset = Offset.zero, + this.useScreenCenter = false, + this.useTouchAsCenter = false, + this.checkItemsScreenBoundaries = false, + this.checkMenuScreenBoundaries = true, + this.animationCurve = Curves.fastOutSlowIn, + }); + + /// preset to display items in an arc shape + factory StarMenuParameters.arc( + ArcType type, { + double radiusX = 130, + double radiusY = 130, + }) { + var start = 0.0; + var end = 0.0; + switch (type) { + case ArcType.semiUp: + start = 0.0; + end = 180.0; + case ArcType.semiDown: + start = 180.0; + end = 360.0; + case ArcType.semiLeft: + start = 90.0; + end = 270.0; + case ArcType.semiRight: + start = -90.0; + end = 90.0; + case ArcType.quarterTopRight: + start = 0.0; + end = 90.0; + case ArcType.quarterTopLeft: + start = 90.0; + end = 180.0; + case ArcType.quarterBottomRight: + start = 270.0; + end = 360.0; + case ArcType.quarterBottomLeft: + start = 180.0; + end = 270.0; + } + return StarMenuParameters( + shape: MenuShape.circle, + circleShapeParams: CircleShapeParams( + startAngle: start, + endAngle: end, + radiusX: radiusX, + radiusY: radiusY, + ), + backgroundParams: const BackgroundParams( + backgroundColor: Colors.transparent, + ), + onHoverScale: 1.5, + ); + } + + /// preset to display a grid menu inscribed into a card + factory StarMenuParameters.panel(BuildContext context, {int columns = 3}) { + return StarMenuParameters( + shape: MenuShape.grid, + onHoverScale: 1.3, + gridShapeParams: GridShapeParams( + columns: columns, + columnsSpaceV: 20, + columnsSpaceH: 40, + ), + backgroundParams: const BackgroundParams( + backgroundColor: Colors.transparent, + ), + boundaryBackground: BoundaryBackground( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Theme.of(context).cardColor, + boxShadow: kElevationToShadow[6], + ), + ), + ); + } + + /// preset to display a dropdown menu like + factory StarMenuParameters.dropdown(BuildContext context) { + return StarMenuParameters( + shape: MenuShape.linear, + centerOffset: const Offset(0, 40), + openDurationMs: 200, + closeDurationMs: 60, + startItemScaleAnimation: 1, + linearShapeParams: const LinearShapeParams( + space: 16, + angle: 270, + ), + backgroundParams: const BackgroundParams( + backgroundColor: Colors.transparent, + ), + boundaryBackground: BoundaryBackground( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: Theme.of(context).popupMenuTheme.color ?? + Theme.of(context).cardColor, + boxShadow: kElevationToShadow[6], + ), + ), + ); + } + /// Menu shape kind: [MenuShape.circle], [MenuShape.linear], [MenuShape.grid] final MenuShape shape; @@ -17,7 +140,7 @@ class StarMenuParameters { final BoundaryBackground? boundaryBackground; /// parameters for the linear shape - final linearShapeParams; + final LinearShapeParams linearShapeParams; /// parameters for the circle shape final CircleShapeParams circleShapeParams; @@ -43,7 +166,8 @@ class StarMenuParameters { /// Close animation duration final int closeDurationMs; - /// Starting rotation angle of the items that will reach 0 DEG when animation ends + /// Starting rotation angle of the items that will + /// reach 0 DEG when animation ends final double rotateItemsAnimationAngle; /// Starting scale of the items that will reach 1 when animation ends @@ -58,7 +182,8 @@ class StarMenuParameters { /// Use the touch coordinate as the menu center final bool useTouchAsCenter; - /// Checks if the whole menu boundaries exceed screen edges, if so set it in place to be all visible + /// Checks if the whole menu boundaries exceed screen edges, + /// if so set it in place to be all visible final bool checkItemsScreenBoundaries; /// Checks if items exceed screen edges, if so set them in place to be visible @@ -67,28 +192,6 @@ class StarMenuParameters { /// Animation curve kind to use final Curve animationCurve; - const StarMenuParameters({ - this.linearShapeParams = const LinearShapeParams(), - this.circleShapeParams = const CircleShapeParams(), - this.gridShapeParams = const GridShapeParams(), - this.backgroundParams = const BackgroundParams(), - this.shape = MenuShape.circle, - this.boundaryBackground, - this.useLongPress = false, - this.longPressDuration = const Duration(milliseconds: 500), - this.onHoverScale = 1.0, - this.openDurationMs = 400, - this.closeDurationMs = 150, - this.rotateItemsAnimationAngle = 0.0, - this.startItemScaleAnimation = 1.0, - this.centerOffset = Offset.zero, - this.useScreenCenter = false, - this.useTouchAsCenter = false, - this.checkItemsScreenBoundaries = false, - this.checkMenuScreenBoundaries = true, - this.animationCurve = Curves.fastOutSlowIn, - }); - StarMenuParameters copyWith({ MenuShape? shape, BoundaryBackground? boundaryBackground, @@ -136,113 +239,4 @@ class StarMenuParameters { animationCurve: animationCurve ?? this.animationCurve, ); } - - /// preset to display a dropdown menu like - factory StarMenuParameters.dropdown(BuildContext context) { - return StarMenuParameters( - shape: MenuShape.linear, - centerOffset: Offset(0, 40), - openDurationMs: 200, - closeDurationMs: 60, - startItemScaleAnimation: 1.0, - linearShapeParams: LinearShapeParams( - space: 16, - angle: 270, - ), - backgroundParams: BackgroundParams( - backgroundColor: Colors.transparent, - ), - boundaryBackground: BoundaryBackground( - padding: EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: Theme.of(context).popupMenuTheme.color ?? - Theme.of(context).cardColor, - boxShadow: kElevationToShadow[6], - ), - ), - ); - } - - /// preset to display items in an arc shape - factory StarMenuParameters.arc( - BuildContext context, - ArcType type, { - double radiusX = 130, - double radiusY = 130, - }) { - double start = 0; - double end = 0; - switch (type) { - case ArcType.semiUp: - start = 0.0; - end = 180.0; - break; - case ArcType.semiDown: - start = 180.0; - end = 360.0; - break; - case ArcType.semiLeft: - start = 90.0; - end = 270.0; - break; - case ArcType.semiRight: - start = -90.0; - end = 90.0; - break; - case ArcType.quarterTopRight: - start = 0.0; - end = 90.0; - break; - case ArcType.quarterTopLeft: - start = 90.0; - end = 180.0; - break; - case ArcType.quarterBottomRight: - start = 270.0; - end = 360.0; - break; - case ArcType.quarterBottomLeft: - start = 180.0; - end = 270.0; - break; - } - return StarMenuParameters( - shape: MenuShape.circle, - circleShapeParams: CircleShapeParams( - startAngle: start, - endAngle: end, - radiusX: radiusX, - radiusY: radiusY, - ), - backgroundParams: BackgroundParams( - backgroundColor: Colors.transparent, - ), - onHoverScale: 1.5, - ); - } - - /// preset to display a grid menu inscribed into a card - factory StarMenuParameters.panel(BuildContext context, {int columns = 3}) { - return StarMenuParameters( - shape: MenuShape.grid, - onHoverScale: 1.3, - gridShapeParams: GridShapeParams( - columns: columns, - columnsSpaceV: 20, - columnsSpaceH: 40, - ), - backgroundParams: BackgroundParams( - backgroundColor: Colors.transparent, - ), - boundaryBackground: BoundaryBackground( - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context).cardColor, - boxShadow: kElevationToShadow[6], - ), - ), - ); - } } diff --git a/lib/src/star_item.dart b/lib/src/star_item.dart index 0bc2e6b..129aeb2 100644 --- a/lib/src/star_item.dart +++ b/lib/src/star_item.dart @@ -6,9 +6,29 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'center_widget.dart'; +import 'package:star_menu/src/center_widget.dart'; class StarItem extends StatelessWidget { + const StarItem({ + required Key key, + required this.totItems, + required this.index, + required this.itemMatrix, + required this.onItemTapped, + required this.child, + this.animValue = 0.0, + this.center = Offset.zero, + this.shift = Offset.zero, + this.rotateRAD = 0.0, + this.scale = 1.0, + this.onHoverScale = 1.0, + }) : assert(totItems > 0, '[totItems] must be > 0'), + assert( + index >= 0 && index < totItems, + '0<[index]<[totItems] not in range ', + ), + super(key: key); + final double animValue; final int totItems; final int index; @@ -18,26 +38,9 @@ class StarItem extends StatelessWidget { final double rotateRAD; final double scale; final double onHoverScale; - final Function(int index) onItemTapped; + final void Function(int index) onItemTapped; final Widget child; - const StarItem( - {required Key key, - this.animValue = 0.0, - required this.totItems, - required this.index, - this.center = Offset.zero, - this.shift = Offset.zero, - required this.itemMatrix, - this.rotateRAD = 0.0, - this.scale = 1.0, - this.onHoverScale = 1.0, - required this.onItemTapped, - required this.child}) - : assert(totItems > 0), - assert(index >= 0 && index < totItems), - super(key: key); - @override Widget build(BuildContext context) { // stretch item animation: the last one starts when animValue reach c @@ -46,25 +49,26 @@ class StarItem extends StatelessWidget { // 0.0 0.3 1.0 // ie: item3 starts when animValue reach 0.3. // So for item3, lerp animValue to be 0.0 when it is 0.3 - double step = (1.0 / totItems) * index; - double stepDelta = 1.0 / (1 - step); - double a = (animValue - step < 0.0 ? 0.0 : animValue - step) * stepDelta; + final step = (1.0 / totItems) * index; + final stepDelta = 1.0 / (1 - step); + final a = (animValue - step < 0.0 ? 0.0 : animValue - step) * stepDelta; - ValueNotifier onHover = ValueNotifier(false); + final onHover = ValueNotifier(false); // lerp from parentBounds position to items end position - Matrix4 mat = Matrix4.identity() - ..translate(lerpDouble(center.dx, shift.dx, a) ?? 0, - lerpDouble(center.dy, shift.dy, a) ?? 0, 0); + final mat = Matrix4.identity() + ..translate( + lerpDouble(center.dx, shift.dx, a) ?? 0, + lerpDouble(center.dy, shift.dy, a) ?? 0, + ); if (rotateRAD > 0) mat.setRotationZ((1.0 - a) * rotateRAD); // if (scale < 1) // mat.scale(lerpDouble(scale, 1.0, a)); - double newScale = lerpDouble(scale, 1.0, a)!; + final newScale = lerpDouble(scale, 1.0, a)!; return Transform( // key: itemKeys.elementAt(index), transform: mat, - transformHitTests: true, child: CenteredWidget( child: Opacity( opacity: a, @@ -81,9 +85,9 @@ class StarItem extends StatelessWidget { scale: a < 1.0 ? newScale : (isHover ? newScale * onHoverScale : newScale), - duration: Duration(milliseconds: 200), - child: child); - }), + duration: const Duration(milliseconds: 200), + child: child,); + },), ), ), ), diff --git a/lib/src/star_menu.dart b/lib/src/star_menu.dart index 29cfb87..c287b7a 100644 --- a/lib/src/star_menu.dart +++ b/lib/src/star_menu.dart @@ -4,17 +4,15 @@ All rights reserved. */ import 'dart:async'; import 'dart:math'; -import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:star_menu/src/dinamyc_star_menu.dart'; -import 'package:vector_math/vector_math_64.dart' as vector; - import 'package:star_menu/src/params/linear_shape_params.dart'; import 'package:star_menu/src/params/star_menu_params.dart'; import 'package:star_menu/src/star_item.dart'; import 'package:star_menu/src/widget_params.dart'; +import 'package:vector_math/vector_math_64.dart' as vector; enum MenuState { closed, @@ -42,13 +40,13 @@ enum ArcType { /// Extension on Widget to add a StarMenu easily extension AddStarMenu on Widget { - addStarMenu({ + Widget addStarMenu({ List? items, Future> Function()? lazyItems, - Function(MenuState state)? onStateChanged, + void Function(MenuState state)? onStateChanged, StarMenuParameters params = const StarMenuParameters(), StarMenuController? controller, - Function(int index, StarMenuController controller)? onItemTapped, + void Function(int index, StarMenuController controller)? onItemTapped, }) { return StarMenu( params: params, @@ -62,7 +60,7 @@ extension AddStarMenu on Widget { } } -/// Controller sent back with [onItemTapped] to +/// Controller sent back with onItemTapped to /// let you choose to close the menu class StarMenuController { VoidCallback? openMenu; @@ -79,6 +77,34 @@ class StarMenuController { } class StarMenu extends StatefulWidget { + StarMenu({ + super.key, + StarMenuController? controller, + this.params = const StarMenuParameters(), + this.items, + this.lazyItems, + this.onStateChanged, + this.onItemTapped, + this.child, + this.parentContext, + }) : assert( + !(items == null && lazyItems == null), + 'StarMenu: You have to set items or lazyItems!', + ), + assert( + !(items != null && lazyItems != null), + 'StarMenu: You can only pass items or lazyItems, not both.', + ), + assert( + !(child == null && parentContext == null), + 'StarMenu: You have to set child or parentContext!', + ), + assert( + !(child != null && parentContext != null), + 'StarMenu: You can set child or parentContext, not both!', + ), + controller = controller ?? StarMenuController(); + /// parameters of this menu final StarMenuParameters params; @@ -100,37 +126,12 @@ class StarMenu extends StatefulWidget { final StarMenuController? controller; /// return current menu state - final Function(MenuState state)? onStateChanged; + final void Function(MenuState state)? onStateChanged; /// The callback that is called when a menu item is tapped. /// It gives the `ID` of the item and a `controller` to /// eventually close the menu - final Function(int index, StarMenuController controller)? onItemTapped; - - StarMenu({ - Key? key, - controller, - this.params = const StarMenuParameters(), - this.items, - this.lazyItems, - this.onStateChanged, - this.onItemTapped, - this.child, - this.parentContext, - }) : assert( - !(items == null && lazyItems == null), - 'StarMenu: You have to set items or lazyItems!', - ), - assert( - !(items != null && lazyItems != null), - 'StarMenu: You can only pass items or lazyItems, not both.', - ), - assert(!(child == null && parentContext == null), - 'StarMenu: You have to set child or parentContext!'), - assert(!(child != null && parentContext != null), - 'StarMenu: You can set child or parentContext, not both!'), - controller = controller ?? StarMenuController(), - super(key: key); + final void Function(int index, StarMenuController controller)? onItemTapped; @override StarMenuState createState() => StarMenuState(); @@ -139,8 +140,6 @@ class StarMenu extends StatefulWidget { class StarMenuState extends State with TickerProviderStateMixin, WidgetsBindingObserver { AnimationController? controller; - AnimationController? panController; - Animation? panAnimation; late Animation animationPercent; List _items = []; Rect itemsBounds = Rect.zero; @@ -158,7 +157,6 @@ class StarMenuState extends State late List itemKeys; late List itemsMatrix; // final position matrix of animation late ValueNotifier animationProgress; - late ValueNotifier panProgress; late bool paramsAlreadyGot; late Offset offsetToFitMenuIntoScreen; Offset touchLocalPoint = Offset.zero; @@ -171,7 +169,7 @@ class StarMenuState extends State _init(); } - _init() { + void _init() { if (widget.items != null) _items = widget.items!; menuState = MenuState.closed; @@ -184,12 +182,13 @@ class StarMenuState extends State rotateItemsAnimationAngleRAD = vector.radians(widget.params.rotateItemsAnimationAngle); - animationProgress = ValueNotifier(0.0); - panProgress = ValueNotifier(0.0); + animationProgress = ValueNotifier(0); offsetToFitMenuIntoScreen = Offset.zero; paramsAlreadyGot = false; - itemsParams = List.generate(_items.length, - (index) => WidgetParams(xPosition: 0, yPosition: 0, rect: Rect.zero)); + itemsParams = List.generate( + _items.length, + (index) => WidgetParams(xPosition: 0, yPosition: 0, rect: Rect.zero), + ); itemsMatrix = List.generate(_items.length, (index) => Matrix4.identity()); setupAnimationController(); @@ -202,10 +201,9 @@ class StarMenuState extends State super.dispose(); } - _dispose() { + void _dispose() { WidgetsBinding.instance.removeObserver(this); animationPercent.removeListener(animationListener); - panAnimation?.removeListener(panAnimationListener); overlayEntry?.remove(); overlayEntry = null; menuState = MenuState.closed; @@ -235,7 +233,7 @@ class StarMenuState extends State void resetForChanges() { if (_items.isEmpty || menuState == MenuState.closed) return; - MenuState state = menuState; + final state = menuState; _dispose(); _init(); @@ -253,9 +251,10 @@ class StarMenuState extends State /// Time to get items parameters? if (animationPercent.value > 0 && !paramsAlreadyGot) { itemsParams = List.generate( - _items.length, - (index) => WidgetParams.fromContext( - itemKeys.elementAt(index).currentContext)); + _items.length, + (index) => + WidgetParams.fromContext(itemKeys.elementAt(index).currentContext), + ); itemsMatrix = calcPosition(); checkBoundaries(); @@ -263,7 +262,7 @@ class StarMenuState extends State // maybe lazyItems are not yet in the tree. Maybe there is a better way if (widget.lazyItems != null) { - for (int i = 0; i < itemsParams.length; i++) { + for (var i = 0; i < itemsParams.length; i++) { if (itemsParams.elementAt(i).rect.isEmpty) { paramsAlreadyGot = false; return; @@ -275,7 +274,7 @@ class StarMenuState extends State @override Widget build(BuildContext context) { - Point startPoint = Point(0, 0); + var startPoint = const Point(0.0, 0.0); if (!widget.controller!.isInitialized()) { widget.controller!.openMenu = showMenu; @@ -309,7 +308,7 @@ class StarMenuState extends State if (startPoint.distanceTo(Point(event.position.dx, event.position.dy)) < 10) showMenu(); }, - child: widget.child ?? SizedBox.shrink(), + child: widget.child ?? const SizedBox.shrink(), ); } @@ -329,9 +328,9 @@ class StarMenuState extends State case AnimationStatus.completed: if (controller?.value == 1.0) { menuState = MenuState.open; - } else + } else { menuState = MenuState.closed; - break; + } case AnimationStatus.dismissed: if (animationPercent.value == 0) { overlayEntry?.remove(); @@ -339,21 +338,20 @@ class StarMenuState extends State controller?.value = 0; menuState = MenuState.closed; } - break; case AnimationStatus.reverse: menuState = MenuState.closing; - break; case AnimationStatus.forward: menuState = MenuState.opening; - break; } - if (widget.onStateChanged != null) widget.onStateChanged!(menuState); + if (widget.onStateChanged != null) { + widget.onStateChanged!.call(menuState); + } }); } /// Close the menu - closeMenu() { + void closeMenu() { controller ?.animateBack( 0, @@ -368,12 +366,14 @@ class StarMenuState extends State } /// Open the menu - showMenu() { + void showMenu() { // padding, viewInsets and viewPadding return 0 here! Force to be 24 // topPadding = MediaQuery.of(context).viewPadding.top; topPadding = 24; // system toolBar height screenSize = Size( - MediaQuery.of(context).size.width, MediaQuery.of(context).size.height); + MediaQuery.of(context).size.width, + MediaQuery.of(context).size.height, + ); if (widget.lazyItems != null) { widget.lazyItems!().then((value) { @@ -387,17 +387,17 @@ class StarMenuState extends State } } - _showMenu() { + void _showMenu() { overlayEntry = _overlayEntryBuilder(); controller?.reset(); if (overlayEntry != null) { // find parent widget bounds - RenderBox? renderBox = widget.child != null - ? (context.findRenderObject() as RenderBox) - : (widget.parentContext!.findRenderObject() as RenderBox); - Rect widgetRect = renderBox.paintBounds; - Offset parentPosition = renderBox.localToGlobal(Offset.zero); + final renderBox = widget.child != null + ? (context.findRenderObject()! as RenderBox) + : (widget.parentContext!.findRenderObject()! as RenderBox); + final widgetRect = renderBox.paintBounds; + final parentPosition = renderBox.localToGlobal(Offset.zero); parentBounds = widgetRect.translate(parentPosition.dx, parentPosition.dy); Overlay.of(context).insert(overlayEntry!); @@ -409,29 +409,6 @@ class StarMenuState extends State } } - Offset endPanDelta = Offset.zero; - Offset precPanDelta = Offset.zero; - Float32List distanceBuffer = Float32List(4); - int bufferIndex = 0; - Duration startAverageTimeStamp = Duration.zero; - Duration endAverageTimeStamp = Duration.zero; - void panAnimationListener() { - panProgress.value = panAnimation?.value ?? 0; - circleEndAngleRAD -= circleStartAngleRAD - panProgress.value; - circleStartAngleRAD = panProgress.value; - // print('ANIM $circleStartAngleRAD $circleEndAngleRAD'); - itemsMatrix = calcPosition(); - checkBoundaries(); - } - - void panAnimationStatusListener(status) { - if (status == AnimationStatus.completed || - status == AnimationStatus.dismissed) { - panAnimation?.removeListener(panAnimationListener); - panAnimation?.removeStatusListener(panAnimationStatusListener); - } - } - /// Create the overlay object OverlayEntry _overlayEntryBuilder() { // keys used to get items rect @@ -443,19 +420,19 @@ class StarMenuState extends State return Directionality( textDirection: TextDirection.ltr, child: ValueListenableBuilder( - valueListenable: animationProgress, - builder: (_, double animValue, __) { - Color background = - widget.params.backgroundParams.backgroundColor; - if (widget.params.backgroundParams.animatedBackgroundColor) - background = - Color.lerp(Colors.transparent, background, animValue) ?? - background; - - Widget child = Material( - color: background, - child: Stack( - children: [ + valueListenable: animationProgress, + builder: (_, double animValue, __) { + var background = widget.params.backgroundParams.backgroundColor; + if (widget.params.backgroundParams.animatedBackgroundColor) { + background = + Color.lerp(Colors.transparent, background, animValue) ?? + background; + } + + Widget child = Material( + color: background, + child: Stack( + children: [ GestureDetector( onTap: () { // this optional check is to just not call @@ -465,74 +442,6 @@ class StarMenuState extends State if (!(menuState == MenuState.closing || menuState == MenuState.closed)) closeMenu(); }, - onPanStart: (details) { - precPanDelta = Offset.zero; - panController?.stop(); - }, - onPanUpdate: (event) { - if (widget.params.shape == MenuShape.circle) { - if (event.delta.dx >= 0) { - circleStartAngleRAD -= 0.07; - circleEndAngleRAD -= 0.07; - } else { - circleStartAngleRAD += 0.07; - circleEndAngleRAD += 0.07; - } - itemsMatrix = calcPosition(); - checkBoundaries(); - panProgress.value += 0.1; - precPanDelta = event.delta; - - if (bufferIndex == 0) { - startAverageTimeStamp = - event.sourceTimeStamp ?? Duration.zero; - } else if (bufferIndex >= 4) { - endAverageTimeStamp = - event.sourceTimeStamp ?? Duration.zero; - bufferIndex = 0; - } - distanceBuffer[bufferIndex] = event.delta.distance; - bufferIndex++; - print( - 'MOVE ${event.delta.distance} $circleStartAngleRAD $circleEndAngleRAD'); - } - }, - onPanEnd: (details) { - panProgress.value = 0; - double totDistance = 0; - for (int i = 0; i < 4; i++) - totDistance += distanceBuffer[i]; - - /// pixel over milliseconds - double velocity = totDistance / - (endAverageTimeStamp.inMilliseconds - - startAverageTimeStamp.inMilliseconds); - - // double end = circleStartAngleRAD + velocity * 5000; - double end = - circleStartAngleRAD + atan(totDistance * 10); - print( - 'PANEND distance: $totDistance velocity: $velocity ' - '${atan(totDistance)}'); - - if (velocity == 0) return; - panController = AnimationController( - duration: Duration(milliseconds: 400), - vsync: this, - ); - panAnimation = Tween( - begin: circleStartAngleRAD, - end: end, - ).animate( - CurvedAnimation( - parent: panController!, - curve: Curves.bounceOut, - ), - ) - ..addListener(panAnimationListener) - ..addStatusListener((panAnimationStatusListener)); - panController?.forward(); - }, ), // draw background container @@ -547,12 +456,13 @@ class StarMenuState extends State child: ClipRRect( child: BackdropFilter( filter: ImageFilter.blur( - sigmaX: widget.params.boundaryBackground - ?.blurSigmaX ?? - 0.0, - sigmaY: widget.params.boundaryBackground - ?.blurSigmaY ?? - 0.0), + sigmaX: widget + .params.boundaryBackground?.blurSigmaX ?? + 0.0, + sigmaY: widget + .params.boundaryBackground?.blurSigmaY ?? + 0.0, + ), child: Container( width: itemsBounds.width + widget @@ -591,28 +501,33 @@ class StarMenuState extends State widget.params.boundaryBackground!.decoration, ), ), - ]..addAll(generateItems(animValue))), - ); - - // is background blurred? - if ((widget.params.backgroundParams.sigmaX > 0 || - widget.params.backgroundParams.sigmaY > 0) && - animValue > 0) { - late double db; - if (widget.params.backgroundParams.animatedBlur) - db = animValue; - else - db = 1.0; - child = BackdropFilter( - filter: ImageFilter.blur( - sigmaX: widget.params.backgroundParams.sigmaX * db, - sigmaY: widget.params.backgroundParams.sigmaY * db), - child: child, - ); + ...generateItems(animValue), + ], + ), + ); + + // is background blurred? + if ((widget.params.backgroundParams.sigmaX > 0 || + widget.params.backgroundParams.sigmaY > 0) && + animValue > 0) { + late double db; + if (widget.params.backgroundParams.animatedBlur) { + db = animValue; + } else { + db = 1.0; } + child = BackdropFilter( + filter: ImageFilter.blur( + sigmaX: widget.params.backgroundParams.sigmaX * db, + sigmaY: widget.params.backgroundParams.sigmaY * db, + ), + child: child, + ); + } - return child; - }), + return child; + }, + ), ); }, ); @@ -624,51 +539,52 @@ class StarMenuState extends State if (index >= itemKeys.length) return Container(); if (index >= itemsMatrix.length) return Container(); if (index >= _items.length) return Container(); - return ValueListenableBuilder( - valueListenable: panProgress, - builder: (_, pan, __) { - return StarItem( - key: itemKeys.elementAt(index), - child: _items[index], - totItems: _items.length, - index: index, - // center: parentBounds.center, - // animation start from previous item position - center: Offset( - itemsMatrix - .elementAt(index - 1 >= 0 ? index - 1 : 0) - .getTranslation() - .x, - itemsMatrix - .elementAt(index - 1 >= 0 ? index - 1 : 0) - .getTranslation() - .y), - itemMatrix: itemsMatrix[index], - rotateRAD: rotateItemsAnimationAngleRAD, - scale: widget.params.startItemScaleAnimation, - onHoverScale: widget.params.onHoverScale, - shift: Offset( - itemsMatrix.elementAt(index).getTranslation().x + - offsetToFitMenuIntoScreen.dx, - itemsMatrix.elementAt(index).getTranslation().y + - offsetToFitMenuIntoScreen.dy), - animValue: animValue, - onItemTapped: (id) { - if (widget.onItemTapped != null) - widget.onItemTapped!(id, widget.controller!); - }, - ); - }); + return StarItem( + key: itemKeys.elementAt(index), + totItems: _items.length, + index: index, + // center: parentBounds.center, + // animation start from previous item position + center: Offset( + itemsMatrix + .elementAt(index - 1 >= 0 ? index - 1 : 0) + .getTranslation() + .x, + itemsMatrix + .elementAt(index - 1 >= 0 ? index - 1 : 0) + .getTranslation() + .y, + ), + itemMatrix: itemsMatrix[index], + rotateRAD: rotateItemsAnimationAngleRAD, + scale: widget.params.startItemScaleAnimation, + onHoverScale: widget.params.onHoverScale, + shift: Offset( + itemsMatrix.elementAt(index).getTranslation().x + + offsetToFitMenuIntoScreen.dx, + itemsMatrix.elementAt(index).getTranslation().y + + offsetToFitMenuIntoScreen.dy, + ), + animValue: animValue, + onItemTapped: (id) { + if (widget.onItemTapped != null) { + widget.onItemTapped!.call(id, widget.controller!); + } + }, + child: _items[index], + ); }); } // Calculate final item center position List calcPosition() { - List ret = - List.generate(_items.length, (index) => Matrix4.identity()); - Offset newCenter = widget.params.useScreenCenter - ? Offset(screenSize!.width / 2 + widget.params.centerOffset.dx, - screenSize!.height / 2 + widget.params.centerOffset.dy) + final ret = + List.generate(_items.length, (index) => Matrix4.identity()); + var newCenter = widget.params.useScreenCenter + ? Offset( + screenSize!.width / 2 + widget.params.centerOffset.dx, + screenSize!.height / 2 + widget.params.centerOffset.dy, + ) : parentBounds.center + widget.params.centerOffset; if (widget.params.useTouchAsCenter && touchLocalPoint != Offset.zero) { newCenter = Offset( @@ -683,35 +599,39 @@ class StarMenuState extends State // if the circle isn't complete, the last item should // be positioned at the ending angle. Otherwise on the ending // angle there is already the 1st item - int nItems = (circleEndAngleRAD - circleStartAngleRAD < 2 * pi) + final nItems = (circleEndAngleRAD - circleStartAngleRAD < 2 * pi) ? _items.length - 1 : _items.length; ret.asMap().forEach((index, mat) { mat.translate( - newCenter.dx + - cos((circleEndAngleRAD - circleStartAngleRAD) / + newCenter.dx + + cos( + (circleEndAngleRAD - circleStartAngleRAD) / nItems * index + - circleStartAngleRAD) * - widget.params.circleShapeParams.radiusX, - newCenter.dy - - sin((circleEndAngleRAD - circleStartAngleRAD) / + circleStartAngleRAD, + ) * + widget.params.circleShapeParams.radiusX, + newCenter.dy - + sin( + (circleEndAngleRAD - circleStartAngleRAD) / nItems * index + - circleStartAngleRAD) * - widget.params.circleShapeParams.radiusY); + circleStartAngleRAD, + ) * + widget.params.circleShapeParams.radiusY, + ); }); - break; case MenuShape.linear: - double radius = 0.0; - double rotate = lineAngleRAD; - double itemDiameter = 0.0; - double firstItemHalfWidth = 0.0; - double firstItemHalfHeight = 0.0; - late double halfWidth; - late double halfHeight; + var radius = 0.0; + final rotate = lineAngleRAD; + var itemDiameter = 0.0; + var firstItemHalfWidth = 0.0; + var firstItemHalfHeight = 0.0; + var halfWidth = 0.0; + var halfHeight = 0.0; double secH; double secV; @@ -726,14 +646,16 @@ class StarMenuState extends State secV = (halfWidth / sin(pi / 2 - rotate)).abs(); // checks if the line intersect horizontal or vertical edges - if (secH < secV) + if (secH < secV) { itemDiameter = secH * 2.0; - else + } else { itemDiameter = secV * 2.0; + } // These checks if the line is perfectly vertical or horizontal - if ((rotate + pi / 2) / pi == ((rotate + pi / 2) / pi).ceil()) + if ((rotate + pi / 2) / pi == ((rotate + pi / 2) / pi).ceil()) { itemDiameter = halfHeight * 2; + } if (rotate / pi == (rotate / pi).ceil()) itemDiameter = halfWidth * 2; if (index == 0) { @@ -741,8 +663,8 @@ class StarMenuState extends State firstItemHalfHeight = halfHeight; mat.translate(newCenter.dx, newCenter.dy); } else { - double alignmentShiftX = 0; - double alignmentShiftY = 0; + var alignmentShiftX = 0.0; + var alignmentShiftY = 0.0; if (widget.params.linearShapeParams.alignment == LinearAlignment.left) { alignmentShiftX = halfWidth - firstItemHalfWidth; @@ -760,33 +682,31 @@ class StarMenuState extends State alignmentShiftY = -halfHeight + firstItemHalfHeight; } mat.translate( - cos(lineAngleRAD) * (radius + halfWidth - firstItemHalfWidth) + - newCenter.dx + - alignmentShiftX, - -sin(lineAngleRAD) * - (radius + halfHeight - firstItemHalfHeight) + - newCenter.dy + - alignmentShiftY); + cos(lineAngleRAD) * (radius + halfWidth - firstItemHalfWidth) + + newCenter.dx + + alignmentShiftX, + -sin(lineAngleRAD) * (radius + halfHeight - firstItemHalfHeight) + + newCenter.dy + + alignmentShiftY, + ); } radius += itemDiameter + widget.params.linearShapeParams.space; }); - break; - case MenuShape.grid: - int j = 0; - int k = 0; - int n = 0; - double x = 0; - double y = 0; - int count = 0; - double hMax = 0; - double wMax = 0; + var j = 0; + var k = 0; + var n = 0; + var x = 0.0; + var y = 0.0; + var count = 0; + var hMax = 0.0; + var wMax = 0.0; double itemWidth; double itemHeight; - List rowsWidth = []; - List itemPos = []; + final rowsWidth = []; + final itemPos = []; // Calculating the grid while (j * widget.params.gridShapeParams.columns + k < _items.length) { @@ -815,13 +735,13 @@ class StarMenuState extends State wMax = max(wMax, x); rowsWidth.add(x - widget.params.gridShapeParams.columnsSpaceH); // Calculate y position for items in current row - for (int i = 0; i < count; i++) { + for (var i = 0; i < count; i++) { itemHeight = itemsParams[ widget.params.gridShapeParams.columns * j + k - i - 1] .rect .height; - double x1 = itemPos[itemPos.length - i - 1].x.toDouble(); - double y1 = y + hMax / 2; + final x1 = itemPos[itemPos.length - i - 1].x.toDouble(); + final y1 = y + hMax / 2; itemPos[itemPos.length - i - 1] = Point(x1, y1); } y += hMax + widget.params.gridShapeParams.columnsSpaceV; @@ -845,11 +765,12 @@ class StarMenuState extends State 2) .floor(); ret[n] = Matrix4.identity() - ..translate((itemPos[n].x + dx - wMax / 2) + newCenter.dx, - (itemPos[n].y - y / 2) + newCenter.dy); + ..translate( + (itemPos[n].x + dx - wMax / 2) + newCenter.dx, + (itemPos[n].y - y / 2) + newCenter.dy, + ); n++; } - break; } return ret; @@ -857,41 +778,49 @@ class StarMenuState extends State // check if the items rect exceeds the screen. Move the item positions // to fit into the screen - checkBoundaries() { + void checkBoundaries() { if (widget.params.checkItemsScreenBoundaries && itemsParams.isNotEmpty) { - for (int i = 0; i < itemsParams.length; i++) { - Rect shifted = itemsParams[i].rect.translate( - itemsMatrix.elementAt(i).getTranslation().x - - itemsParams[i].rect.width / 2, - itemsMatrix.elementAt(i).getTranslation().y - - itemsParams[i].rect.height / 2); + for (var i = 0; i < itemsParams.length; i++) { + final shifted = itemsParams[i].rect.translate( + itemsMatrix.elementAt(i).getTranslation().x - + itemsParams[i].rect.width / 2, + itemsMatrix.elementAt(i).getTranslation().y - + itemsParams[i].rect.height / 2, + ); if (shifted.left < 0) itemsMatrix.elementAt(i).translate(-shifted.left); - if (shifted.right > screenSize!.width) + if (shifted.right > screenSize!.width) { itemsMatrix.elementAt(i).translate(screenSize!.width - shifted.right); - if (shifted.top < topPadding) + } + if (shifted.top < topPadding) { itemsMatrix.elementAt(i).translate(0.0, topPadding - shifted.top); - if (shifted.bottom > screenSize!.height) + } + if (shifted.bottom > screenSize!.height) { itemsMatrix .elementAt(i) .translate(0.0, screenSize!.height - shifted.bottom); + } } } // check if the rect that include all the items on final position // exceeds the screen. Move all items position accordingly if (widget.params.checkMenuScreenBoundaries && itemsParams.isNotEmpty) { - Rect boundaries = itemsParams[0].rect.translate( - itemsMatrix.elementAt(0).getTranslation().x - - itemsParams[0].rect.width / 2, - itemsMatrix.elementAt(0).getTranslation().y - - itemsParams[0].rect.height / 2); - for (int i = 1; i < itemsParams.length; i++) { - boundaries = boundaries.expandToInclude(itemsParams[i].rect.translate( - itemsMatrix.elementAt(i).getTranslation().x - - itemsParams[i].rect.width / 2, - itemsMatrix.elementAt(i).getTranslation().y - - itemsParams[i].rect.height / 2)); + var boundaries = itemsParams[0].rect.translate( + itemsMatrix.elementAt(0).getTranslation().x - + itemsParams[0].rect.width / 2, + itemsMatrix.elementAt(0).getTranslation().y - + itemsParams[0].rect.height / 2, + ); + for (var i = 1; i < itemsParams.length; i++) { + boundaries = boundaries.expandToInclude( + itemsParams[i].rect.translate( + itemsMatrix.elementAt(i).getTranslation().x - + itemsParams[i].rect.width / 2, + itemsMatrix.elementAt(i).getTranslation().y - + itemsParams[i].rect.height / 2, + ), + ); } // if there is a [boundaryBackground], add its padding to the [boundaries] @@ -904,34 +833,46 @@ class StarMenuState extends State ); } - if (boundaries.top < topPadding) + if (boundaries.top < topPadding) { offsetToFitMenuIntoScreen = offsetToFitMenuIntoScreen.translate( - 0, -boundaries.top + topPadding); - if (boundaries.bottom > screenSize!.height) + 0, + -boundaries.top + topPadding, + ); + } + if (boundaries.bottom > screenSize!.height) { offsetToFitMenuIntoScreen = offsetToFitMenuIntoScreen.translate( - 0, screenSize!.height - boundaries.bottom); - if (boundaries.left < 0) + 0, + screenSize!.height - boundaries.bottom, + ); + } + if (boundaries.left < 0) { offsetToFitMenuIntoScreen = offsetToFitMenuIntoScreen.translate(-boundaries.left, 0); - if (boundaries.right > screenSize!.width) + } + if (boundaries.right > screenSize!.width) { offsetToFitMenuIntoScreen = offsetToFitMenuIntoScreen.translate( - screenSize!.width - boundaries.right, 0); + screenSize!.width - boundaries.right, + 0, + ); + } } // calculate the whole items boundary if (itemsMatrix.isNotEmpty) { itemsBounds = Rect.fromLTWH( - itemsMatrix.elementAt(0).getTranslation().x - - itemsParams[0].rect.width / 2 + - offsetToFitMenuIntoScreen.dx, - itemsMatrix.elementAt(0).getTranslation().y - - itemsParams[0].rect.height / 2 + - offsetToFitMenuIntoScreen.dy, - itemsParams[0].rect.width, - itemsParams[0].rect.height); + itemsMatrix.elementAt(0).getTranslation().x - + itemsParams[0].rect.width / 2 + + offsetToFitMenuIntoScreen.dx, + itemsMatrix.elementAt(0).getTranslation().y - + itemsParams[0].rect.height / 2 + + offsetToFitMenuIntoScreen.dy, + itemsParams[0].rect.width, + itemsParams[0].rect.height, + ); } - for (int i = 1; i < itemsParams.length; i++) { - itemsBounds = itemsBounds.expandToInclude(Rect.fromLTWH( + for (var i = 1; i < itemsParams.length; i++) { + itemsBounds = itemsBounds.expandToInclude( + Rect.fromLTWH( itemsMatrix.elementAt(i).getTranslation().x - itemsParams[i].rect.width / 2 + offsetToFitMenuIntoScreen.dx, @@ -939,7 +880,9 @@ class StarMenuState extends State itemsParams[i].rect.height / 2 + offsetToFitMenuIntoScreen.dy, itemsParams[i].rect.width, - itemsParams[i].rect.height)); + itemsParams[i].rect.height, + ), + ); } } } diff --git a/lib/src/widget_params.dart b/lib/src/widget_params.dart index 8afc8b9..edf166b 100644 --- a/lib/src/widget_params.dart +++ b/lib/src/widget_params.dart @@ -3,10 +3,6 @@ import 'package:vector_math/vector_math_64.dart' as vector; /// Helper class to determine the size and position of a widget class WidgetParams { - late double xPosition; - late double yPosition; - late Rect rect; - WidgetParams({ required this.xPosition, required this.yPosition, @@ -15,17 +11,21 @@ class WidgetParams { WidgetParams.fromContext(BuildContext? context) { // Get the widget RenderObject - final RenderObject? object = context?.findRenderObject(); + final object = context?.findRenderObject(); // Get the dimensions and position of the widget final translation = object?.getTransformTo(null).getTranslation() ?? vector.Vector3.zero(); - final Size size = object?.semanticBounds.size ?? Size.zero; + final size = object?.semanticBounds.size ?? Size.zero; xPosition = translation.x; yPosition = translation.y; rect = Rect.fromLTWH(translation.x, translation.y, size.width, size.height); } + late double xPosition; + late double yPosition; + late Rect rect; + @override String toString() { return 'X,Y,rect: $xPosition,$yPosition $rect'; diff --git a/lib/star_menu.dart b/lib/star_menu.dart index 32db575..157ddce 100644 --- a/lib/star_menu.dart +++ b/lib/star_menu.dart @@ -1,5 +1,6 @@ library star_menu; +export 'src/dinamyc_star_menu.dart'; export 'src/params/background_params.dart'; export 'src/params/boundary_background.dart'; export 'src/params/circle_shape_params.dart'; @@ -7,4 +8,3 @@ export 'src/params/grid_shape_params.dart'; export 'src/params/linear_shape_params.dart'; export 'src/params/star_menu_params.dart'; export 'src/star_menu.dart'; -export 'src/dinamyc_star_menu.dart'; diff --git a/pubspec.lock b/pubspec.lock index 69c6781..9769190 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -59,38 +59,62 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" sky_engine: dependency: transitive description: flutter @@ -160,14 +184,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" - web: + vm_service: dependency: transitive description: - name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "13.0.0" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" + dart: ">=3.2.0 <4.0.0" flutter: ">=1.17.0" diff --git a/pubspec.yaml b/pubspec.yaml index ed605e4..8c03ce3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,19 +1,19 @@ name: star_menu description: Contextual popup menu with different shapes and multiple ways to fine-tune animation and position. The menu entries can be almost any kind of widgets. -version: 3.1.6 +version: 3.1.7 homepage: https://github.com/alnitak/flutter_star_menu issue_tracker: https://github.com/alnitak/flutter_star_menu/issues repository: https://github.com/alnitak/flutter_star_menu environment: - sdk: ">=2.12.0 <4.0.0" + sdk: '>=3.2.0 <4.0.0' flutter: ">=1.17.0" dependencies: flutter: sdk: flutter - vector_math: ^2.1.2 + vector_math: ^2.1.4 dev_dependencies: flutter_test: