Line data Source code
1 : // Copyright 2014 The Flutter Authors.
2 : // Copyright 2021 Suragch.
3 : // All rights reserved.
4 : // Use of this source code is governed by a BSD-style license that can be
5 : // found in the LICENSE file.
6 :
7 : // ignore_for_file: omit_local_variable_types
8 :
9 : import 'dart:math' as math;
10 : import 'dart:ui' show lerpDouble;
11 :
12 : import 'package:flutter/foundation.dart';
13 : import 'package:flutter/material.dart'
14 : show
15 : InputBorder,
16 : InputDecoration,
17 : Colors,
18 : VisualDensity,
19 : kMinInteractiveDimension,
20 : FloatingLabelBehavior,
21 : Theme,
22 : ThemeData,
23 : Brightness;
24 : import 'package:flutter/rendering.dart';
25 : import 'package:flutter/widgets.dart';
26 :
27 : import '../base/mongol_text_align.dart';
28 : import '../text/mongol_text.dart';
29 : import 'alignment.dart';
30 : import 'input_border.dart';
31 :
32 : const Duration _kTransitionDuration = Duration(milliseconds: 200);
33 : const Curve _kTransitionCurve = Curves.fastOutSlowIn;
34 : const double _kFinalLabelScale = 0.75;
35 :
36 : // Defines the gap in the MongolInputDecorator's outline border where the
37 : // floating label will appear.
38 : class _InputBorderGap extends ChangeNotifier {
39 : double? _start;
40 2 : double? get start => _start;
41 1 : set start(double? value) {
42 2 : if (value != _start) {
43 1 : _start = value;
44 1 : notifyListeners();
45 : }
46 : }
47 :
48 : double _extent = 0.0;
49 2 : double get extent => _extent;
50 1 : set extent(double value) {
51 2 : if (value != _extent) {
52 1 : _extent = value;
53 1 : notifyListeners();
54 : }
55 : }
56 :
57 1 : @override
58 : // ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection
59 : bool operator ==(Object other) {
60 : if (identical(this, other)) {
61 : return true;
62 : }
63 0 : if (other.runtimeType != runtimeType) {
64 : return false;
65 : }
66 0 : return other is _InputBorderGap &&
67 0 : other.start == start &&
68 0 : other.extent == extent;
69 : }
70 :
71 0 : @override
72 : // ignore: avoid_equals_and_hash_code_on_mutable_classes, this class is not used in collection
73 0 : int get hashCode => hashValues(start, extent);
74 : }
75 :
76 : // Used to interpolate between two InputBorders.
77 : class _InputBorderTween extends Tween<InputBorder> {
78 1 : _InputBorderTween({InputBorder? begin, InputBorder? end})
79 1 : : super(begin: begin, end: end);
80 :
81 1 : @override
82 3 : InputBorder lerp(double t) => ShapeBorder.lerp(begin, end, t)! as InputBorder;
83 : }
84 :
85 : // Passes the _InputBorderGap parameters along to an InputBorder's paint method.
86 : class _InputBorderPainter extends CustomPainter {
87 1 : _InputBorderPainter({
88 : required Listenable repaint,
89 : required this.borderAnimation,
90 : required this.border,
91 : required this.gapAnimation,
92 : required this.gap,
93 : required this.fillColor,
94 : required this.hoverAnimation,
95 : required this.hoverColorTween,
96 1 : }) : super(repaint: repaint);
97 :
98 : final Animation<double> borderAnimation;
99 : final _InputBorderTween border;
100 : final Animation<double> gapAnimation;
101 : final _InputBorderGap gap;
102 : final Color fillColor;
103 : final ColorTween hoverColorTween;
104 : final Animation<double> hoverAnimation;
105 :
106 1 : Color get blendedColor =>
107 5 : Color.alphaBlend(hoverColorTween.evaluate(hoverAnimation)!, fillColor);
108 :
109 1 : @override
110 : void paint(Canvas canvas, Size size) {
111 3 : final borderValue = border.evaluate(borderAnimation);
112 1 : final canvasRect = Offset.zero & size;
113 1 : final blendedFillColor = blendedColor;
114 2 : if (blendedFillColor.alpha > 0) {
115 0 : canvas.drawPath(
116 0 : borderValue.getOuterPath(canvasRect, textDirection: TextDirection.ltr),
117 0 : Paint()
118 0 : ..color = blendedFillColor
119 0 : ..style = PaintingStyle.fill,
120 : );
121 : }
122 :
123 1 : borderValue.paint(
124 : canvas,
125 : canvasRect,
126 2 : gapStart: gap.start,
127 2 : gapExtent: gap.extent,
128 2 : gapPercentage: gapAnimation.value,
129 : textDirection: TextDirection.ltr,
130 : );
131 : }
132 :
133 1 : @override
134 : bool shouldRepaint(_InputBorderPainter oldPainter) {
135 3 : return borderAnimation != oldPainter.borderAnimation ||
136 3 : hoverAnimation != oldPainter.hoverAnimation ||
137 3 : gapAnimation != oldPainter.gapAnimation ||
138 3 : border != oldPainter.border ||
139 3 : gap != oldPainter.gap;
140 : }
141 : }
142 :
143 : // An analog of AnimatedContainer, which can animate its shaped border, for
144 : // _InputBorder. This specialized animated container is needed because the
145 : // _InputBorderGap, which is computed at layout time, is required by the
146 : // _InputBorder's paint method.
147 : class _BorderContainer extends StatefulWidget {
148 1 : const _BorderContainer({
149 : Key? key,
150 : required this.border,
151 : required this.gap,
152 : required this.gapAnimation,
153 : required this.fillColor,
154 : required this.hoverColor,
155 : required this.isHovering,
156 : this.child,
157 1 : }) : super(key: key);
158 :
159 : final InputBorder border;
160 : final _InputBorderGap gap;
161 : final Animation<double> gapAnimation;
162 : final Color fillColor;
163 : final Color hoverColor;
164 : final bool isHovering;
165 : final Widget? child;
166 :
167 1 : @override
168 1 : _BorderContainerState createState() => _BorderContainerState();
169 : }
170 :
171 : class _BorderContainerState extends State<_BorderContainer>
172 : with TickerProviderStateMixin {
173 : static const Duration _kHoverDuration = Duration(milliseconds: 15);
174 :
175 : late AnimationController _controller;
176 : late AnimationController _hoverColorController;
177 : late Animation<double> _borderAnimation;
178 : late _InputBorderTween _border;
179 : late Animation<double> _hoverAnimation;
180 : late ColorTween _hoverColorTween;
181 :
182 1 : @override
183 : void initState() {
184 1 : super.initState();
185 2 : _hoverColorController = AnimationController(
186 : duration: _kHoverDuration,
187 2 : value: widget.isHovering ? 1.0 : 0.0,
188 : vsync: this,
189 : );
190 2 : _controller = AnimationController(
191 : duration: _kTransitionDuration,
192 : vsync: this,
193 : );
194 2 : _borderAnimation = CurvedAnimation(
195 1 : parent: _controller,
196 : curve: _kTransitionCurve,
197 : );
198 2 : _border = _InputBorderTween(
199 2 : begin: widget.border,
200 2 : end: widget.border,
201 : );
202 2 : _hoverAnimation = CurvedAnimation(
203 1 : parent: _hoverColorController,
204 : curve: Curves.linear,
205 : );
206 1 : _hoverColorTween =
207 3 : ColorTween(begin: Colors.transparent, end: widget.hoverColor);
208 : }
209 :
210 1 : @override
211 : void dispose() {
212 2 : _controller.dispose();
213 2 : _hoverColorController.dispose();
214 1 : super.dispose();
215 : }
216 :
217 1 : @override
218 : void didUpdateWidget(_BorderContainer oldWidget) {
219 1 : super.didUpdateWidget(oldWidget);
220 4 : if (widget.border != oldWidget.border) {
221 2 : _border = _InputBorderTween(
222 1 : begin: oldWidget.border,
223 2 : end: widget.border,
224 : );
225 1 : _controller
226 1 : ..value = 0.0
227 1 : ..forward();
228 : }
229 4 : if (widget.hoverColor != oldWidget.hoverColor) {
230 0 : _hoverColorTween =
231 0 : ColorTween(begin: Colors.transparent, end: widget.hoverColor);
232 : }
233 4 : if (widget.isHovering != oldWidget.isHovering) {
234 2 : if (widget.isHovering) {
235 2 : _hoverColorController.forward();
236 : } else {
237 2 : _hoverColorController.reverse();
238 : }
239 : }
240 : }
241 :
242 1 : @override
243 : Widget build(BuildContext context) {
244 1 : return CustomPaint(
245 1 : foregroundPainter: _InputBorderPainter(
246 2 : repaint: Listenable.merge(<Listenable>[
247 1 : _borderAnimation,
248 2 : widget.gap,
249 1 : _hoverColorController,
250 : ]),
251 1 : borderAnimation: _borderAnimation,
252 1 : border: _border,
253 2 : gapAnimation: widget.gapAnimation,
254 2 : gap: widget.gap,
255 2 : fillColor: widget.fillColor,
256 1 : hoverColorTween: _hoverColorTween,
257 1 : hoverAnimation: _hoverAnimation,
258 : ),
259 2 : child: widget.child,
260 : );
261 : }
262 : }
263 :
264 : // Used to "shake" the floating label to the up and down
265 : // when the errorText first appears.
266 : class _Shaker extends AnimatedWidget {
267 1 : const _Shaker({
268 : Key? key,
269 : required Animation<double> animation,
270 : this.child,
271 1 : }) : super(key: key, listenable: animation);
272 :
273 : final Widget? child;
274 :
275 2 : Animation<double> get animation => listenable as Animation<double>;
276 :
277 1 : double get translateY {
278 : const shakeDelta = 4.0;
279 2 : final t = animation.value;
280 1 : if (t <= 0.25) {
281 2 : return -t * shakeDelta;
282 0 : } else if (t < 0.75) {
283 0 : return (t - 0.5) * shakeDelta;
284 : } else {
285 0 : return (1.0 - t) * 4.0 * shakeDelta;
286 : }
287 : }
288 :
289 1 : @override
290 : Widget build(BuildContext context) {
291 1 : return Transform(
292 2 : transform: Matrix4.translationValues(0.0, translateY, 0.0),
293 1 : child: child,
294 : );
295 : }
296 : }
297 :
298 : // Display the helper and error text. When the error text appears
299 : // it fades and the helper text fades out. The error text also
300 : // slides leftwards a little when it first appears.
301 : class _HelperError extends StatefulWidget {
302 1 : const _HelperError({
303 : Key? key,
304 : this.textAlign,
305 : this.helperText,
306 : this.helperStyle,
307 : this.helperMaxLines,
308 : this.errorText,
309 : this.errorStyle,
310 : this.errorMaxLines,
311 1 : }) : super(key: key);
312 :
313 : final MongolTextAlign? textAlign;
314 : final String? helperText;
315 : final TextStyle? helperStyle;
316 : final int? helperMaxLines;
317 : final String? errorText;
318 : final TextStyle? errorStyle;
319 : final int? errorMaxLines;
320 :
321 1 : @override
322 1 : _HelperErrorState createState() => _HelperErrorState();
323 : }
324 :
325 : class _HelperErrorState extends State<_HelperError>
326 : with SingleTickerProviderStateMixin {
327 : // If the width of this widget and the counter are zero ("empty") at
328 : // layout time, no space is allocated for the subtext.
329 : static const Widget empty = SizedBox();
330 :
331 : late AnimationController _controller;
332 : Widget? _helper;
333 : Widget? _error;
334 :
335 1 : @override
336 : void initState() {
337 1 : super.initState();
338 2 : _controller = AnimationController(
339 : duration: _kTransitionDuration,
340 : vsync: this,
341 : );
342 2 : if (widget.errorText != null) {
343 2 : _error = _buildError();
344 2 : _controller.value = 1.0;
345 2 : } else if (widget.helperText != null) {
346 2 : _helper = _buildHelper();
347 : }
348 3 : _controller.addListener(_handleChange);
349 : }
350 :
351 1 : @override
352 : void dispose() {
353 2 : _controller.dispose();
354 1 : super.dispose();
355 : }
356 :
357 0 : void _handleChange() {
358 0 : setState(() {
359 : // The _controller's value has changed.
360 : });
361 : }
362 :
363 1 : @override
364 : void didUpdateWidget(_HelperError old) {
365 1 : super.didUpdateWidget(old);
366 :
367 2 : final newErrorText = widget.errorText;
368 2 : final newHelperText = widget.helperText;
369 1 : final oldErrorText = old.errorText;
370 1 : final oldHelperText = old.helperText;
371 :
372 : final errorTextStateChanged =
373 1 : (newErrorText != null) != (oldErrorText != null);
374 : final helperTextStateChanged = newErrorText == null &&
375 1 : (newHelperText != null) != (oldHelperText != null);
376 :
377 : if (errorTextStateChanged || helperTextStateChanged) {
378 : if (newErrorText != null) {
379 0 : _error = _buildError();
380 0 : _controller.forward();
381 : } else if (newHelperText != null) {
382 0 : _helper = _buildHelper();
383 0 : _controller.reverse();
384 : } else {
385 0 : _controller.reverse();
386 : }
387 : }
388 : }
389 :
390 1 : Widget _buildHelper() {
391 2 : assert(widget.helperText != null);
392 1 : return Semantics(
393 : container: true,
394 1 : child: Opacity(
395 3 : opacity: 1.0 - _controller.value,
396 1 : child: MongolText(
397 2 : widget.helperText!,
398 2 : style: widget.helperStyle,
399 2 : textAlign: widget.textAlign,
400 : overflow: TextOverflow.ellipsis,
401 2 : maxLines: widget.helperMaxLines,
402 : ),
403 : ),
404 : );
405 : }
406 :
407 1 : Widget _buildError() {
408 2 : assert(widget.errorText != null);
409 1 : return Semantics(
410 : container: true,
411 : liveRegion: true,
412 1 : child: Opacity(
413 2 : opacity: _controller.value,
414 1 : child: FractionalTranslation(
415 1 : translation: Tween<Offset>(
416 : begin: const Offset(-0.25, 0.0),
417 : end: const Offset(0.0, 0.0),
418 3 : ).evaluate(_controller.view),
419 1 : child: MongolText(
420 2 : widget.errorText!,
421 2 : style: widget.errorStyle,
422 2 : textAlign: widget.textAlign,
423 : overflow: TextOverflow.ellipsis,
424 2 : maxLines: widget.errorMaxLines,
425 : ),
426 : ),
427 : ),
428 : );
429 : }
430 :
431 1 : @override
432 : Widget build(BuildContext context) {
433 2 : if (_controller.isDismissed) {
434 1 : _error = null;
435 2 : if (widget.helperText != null) {
436 2 : return _helper = _buildHelper();
437 : } else {
438 1 : _helper = null;
439 : return empty;
440 : }
441 : }
442 :
443 2 : if (_controller.isCompleted) {
444 1 : _helper = null;
445 2 : if (widget.errorText != null) {
446 2 : return _error = _buildError();
447 : } else {
448 0 : _error = null;
449 : return empty;
450 : }
451 : }
452 :
453 0 : if (_helper == null && widget.errorText != null) return _buildError();
454 :
455 0 : if (_error == null && widget.helperText != null) return _buildHelper();
456 :
457 0 : if (widget.errorText != null) {
458 0 : return Stack(
459 0 : children: <Widget>[
460 0 : Opacity(
461 0 : opacity: 1.0 - _controller.value,
462 0 : child: _helper,
463 : ),
464 0 : _buildError(),
465 : ],
466 : );
467 : }
468 :
469 0 : if (widget.helperText != null) {
470 0 : return Stack(
471 0 : children: <Widget>[
472 0 : _buildHelper(),
473 0 : Opacity(
474 0 : opacity: _controller.value,
475 0 : child: _error,
476 : ),
477 : ],
478 : );
479 : }
480 :
481 : return empty;
482 : }
483 : }
484 :
485 : // Identifies the children of a _RenderDecorationElement.
486 10 : enum _DecorationSlot {
487 : icon,
488 : input,
489 : label,
490 : hint,
491 : prefix,
492 : suffix,
493 : prefixIcon,
494 : suffixIcon,
495 : helperError,
496 : counter,
497 : container,
498 : }
499 :
500 : // An analog of InputDecoration for the _Decorator widget.
501 : @immutable
502 : class _Decoration {
503 1 : const _Decoration({
504 : required this.contentPadding,
505 : required this.isCollapsed,
506 : required this.floatingLabelWidth,
507 : required this.floatingLabelProgress,
508 : this.border,
509 : this.borderGap,
510 : required this.alignLabelWithHint,
511 : required this.isDense,
512 : this.visualDensity,
513 : this.icon,
514 : this.input,
515 : this.label,
516 : this.hint,
517 : this.prefix,
518 : this.suffix,
519 : this.prefixIcon,
520 : this.suffixIcon,
521 : this.helperError,
522 : this.counter,
523 : this.container,
524 : this.fixTextFieldOutlineLabel = false,
525 : });
526 :
527 : final EdgeInsetsGeometry contentPadding;
528 : final bool isCollapsed;
529 : final double floatingLabelWidth;
530 : final double floatingLabelProgress;
531 : final InputBorder? border;
532 : final _InputBorderGap? borderGap;
533 : final bool alignLabelWithHint;
534 : final bool? isDense;
535 : final VisualDensity? visualDensity;
536 : final Widget? icon;
537 : final Widget? input;
538 : final Widget? label;
539 : final Widget? hint;
540 : final Widget? prefix;
541 : final Widget? suffix;
542 : final Widget? prefixIcon;
543 : final Widget? suffixIcon;
544 : final Widget? helperError;
545 : final Widget? counter;
546 : final Widget? container;
547 : final bool fixTextFieldOutlineLabel;
548 :
549 1 : @override
550 : bool operator ==(Object other) {
551 : if (identical(this, other)) return true;
552 3 : if (other.runtimeType != runtimeType) return false;
553 1 : return other is _Decoration &&
554 3 : other.contentPadding == contentPadding &&
555 3 : other.isCollapsed == isCollapsed &&
556 3 : other.floatingLabelWidth == floatingLabelWidth &&
557 3 : other.floatingLabelProgress == floatingLabelProgress &&
558 3 : other.border == border &&
559 3 : other.borderGap == borderGap &&
560 3 : other.alignLabelWithHint == alignLabelWithHint &&
561 3 : other.isDense == isDense &&
562 3 : other.visualDensity == visualDensity &&
563 3 : other.icon == icon &&
564 3 : other.input == input &&
565 3 : other.label == label &&
566 3 : other.hint == hint &&
567 3 : other.prefix == prefix &&
568 3 : other.suffix == suffix &&
569 3 : other.prefixIcon == prefixIcon &&
570 3 : other.suffixIcon == suffixIcon &&
571 3 : other.helperError == helperError &&
572 0 : other.counter == counter &&
573 0 : other.container == container &&
574 0 : other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel;
575 : }
576 :
577 0 : @override
578 : int get hashCode {
579 0 : return hashValues(
580 0 : contentPadding,
581 0 : floatingLabelWidth,
582 0 : floatingLabelProgress,
583 0 : border,
584 0 : borderGap,
585 0 : alignLabelWithHint,
586 0 : isDense,
587 0 : visualDensity,
588 0 : icon,
589 0 : input,
590 0 : label,
591 0 : hint,
592 0 : prefix,
593 0 : suffix,
594 0 : prefixIcon,
595 0 : suffixIcon,
596 0 : helperError,
597 0 : counter,
598 0 : container,
599 0 : fixTextFieldOutlineLabel,
600 : );
601 : }
602 : }
603 :
604 : // A container for the layout values computed by _RenderDecoration._layout.
605 : // These values are used by _RenderDecoration.performLayout to position
606 : // all of the renderer children of a _RenderDecoration.
607 : class _RenderDecorationLayout {
608 1 : const _RenderDecorationLayout({
609 : required this.boxToBaseline,
610 : required this.inputBaseline,
611 : required this.outlineBaseline,
612 : required this.subtextBaseline,
613 : required this.containerWidth,
614 : required this.subtextWidth,
615 : });
616 :
617 : final Map<RenderBox?, double> boxToBaseline;
618 : final double inputBaseline;
619 : final double outlineBaseline;
620 : final double subtextBaseline; // helper/error counter
621 : final double containerWidth;
622 : final double subtextWidth;
623 : }
624 :
625 : // The workhorse: layout and paint a _Decorator widget's _Decoration.
626 : class _RenderDecoration extends RenderBox {
627 1 : _RenderDecoration({
628 : required _Decoration decoration,
629 : //required TextDirection textDirection,
630 : required TextBaseline textBaseline,
631 : required bool isFocused,
632 : required bool expands,
633 : TextAlignHorizontal? textAlignHorizontal,
634 : }) : _decoration = decoration,
635 : _textBaseline = textBaseline,
636 : _textAlignHorizontal = textAlignHorizontal,
637 : _isFocused = isFocused,
638 : _expands = expands;
639 :
640 : static const double subtextGap = 8.0;
641 : final Map<_DecorationSlot, RenderBox> children =
642 : <_DecorationSlot, RenderBox>{};
643 :
644 1 : RenderBox? _updateChild(
645 : RenderBox? oldChild, RenderBox? newChild, _DecorationSlot slot) {
646 : if (oldChild != null) {
647 0 : dropChild(oldChild);
648 0 : children.remove(slot);
649 : }
650 : if (newChild != null) {
651 2 : children[slot] = newChild;
652 1 : adoptChild(newChild);
653 : }
654 : return newChild;
655 : }
656 :
657 : RenderBox? _icon;
658 2 : RenderBox? get icon => _icon;
659 1 : set icon(RenderBox? value) {
660 3 : _icon = _updateChild(_icon, value, _DecorationSlot.icon);
661 : }
662 :
663 : RenderBox? _input;
664 2 : RenderBox? get input => _input;
665 1 : set input(RenderBox? value) {
666 3 : _input = _updateChild(_input, value, _DecorationSlot.input);
667 : }
668 :
669 : RenderBox? _label;
670 2 : RenderBox? get label => _label;
671 1 : set label(RenderBox? value) {
672 3 : _label = _updateChild(_label, value, _DecorationSlot.label);
673 : }
674 :
675 : RenderBox? _hint;
676 2 : RenderBox? get hint => _hint;
677 1 : set hint(RenderBox? value) {
678 3 : _hint = _updateChild(_hint, value, _DecorationSlot.hint);
679 : }
680 :
681 : RenderBox? _prefix;
682 2 : RenderBox? get prefix => _prefix;
683 1 : set prefix(RenderBox? value) {
684 3 : _prefix = _updateChild(_prefix, value, _DecorationSlot.prefix);
685 : }
686 :
687 : RenderBox? _suffix;
688 2 : RenderBox? get suffix => _suffix;
689 1 : set suffix(RenderBox? value) {
690 3 : _suffix = _updateChild(_suffix, value, _DecorationSlot.suffix);
691 : }
692 :
693 : RenderBox? _prefixIcon;
694 2 : RenderBox? get prefixIcon => _prefixIcon;
695 0 : set prefixIcon(RenderBox? value) {
696 0 : _prefixIcon = _updateChild(_prefixIcon, value, _DecorationSlot.prefixIcon);
697 : }
698 :
699 : RenderBox? _suffixIcon;
700 2 : RenderBox? get suffixIcon => _suffixIcon;
701 1 : set suffixIcon(RenderBox? value) {
702 3 : _suffixIcon = _updateChild(_suffixIcon, value, _DecorationSlot.suffixIcon);
703 : }
704 :
705 : RenderBox? _helperError;
706 2 : RenderBox? get helperError => _helperError;
707 1 : set helperError(RenderBox? value) {
708 1 : _helperError =
709 2 : _updateChild(_helperError, value, _DecorationSlot.helperError);
710 : }
711 :
712 : RenderBox? _counter;
713 2 : RenderBox? get counter => _counter;
714 1 : set counter(RenderBox? value) {
715 3 : _counter = _updateChild(_counter, value, _DecorationSlot.counter);
716 : }
717 :
718 : RenderBox? _container;
719 2 : RenderBox? get container => _container;
720 1 : set container(RenderBox? value) {
721 3 : _container = _updateChild(_container, value, _DecorationSlot.container);
722 : }
723 :
724 : // The returned list is ordered for hit testing.
725 : Iterable<RenderBox> get _children sync* {
726 : if (icon != null) yield icon!;
727 : if (input != null) yield input!;
728 : if (prefixIcon != null) yield prefixIcon!;
729 : if (suffixIcon != null) yield suffixIcon!;
730 : if (prefix != null) yield prefix!;
731 : if (suffix != null) yield suffix!;
732 : if (label != null) yield label!;
733 : if (hint != null) yield hint!;
734 : if (helperError != null) yield helperError!;
735 : if (counter != null) yield counter!;
736 : if (container != null) yield container!;
737 : }
738 :
739 2 : _Decoration get decoration => _decoration;
740 : _Decoration _decoration;
741 1 : set decoration(_Decoration value) {
742 2 : if (_decoration == value) return;
743 1 : _decoration = value;
744 1 : markNeedsLayout();
745 : }
746 :
747 0 : TextBaseline get textBaseline => _textBaseline;
748 : TextBaseline _textBaseline;
749 1 : set textBaseline(TextBaseline value) {
750 2 : if (_textBaseline == value) return;
751 0 : _textBaseline = value;
752 0 : markNeedsLayout();
753 : }
754 :
755 1 : TextAlignHorizontal get _defaultTextAlignHorizontal =>
756 1 : _isOutlineAligned ? TextAlignHorizontal.center : TextAlignHorizontal.left;
757 1 : TextAlignHorizontal? get textAlignHorizontal =>
758 2 : _textAlignHorizontal ?? _defaultTextAlignHorizontal;
759 : TextAlignHorizontal? _textAlignHorizontal;
760 1 : set textAlignHorizontal(TextAlignHorizontal? value) {
761 2 : if (_textAlignHorizontal == value) {
762 : return;
763 : }
764 : // No need to relayout if the effective value is still the same.
765 0 : if (textAlignHorizontal!.x == (value?.x ?? _defaultTextAlignHorizontal.x)) {
766 0 : _textAlignHorizontal = value;
767 : return;
768 : }
769 0 : _textAlignHorizontal = value;
770 0 : markNeedsLayout();
771 : }
772 :
773 2 : bool get isFocused => _isFocused;
774 : bool _isFocused;
775 1 : set isFocused(bool value) {
776 2 : if (_isFocused == value) return;
777 1 : _isFocused = value;
778 1 : markNeedsSemanticsUpdate();
779 : }
780 :
781 2 : bool get expands => _expands;
782 : bool _expands = false;
783 1 : set expands(bool value) {
784 2 : if (_expands == value) return;
785 0 : _expands = value;
786 0 : markNeedsLayout();
787 : }
788 :
789 : // Indicates that the decoration should be aligned to accommodate an outline
790 : // border.
791 1 : bool get _isOutlineAligned {
792 5 : return !decoration.isCollapsed && decoration.border!.isOutline;
793 : }
794 :
795 1 : @override
796 : void attach(PipelineOwner owner) {
797 1 : super.attach(owner);
798 1 : for (final child in _children) {
799 0 : child.attach(owner);
800 : }
801 : }
802 :
803 1 : @override
804 : void detach() {
805 1 : super.detach();
806 2 : for (final child in _children) {
807 1 : child.detach();
808 : }
809 : }
810 :
811 1 : @override
812 : void redepthChildren() {
813 3 : _children.forEach(redepthChild);
814 : }
815 :
816 1 : @override
817 : void visitChildren(RenderObjectVisitor visitor) {
818 2 : _children.forEach(visitor);
819 : }
820 :
821 1 : @override
822 : void visitChildrenForSemantics(RenderObjectVisitor visitor) {
823 3 : if (icon != null) visitor(icon!);
824 3 : if (prefix != null) visitor(prefix!);
825 1 : if (prefixIcon != null) visitor(prefixIcon!);
826 :
827 1 : if (label != null) {
828 2 : visitor(label!);
829 : }
830 1 : if (hint != null) {
831 1 : if (isFocused) {
832 2 : visitor(hint!);
833 1 : } else if (label == null) {
834 2 : visitor(hint!);
835 : }
836 : }
837 :
838 3 : if (input != null) visitor(input!);
839 3 : if (suffixIcon != null) visitor(suffixIcon!);
840 3 : if (suffix != null) visitor(suffix!);
841 3 : if (container != null) visitor(container!);
842 3 : if (helperError != null) visitor(helperError!);
843 3 : if (counter != null) visitor(counter!);
844 : }
845 :
846 0 : @override
847 : List<DiagnosticsNode> debugDescribeChildren() {
848 0 : final value = <DiagnosticsNode>[];
849 0 : void add(RenderBox? child, String name) {
850 0 : if (child != null) value.add(child.toDiagnosticsNode(name: name));
851 : }
852 :
853 0 : add(icon, 'icon');
854 0 : add(input, 'input');
855 0 : add(label, 'label');
856 0 : add(hint, 'hint');
857 0 : add(prefix, 'prefix');
858 0 : add(suffix, 'suffix');
859 0 : add(prefixIcon, 'prefixIcon');
860 0 : add(suffixIcon, 'suffixIcon');
861 0 : add(helperError, 'helperError');
862 0 : add(counter, 'counter');
863 0 : add(container, 'container');
864 : return value;
865 : }
866 :
867 1 : @override
868 : bool get sizedByParent => false;
869 :
870 1 : static double _minHeight(RenderBox? box, double width) {
871 1 : return box == null ? 0.0 : box.getMinIntrinsicHeight(width);
872 : }
873 :
874 1 : static double _maxHeight(RenderBox? box, double width) {
875 1 : return box == null ? 0.0 : box.getMaxIntrinsicHeight(width);
876 : }
877 :
878 1 : static double _minWidth(RenderBox? box, double height) {
879 1 : return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
880 : }
881 :
882 2 : static Size _boxSize(RenderBox? box) => box == null ? Size.zero : box.size;
883 :
884 1 : static BoxParentData _boxParentData(RenderBox box) =>
885 1 : box.parentData! as BoxParentData;
886 :
887 3 : EdgeInsets get contentPadding => decoration.contentPadding as EdgeInsets;
888 :
889 : // Lay out the given box if needed, and return its baseline.
890 1 : double _layoutLineBox(RenderBox? box, BoxConstraints constraints) {
891 : if (box == null) {
892 : return 0.0;
893 : }
894 1 : box.layout(constraints, parentUsesSize: true);
895 : // Since internally, all layout is performed against the alphabetic baseline,
896 : // (eg, ascents/descents are all relative to alphabetic, even if the font is
897 : // an ideographic or hanging font), we should always obtain the reference
898 : // baseline from the alphabetic baseline. The ideographic baseline is for
899 : // use post-layout and is derived from the alphabetic baseline combined with
900 : // the font metrics.
901 1 : final baseline = box.getDistanceToBaseline(TextBaseline.alphabetic);
902 1 : assert(baseline != null && baseline >= 0.0);
903 : return baseline!;
904 : }
905 :
906 : // Returns a value used by performLayout to position all of the renderers.
907 : // This method applies layout to all of the renderers except the container.
908 : // For convenience, the container is laid out in performLayout().
909 1 : _RenderDecorationLayout _layout(BoxConstraints layoutConstraints) {
910 : assert(
911 2 : layoutConstraints.maxHeight < double.infinity,
912 : 'A MongolInputDecorator, which is typically created by a MongolTextField, '
913 : 'cannot have an unbounded height.\n'
914 : 'This happens when the parent widget does not provide a finite height '
915 : 'constraint. For example, if the MongolInputDecorator is contained by a Column, '
916 : 'then its height must be constrained. An Expanded widget or a SizedBox '
917 : 'can be used to constrain the height of the MongolInputDecorator or the '
918 : 'MongolTextField that contains it.',
919 : );
920 :
921 : // Margin on each side of subtext (counter and helperError)
922 1 : final boxToBaseline = <RenderBox?, double>{};
923 1 : final boxConstraints = layoutConstraints.loosen();
924 :
925 : // Layout all the widgets used by MongolInputDecorator
926 4 : boxToBaseline[prefix] = _layoutLineBox(prefix, boxConstraints);
927 4 : boxToBaseline[suffix] = _layoutLineBox(suffix, boxConstraints);
928 4 : boxToBaseline[icon] = _layoutLineBox(icon, boxConstraints);
929 4 : boxToBaseline[prefixIcon] = _layoutLineBox(prefixIcon, boxConstraints);
930 4 : boxToBaseline[suffixIcon] = _layoutLineBox(suffixIcon, boxConstraints);
931 :
932 1 : final inputHeight = math.max(
933 : 0.0,
934 3 : constraints.maxHeight -
935 4 : (_boxSize(icon).height +
936 3 : contentPadding.top +
937 4 : _boxSize(prefixIcon).height +
938 4 : _boxSize(prefix).height +
939 4 : _boxSize(suffix).height +
940 4 : _boxSize(suffixIcon).height +
941 2 : contentPadding.bottom));
942 : // Increase the available height for the label when it is scaled down.
943 1 : final invertedLabelScale = lerpDouble(
944 3 : 1.00, 1 / _kFinalLabelScale, decoration.floatingLabelProgress)!;
945 3 : var suffixIconHeight = _boxSize(suffixIcon).height;
946 3 : if (decoration.border!.isOutline) {
947 : suffixIconHeight =
948 0 : lerpDouble(suffixIconHeight, 0.0, decoration.floatingLabelProgress)!;
949 : }
950 1 : final labelHeight = math.max(
951 : 0.0,
952 3 : constraints.maxHeight -
953 4 : (_boxSize(icon).height +
954 3 : contentPadding.top +
955 4 : _boxSize(prefixIcon).height +
956 1 : suffixIconHeight +
957 2 : contentPadding.bottom));
958 3 : boxToBaseline[label] = _layoutLineBox(
959 1 : label,
960 2 : boxConstraints.copyWith(maxHeight: labelHeight * invertedLabelScale),
961 : );
962 3 : boxToBaseline[hint] = _layoutLineBox(
963 1 : hint,
964 1 : boxConstraints.copyWith(minHeight: inputHeight, maxHeight: inputHeight),
965 : );
966 4 : boxToBaseline[counter] = _layoutLineBox(counter, boxConstraints);
967 :
968 : // The helper or error text can occupy the full height less the space
969 : // occupied by the icon and counter.
970 3 : boxToBaseline[helperError] = _layoutLineBox(
971 1 : helperError,
972 1 : boxConstraints.copyWith(
973 1 : maxWidth: math.max(
974 : 0.0,
975 2 : boxConstraints.maxHeight -
976 4 : _boxSize(icon).height -
977 4 : _boxSize(counter).height -
978 2 : contentPadding.vertical,
979 : ),
980 : ),
981 : );
982 :
983 : // The width of the input needs to accommodate label to the left and counter and
984 : // helperError to the right, when they exist.
985 3 : final labelWidth = label == null ? 0 : decoration.floatingLabelWidth;
986 3 : final leftWidth = decoration.border!.isOutline
987 0 : ? math.max(labelWidth - boxToBaseline[label]!, 0)
988 : : labelWidth;
989 : final counterWidth =
990 4 : counter == null ? 0 : boxToBaseline[counter]! + subtextGap;
991 : final helperErrorExists =
992 6 : helperError?.size != null && helperError!.size.width > 0;
993 : final double helperErrorWidth =
994 4 : !helperErrorExists ? 0 : helperError!.size.width + subtextGap;
995 1 : final rightWidth = math.max(
996 : counterWidth,
997 : helperErrorWidth,
998 : );
999 3 : final densityOffset = decoration.visualDensity!.baseSizeAdjustment;
1000 3 : boxToBaseline[input] = _layoutLineBox(
1001 1 : input,
1002 : boxConstraints
1003 2 : .deflate(EdgeInsets.only(
1004 6 : left: contentPadding.left + leftWidth + densityOffset.dx / 2,
1005 6 : right: contentPadding.right + rightWidth + densityOffset.dx / 2,
1006 : ))
1007 1 : .copyWith(
1008 : minHeight: inputHeight,
1009 : maxHeight: inputHeight,
1010 : ),
1011 : );
1012 :
1013 : // The field can be occupied by a hint or by the input itself
1014 4 : final hintWidth = hint == null ? 0 : hint!.size.width;
1015 4 : final inputDirectWidth = input == null ? 0 : input!.size.width;
1016 1 : final inputWidth = math.max(hintWidth, inputDirectWidth);
1017 1 : final inputInternalBaseline = math.max(
1018 2 : boxToBaseline[input]!,
1019 2 : boxToBaseline[hint]!,
1020 : );
1021 :
1022 : // Calculate the amount that prefix/suffix affects width to the left and right of
1023 : // the input.
1024 3 : final prefixWidth = prefix?.size.width ?? 0;
1025 3 : final suffixWidth = suffix?.size.width ?? 0;
1026 1 : final fixWidth = math.max(
1027 2 : boxToBaseline[prefix]!,
1028 2 : boxToBaseline[suffix]!,
1029 : );
1030 2 : final fixLeftOfInput = math.max(0, fixWidth - inputInternalBaseline);
1031 1 : final fixRightOfBaseline = math.max(
1032 3 : prefixWidth - boxToBaseline[prefix]!,
1033 3 : suffixWidth - boxToBaseline[suffix]!,
1034 : );
1035 1 : final fixRightOfInput = math.max(
1036 : 0,
1037 2 : fixRightOfBaseline - (inputWidth - inputInternalBaseline),
1038 : );
1039 :
1040 : // Calculate the width of the input text container.
1041 : final double prefixIconWidth =
1042 1 : prefixIcon == null ? 0 : prefixIcon!.size.width;
1043 : final double suffixIconWidth =
1044 4 : suffixIcon == null ? 0 : suffixIcon!.size.width;
1045 1 : final double fixIconWidth = math.max(prefixIconWidth, suffixIconWidth);
1046 1 : final double contentWidth = math.max(
1047 : fixIconWidth,
1048 1 : leftWidth +
1049 3 : contentPadding.left +
1050 1 : fixLeftOfInput +
1051 1 : inputWidth +
1052 1 : fixRightOfInput +
1053 3 : contentPadding.right +
1054 1 : densityOffset.dy,
1055 : );
1056 : final minContainerWidth =
1057 5 : decoration.isDense! || decoration.isCollapsed || expands
1058 : ? 0.0
1059 : : kMinInteractiveDimension;
1060 2 : final maxContainerWidth = boxConstraints.maxWidth - rightWidth;
1061 1 : final double containerWidth = expands
1062 : ? maxContainerWidth
1063 1 : : math.min(
1064 1 : math.max(contentWidth, minContainerWidth), maxContainerWidth);
1065 :
1066 : // Ensure the text is horizontally centered in cases where the content is
1067 : // shorter than kMinInteractiveDimension.
1068 1 : final interactiveAdjustment = minContainerWidth > contentWidth
1069 2 : ? (minContainerWidth - contentWidth) / 2.0
1070 : : 0.0;
1071 :
1072 : // Try to consider the prefix/suffix as part of the text when aligning it.
1073 : // If the prefix/suffix overflows however, allow it to extend outside of the
1074 : // input and align the remaining part of the text and prefix/suffix.
1075 2 : final overflow = math.max(0, contentWidth - maxContainerWidth);
1076 : // Map textAlignHorizontal from -1:1 to 0:1 so that it can be used to scale
1077 : // the baseline from its minimum to maximum values.
1078 4 : final textAlignHorizontalFactor = (textAlignHorizontal!.x + 1.0) / 2.0;
1079 : // Adjust to try to fit left overflow inside the input on an inverse scale of
1080 : // textAlignHorizontal, so that left aligned text adjusts the most and right
1081 : // aligned text doesn't adjust at all.
1082 : final baselineAdjustment =
1083 3 : fixLeftOfInput - overflow * (1 - textAlignHorizontalFactor);
1084 :
1085 : // The baselines that will be used to draw the actual input text content.
1086 3 : final leftInputBaseline = contentPadding.left +
1087 1 : leftWidth +
1088 1 : inputInternalBaseline +
1089 1 : baselineAdjustment +
1090 : interactiveAdjustment;
1091 : final maxContentWidth =
1092 7 : containerWidth - contentPadding.left - leftWidth - contentPadding.right;
1093 2 : final alignableWidth = fixLeftOfInput + inputWidth + fixRightOfInput;
1094 1 : final maxHorizontalOffset = maxContentWidth - alignableWidth;
1095 : final textAlignHorizontalOffset =
1096 1 : maxHorizontalOffset * textAlignHorizontalFactor;
1097 : final inputBaseline =
1098 4 : leftInputBaseline + textAlignHorizontalOffset + densityOffset.dx / 2.0;
1099 :
1100 : // The three main alignments for the baseline when an outline is present are
1101 : //
1102 : // * left (-1.0): leftmost point considering padding.
1103 : // * center (0.0): the absolute center of the input ignoring padding but
1104 : // accommodating the border and floating label.
1105 : // * right (1.0): rightmost point considering padding.
1106 : //
1107 : // That means that if the padding is uneven, center is not the exact
1108 : // midpoint of left and right. To account for this, the left of center and
1109 : // right of center alignments are interpolated independently.
1110 1 : final outlineCenterBaseline = inputInternalBaseline +
1111 2 : baselineAdjustment / 2.0 +
1112 3 : (containerWidth - (2.0 + inputWidth)) / 2.0;
1113 : final outlineLeftBaseline = leftInputBaseline;
1114 1 : final outlineRightBaseline = leftInputBaseline + maxHorizontalOffset;
1115 1 : final outlineBaseline = _interpolateThree(
1116 : outlineLeftBaseline,
1117 : outlineCenterBaseline,
1118 : outlineRightBaseline,
1119 1 : textAlignHorizontal!,
1120 : );
1121 :
1122 : // Find the positions of the text to the left of the input when it exists.
1123 : var subtextCounterBaseline = 0.0;
1124 : var subtextHelperBaseline = 0.0;
1125 : var subtextCounterWidth = 0.0;
1126 : var subtextHelperWidth = 0.0;
1127 1 : if (counter != null) {
1128 : subtextCounterBaseline =
1129 4 : containerWidth + subtextGap + boxToBaseline[counter]!;
1130 4 : subtextCounterWidth = counter!.size.width + subtextGap;
1131 : }
1132 : if (helperErrorExists) {
1133 : subtextHelperBaseline =
1134 4 : containerWidth + subtextGap + boxToBaseline[helperError]!;
1135 : subtextHelperWidth = helperErrorWidth;
1136 : }
1137 1 : final subtextBaseline = math.max(
1138 : subtextCounterBaseline,
1139 : subtextHelperBaseline,
1140 : );
1141 1 : final subtextWidth = math.max(
1142 : subtextCounterWidth,
1143 : subtextHelperWidth,
1144 : );
1145 :
1146 1 : return _RenderDecorationLayout(
1147 : boxToBaseline: boxToBaseline,
1148 : containerWidth: containerWidth,
1149 : inputBaseline: inputBaseline,
1150 : outlineBaseline: outlineBaseline,
1151 : subtextBaseline: subtextBaseline,
1152 : subtextWidth: subtextWidth,
1153 : );
1154 : }
1155 :
1156 : // Interpolate between three stops using textAlignHorizontal. This is used to
1157 : // calculate the outline baseline, which ignores padding when the alignment is
1158 : // middle. When the alignment is less than zero, it interpolates between the
1159 : // centered text box's left side and the left of the content padding. When the
1160 : // alignment is greater than zero, it interpolates between the centered box's
1161 : // left and the position that would align the right side of the box with the right
1162 : // padding.
1163 1 : double _interpolateThree(double begin, double middle, double end,
1164 : TextAlignHorizontal textAlignHorizontal) {
1165 2 : if (textAlignHorizontal.x <= 0) {
1166 : // It's possible for begin, middle, and end to not be in order because of
1167 : // excessive padding. Those cases are handled by using middle.
1168 1 : if (begin >= middle) {
1169 : return middle;
1170 : }
1171 : // Do a standard linear interpolation on the first half, between begin and
1172 : // middle.
1173 2 : final t = textAlignHorizontal.x + 1;
1174 3 : return begin + (middle - begin) * t;
1175 : }
1176 :
1177 0 : if (middle >= end) {
1178 : return middle;
1179 : }
1180 : // Do a standard linear interpolation on the second half, between middle and
1181 : // end.
1182 0 : final t = textAlignHorizontal.x;
1183 0 : return middle + (end - middle) * t;
1184 : }
1185 :
1186 1 : @override
1187 : double computeMinIntrinsicHeight(double width) {
1188 3 : return _minHeight(icon, width) +
1189 3 : contentPadding.top +
1190 3 : _minHeight(prefixIcon, width) +
1191 3 : _minHeight(prefix, width) +
1192 6 : math.max(_minHeight(input, width), _minHeight(hint, width)) +
1193 3 : _minHeight(suffix, width) +
1194 3 : _minHeight(suffixIcon, width) +
1195 2 : contentPadding.bottom;
1196 : }
1197 :
1198 1 : @override
1199 : double computeMaxIntrinsicHeight(double width) {
1200 3 : return _maxHeight(icon, width) +
1201 3 : contentPadding.top +
1202 3 : _maxHeight(prefixIcon, width) +
1203 3 : _maxHeight(prefix, width) +
1204 6 : math.max(_maxHeight(input, width), _maxHeight(hint, width)) +
1205 3 : _maxHeight(suffix, width) +
1206 3 : _maxHeight(suffixIcon, width) +
1207 2 : contentPadding.bottom;
1208 : }
1209 :
1210 1 : double _lineWidth(double height, List<RenderBox?> boxes) {
1211 : var width = 0.0;
1212 2 : for (final box in boxes) {
1213 : if (box == null) continue;
1214 2 : width = math.max(_minWidth(box, height), width);
1215 : }
1216 : return width;
1217 : }
1218 :
1219 1 : @override
1220 : double computeMinIntrinsicWidth(double height) {
1221 4 : var subtextWidth = _lineWidth(height, <RenderBox?>[helperError, counter]);
1222 2 : if (subtextWidth > 0.0) subtextWidth += subtextGap;
1223 3 : final densityOffset = decoration.visualDensity!.baseSizeAdjustment;
1224 3 : final containerWidth = contentPadding.left +
1225 4 : (label == null ? 0.0 : decoration.floatingLabelWidth) +
1226 6 : _lineWidth(height, <RenderBox?>[prefix, input, suffix]) +
1227 1 : subtextWidth +
1228 3 : contentPadding.right +
1229 1 : densityOffset.dx;
1230 : final minContainerWidth =
1231 3 : decoration.isDense! || expands ? 0.0 : kMinInteractiveDimension;
1232 1 : return math.max(containerWidth, minContainerWidth);
1233 : }
1234 :
1235 1 : @override
1236 : double computeMaxIntrinsicWidth(double height) {
1237 1 : return computeMinIntrinsicWidth(height);
1238 : }
1239 :
1240 0 : @override
1241 : double computeDistanceToActualBaseline(TextBaseline baseline) {
1242 0 : return _boxParentData(input!).offset.dx +
1243 0 : input!.computeDistanceToActualBaseline(baseline)!;
1244 : }
1245 :
1246 : // Records where the label was painted.
1247 : Matrix4? _labelTransform;
1248 :
1249 1 : @override
1250 : Size computeDryLayout(BoxConstraints constraints) {
1251 1 : assert(debugCannotComputeDryLayout(
1252 : reason:
1253 : 'Layout requires baseline metrics, which are only available after a full layout.',
1254 : ));
1255 : return const Size(0, 0);
1256 : }
1257 :
1258 1 : @override
1259 : void performLayout() {
1260 1 : final constraints = this.constraints;
1261 1 : _labelTransform = null;
1262 1 : final layout = _layout(constraints);
1263 :
1264 1 : final overallHeight = constraints.maxHeight;
1265 3 : final overallWidth = layout.containerWidth + layout.subtextWidth;
1266 :
1267 1 : if (container != null) {
1268 1 : final containerConstraints = BoxConstraints.tightFor(
1269 1 : width: layout.containerWidth,
1270 4 : height: overallHeight - _boxSize(icon).height,
1271 : );
1272 2 : container!.layout(containerConstraints, parentUsesSize: true);
1273 3 : final y = _boxSize(icon).height;
1274 4 : _boxParentData(container!).offset = Offset(0.0, y);
1275 : }
1276 :
1277 : double? width;
1278 1 : double centerLayout(RenderBox box, double y) {
1279 7 : _boxParentData(box).offset = Offset((width! - box.size.width) / 2.0, y);
1280 2 : return box.size.height;
1281 : }
1282 :
1283 : double? baseline;
1284 1 : double baselineLayout(RenderBox box, double y) {
1285 2 : _boxParentData(box).offset =
1286 4 : Offset(baseline! - layout.boxToBaseline[box]!, y);
1287 2 : return box.size.height;
1288 : }
1289 :
1290 2 : final top = contentPadding.top;
1291 3 : final bottom = overallHeight - contentPadding.bottom;
1292 :
1293 1 : width = layout.containerWidth;
1294 : baseline =
1295 2 : _isOutlineAligned ? layout.outlineBaseline : layout.inputBaseline;
1296 :
1297 1 : if (icon != null) {
1298 : const y = 0.0;
1299 2 : centerLayout(icon!, y);
1300 : }
1301 :
1302 4 : var start = top + _boxSize(icon).height;
1303 : var end = bottom;
1304 1 : if (prefixIcon != null) {
1305 0 : start -= contentPadding.top;
1306 0 : start += centerLayout(prefixIcon!, start);
1307 : }
1308 1 : if (label != null) {
1309 2 : if (decoration.alignLabelWithHint) {
1310 0 : baselineLayout(label!, start);
1311 : } else {
1312 2 : centerLayout(label!, start);
1313 : }
1314 : }
1315 4 : if (prefix != null) start += baselineLayout(prefix!, start);
1316 3 : if (input != null) baselineLayout(input!, start);
1317 3 : if (hint != null) baselineLayout(hint!, start);
1318 1 : if (suffixIcon != null) {
1319 3 : end += contentPadding.bottom;
1320 7 : end -= centerLayout(suffixIcon!, end - suffixIcon!.size.height);
1321 : }
1322 1 : if (suffix != null) {
1323 7 : end -= baselineLayout(suffix!, end - suffix!.size.height);
1324 : }
1325 :
1326 1 : if (helperError != null || counter != null) {
1327 1 : width = layout.subtextWidth;
1328 1 : baseline = layout.subtextBaseline;
1329 :
1330 1 : if (helperError != null) {
1331 6 : baselineLayout(helperError!, top + _boxSize(icon).height);
1332 : }
1333 1 : if (counter != null) {
1334 6 : baselineLayout(counter!, bottom - counter!.size.height);
1335 : }
1336 : }
1337 :
1338 1 : if (label != null) {
1339 4 : final labelY = _boxParentData(label!).offset.dy;
1340 : // The value of _InputBorderGap.start is relative to the origin of the
1341 : // _BorderContainer which is inset by the icon's height.
1342 7 : decoration.borderGap!.start = labelY - _boxSize(icon).height;
1343 7 : decoration.borderGap!.extent = label!.size.height * 0.75;
1344 : } else {
1345 3 : decoration.borderGap!.start = null;
1346 3 : decoration.borderGap!.extent = 0.0;
1347 : }
1348 :
1349 3 : size = constraints.constrain(Size(overallWidth, overallHeight));
1350 4 : assert(size.height == constraints.constrainHeight(overallHeight));
1351 4 : assert(size.width == constraints.constrainWidth(overallWidth));
1352 : }
1353 :
1354 1 : void _paintLabel(PaintingContext context, Offset offset) {
1355 2 : context.paintChild(label!, offset);
1356 : }
1357 :
1358 1 : @override
1359 : void paint(PaintingContext context, Offset offset) {
1360 1 : void doPaint(RenderBox? child) {
1361 : if (child != null) {
1362 4 : context.paintChild(child, _boxParentData(child).offset + offset);
1363 : }
1364 : }
1365 :
1366 2 : doPaint(container);
1367 :
1368 1 : if (label != null) {
1369 3 : final labelOffset = _boxParentData(label!).offset;
1370 3 : final labelWidth = label!.size.width;
1371 4 : final borderWeight = decoration.border!.borderSide.width;
1372 2 : final t = decoration.floatingLabelProgress;
1373 : // The center of the outline border label ends up a little below the
1374 : // center of the top border line.
1375 : final isOutlineBorder =
1376 5 : decoration.border != null && decoration.border!.isOutline;
1377 : // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
1378 : // Center the scaled label relative to the border.
1379 2 : final floatingX = decoration.fixTextFieldOutlineLabel
1380 : ? isOutlineBorder
1381 0 : ? (-labelWidth * _kFinalLabelScale) / 2.0 + borderWeight / 2.0
1382 0 : : contentPadding.left
1383 : : isOutlineBorder
1384 0 : ? -labelWidth * 0.25
1385 2 : : contentPadding.left;
1386 1 : final scale = lerpDouble(1.0, _kFinalLabelScale, t)!;
1387 1 : final dy = labelOffset.dy;
1388 3 : final dx = lerpDouble(0.0, floatingX - labelOffset.dx, t)!;
1389 2 : _labelTransform = Matrix4.identity()
1390 3 : ..translate(labelOffset.dx + dx, dy)
1391 1 : ..scale(scale);
1392 2 : _transformLayer = context.pushTransform(
1393 3 : needsCompositing, offset, _labelTransform!, _paintLabel,
1394 1 : oldLayer: _transformLayer);
1395 : } else {
1396 1 : _transformLayer = null;
1397 : }
1398 :
1399 2 : doPaint(icon);
1400 2 : doPaint(prefix);
1401 2 : doPaint(suffix);
1402 2 : doPaint(prefixIcon);
1403 2 : doPaint(suffixIcon);
1404 2 : doPaint(hint);
1405 2 : doPaint(input);
1406 2 : doPaint(helperError);
1407 2 : doPaint(counter);
1408 : }
1409 :
1410 : TransformLayer? _transformLayer;
1411 :
1412 1 : @override
1413 : bool hitTestSelf(Offset position) => true;
1414 :
1415 1 : @override
1416 : bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
1417 2 : for (final child in _children) {
1418 : // The label must be handled specially since we've transformed it.
1419 2 : final offset = _boxParentData(child).offset;
1420 1 : final isHit = result.addWithPaintOffset(
1421 : offset: offset,
1422 : position: position,
1423 1 : hitTest: (BoxHitTestResult result, Offset transformed) {
1424 2 : assert(transformed == position - offset);
1425 1 : return child.hitTest(result, position: transformed);
1426 : },
1427 : );
1428 : if (isHit) return true;
1429 : }
1430 : return false;
1431 : }
1432 :
1433 1 : @override
1434 : void applyPaintTransform(RenderObject child, Matrix4 transform) {
1435 3 : if (child == label && _labelTransform != null) {
1436 3 : final labelOffset = _boxParentData(label!).offset;
1437 : transform
1438 2 : ..multiply(_labelTransform!)
1439 5 : ..translate(-labelOffset.dx, -labelOffset.dy);
1440 : }
1441 1 : super.applyPaintTransform(child, transform);
1442 : }
1443 : }
1444 :
1445 : class _DecorationElement extends RenderObjectElement {
1446 2 : _DecorationElement(_Decorator widget) : super(widget);
1447 :
1448 : final Map<_DecorationSlot, Element> slotToChild =
1449 : <_DecorationSlot, Element>{};
1450 :
1451 1 : @override
1452 1 : _Decorator get widget => super.widget as _Decorator;
1453 :
1454 1 : @override
1455 1 : _RenderDecoration get renderObject => super.renderObject as _RenderDecoration;
1456 :
1457 1 : @override
1458 : void visitChildren(ElementVisitor visitor) {
1459 3 : slotToChild.values.forEach(visitor);
1460 : }
1461 :
1462 0 : @override
1463 : void forgetChild(Element child) {
1464 0 : assert(slotToChild.containsValue(child));
1465 0 : assert(child.slot is _DecorationSlot);
1466 0 : assert(slotToChild.containsKey(child.slot));
1467 0 : slotToChild.remove(child.slot);
1468 0 : super.forgetChild(child);
1469 : }
1470 :
1471 1 : void _mountChild(Widget? widget, _DecorationSlot slot) {
1472 2 : final oldChild = slotToChild[slot];
1473 1 : final newChild = updateChild(oldChild, widget, slot);
1474 : if (oldChild != null) {
1475 0 : slotToChild.remove(slot);
1476 : }
1477 : if (newChild != null) {
1478 2 : slotToChild[slot] = newChild;
1479 : }
1480 : }
1481 :
1482 1 : @override
1483 : void mount(Element? parent, dynamic newSlot) {
1484 1 : super.mount(parent, newSlot);
1485 4 : _mountChild(widget.decoration.icon, _DecorationSlot.icon);
1486 4 : _mountChild(widget.decoration.input, _DecorationSlot.input);
1487 4 : _mountChild(widget.decoration.label, _DecorationSlot.label);
1488 4 : _mountChild(widget.decoration.hint, _DecorationSlot.hint);
1489 4 : _mountChild(widget.decoration.prefix, _DecorationSlot.prefix);
1490 4 : _mountChild(widget.decoration.suffix, _DecorationSlot.suffix);
1491 4 : _mountChild(widget.decoration.prefixIcon, _DecorationSlot.prefixIcon);
1492 4 : _mountChild(widget.decoration.suffixIcon, _DecorationSlot.suffixIcon);
1493 4 : _mountChild(widget.decoration.helperError, _DecorationSlot.helperError);
1494 4 : _mountChild(widget.decoration.counter, _DecorationSlot.counter);
1495 4 : _mountChild(widget.decoration.container, _DecorationSlot.container);
1496 : }
1497 :
1498 1 : void _updateChild(Widget? widget, _DecorationSlot slot) {
1499 2 : final oldChild = slotToChild[slot];
1500 1 : final newChild = updateChild(oldChild, widget, slot);
1501 : if (oldChild != null) {
1502 2 : slotToChild.remove(slot);
1503 : }
1504 : if (newChild != null) {
1505 2 : slotToChild[slot] = newChild;
1506 : }
1507 : }
1508 :
1509 1 : @override
1510 : void update(_Decorator newWidget) {
1511 1 : super.update(newWidget);
1512 2 : assert(widget == newWidget);
1513 4 : _updateChild(widget.decoration.icon, _DecorationSlot.icon);
1514 4 : _updateChild(widget.decoration.input, _DecorationSlot.input);
1515 4 : _updateChild(widget.decoration.label, _DecorationSlot.label);
1516 4 : _updateChild(widget.decoration.hint, _DecorationSlot.hint);
1517 4 : _updateChild(widget.decoration.prefix, _DecorationSlot.prefix);
1518 4 : _updateChild(widget.decoration.suffix, _DecorationSlot.suffix);
1519 4 : _updateChild(widget.decoration.prefixIcon, _DecorationSlot.prefixIcon);
1520 4 : _updateChild(widget.decoration.suffixIcon, _DecorationSlot.suffixIcon);
1521 4 : _updateChild(widget.decoration.helperError, _DecorationSlot.helperError);
1522 4 : _updateChild(widget.decoration.counter, _DecorationSlot.counter);
1523 4 : _updateChild(widget.decoration.container, _DecorationSlot.container);
1524 : }
1525 :
1526 1 : void _updateRenderObject(RenderBox? child, _DecorationSlot slot) {
1527 : switch (slot) {
1528 1 : case _DecorationSlot.icon:
1529 2 : renderObject.icon = child;
1530 : break;
1531 1 : case _DecorationSlot.input:
1532 2 : renderObject.input = child;
1533 : break;
1534 1 : case _DecorationSlot.label:
1535 2 : renderObject.label = child;
1536 : break;
1537 1 : case _DecorationSlot.hint:
1538 2 : renderObject.hint = child;
1539 : break;
1540 1 : case _DecorationSlot.prefix:
1541 2 : renderObject.prefix = child;
1542 : break;
1543 1 : case _DecorationSlot.suffix:
1544 2 : renderObject.suffix = child;
1545 : break;
1546 1 : case _DecorationSlot.prefixIcon:
1547 0 : renderObject.prefixIcon = child;
1548 : break;
1549 1 : case _DecorationSlot.suffixIcon:
1550 2 : renderObject.suffixIcon = child;
1551 : break;
1552 1 : case _DecorationSlot.helperError:
1553 2 : renderObject.helperError = child;
1554 : break;
1555 1 : case _DecorationSlot.counter:
1556 2 : renderObject.counter = child;
1557 : break;
1558 1 : case _DecorationSlot.container:
1559 2 : renderObject.container = child;
1560 : break;
1561 : }
1562 : }
1563 :
1564 1 : @override
1565 : void insertRenderObjectChild(RenderObject child, _DecorationSlot slot) {
1566 1 : assert(child is RenderBox);
1567 1 : _updateRenderObject(child as RenderBox, slot);
1568 4 : assert(renderObject.children.keys.contains(slot));
1569 : }
1570 :
1571 0 : @override
1572 : void removeRenderObjectChild(RenderObject child, _DecorationSlot slot) {
1573 0 : assert(child is RenderBox);
1574 0 : assert(renderObject.children[slot] == child);
1575 0 : _updateRenderObject(null, slot);
1576 0 : assert(!renderObject.children.keys.contains(slot));
1577 : }
1578 :
1579 0 : @override
1580 : void moveRenderObjectChild(
1581 : RenderObject child, dynamic oldSlot, dynamic newSlot) {
1582 0 : assert(false, 'not reachable');
1583 : }
1584 : }
1585 :
1586 : class _Decorator extends RenderObjectWidget {
1587 1 : const _Decorator({
1588 : Key? key,
1589 : required this.textAlignHorizontal,
1590 : required this.decoration,
1591 : required this.textBaseline,
1592 : required this.isFocused,
1593 : required this.expands,
1594 1 : }) : super(key: key);
1595 :
1596 : final _Decoration decoration;
1597 : final TextBaseline textBaseline;
1598 : final TextAlignHorizontal? textAlignHorizontal;
1599 : final bool isFocused;
1600 : final bool expands;
1601 :
1602 1 : @override
1603 1 : _DecorationElement createElement() => _DecorationElement(this);
1604 :
1605 1 : @override
1606 : _RenderDecoration createRenderObject(BuildContext context) {
1607 1 : return _RenderDecoration(
1608 1 : decoration: decoration,
1609 1 : textBaseline: textBaseline,
1610 1 : textAlignHorizontal: textAlignHorizontal,
1611 1 : isFocused: isFocused,
1612 1 : expands: expands,
1613 : );
1614 : }
1615 :
1616 1 : @override
1617 : void updateRenderObject(
1618 : BuildContext context, _RenderDecoration renderObject) {
1619 : renderObject
1620 2 : ..decoration = decoration
1621 2 : ..expands = expands
1622 2 : ..isFocused = isFocused
1623 2 : ..textAlignHorizontal = textAlignHorizontal
1624 2 : ..textBaseline = textBaseline;
1625 : }
1626 : }
1627 :
1628 : class _AffixText extends StatelessWidget {
1629 1 : const _AffixText({
1630 : required this.labelIsFloating,
1631 : this.text,
1632 : this.style,
1633 : this.child,
1634 : });
1635 :
1636 : final bool labelIsFloating;
1637 : final String? text;
1638 : final TextStyle? style;
1639 : final Widget? child;
1640 :
1641 1 : @override
1642 : Widget build(BuildContext context) {
1643 1 : return DefaultTextStyle.merge(
1644 1 : style: style,
1645 1 : child: AnimatedOpacity(
1646 : duration: _kTransitionDuration,
1647 : curve: _kTransitionCurve,
1648 1 : opacity: labelIsFloating ? 1.0 : 0.0,
1649 1 : child: child ??
1650 1 : (text == null
1651 : ? null
1652 1 : : MongolText(
1653 1 : text!,
1654 1 : style: style,
1655 : )),
1656 : ),
1657 : );
1658 : }
1659 : }
1660 :
1661 : /// Defines the appearance of a Material Design Mongol text field.
1662 : ///
1663 : /// [MongolInputDecorator] displays the visual elements of a Material Design text
1664 : /// Mongol field around its input [child]. The visual elements themselves are defined
1665 : /// by an [InputDecoration] object and their layout and appearance depend
1666 : /// on the `baseStyle`, `textAlign`, `isFocused`, and `isEmpty` parameters.
1667 : ///
1668 : /// [MongolTextField] uses this widget to decorate its [MongolEditableText] child.
1669 : ///
1670 : /// [MongolInputDecorator] can be used to create widgets that look and behave like a
1671 : /// [MongolTextField] but support other kinds of input.
1672 : ///
1673 : /// Requires one of its ancestors to be a [Material] widget.
1674 : ///
1675 : /// See also:
1676 : ///
1677 : /// * [MongolTextField], which uses a [MongolInputDecorator] to display a border,
1678 : /// labels, and icons, around its [MongolEditableText] child.
1679 : /// * [Decoration] and [DecoratedBox], for drawing arbitrary decorations
1680 : /// around other widgets.
1681 : class MongolInputDecorator extends StatefulWidget {
1682 : /// Creates a widget that displays a border, labels, and icons,
1683 : /// for a [MongolTextField].
1684 : ///
1685 : /// The [isFocused], [isHovering], [expands], and [isEmpty] arguments must not
1686 : /// be null.
1687 1 : const MongolInputDecorator({
1688 : Key? key,
1689 : required this.decoration,
1690 : this.baseStyle,
1691 : this.textAlign,
1692 : this.textAlignHorizontal,
1693 : this.isFocused = false,
1694 : this.isHovering = false,
1695 : this.expands = false,
1696 : this.isEmpty = false,
1697 : this.child,
1698 1 : }) : super(key: key);
1699 :
1700 : /// The text and styles to use when decorating the child.
1701 : ///
1702 : /// Null [InputDecoration] properties are initialized with the corresponding
1703 : /// values from [ThemeData.inputDecorationTheme].
1704 : ///
1705 : /// Must not be null.
1706 : final InputDecoration decoration;
1707 :
1708 : /// The style on which to base the label, hint, counter, and error styles
1709 : /// if the [decoration] does not provide explicit styles.
1710 : ///
1711 : /// If null, `baseStyle` defaults to the `subtitle1` style from the
1712 : /// current [Theme], see [ThemeData.textTheme].
1713 : ///
1714 : /// The [TextStyle.textBaseline] of the [baseStyle] is used to determine
1715 : /// the baseline used for text alignment.
1716 : final TextStyle? baseStyle;
1717 :
1718 : /// How the text in the decoration should be aligned vertically.
1719 : final MongolTextAlign? textAlign;
1720 :
1721 : /// How the text should be aligned horizontally.
1722 : ///
1723 : /// Determines the alignment of the baseline within the available space of
1724 : /// the input (typically a MongolTextField). For example, TextAlignHorizontal.left will
1725 : /// place the baseline such that the text, and any attached decoration like
1726 : /// prefix and suffix, is as close to the left side of the input as possible without
1727 : /// overflowing. The widths of the prefix and suffix are similarly included
1728 : /// for other alignment values. If the width is greater than the width
1729 : /// available, then the prefix and suffix will be allowed to overflow first
1730 : /// before the text scrolls.
1731 : final TextAlignHorizontal? textAlignHorizontal;
1732 :
1733 : /// Whether the input field has focus.
1734 : ///
1735 : /// Determines the position of the label text and the color and weight of the
1736 : /// border.
1737 : ///
1738 : /// Defaults to false.
1739 : ///
1740 : /// See also:
1741 : ///
1742 : /// * [InputDecoration.hoverColor], which is also blended into the focus
1743 : /// color and fill color when the [isHovering] is true to produce the final
1744 : /// color.
1745 : final bool isFocused;
1746 :
1747 : /// Whether the input field is being hovered over by a mouse pointer.
1748 : ///
1749 : /// Determines the container fill color, which is a blend of
1750 : /// [InputDecoration.hoverColor] with [InputDecoration.fillColor] when
1751 : /// true, and [InputDecoration.fillColor] when not.
1752 : ///
1753 : /// Defaults to false.
1754 : final bool isHovering;
1755 :
1756 : /// If true, the width of the input field will be as large as possible.
1757 : ///
1758 : /// If wrapped in a widget that constrains its child's width, like Expanded
1759 : /// or SizedBox, the input field will only be affected if [expands] is set to
1760 : /// true.
1761 : ///
1762 : /// See [MongolTextField.minLines] and [MongolTextField.maxLines] for related ways to
1763 : /// affect the width of an input. When [expands] is true, both must be null
1764 : /// in order to avoid ambiguity in determining the width.
1765 : ///
1766 : /// Defaults to false.
1767 : final bool expands;
1768 :
1769 : /// Whether the input field is empty.
1770 : ///
1771 : /// Determines the position of the label text and whether to display the hint
1772 : /// text.
1773 : ///
1774 : /// Defaults to false.
1775 : final bool isEmpty;
1776 :
1777 : /// The widget below this widget in the tree.
1778 : ///
1779 : /// Typically a [MongolEditableText], [DropdownButton], or [InkWell].
1780 : final Widget? child;
1781 :
1782 : /// Whether the label needs to get out of the way of the input, either by
1783 : /// floating or disappearing.
1784 : ///
1785 : /// Will withdraw when not empty, or when focused while enabled.
1786 1 : bool get _labelShouldWithdraw =>
1787 4 : !isEmpty || (isFocused && decoration.enabled);
1788 :
1789 1 : @override
1790 1 : _InputDecoratorState createState() => _InputDecoratorState();
1791 :
1792 : /// The RenderBox that defines this decorator's "container". That's the
1793 : /// area which is filled if [InputDecoration.filled] is true. It's the area
1794 : /// adjacent to [InputDecoration.icon] and to the left of the widgets that contain
1795 : /// [InputDecoration.helperText], [InputDecoration.errorText], and
1796 : /// [InputDecoration.counterText].
1797 : ///
1798 : /// [MongolTextField] renders ink splashes within the container.
1799 0 : static RenderBox? containerOf(BuildContext context) {
1800 0 : final result = context.findAncestorRenderObjectOfType<_RenderDecoration>();
1801 0 : return result?.container;
1802 : }
1803 :
1804 0 : @override
1805 : void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1806 0 : super.debugFillProperties(properties);
1807 : properties
1808 0 : .add(DiagnosticsProperty<InputDecoration>('decoration', decoration));
1809 0 : properties.add(DiagnosticsProperty<TextStyle>('baseStyle', baseStyle,
1810 : defaultValue: null));
1811 0 : properties.add(DiagnosticsProperty<bool>('isFocused', isFocused));
1812 0 : properties.add(
1813 0 : DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
1814 0 : properties.add(DiagnosticsProperty<bool>('isEmpty', isEmpty));
1815 : }
1816 : }
1817 :
1818 : class _InputDecoratorState extends State<MongolInputDecorator>
1819 : with TickerProviderStateMixin {
1820 : late AnimationController _floatingLabelController;
1821 : late AnimationController _shakingLabelController;
1822 : final _InputBorderGap _borderGap = _InputBorderGap();
1823 :
1824 1 : @override
1825 : void initState() {
1826 1 : super.initState();
1827 :
1828 4 : final labelIsInitiallyFloating = widget.decoration.floatingLabelBehavior ==
1829 : FloatingLabelBehavior.always ||
1830 4 : (widget.decoration.floatingLabelBehavior !=
1831 : FloatingLabelBehavior.never &&
1832 2 : widget._labelShouldWithdraw);
1833 :
1834 2 : _floatingLabelController = AnimationController(
1835 : duration: _kTransitionDuration,
1836 : vsync: this,
1837 : value: labelIsInitiallyFloating ? 1.0 : 0.0);
1838 3 : _floatingLabelController.addListener(_handleChange);
1839 :
1840 2 : _shakingLabelController = AnimationController(
1841 : duration: _kTransitionDuration,
1842 : vsync: this,
1843 : );
1844 : }
1845 :
1846 1 : @override
1847 : void didChangeDependencies() {
1848 1 : super.didChangeDependencies();
1849 1 : _effectiveDecoration = null;
1850 : }
1851 :
1852 1 : @override
1853 : void dispose() {
1854 2 : _floatingLabelController.dispose();
1855 2 : _shakingLabelController.dispose();
1856 1 : super.dispose();
1857 : }
1858 :
1859 1 : void _handleChange() {
1860 2 : setState(() {
1861 : // The _floatingLabelController's value has changed.
1862 : });
1863 : }
1864 :
1865 : InputDecoration? _effectiveDecoration;
1866 1 : InputDecoration? get decoration {
1867 4 : _effectiveDecoration ??= widget.decoration.applyDefaults(
1868 3 : Theme.of(context).inputDecorationTheme,
1869 : );
1870 1 : return _effectiveDecoration;
1871 : }
1872 :
1873 3 : MongolTextAlign? get textAlign => widget.textAlign;
1874 3 : bool get isFocused => widget.isFocused;
1875 5 : bool get isHovering => widget.isHovering && decoration!.enabled;
1876 3 : bool get isEmpty => widget.isEmpty;
1877 1 : bool get _floatingLabelEnabled {
1878 3 : return decoration!.floatingLabelBehavior != FloatingLabelBehavior.never;
1879 : }
1880 :
1881 1 : @override
1882 : void didUpdateWidget(MongolInputDecorator old) {
1883 1 : super.didUpdateWidget(old);
1884 5 : if (widget.decoration != old.decoration) _effectiveDecoration = null;
1885 :
1886 4 : final floatBehaviorChanged = widget.decoration.floatingLabelBehavior !=
1887 2 : old.decoration.floatingLabelBehavior;
1888 :
1889 4 : if (widget._labelShouldWithdraw != old._labelShouldWithdraw ||
1890 : floatBehaviorChanged) {
1891 1 : if (_floatingLabelEnabled &&
1892 2 : (widget._labelShouldWithdraw ||
1893 4 : widget.decoration.floatingLabelBehavior ==
1894 : FloatingLabelBehavior.always)) {
1895 2 : _floatingLabelController.forward();
1896 : } else {
1897 2 : _floatingLabelController.reverse();
1898 : }
1899 : }
1900 :
1901 2 : final String? errorText = decoration!.errorText;
1902 2 : final String? oldErrorText = old.decoration.errorText;
1903 :
1904 2 : if (_floatingLabelController.isCompleted &&
1905 : errorText != null &&
1906 0 : errorText != oldErrorText) {
1907 0 : _shakingLabelController
1908 0 : ..value = 0.0
1909 0 : ..forward();
1910 : }
1911 : }
1912 :
1913 1 : Color _getActiveColor(ThemeData themeData) {
1914 1 : if (isFocused) {
1915 1 : switch (themeData.brightness) {
1916 1 : case Brightness.dark:
1917 0 : return themeData.accentColor;
1918 1 : case Brightness.light:
1919 1 : return themeData.primaryColor;
1920 : }
1921 : }
1922 1 : return themeData.hintColor;
1923 : }
1924 :
1925 1 : Color _getDefaultBorderColor(ThemeData themeData) {
1926 1 : if (isFocused) {
1927 1 : switch (themeData.brightness) {
1928 1 : case Brightness.dark:
1929 0 : return themeData.accentColor;
1930 1 : case Brightness.light:
1931 1 : return themeData.primaryColor;
1932 : }
1933 : }
1934 2 : if (decoration!.filled!) {
1935 0 : return themeData.hintColor;
1936 : }
1937 3 : final enabledColor = themeData.colorScheme.onSurface.withOpacity(0.38);
1938 1 : if (isHovering) {
1939 2 : final hoverColor = decoration!.hoverColor ??
1940 2 : themeData.inputDecorationTheme.hoverColor ??
1941 1 : themeData.hoverColor;
1942 2 : return Color.alphaBlend(hoverColor.withOpacity(0.12), enabledColor);
1943 : }
1944 : return enabledColor;
1945 : }
1946 :
1947 1 : Color _getFillColor(ThemeData themeData) {
1948 3 : if (decoration!.filled != true) // filled == null same as filled == false
1949 : {
1950 : return Colors.transparent;
1951 : }
1952 0 : if (decoration!.fillColor != null) return decoration!.fillColor!;
1953 :
1954 : // dark theme: 10% white (enabled), 5% white (disabled)
1955 : // light theme: 4% black (enabled), 2% black (disabled)
1956 : const darkEnabled = Color(0x1AFFFFFF);
1957 : const darkDisabled = Color(0x0DFFFFFF);
1958 : const lightEnabled = Color(0x0A000000);
1959 : const lightDisabled = Color(0x05000000);
1960 :
1961 0 : switch (themeData.brightness) {
1962 0 : case Brightness.dark:
1963 0 : return decoration!.enabled ? darkEnabled : darkDisabled;
1964 0 : case Brightness.light:
1965 0 : return decoration!.enabled ? lightEnabled : lightDisabled;
1966 : }
1967 : }
1968 :
1969 1 : Color _getHoverColor(ThemeData themeData) {
1970 2 : if (decoration!.filled == null ||
1971 2 : !decoration!.filled! ||
1972 0 : isFocused ||
1973 0 : !decoration!.enabled) return Colors.transparent;
1974 0 : return decoration!.hoverColor ??
1975 0 : themeData.inputDecorationTheme.hoverColor ??
1976 0 : themeData.hoverColor;
1977 : }
1978 :
1979 1 : Color _getDefaultIconColor(ThemeData themeData) {
1980 4 : if (!decoration!.enabled && !isFocused) return themeData.disabledColor;
1981 :
1982 1 : switch (themeData.brightness) {
1983 1 : case Brightness.dark:
1984 : return Colors.white70;
1985 1 : case Brightness.light:
1986 : return Colors.black45;
1987 : }
1988 : }
1989 :
1990 : // True if the label will be shown and the hint will not.
1991 : // If we're not focused, there's no value, labelText was provided, and
1992 : // floatingLabelBehavior isn't set to always, then the label appears where the
1993 : // hint would.
1994 1 : bool get _hasInlineLabel {
1995 2 : return !widget._labelShouldWithdraw &&
1996 2 : decoration!.labelText != null &&
1997 3 : decoration!.floatingLabelBehavior != FloatingLabelBehavior.always;
1998 : }
1999 :
2000 : // If the label is a floating placeholder, it's always shown.
2001 3 : bool get _shouldShowLabel => _hasInlineLabel || _floatingLabelEnabled;
2002 :
2003 : // The base style for the inline label or hint when they're displayed "inline",
2004 : // i.e. when they appear in place of the empty text field.
2005 1 : TextStyle _getInlineStyle(ThemeData themeData) {
2006 6 : return themeData.textTheme.subtitle1!.merge(widget.baseStyle).copyWith(
2007 2 : color: decoration!.enabled
2008 1 : ? themeData.hintColor
2009 1 : : themeData.disabledColor);
2010 : }
2011 :
2012 1 : TextStyle _getFloatingLabelStyle(ThemeData themeData) {
2013 2 : final Color color = decoration!.errorText != null
2014 0 : ? decoration!.errorStyle?.color ?? themeData.errorColor
2015 1 : : _getActiveColor(themeData);
2016 5 : final style = themeData.textTheme.subtitle1!.merge(widget.baseStyle);
2017 : // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
2018 : // Setting TextStyle.height to 1 ensures that the label's width (in
2019 : // vertical orientation) will equal its font size.
2020 1 : return themeData.fixTextFieldOutlineLabel
2021 : ? style
2022 0 : .copyWith(
2023 : height: 1,
2024 0 : color: decoration!.enabled ? color : themeData.disabledColor)
2025 0 : .merge(decoration!.labelStyle)
2026 : : style
2027 1 : .copyWith(
2028 2 : color: decoration!.enabled ? color : themeData.disabledColor)
2029 3 : .merge(decoration!.labelStyle);
2030 : }
2031 :
2032 1 : TextStyle _getHelperStyle(ThemeData themeData) {
2033 : final color =
2034 3 : decoration!.enabled ? themeData.hintColor : Colors.transparent;
2035 2 : return themeData.textTheme.caption!
2036 1 : .copyWith(color: color)
2037 3 : .merge(decoration!.helperStyle);
2038 : }
2039 :
2040 1 : TextStyle _getErrorStyle(ThemeData themeData) {
2041 : final color =
2042 3 : decoration!.enabled ? themeData.errorColor : Colors.transparent;
2043 2 : return themeData.textTheme.caption!
2044 1 : .copyWith(color: color)
2045 3 : .merge(decoration!.errorStyle);
2046 : }
2047 :
2048 1 : InputBorder _getDefaultBorder(ThemeData themeData) {
2049 4 : if (decoration!.border?.borderSide == BorderSide.none) {
2050 0 : return decoration!.border!;
2051 : }
2052 :
2053 : final Color borderColor;
2054 3 : if (decoration!.enabled || isFocused) {
2055 2 : borderColor = decoration!.errorText == null
2056 1 : ? _getDefaultBorderColor(themeData)
2057 1 : : themeData.errorColor;
2058 : } else {
2059 : borderColor =
2060 3 : (decoration!.filled == true && decoration!.border?.isOutline != true)
2061 : ? Colors.transparent
2062 1 : : themeData.disabledColor;
2063 : }
2064 :
2065 : final double borderWeight;
2066 2 : if (decoration!.isCollapsed ||
2067 3 : decoration?.border == InputBorder.none ||
2068 2 : !decoration!.enabled) {
2069 : borderWeight = 0.0;
2070 : } else {
2071 1 : borderWeight = isFocused ? 2.0 : 1.0;
2072 : }
2073 :
2074 2 : final border = decoration!.border ?? const SidelineInputBorder();
2075 1 : return border.copyWith(
2076 1 : borderSide: BorderSide(color: borderColor, width: borderWeight));
2077 : }
2078 :
2079 1 : @override
2080 : Widget build(BuildContext context) {
2081 1 : final themeData = Theme.of(context);
2082 1 : final inlineStyle = _getInlineStyle(themeData);
2083 1 : final textBaseline = inlineStyle.textBaseline!;
2084 :
2085 3 : final hintStyle = inlineStyle.merge(decoration!.hintStyle);
2086 2 : final Widget? hint = decoration!.hintText == null
2087 : ? null
2088 1 : : AnimatedOpacity(
2089 2 : opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0,
2090 : duration: _kTransitionDuration,
2091 : curve: _kTransitionCurve,
2092 : alwaysIncludeSemantics: true,
2093 1 : child: MongolText(
2094 2 : decoration!.hintText!,
2095 : style: hintStyle,
2096 : overflow: TextOverflow.ellipsis,
2097 1 : textAlign: textAlign,
2098 2 : maxLines: decoration!.hintMaxLines,
2099 : ),
2100 : );
2101 :
2102 2 : final isError = decoration!.errorText != null;
2103 : InputBorder? border;
2104 2 : if (!decoration!.enabled) {
2105 2 : border = isError ? decoration!.errorBorder : decoration!.disabledBorder;
2106 1 : } else if (isFocused) {
2107 : border =
2108 2 : isError ? decoration!.focusedErrorBorder : decoration!.focusedBorder;
2109 : } else {
2110 4 : border = isError ? decoration!.errorBorder : decoration!.enabledBorder;
2111 : }
2112 1 : border ??= _getDefaultBorder(themeData);
2113 :
2114 1 : final Widget container = _BorderContainer(
2115 : border: border,
2116 1 : gap: _borderGap,
2117 2 : gapAnimation: _floatingLabelController.view,
2118 1 : fillColor: _getFillColor(themeData),
2119 1 : hoverColor: _getHoverColor(themeData),
2120 1 : isHovering: isHovering,
2121 : );
2122 :
2123 : // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
2124 : // Setting TextStyle.height to 1 ensures that the label's width (
2125 : // in vertical orientation) will equal its font size.
2126 1 : final inlineLabelStyle = themeData.fixTextFieldOutlineLabel
2127 0 : ? inlineStyle.merge(decoration!.labelStyle).copyWith(height: 1)
2128 3 : : inlineStyle.merge(decoration!.labelStyle);
2129 2 : final Widget? label = decoration!.labelText == null
2130 : ? null
2131 1 : : _Shaker(
2132 2 : animation: _shakingLabelController.view,
2133 1 : child: AnimatedOpacity(
2134 : duration: _kTransitionDuration,
2135 : curve: _kTransitionCurve,
2136 1 : opacity: _shouldShowLabel ? 1.0 : 0.0,
2137 1 : child: AnimatedDefaultTextStyle(
2138 : duration: _kTransitionDuration,
2139 : curve: _kTransitionCurve,
2140 2 : style: widget._labelShouldWithdraw
2141 1 : ? _getFloatingLabelStyle(themeData)
2142 : : inlineLabelStyle,
2143 1 : child: MongolText(
2144 2 : decoration!.labelText!,
2145 : overflow: TextOverflow.ellipsis,
2146 1 : textAlign: textAlign,
2147 : ),
2148 : ),
2149 : ),
2150 : );
2151 :
2152 : final Widget? prefix =
2153 4 : decoration!.prefix == null && decoration!.prefixText == null
2154 : ? null
2155 1 : : _AffixText(
2156 2 : labelIsFloating: widget._labelShouldWithdraw,
2157 2 : text: decoration!.prefixText,
2158 2 : style: decoration!.prefixStyle ?? hintStyle,
2159 2 : child: decoration!.prefix,
2160 : );
2161 :
2162 : final Widget? suffix =
2163 4 : decoration!.suffix == null && decoration!.suffixText == null
2164 : ? null
2165 1 : : _AffixText(
2166 2 : labelIsFloating: widget._labelShouldWithdraw,
2167 2 : text: decoration!.suffixText,
2168 2 : style: decoration!.suffixStyle ?? hintStyle,
2169 2 : child: decoration!.suffix,
2170 : );
2171 :
2172 1 : final activeColor = _getActiveColor(themeData);
2173 : final decorationIsDense =
2174 3 : decoration!.isDense == true; // isDense == null, same as false
2175 : final iconSize = decorationIsDense ? 18.0 : 24.0;
2176 2 : final iconColor = isFocused ? activeColor : _getDefaultIconColor(themeData);
2177 :
2178 2 : final Widget? icon = decoration!.icon == null
2179 : ? null
2180 1 : : Padding(
2181 : padding: const EdgeInsetsDirectional.only(end: 16.0),
2182 1 : child: IconTheme.merge(
2183 1 : data: IconThemeData(
2184 : color: iconColor,
2185 : size: iconSize,
2186 : ),
2187 2 : child: decoration!.icon!,
2188 : ),
2189 : );
2190 :
2191 2 : final Widget? prefixIcon = decoration!.prefixIcon == null
2192 : ? null
2193 0 : : Center(
2194 : widthFactor: 1.0,
2195 : heightFactor: 1.0,
2196 0 : child: ConstrainedBox(
2197 0 : constraints: decoration!.prefixIconConstraints ??
2198 0 : themeData.visualDensity.effectiveConstraints(
2199 : const BoxConstraints(
2200 : minWidth: kMinInteractiveDimension,
2201 : minHeight: kMinInteractiveDimension,
2202 : ),
2203 : ),
2204 0 : child: IconTheme.merge(
2205 0 : data: IconThemeData(
2206 : color: iconColor,
2207 : size: iconSize,
2208 : ),
2209 0 : child: decoration!.prefixIcon!,
2210 : ),
2211 : ),
2212 : );
2213 :
2214 2 : final Widget? suffixIcon = decoration!.suffixIcon == null
2215 : ? null
2216 1 : : Center(
2217 : widthFactor: 1.0,
2218 : heightFactor: 1.0,
2219 1 : child: ConstrainedBox(
2220 2 : constraints: decoration!.suffixIconConstraints ??
2221 2 : themeData.visualDensity.effectiveConstraints(
2222 : const BoxConstraints(
2223 : minWidth: kMinInteractiveDimension,
2224 : minHeight: kMinInteractiveDimension,
2225 : ),
2226 : ),
2227 1 : child: IconTheme.merge(
2228 1 : data: IconThemeData(
2229 : color: iconColor,
2230 : size: iconSize,
2231 : ),
2232 2 : child: decoration!.suffixIcon!,
2233 : ),
2234 : ),
2235 : );
2236 :
2237 1 : final Widget helperError = _HelperError(
2238 1 : textAlign: textAlign,
2239 2 : helperText: decoration!.helperText,
2240 1 : helperStyle: _getHelperStyle(themeData),
2241 2 : helperMaxLines: decoration!.helperMaxLines,
2242 2 : errorText: decoration!.errorText,
2243 1 : errorStyle: _getErrorStyle(themeData),
2244 2 : errorMaxLines: decoration!.errorMaxLines,
2245 : );
2246 :
2247 : Widget? counter;
2248 2 : if (decoration!.counter != null) {
2249 0 : counter = decoration!.counter;
2250 2 : } else if (decoration!.counterText != null &&
2251 3 : decoration!.counterText != '') {
2252 1 : counter = Semantics(
2253 : container: true,
2254 1 : liveRegion: isFocused,
2255 1 : child: Text(
2256 2 : decoration!.counterText!,
2257 4 : style: _getHelperStyle(themeData).merge(decoration!.counterStyle),
2258 : overflow: TextOverflow.ellipsis,
2259 2 : semanticsLabel: decoration!.semanticCounterText,
2260 : ),
2261 : );
2262 : }
2263 :
2264 : // The _Decoration widget and _RenderDecoration assume that contentPadding
2265 : // has been resolved to EdgeInsets.
2266 : const textDirection = TextDirection.ltr;
2267 : final decorationContentPadding =
2268 2 : decoration!.contentPadding?.resolve(textDirection);
2269 :
2270 : final EdgeInsets contentPadding;
2271 : final double floatingLabelWidth;
2272 2 : if (decoration!.isCollapsed) {
2273 : floatingLabelWidth = 0.0;
2274 : contentPadding = decorationContentPadding ?? EdgeInsets.zero;
2275 1 : } else if (!border.isOutline) {
2276 : // 4.0: the horizontal gap between the inline elements and the floating label.
2277 4 : floatingLabelWidth = (4.0 + 0.75 * inlineLabelStyle.fontSize!) *
2278 1 : MediaQuery.textScaleFactorOf(context);
2279 3 : if (decoration!.filled == true) {
2280 : // filled == null same as filled == false
2281 : contentPadding = decorationContentPadding ??
2282 : (decorationIsDense
2283 : ? const EdgeInsets.fromLTRB(8.0, 12.0, 8.0, 12.0)
2284 : : const EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 12.0));
2285 : } else {
2286 : // Not top or bottom padding for sideline borders that aren't filled
2287 : // is a small concession to backwards compatibility. This eliminates
2288 : // the most noticeable layout change introduced by #13734.
2289 : contentPadding = decorationContentPadding ??
2290 : (decorationIsDense
2291 : ? const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.0)
2292 : : const EdgeInsets.fromLTRB(12.0, 0.0, 12.0, 0.0));
2293 : }
2294 : } else {
2295 : floatingLabelWidth = 0.0;
2296 : contentPadding = decorationContentPadding ??
2297 : (decorationIsDense
2298 : ? const EdgeInsets.fromLTRB(20.0, 12.0, 12.0, 12.0)
2299 : : const EdgeInsets.fromLTRB(24.0, 12.0, 16.0, 12.0));
2300 : }
2301 :
2302 1 : return _Decorator(
2303 1 : decoration: _Decoration(
2304 : contentPadding: contentPadding,
2305 2 : isCollapsed: decoration!.isCollapsed,
2306 : floatingLabelWidth: floatingLabelWidth,
2307 2 : floatingLabelProgress: _floatingLabelController.value,
2308 : border: border,
2309 1 : borderGap: _borderGap,
2310 2 : alignLabelWithHint: decoration!.alignLabelWithHint ?? false,
2311 2 : isDense: decoration!.isDense,
2312 1 : visualDensity: themeData.visualDensity,
2313 : icon: icon,
2314 2 : input: widget.child,
2315 : label: label,
2316 : hint: hint,
2317 : prefix: prefix,
2318 : suffix: suffix,
2319 : prefixIcon: prefixIcon,
2320 : suffixIcon: suffixIcon,
2321 : helperError: helperError,
2322 : counter: counter,
2323 : container: container,
2324 1 : fixTextFieldOutlineLabel: themeData.fixTextFieldOutlineLabel,
2325 : ),
2326 : textBaseline: textBaseline,
2327 2 : textAlignHorizontal: widget.textAlignHorizontal,
2328 1 : isFocused: isFocused,
2329 2 : expands: widget.expands,
2330 : );
2331 : }
2332 : }
|