-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
5 changed files
with
236 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
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,53 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:yaru/yaru.dart'; | ||
|
||
class SplitButtonPage extends StatelessWidget { | ||
const SplitButtonPage({super.key}); | ||
|
||
@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: [ | ||
YaruSplitButton( | ||
onPressed: () => ScaffoldMessenger.of(context) | ||
.showSnackBar(const SnackBar(content: Text('Main Action'))), | ||
items: items, | ||
child: const Text('Main Action'), | ||
), | ||
const SizedBox(height: 10), | ||
YaruSplitButton.filled( | ||
onPressed: () => ScaffoldMessenger.of(context) | ||
.showSnackBar(const SnackBar(content: Text('Main Action'))), | ||
items: items, | ||
child: const Text('Main Action'), | ||
), | ||
const SizedBox(height: 10), | ||
YaruSplitButton.outlined( | ||
onPressed: () => ScaffoldMessenger.of(context) | ||
.showSnackBar(const SnackBar(content: Text('Main Action'))), | ||
items: items, | ||
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,171 @@ | ||
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, | ||
required this.items, | ||
this.onPressed, | ||
this.child, | ||
this.onOptionsPressed, | ||
this.icon, | ||
this.radius, | ||
this.menuWidth = menuDefaultWidth, | ||
}) : _variant = _YaruSplitButtonVariant.elevated; | ||
|
||
const YaruSplitButton.filled({ | ||
super.key, | ||
required this.items, | ||
this.onPressed, | ||
this.child, | ||
this.onOptionsPressed, | ||
this.icon, | ||
this.radius, | ||
this.menuWidth = menuDefaultWidth, | ||
}) : _variant = _YaruSplitButtonVariant.filled; | ||
|
||
const YaruSplitButton.outlined({ | ||
super.key, | ||
required this.items, | ||
this.onPressed, | ||
this.child, | ||
this.onOptionsPressed, | ||
this.icon, | ||
this.radius, | ||
this.menuWidth = menuDefaultWidth, | ||
}) : _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; | ||
|
||
static const menuDefaultWidth = 148.0; | ||
|
||
@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 => | ||
const NonUniformRoundedRectangleBorder(hideLeftSide: true), | ||
_ => RoundedRectangleBorder( | ||
borderRadius: BorderRadius.only( | ||
topRight: defaultRadius, | ||
bottomRight: defaultRadius, | ||
), | ||
), | ||
}; | ||
|
||
final onDropdownPressed = onPressed == null | ||
? null | ||
: (onOptionsPressed ?? | ||
() => showMenu( | ||
context: context, | ||
position: _menuPosition(context), | ||
items: items, | ||
menuPadding: EdgeInsets.symmetric(vertical: defaultRadius.x), | ||
constraints: BoxConstraints( | ||
minWidth: menuWidth, | ||
maxWidth: menuWidth, | ||
), | ||
)); | ||
|
||
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: icon ?? const Icon(YaruIcons.pan_down), | ||
), | ||
_YaruSplitButtonVariant.filled => FilledButton( | ||
style: FilledButton.styleFrom( | ||
fixedSize: size, | ||
minimumSize: size, | ||
maximumSize: size, | ||
padding: dropdownPadding, | ||
shape: dropdownShape, | ||
), | ||
onPressed: onDropdownPressed, | ||
child: icon ?? const Icon(YaruIcons.pan_down), | ||
), | ||
_YaruSplitButtonVariant.outlined => OutlinedButton( | ||
style: OutlinedButton.styleFrom( | ||
fixedSize: size, | ||
minimumSize: size, | ||
maximumSize: size, | ||
padding: dropdownPadding, | ||
shape: dropdownShape, | ||
), | ||
onPressed: onDropdownPressed, | ||
child: icon ?? const Icon(YaruIcons.pan_down), | ||
), | ||
}, | ||
], | ||
); | ||
} | ||
|
||
RelativeRect _menuPosition(BuildContext context) { | ||
final bar = context.findRenderObject() as RenderBox; | ||
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; | ||
const offset = Offset.zero; | ||
|
||
return RelativeRect.fromRect( | ||
Rect.fromPoints( | ||
bar.localToGlobal( | ||
bar.size.bottomCenter(offset), | ||
ancestor: overlay, | ||
), | ||
bar.localToGlobal( | ||
bar.size.bottomLeft(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