Unverified Commit 2d2bb6bf authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

CupertinoTextField vertical alignment (#34723)

CupertinoTextField now supports vertical alignment via the textAlignVertical parameter.
parent 4cd12fc8
......@@ -174,6 +174,7 @@ class CupertinoTextField extends StatefulWidget {
this.style,
this.strutStyle,
this.textAlign = TextAlign.start,
this.textAlignVertical,
this.readOnly = false,
this.showCursor,
this.autofocus = false,
......@@ -324,6 +325,9 @@ class CupertinoTextField extends StatefulWidget {
/// {@macro flutter.widgets.editableText.textAlign}
final TextAlign textAlign;
/// {@macro flutter.material.inputDecorator.textAlignVertical}
final TextAlignVertical textAlignVertical;
/// {@macro flutter.widgets.editableText.readOnly}
final bool readOnly;
......@@ -491,10 +495,13 @@ class CupertinoTextField extends StatefulWidget {
properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
}
}
class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticKeepAliveClientMixin {
final GlobalKey _clearGlobalKey = GlobalKey();
final GlobalKey<EditableTextState> _editableTextKey = GlobalKey<EditableTextState>();
TextEditingController _controller;
......@@ -584,6 +591,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
}
void _handleSingleTapUp(TapUpDetails details) {
// Because TextSelectionGestureDetector listens to taps that happen on
// widgets in front of it, tapping the clear button will also trigger
// this handler. If the the clear button widget recognizes the up event,
// then do not handle it.
if (_clearGlobalKey.currentContext != null) {
final RenderBox renderBox = _clearGlobalKey.currentContext.findRenderObject();
final Offset localOffset = renderBox.globalToLocal(details.globalPosition);
if(renderBox.hitTest(BoxHitTestResult(), position: localOffset)) {
return;
}
}
if (widget.selectionEnabled) {
_renderEditable.selectWordEdge(cause: SelectionChangedCause.tap);
}
......@@ -719,16 +738,31 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
);
}
// True if any surrounding decoration widgets will be shown.
bool get _hasDecoration {
return widget.placeholder != null ||
widget.clearButtonMode != OverlayVisibilityMode.never ||
widget.prefix != null ||
widget.suffix != null;
}
// Provide default behavior if widget.textAlignVertical is not set.
// CupertinoTextField has top alignment by default, unless it has decoration
// like a prefix or suffix, in which case it's aligned to the center.
TextAlignVertical get _textAlignVertical {
if (widget.textAlignVertical != null) {
return widget.textAlignVertical;
}
return _hasDecoration ? TextAlignVertical.center : TextAlignVertical.top;
}
Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) {
assert(editableText != null);
assert(textStyle != null);
assert(placeholderStyle != null);
// If there are no surrounding widgets, just return the core editable text
// part.
if (widget.placeholder == null &&
widget.clearButtonMode == OverlayVisibilityMode.never &&
widget.prefix == null &&
widget.suffix == null) {
if (!_hasDecoration) {
return editableText;
}
......@@ -776,6 +810,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
} else if (_showClearButton(text)) {
rowChildren.add(
GestureDetector(
key: _clearGlobalKey,
onTap: widget.enabled ?? true ? () {
// Special handle onChanged for ClearButton
// Also call onChanged when the clear button is tapped.
......@@ -825,20 +860,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
? widget.decoration
: widget.decoration?.copyWith(color: widget.decoration?.color ?? disabledColor);
final Widget paddedEditable = TextSelectionGestureDetector(
onTapDown: _handleTapDown,
onForcePressStart: _handleForcePressStarted,
onForcePressEnd: _handleForcePressEnded,
onSingleTapUp: _handleSingleTapUp,
onSingleLongTapStart: _handleSingleLongTapStart,
onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate,
onSingleLongTapEnd: _handleSingleLongTapEnd,
onDoubleTapDown: _handleDoubleTapDown,
onDragSelectionStart: _handleMouseDragSelectionStart,
onDragSelectionUpdate: _handleMouseDragSelectionUpdate,
onDragSelectionEnd: _handleMouseDragSelectionEnd,
behavior: HitTestBehavior.translucent,
child: Padding(
final Widget paddedEditable = Padding(
padding: widget.padding,
child: RepaintBoundary(
child: EditableText(
......@@ -884,7 +906,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
enableInteractiveSelection: widget.enableInteractiveSelection,
),
),
),
);
return Semantics(
......@@ -898,9 +919,28 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
ignoring: !enabled,
child: Container(
decoration: effectiveDecoration,
child: TextSelectionGestureDetector(
onTapDown: _handleTapDown,
onForcePressStart: _handleForcePressStarted,
onForcePressEnd: _handleForcePressEnded,
onSingleTapUp: _handleSingleTapUp,
onSingleLongTapStart: _handleSingleLongTapStart,
onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate,
onSingleLongTapEnd: _handleSingleLongTapEnd,
onDoubleTapDown: _handleDoubleTapDown,
onDragSelectionStart: _handleMouseDragSelectionStart,
onDragSelectionUpdate: _handleMouseDragSelectionUpdate,
onDragSelectionEnd: _handleMouseDragSelectionEnd,
behavior: HitTestBehavior.translucent,
child: Align(
alignment: Alignment(-1.0, _textAlignVertical.y),
widthFactor: 1.0,
heightFactor: 1.0,
child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle),
),
),
),
),
);
}
}
......@@ -3576,42 +3576,3 @@ class InputDecorationTheme extends Diagnosticable {
properties.add(DiagnosticsProperty<bool>('alignLabelWithHint', alignLabelWithHint, defaultValue: defaultTheme.alignLabelWithHint));
}
}
/// The vertical alignment of text within an input.
///
/// A single [y] value that can range from -1.0 to 1.0. -1.0 aligns to the top
/// of the input so that the top of the first line of text fits within the input
/// and its padding. 0.0 aligns to the center of the input. 1.0 aligns so that
/// the bottom of the last line of text aligns with the bottom interior edge of
/// the input.
///
/// See also:
///
/// * [TextField.textAlignVertical], which is passed on to the [InputDecorator].
/// * [InputDecorator.textAlignVertical], which defines the alignment of
/// prefix, input, and suffix, within the [InputDecorator].
class TextAlignVertical {
/// Construct TextAlignVertical from any given y value.
const TextAlignVertical({
@required this.y,
}) : assert(y != null),
assert(y >= -1.0 && y <= 1.0);
/// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost
/// locations of the top and bottom of the input text box.
final double y;
/// Aligns a TextField's input Text with the topmost location within the
/// TextField.
static const TextAlignVertical top = TextAlignVertical(y: -1.0);
/// Aligns a TextField's input Text to the center of the TextField.
static const TextAlignVertical center = TextAlignVertical(y: 0.0);
/// Aligns a TextField's input Text with the bottommost location within the
/// TextField.
static const TextAlignVertical bottom = TextAlignVertical(y: 1.0);
@override
String toString() {
return '$runtimeType(y: $y)';
}
}
......@@ -633,3 +633,44 @@ class _MixedAlignment extends AlignmentGeometry {
return null;
}
}
/// The vertical alignment of text within an input box.
///
/// A single [y] value that can range from -1.0 to 1.0. -1.0 aligns to the top
/// of an input box so that the top of the first line of text fits within the
/// box and its padding. 0.0 aligns to the center of the box. 1.0 aligns so that
/// the bottom of the last line of text aligns with the bottom interior edge of
/// the input box.
///
/// See also:
///
/// * [TextField.textAlignVertical], which is passed on to the [InputDecorator].
/// * [CupertinoTextField.textAlignVertical], which behaves in the same way as
/// the parameter in TextField.
/// * [InputDecorator.textAlignVertical], which defines the alignment of
/// prefix, input, and suffix within an [InputDecorator].
class TextAlignVertical {
/// Creates a TextAlignVertical from any y value between -1.0 and 1.0.
const TextAlignVertical({
@required this.y,
}) : assert(y != null),
assert(y >= -1.0 && y <= 1.0);
/// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost
/// locations of the top and bottom of the input box.
final double y;
/// Aligns a TextField's input Text with the topmost location within a
/// TextField's input box.
static const TextAlignVertical top = TextAlignVertical(y: -1.0);
/// Aligns a TextField's input Text to the center of the TextField.
static const TextAlignVertical center = TextAlignVertical(y: 0.0);
/// Aligns a TextField's input Text with the bottommost location within a
/// TextField.
static const TextAlignVertical bottom = TextAlignVertical(y: 1.0);
@override
String toString() {
return '$runtimeType(y: $y)';
}
}
......@@ -1252,7 +1252,7 @@ class RenderEditable extends RenderBox {
double get preferredLineHeight => _textPainter.preferredLineHeight;
double _preferredHeight(double width) {
// Lock height to maxLines if needed
// Lock height to maxLines if needed.
final bool lockedMax = maxLines != null && minLines == null;
final bool lockedBoth = minLines != null && minLines == maxLines;
final bool singleLine = maxLines == 1;
......@@ -1260,7 +1260,7 @@ class RenderEditable extends RenderBox {
return preferredLineHeight * maxLines;
}
// Clamp height to minLines or maxLines if needed
// Clamp height to minLines or maxLines if needed.
final bool minLimited = minLines != null && minLines > 1;
final bool maxLimited = maxLines != null;
if (minLimited || maxLimited) {
......@@ -1273,7 +1273,7 @@ class RenderEditable extends RenderBox {
}
}
// Set the height based on the content
// Set the height based on the content.
if (width == double.infinity) {
final String text = _textPainter.text.toPlainText();
int lines = 1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment