overlay.dart 4.87 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'basic.dart';
import 'framework.dart';

8
/// A place in the overlay that can contain a widget.
9 10
class OverlayEntry {
  OverlayEntry({
11
    this.builder,
12
    bool opaque: false
13
  }) : _opaque = opaque;
14

15
  /// This entry will include the widget built by this builder in the overlay at the entry's position.
16
  final WidgetBuilder builder;
17

18 19 20 21
  /// Whether this entry occludes the entire overlay.
  ///
  /// If an entry claims to be opaque, the overlay will skip building all the
  /// entries below that entry for efficiency.
22 23
  bool get opaque => _opaque;
  bool _opaque;
24
  void set opaque (bool value) {
25
    assert(_overlay != null);
Adam Barth's avatar
Adam Barth committed
26
    if (_opaque == value)
27
      return;
28 29 30
    _overlay.setState(() {
      _opaque = value;
    });
31 32
  }

33 34
  OverlayState _overlay;
  final GlobalKey _key = new GlobalKey();
35

36
  /// Remove this entry from the overlay.
37
  void remove() {
38 39
    _overlay?._remove(this);
    _overlay = null;
40
  }
41

42 43 44
  /// Cause this entry to rebuild during the next pipeline flush.
  ///
  /// You need to call this function if the output of [builder] has changed.
45
  void markNeedsBuild() {
46
    _key.currentState?.setState(() { /* the state that changed is in the builder */ });
47
  }
48 49

  String toString() => '$runtimeType@$hashCode(opaque: $opaque)';
50 51
}

52 53 54 55 56 57 58 59 60
class _OverlayEntry extends StatefulComponent {
  _OverlayEntry(OverlayEntry entry) : entry = entry, super(key: entry._key);
  final OverlayEntry entry;
  _OverlayEntryState createState() => new _OverlayEntryState();
}
class _OverlayEntryState extends State<_OverlayEntry> {
  Widget build(BuildContext context) => config.entry.builder(context);
}

61
/// A [Stack] of entries that can be managed independently.
62 63 64 65 66 67
class Overlay extends StatefulComponent {
  Overlay({
    Key key,
    this.initialEntries
  }) : super(key: key);

68
  /// The entries to include in the overlay initially.
69 70
  final List<OverlayEntry> initialEntries;

71
  /// The state from the closest instance of this class that encloses the given context.
Ian Hickson's avatar
Ian Hickson committed
72
  static OverlayState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<OverlayState>());
Hixie's avatar
Hixie committed
73

74 75 76
  OverlayState createState() => new OverlayState();
}

77
/// The current state of an [Overlay].
78 79 80 81 82
class OverlayState extends State<Overlay> {
  final List<OverlayEntry> _entries = new List<OverlayEntry>();

  void initState() {
    super.initState();
83
    insertAll(config.initialEntries);
84 85
  }

86 87 88 89
  /// Insert the given entry into the overlay.
  ///
  /// If [above] is non-null, the entry is inserted just above [above].
  /// Otherwise, the entry is inserted on top.
90
  void insert(OverlayEntry entry, { OverlayEntry above }) {
91 92 93
    assert(entry._overlay == null);
    assert(above == null || (above._overlay == this && _entries.contains(above)));
    entry._overlay = this;
94 95 96 97 98 99
    setState(() {
      int index = above == null ? _entries.length : _entries.indexOf(above) + 1;
      _entries.insert(index, entry);
    });
  }

100 101 102 103
  /// Insert all the entries in the given iterable.
  ///
  /// If [above] is non-null, the entries are inserted just above [above].
  /// Otherwise, the entries are inserted on top.
104
  void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry above }) {
105
    assert(above == null || (above._overlay == this && _entries.contains(above)));
106
    for (OverlayEntry entry in entries) {
107 108
      assert(entry._overlay == null);
      entry._overlay = this;
109 110 111 112 113 114 115
    }
    setState(() {
      int index = above == null ? _entries.length : _entries.indexOf(above) + 1;
      _entries.insertAll(index, entries);
    });
  }

116 117 118 119 120 121
  void _remove(OverlayEntry entry) {
    setState(() {
      _entries.remove(entry);
    });
  }

122 123
  /// (DEBUG ONLY) Check whether a given entry is visible (i.e., not behind an opaque entry).
  ///
124 125 126
  /// This is an O(N) algorithm, and should not be necessary except for debug
  /// asserts. To avoid people depending on it, this function is implemented
  /// only in checked mode.
127 128 129 130 131 132 133 134 135 136
  bool debugIsVisible(OverlayEntry entry) {
    bool result = false;
    assert(_entries.contains(entry));
    assert(() {
      for (int i = _entries.length - 1; i > 0; i -= 1) {
        OverlayEntry candidate = _entries[i];
        if (candidate == entry) {
          result = true;
          break;
        }
Hixie's avatar
Hixie committed
137
        if (candidate.opaque)
138 139 140 141 142 143 144
          break;
      }
      return true;
    });
    return result;
  }

145 146 147 148 149
  Widget build(BuildContext context) {
    List<Widget> backwardsChildren = <Widget>[];

    for (int i = _entries.length - 1; i >= 0; --i) {
      OverlayEntry entry = _entries[i];
150
      backwardsChildren.add(new _OverlayEntry(entry));
151 152 153 154 155 156
      if (entry.opaque)
        break;
    }

    return new Stack(backwardsChildren.reversed.toList(growable: false));
  }
157 158 159 160 161

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    description.add('entries: $_entries');
  }
162
}