-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
6 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import 'package:flutter/widgets.dart'; | ||
|
||
List<Widget> space({ | ||
required Iterable<Widget> children, | ||
double? widthGap, | ||
double? heightGap, | ||
int skip = 1, | ||
}) => | ||
children | ||
.expand( | ||
(item) sync* { | ||
yield SizedBox( | ||
width: widthGap, | ||
height: heightGap, | ||
); | ||
yield item; | ||
}, | ||
) | ||
.skip(skip) | ||
.toList(); |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:yaru/yaru.dart'; | ||
|
||
import '../common/space.dart'; | ||
|
||
class SplitButtonPage extends StatefulWidget { | ||
const SplitButtonPage({super.key}); | ||
|
||
@override | ||
State<SplitButtonPage> createState() => _SplitButtonPageState(); | ||
} | ||
|
||
class _SplitButtonPageState extends State<SplitButtonPage> { | ||
double width = 200.0; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final items = List.generate( | ||
10, | ||
(index) { | ||
final text = | ||
'${index.isEven ? 'Super long action name' : 'action'} ${index + 1}'; | ||
return PopupMenuItem( | ||
child: Text( | ||
text, | ||
overflow: TextOverflow.ellipsis, | ||
), | ||
onTap: () => ScaffoldMessenger.of(context) | ||
.showSnackBar(SnackBar(content: Text(text))), | ||
); | ||
}, | ||
); | ||
|
||
return Center( | ||
child: Column( | ||
mainAxisSize: MainAxisSize.min, | ||
children: space( | ||
heightGap: 10, | ||
children: [ | ||
YaruSplitButton( | ||
onPressed: () => ScaffoldMessenger.of(context) | ||
.showSnackBar(const SnackBar(content: Text('Main Action'))), | ||
items: items, | ||
child: const Text('Main Action'), | ||
), | ||
YaruSplitButton.filled( | ||
onPressed: () => ScaffoldMessenger.of(context) | ||
.showSnackBar(const SnackBar(content: Text('Main Action'))), | ||
items: items, | ||
child: const Text('Main Action'), | ||
), | ||
YaruSplitButton.outlined( | ||
menuWidth: width, | ||
onPressed: () => ScaffoldMessenger.of(context) | ||
.showSnackBar(const SnackBar(content: Text('Main Action'))), | ||
items: items, | ||
child: const Text('Main Action'), | ||
), | ||
SizedBox( | ||
width: 300, | ||
child: Slider( | ||
min: 100, | ||
max: 500, | ||
value: width, | ||
onChanged: (v) => setState(() => width = v), | ||
), | ||
), | ||
Center( | ||
child: Text('Menu width: ${width.toInt()}'), | ||
), | ||
YaruSplitButton( | ||
menuWidth: width, | ||
items: items, | ||
child: const Text('Main Action'), | ||
), | ||
YaruSplitButton( | ||
menuWidth: width, | ||
child: const Text('Main Action'), | ||
), | ||
], | ||
), | ||
), | ||
); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import 'package:assorted_layout_widgets/assorted_layout_widgets.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:yaru/yaru.dart'; | ||
|
||
enum _YaruSplitButtonVariant { elevated, filled, outlined } | ||
|
||
class YaruSplitButton extends StatelessWidget { | ||
const YaruSplitButton({ | ||
super.key, | ||
this.items, | ||
this.onPressed, | ||
this.child, | ||
this.onOptionsPressed, | ||
this.icon, | ||
this.radius, | ||
this.menuWidth, | ||
}) : _variant = _YaruSplitButtonVariant.elevated; | ||
|
||
const YaruSplitButton.filled({ | ||
super.key, | ||
this.items, | ||
this.onPressed, | ||
this.child, | ||
this.onOptionsPressed, | ||
this.icon, | ||
this.radius, | ||
this.menuWidth, | ||
}) : _variant = _YaruSplitButtonVariant.filled; | ||
|
||
const YaruSplitButton.outlined({ | ||
super.key, | ||
this.items, | ||
this.onPressed, | ||
this.child, | ||
this.onOptionsPressed, | ||
this.icon, | ||
this.radius, | ||
this.menuWidth, | ||
}) : _variant = _YaruSplitButtonVariant.outlined; | ||
|
||
final _YaruSplitButtonVariant _variant; | ||
final void Function()? onPressed; | ||
final void Function()? onOptionsPressed; | ||
final Widget? child; | ||
final Widget? icon; | ||
final List<PopupMenuEntry<Object?>>? items; | ||
final double? radius; | ||
final double? menuWidth; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
// TODO: fix common_themes to use a fixed size for buttons instead of fiddling around with padding | ||
// then we can rely on this size here | ||
const size = Size.square(36); | ||
const dropdownPadding = EdgeInsets.only(top: 16, bottom: 16); | ||
|
||
final defaultRadius = Radius.circular(radius ?? kYaruButtonRadius); | ||
|
||
final mainActionShape = RoundedRectangleBorder( | ||
borderRadius: BorderRadius.only( | ||
topLeft: defaultRadius, | ||
bottomLeft: defaultRadius, | ||
), | ||
); | ||
|
||
final dropdownShape = switch (_variant) { | ||
_YaruSplitButtonVariant.outlined => NonUniformRoundedRectangleBorder( | ||
hideLeftSide: true, | ||
borderRadius: BorderRadius.all( | ||
defaultRadius, | ||
), | ||
), | ||
_ => RoundedRectangleBorder( | ||
borderRadius: BorderRadius.only( | ||
topRight: defaultRadius, | ||
bottomRight: defaultRadius, | ||
), | ||
), | ||
}; | ||
|
||
final onDropdownPressed = onOptionsPressed ?? | ||
(items?.isNotEmpty == true | ||
? () => showMenu( | ||
context: context, | ||
position: _menuPosition(context), | ||
items: items!, | ||
menuPadding: EdgeInsets.symmetric(vertical: defaultRadius.x), | ||
constraints: menuWidth == null | ||
? null | ||
: BoxConstraints( | ||
minWidth: menuWidth!, | ||
maxWidth: menuWidth!, | ||
), | ||
) | ||
: null); | ||
|
||
final dropdownIcon = icon ?? const Icon(YaruIcons.pan_down); | ||
|
||
return Row( | ||
mainAxisSize: MainAxisSize.min, | ||
children: [ | ||
switch (_variant) { | ||
_YaruSplitButtonVariant.elevated => ElevatedButton( | ||
style: ElevatedButton.styleFrom(shape: mainActionShape), | ||
onPressed: onPressed, | ||
child: child, | ||
), | ||
_YaruSplitButtonVariant.filled => FilledButton( | ||
style: FilledButton.styleFrom(shape: mainActionShape), | ||
onPressed: onPressed, | ||
child: child, | ||
), | ||
_YaruSplitButtonVariant.outlined => OutlinedButton( | ||
style: OutlinedButton.styleFrom(shape: mainActionShape), | ||
onPressed: onPressed, | ||
child: child, | ||
), | ||
}, | ||
switch (_variant) { | ||
_YaruSplitButtonVariant.elevated => ElevatedButton( | ||
style: ElevatedButton.styleFrom( | ||
fixedSize: size, | ||
minimumSize: size, | ||
maximumSize: size, | ||
padding: dropdownPadding, | ||
shape: dropdownShape, | ||
), | ||
onPressed: onDropdownPressed, | ||
child: dropdownIcon, | ||
), | ||
_YaruSplitButtonVariant.filled => FilledButton( | ||
style: FilledButton.styleFrom( | ||
fixedSize: size, | ||
minimumSize: size, | ||
maximumSize: size, | ||
padding: dropdownPadding, | ||
shape: dropdownShape, | ||
), | ||
onPressed: onDropdownPressed, | ||
child: dropdownIcon, | ||
), | ||
_YaruSplitButtonVariant.outlined => OutlinedButton( | ||
style: OutlinedButton.styleFrom( | ||
fixedSize: size, | ||
minimumSize: size, | ||
maximumSize: size, | ||
padding: dropdownPadding, | ||
shape: dropdownShape, | ||
), | ||
onPressed: onDropdownPressed, | ||
child: dropdownIcon, | ||
), | ||
}, | ||
], | ||
); | ||
} | ||
|
||
RelativeRect _menuPosition(BuildContext context) { | ||
final box = context.findRenderObject() as RenderBox; | ||
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; | ||
const offset = Offset.zero; | ||
|
||
return RelativeRect.fromRect( | ||
Rect.fromPoints( | ||
box.localToGlobal( | ||
box.size.bottomCenter(offset), | ||
ancestor: overlay, | ||
), | ||
box.localToGlobal( | ||
box.size.bottomRight(offset), | ||
ancestor: overlay, | ||
), | ||
), | ||
offset & overlay.size, | ||
); | ||
} | ||
} |
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
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