Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
6b32c069
Unverified
Commit
6b32c069
authored
Oct 07, 2022
by
Greg Spencer
Committed by
GitHub
Oct 07, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add checkbox and radio menu buttons (#112821)
parent
91b5079f
Changes
10
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
995 additions
and
48 deletions
+995
-48
menu_anchor.dart
dev/manual_tests/lib/menu_anchor.dart
+143
-41
checkbox_menu_button.0.dart
.../api/lib/material/menu_anchor/checkbox_menu_button.0.dart
+108
-0
radio_menu_button.0.dart
...les/api/lib/material/menu_anchor/radio_menu_button.0.dart
+114
-0
checkbox_menu_button.0_test.dart
...est/material/menu_anchor/checkbox_menu_button.0_test.dart
+27
-0
radio_menu_button.0_test.dart
...i/test/material/menu_anchor/radio_menu_button.0_test.dart
+77
-0
checkbox.dart
packages/flutter/lib/src/material/checkbox.dart
+1
-1
menu_anchor.dart
packages/flutter/lib/src/material/menu_anchor.dart
+396
-0
checkbox_list_tile_test.dart
packages/flutter/test/material/checkbox_list_tile_test.dart
+1
-1
checkbox_test.dart
packages/flutter/test/material/checkbox_test.dart
+5
-5
menu_anchor_test.dart
packages/flutter/test/material/menu_anchor_test.dart
+123
-0
No files found.
dev/manual_tests/lib/menu_anchor.dart
View file @
6b32c069
This diff is collapsed.
Click to expand it.
examples/api/lib/material/menu_anchor/checkbox_menu_button.0.dart
0 → 100644
View file @
6b32c069
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Flutter code sample for [CheckboxMenuButton].
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
void
main
(
)
=>
runApp
(
const
MenuApp
());
class
MyCheckboxMenu
extends
StatefulWidget
{
const
MyCheckboxMenu
({
super
.
key
,
required
this
.
message
});
final
String
message
;
@override
State
<
MyCheckboxMenu
>
createState
()
=>
_MyCheckboxMenuState
();
}
class
_MyCheckboxMenuState
extends
State
<
MyCheckboxMenu
>
{
final
FocusNode
_buttonFocusNode
=
FocusNode
(
debugLabel:
'Menu Button'
);
static
const
SingleActivator
_showShortcut
=
SingleActivator
(
LogicalKeyboardKey
.
keyS
,
control:
true
);
bool
_showingMessage
=
false
;
@override
void
dispose
()
{
_buttonFocusNode
.
dispose
();
super
.
dispose
();
}
void
_setMessageVisibility
(
bool
visible
)
{
setState
(()
{
_showingMessage
=
visible
;
});
}
@override
Widget
build
(
BuildContext
context
)
{
return
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
_showShortcut:
()
{
_setMessageVisibility
(!
_showingMessage
);
},
},
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
MenuAnchor
(
childFocusNode:
_buttonFocusNode
,
menuChildren:
<
Widget
>[
CheckboxMenuButton
(
value:
_showingMessage
,
onChanged:
(
bool
?
value
)
{
_setMessageVisibility
(
value
!);
},
child:
const
Text
(
'Show Message'
),
),
],
builder:
(
BuildContext
context
,
MenuController
controller
,
Widget
?
child
)
{
return
TextButton
(
focusNode:
_buttonFocusNode
,
onPressed:
()
{
if
(
controller
.
isOpen
)
{
controller
.
close
();
}
else
{
controller
.
open
();
}
},
child:
const
Text
(
'OPEN MENU'
),
);
},
),
Expanded
(
child:
Container
(
alignment:
Alignment
.
center
,
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
<
Widget
>[
Padding
(
padding:
const
EdgeInsets
.
all
(
12.0
),
child:
Text
(
_showingMessage
?
widget
.
message
:
''
,
style:
Theme
.
of
(
context
).
textTheme
.
headlineSmall
,
),
),
],
),
),
),
],
),
);
}
}
class
MenuApp
extends
StatelessWidget
{
const
MenuApp
({
super
.
key
});
static
const
String
kMessage
=
'"Talk less. Smile more." - A. Burr'
;
@override
Widget
build
(
BuildContext
context
)
{
return
const
MaterialApp
(
home:
Scaffold
(
body:
MyCheckboxMenu
(
message:
kMessage
)),
);
}
}
examples/api/lib/material/menu_anchor/radio_menu_button.0.dart
0 → 100644
View file @
6b32c069
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Flutter code sample for [RadioMenuButton].
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
void
main
(
)
=>
runApp
(
const
MenuApp
());
class
MyRadioMenu
extends
StatefulWidget
{
const
MyRadioMenu
({
super
.
key
});
@override
State
<
MyRadioMenu
>
createState
()
=>
_MyRadioMenuState
();
}
class
_MyRadioMenuState
extends
State
<
MyRadioMenu
>
{
final
FocusNode
_buttonFocusNode
=
FocusNode
(
debugLabel:
'Menu Button'
);
Color
_backgroundColor
=
Colors
.
red
;
late
ShortcutRegistryEntry
_entry
;
static
const
SingleActivator
_redShortcut
=
SingleActivator
(
LogicalKeyboardKey
.
keyR
,
control:
true
);
static
const
SingleActivator
_greenShortcut
=
SingleActivator
(
LogicalKeyboardKey
.
keyG
,
control:
true
);
static
const
SingleActivator
_blueShortcut
=
SingleActivator
(
LogicalKeyboardKey
.
keyB
,
control:
true
);
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
_entry
=
ShortcutRegistry
.
of
(
context
).
addAll
(<
ShortcutActivator
,
VoidCallbackIntent
>{
_redShortcut:
VoidCallbackIntent
(()
=>
_setBackgroundColor
(
Colors
.
red
)),
_greenShortcut:
VoidCallbackIntent
(()
=>
_setBackgroundColor
(
Colors
.
green
)),
_blueShortcut:
VoidCallbackIntent
(()
=>
_setBackgroundColor
(
Colors
.
blue
)),
});
}
@override
void
dispose
()
{
_buttonFocusNode
.
dispose
();
_entry
.
dispose
();
super
.
dispose
();
}
void
_setBackgroundColor
(
Color
?
color
)
{
setState
(()
{
_backgroundColor
=
color
!;
});
}
@override
Widget
build
(
BuildContext
context
)
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
MenuAnchor
(
childFocusNode:
_buttonFocusNode
,
menuChildren:
<
Widget
>[
RadioMenuButton
<
Color
>(
value:
Colors
.
red
,
shortcut:
_redShortcut
,
groupValue:
_backgroundColor
,
onChanged:
_setBackgroundColor
,
child:
const
Text
(
'Red Background'
),
),
RadioMenuButton
<
Color
>(
value:
Colors
.
green
,
shortcut:
_greenShortcut
,
groupValue:
_backgroundColor
,
onChanged:
_setBackgroundColor
,
child:
const
Text
(
'Green Background'
),
),
RadioMenuButton
<
Color
>(
value:
Colors
.
blue
,
shortcut:
_blueShortcut
,
groupValue:
_backgroundColor
,
onChanged:
_setBackgroundColor
,
child:
const
Text
(
'Blue Background'
),
),
],
builder:
(
BuildContext
context
,
MenuController
controller
,
Widget
?
child
)
{
return
TextButton
(
focusNode:
_buttonFocusNode
,
onPressed:
()
{
if
(
controller
.
isOpen
)
{
controller
.
close
();
}
else
{
controller
.
open
();
}
},
child:
const
Text
(
'OPEN MENU'
),
);
},
),
Expanded
(
child:
Container
(
color:
_backgroundColor
,
),
),
],
);
}
}
class
MenuApp
extends
StatelessWidget
{
const
MenuApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
const
MaterialApp
(
home:
Scaffold
(
body:
MyRadioMenu
()),
);
}
}
examples/api/test/material/menu_anchor/checkbox_menu_button.0_test.dart
0 → 100644
View file @
6b32c069
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/material.dart'
;
import
'package:flutter_api_samples/material/menu_anchor/checkbox_menu_button.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Can open menu and show message'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
MenuApp
(),
);
await
tester
.
tap
(
find
.
byType
(
TextButton
));
await
tester
.
pump
();
expect
(
find
.
text
(
'Show Message'
),
findsOneWidget
);
expect
(
find
.
text
(
example
.
MenuApp
.
kMessage
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'Show Message'
));
await
tester
.
pump
();
expect
(
find
.
text
(
'Show Message'
),
findsNothing
);
expect
(
find
.
text
(
example
.
MenuApp
.
kMessage
),
findsOneWidget
);
});
}
examples/api/test/material/menu_anchor/radio_menu_button.0_test.dart
0 → 100644
View file @
6b32c069
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_api_samples/material/menu_anchor/radio_menu_button.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Can open menu'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
MenuApp
(),
);
await
tester
.
tap
(
find
.
byType
(
TextButton
));
await
tester
.
pump
();
await
tester
.
pump
();
expect
(
find
.
text
(
'Red Background'
),
findsOneWidget
);
expect
(
find
.
text
(
'Green Background'
),
findsOneWidget
);
expect
(
find
.
text
(
'Blue Background'
),
findsOneWidget
);
expect
(
find
.
byType
(
Radio
<
Color
>),
findsNWidgets
(
3
));
expect
(
tester
.
widget
<
Container
>(
find
.
byType
(
Container
)).
color
,
equals
(
Colors
.
red
));
await
tester
.
tap
(
find
.
text
(
'Green Background'
));
await
tester
.
pump
();
expect
(
tester
.
widget
<
Container
>(
find
.
byType
(
Container
)).
color
,
equals
(
Colors
.
green
));
});
testWidgets
(
'Shortcuts work'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
MenuApp
(),
);
// Open the menu so we can watch state changes resulting from the shortcuts
// firing.
await
tester
.
tap
(
find
.
byType
(
TextButton
));
await
tester
.
pump
();
expect
(
find
.
text
(
'Red Background'
),
findsOneWidget
);
expect
(
find
.
text
(
'Green Background'
),
findsOneWidget
);
expect
(
find
.
text
(
'Blue Background'
),
findsOneWidget
);
expect
(
find
.
byType
(
Radio
<
Color
>),
findsNWidgets
(
3
));
expect
(
tester
.
widget
<
Container
>(
find
.
byType
(
Container
)).
color
,
equals
(
Colors
.
red
));
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
controlLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
keyG
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
controlLeft
);
await
tester
.
pump
();
// Need to pump twice because of the one frame delay in the notification to
// update the overlay entry.
await
tester
.
pump
();
expect
(
tester
.
widget
<
Radio
<
Color
>>(
find
.
descendant
(
of:
find
.
byType
(
RadioMenuButton
<
Color
>).
at
(
0
),
matching:
find
.
byType
(
Radio
<
Color
>))).
groupValue
,
equals
(
Colors
.
green
));
expect
(
tester
.
widget
<
Container
>(
find
.
byType
(
Container
)).
color
,
equals
(
Colors
.
green
));
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
controlLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
keyR
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
controlLeft
);
await
tester
.
pump
();
await
tester
.
pump
();
expect
(
tester
.
widget
<
Radio
<
Color
>>(
find
.
descendant
(
of:
find
.
byType
(
RadioMenuButton
<
Color
>).
at
(
1
),
matching:
find
.
byType
(
Radio
<
Color
>))).
groupValue
,
equals
(
Colors
.
red
));
expect
(
tester
.
widget
<
Container
>(
find
.
byType
(
Container
)).
color
,
equals
(
Colors
.
red
));
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
controlLeft
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
keyB
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
controlLeft
);
await
tester
.
pump
();
await
tester
.
pump
();
expect
(
tester
.
widget
<
Radio
<
Color
>>(
find
.
descendant
(
of:
find
.
byType
(
RadioMenuButton
<
Color
>).
at
(
2
),
matching:
find
.
byType
(
Radio
<
Color
>))).
groupValue
,
equals
(
Colors
.
blue
));
expect
(
tester
.
widget
<
Container
>(
find
.
byType
(
Container
)).
color
,
equals
(
Colors
.
blue
));
});
}
packages/flutter/lib/src/material/checkbox.dart
View file @
6b32c069
...
...
@@ -208,7 +208,7 @@ class Checkbox extends StatefulWidget {
/// If true the checkbox's [value] can be true, false, or null.
///
///
Checkbox
displays a dash when its value is null.
///
[Checkbox]
displays a dash when its value is null.
///
/// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged]
/// callback will be applied to true if the current value is false, to null if
...
...
packages/flutter/lib/src/material/menu_anchor.dart
View file @
6b32c069
This diff is collapsed.
Click to expand it.
packages/flutter/test/material/checkbox_list_tile_test.dart
View file @
6b32c069
...
...
@@ -148,7 +148,7 @@ void main() {
final
Rect
tallerWidget
=
checkboxRect
.
height
>
titleRect
.
height
?
checkboxRect
:
titleRect
;
// Check the offsets of Check
B
ox and title after padding is applied.
// Check the offsets of Check
b
ox and title after padding is applied.
expect
(
paddingRect
.
right
,
checkboxRect
.
right
+
4
);
expect
(
paddingRect
.
left
,
titleRect
.
left
-
10
);
...
...
packages/flutter/test/material/checkbox_test.dart
View file @
6b32c069
...
...
@@ -60,7 +60,7 @@ void main() {
expect
(
tester
.
getSize
(
find
.
byType
(
Checkbox
)),
const
Size
(
40.0
,
40.0
));
});
testWidgets
(
'Check
B
ox semantics'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Check
b
ox semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsHandle
handle
=
tester
.
ensureSemantics
();
await
tester
.
pumpWidget
(
Theme
(
...
...
@@ -193,7 +193,7 @@ void main() {
handle
.
dispose
();
});
testWidgets
(
'Can wrap Check
B
ox with Semantics'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Can wrap Check
b
ox with Semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsHandle
handle
=
tester
.
ensureSemantics
();
await
tester
.
pumpWidget
(
Theme
(
...
...
@@ -222,7 +222,7 @@ void main() {
handle
.
dispose
();
});
testWidgets
(
'Check
B
ox tristate: true'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Check
b
ox tristate: true'
,
(
WidgetTester
tester
)
async
{
bool
?
checkBoxValue
;
await
tester
.
pumpWidget
(
...
...
@@ -388,7 +388,7 @@ void main() {
semanticsTester
.
dispose
();
});
testWidgets
(
'Check
B
ox tristate rendering, programmatic transitions'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Check
b
ox tristate rendering, programmatic transitions'
,
(
WidgetTester
tester
)
async
{
Widget
buildFrame
(
bool
?
checkboxValue
)
{
return
Theme
(
data:
theme
,
...
...
@@ -439,7 +439,7 @@ void main() {
expect
(
getCheckboxRenderer
(),
paints
..
line
());
// null is rendered as a line (a "dash")
});
testWidgets
(
'Check
B
ox color rendering'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Check
b
ox color rendering'
,
(
WidgetTester
tester
)
async
{
const
Color
borderColor
=
Color
(
0xff2196f3
);
Color
checkColor
=
const
Color
(
0xffFFFFFF
);
Color
activeColor
;
...
...
packages/flutter/test/material/menu_anchor_test.dart
View file @
6b32c069
...
...
@@ -1642,6 +1642,129 @@ void main() {
expect
(
find
.
text
(
charExpected
),
findsOneWidget
);
},
variant:
TargetPlatformVariant
.
all
());
});
group
(
'CheckboxMenuButton'
,
()
{
testWidgets
(
'tapping toggles checkbox'
,
(
WidgetTester
tester
)
async
{
bool
?
checkBoxValue
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
MenuBar
(
children:
<
Widget
>[
SubmenuButton
(
menuChildren:
<
Widget
>[
CheckboxMenuButton
(
value:
checkBoxValue
,
onChanged:
(
bool
?
value
)
{
setState
(()
{
checkBoxValue
=
value
;
});
},
tristate:
true
,
child:
const
Text
(
'checkbox'
),
)
],
child:
const
Text
(
'submenu'
),
),
],
);
},
),
),
);
await
tester
.
tap
(
find
.
byType
(
SubmenuButton
));
await
tester
.
pump
();
expect
(
tester
.
widget
<
CheckboxMenuButton
>(
find
.
byType
(
CheckboxMenuButton
)).
value
,
null
);
await
tester
.
tap
(
find
.
byType
(
CheckboxMenuButton
));
await
tester
.
pumpAndSettle
();
expect
(
checkBoxValue
,
false
);
await
tester
.
tap
(
find
.
byType
(
SubmenuButton
));
await
tester
.
pump
();
await
tester
.
tap
(
find
.
byType
(
CheckboxMenuButton
));
await
tester
.
pumpAndSettle
();
expect
(
checkBoxValue
,
true
);
await
tester
.
tap
(
find
.
byType
(
SubmenuButton
));
await
tester
.
pump
();
await
tester
.
tap
(
find
.
byType
(
CheckboxMenuButton
));
await
tester
.
pumpAndSettle
();
expect
(
checkBoxValue
,
null
);
});
});
group
(
'RadioMenuButton'
,
()
{
testWidgets
(
'tapping toggles radio button'
,
(
WidgetTester
tester
)
async
{
int
?
radioValue
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
StatefulBuilder
(
builder:
(
BuildContext
context
,
StateSetter
setState
)
{
return
MenuBar
(
children:
<
Widget
>[
SubmenuButton
(
menuChildren:
<
Widget
>[
RadioMenuButton
<
int
>(
value:
0
,
groupValue:
radioValue
,
onChanged:
(
int
?
value
)
{
setState
(()
{
radioValue
=
value
;
});
},
toggleable:
true
,
child:
const
Text
(
'radio 0'
),
),
RadioMenuButton
<
int
>(
value:
1
,
groupValue:
radioValue
,
onChanged:
(
int
?
value
)
{
setState
(()
{
radioValue
=
value
;
});
},
toggleable:
true
,
child:
const
Text
(
'radio 1'
),
)
],
child:
const
Text
(
'submenu'
),
),
],
);
},
),
),
);
await
tester
.
tap
(
find
.
byType
(
SubmenuButton
));
await
tester
.
pump
();
expect
(
tester
.
widget
<
RadioMenuButton
<
int
>>(
find
.
byType
(
RadioMenuButton
<
int
>).
first
).
groupValue
,
null
,
);
await
tester
.
tap
(
find
.
byType
(
RadioMenuButton
<
int
>).
first
);
await
tester
.
pumpAndSettle
();
expect
(
radioValue
,
0
);
await
tester
.
tap
(
find
.
byType
(
SubmenuButton
));
await
tester
.
pump
();
await
tester
.
tap
(
find
.
byType
(
RadioMenuButton
<
int
>).
first
);
await
tester
.
pumpAndSettle
();
expect
(
radioValue
,
null
);
await
tester
.
tap
(
find
.
byType
(
SubmenuButton
));
await
tester
.
pump
();
await
tester
.
tap
(
find
.
byType
(
RadioMenuButton
<
int
>).
last
);
await
tester
.
pumpAndSettle
();
expect
(
radioValue
,
1
);
});
});
}
List
<
Widget
>
createTestMenus
({
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment