您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
482 行
18 KiB
482 行
18 KiB
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.scheduler;
|
|
|
|
namespace Unity.UIWidgets.widgets {
|
|
public class OverlayEntry {
|
|
public OverlayEntry(WidgetBuilder builder = null, bool opaque = false, bool maintainState = false) {
|
|
D.assert(builder != null);
|
|
this._opaque = opaque;
|
|
this._maintainState = maintainState;
|
|
this.builder = builder;
|
|
}
|
|
|
|
public readonly WidgetBuilder builder;
|
|
|
|
bool _opaque;
|
|
|
|
public bool opaque {
|
|
get { return this._opaque; }
|
|
set {
|
|
if (this._opaque == value) {
|
|
return;
|
|
}
|
|
|
|
this._opaque = value;
|
|
D.assert(this._overlay != null);
|
|
this._overlay._didChangeEntryOpacity();
|
|
}
|
|
}
|
|
|
|
bool _maintainState;
|
|
|
|
public bool maintainState {
|
|
get { return this._maintainState; }
|
|
set {
|
|
if (this._maintainState == value) {
|
|
return;
|
|
}
|
|
|
|
this._maintainState = value;
|
|
D.assert(this._overlay != null);
|
|
this._overlay._didChangeEntryOpacity();
|
|
}
|
|
}
|
|
|
|
internal OverlayState _overlay;
|
|
|
|
internal readonly GlobalKey<_OverlayEntryState> _key = new LabeledGlobalKey<_OverlayEntryState>();
|
|
|
|
public void remove() {
|
|
D.assert(this._overlay != null);
|
|
OverlayState overlay = this._overlay;
|
|
this._overlay = null;
|
|
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
|
|
SchedulerBinding.instance.addPostFrameCallback((duration) => { overlay._remove(this); });
|
|
}
|
|
else {
|
|
overlay._remove(this);
|
|
}
|
|
}
|
|
|
|
public void markNeedsBuild() {
|
|
this._key.currentState?._markNeedsBuild();
|
|
}
|
|
|
|
public override string ToString() {
|
|
return $"{Diagnostics.describeIdentity(this)}(opaque: {this.opaque}; maintainState: {this.maintainState})";
|
|
}
|
|
}
|
|
|
|
|
|
class _OverlayEntry : StatefulWidget {
|
|
internal _OverlayEntry(OverlayEntry entry) : base(key: entry._key) {
|
|
D.assert(entry != null);
|
|
this.entry = entry;
|
|
}
|
|
|
|
public readonly OverlayEntry entry;
|
|
|
|
public override State createState() {
|
|
return new _OverlayEntryState();
|
|
}
|
|
}
|
|
|
|
class _OverlayEntryState : State<_OverlayEntry> {
|
|
public override Widget build(BuildContext context) {
|
|
return this.widget.entry.builder(context);
|
|
}
|
|
|
|
internal void _markNeedsBuild() {
|
|
this.setState(() => {
|
|
/* the state that changed is in the builder */
|
|
});
|
|
}
|
|
}
|
|
|
|
public class Overlay : StatefulWidget {
|
|
public Overlay(Key key = null, List<OverlayEntry> initialEntries = null) : base(key) {
|
|
D.assert(initialEntries != null);
|
|
this.initialEntries = initialEntries;
|
|
}
|
|
|
|
public readonly List<OverlayEntry> initialEntries;
|
|
|
|
public static OverlayState of(BuildContext context, Widget debugRequiredFor = null) {
|
|
OverlayState result = (OverlayState) context.ancestorStateOfType(new TypeMatcher<OverlayState>());
|
|
D.assert(() => {
|
|
if (debugRequiredFor != null && result == null) {
|
|
var additional = context.widget != debugRequiredFor
|
|
? $"\nThe context from which that widget was searching for an overlay was:\n {context}"
|
|
: "";
|
|
throw new UIWidgetsError(
|
|
"No Overlay widget found.\n" +
|
|
$"{debugRequiredFor.GetType()} widgets require an Overlay widget ancestor for correct operation.\n" +
|
|
"The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.\n" +
|
|
"The specific widget that failed to find an overlay was:\n" +
|
|
$" {debugRequiredFor}" +
|
|
$"{additional}"
|
|
);
|
|
}
|
|
|
|
return true;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
public override State createState() {
|
|
return new OverlayState();
|
|
}
|
|
}
|
|
|
|
public class OverlayState : TickerProviderStateMixin<Overlay> {
|
|
readonly List<OverlayEntry> _entries = new List<OverlayEntry>();
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
this.insertAll(this.widget.initialEntries);
|
|
}
|
|
|
|
internal int _insertionIndex(OverlayEntry below, OverlayEntry above) {
|
|
D.assert(below == null || above == null);
|
|
if (below != null) {
|
|
return this._entries.IndexOf(below);
|
|
}
|
|
|
|
if (above != null) {
|
|
return this._entries.IndexOf(above) + 1;
|
|
}
|
|
|
|
return this._entries.Count;
|
|
}
|
|
|
|
public void insert(OverlayEntry entry, OverlayEntry below = null, OverlayEntry above = null) {
|
|
D.assert(above == null || below == null, () => "Only one of `above` and `below` may be specified.");
|
|
D.assert(above == null || (above._overlay == this && this._entries.Contains(above)),
|
|
() => "The provided entry for `above` is not present in the Overlay.");
|
|
D.assert(below == null || (below._overlay == this && this._entries.Contains(below)),
|
|
() => "The provided entry for `below` is not present in the Overlay.");
|
|
D.assert(!this._entries.Contains(entry), () => "The specified entry is already present in the Overlay.");
|
|
D.assert(entry._overlay == null, () => "The specified entry is already present in another Overlay.");
|
|
entry._overlay = this;
|
|
this.setState(() => { this._entries.Insert(this._insertionIndex(below, above), entry); });
|
|
}
|
|
|
|
public void insertAll(ICollection<OverlayEntry> entries, OverlayEntry below = null, OverlayEntry above = null) {
|
|
D.assert(above == null || below == null, () => "Only one of `above` and `below` may be specified.");
|
|
D.assert(above == null || (above._overlay == this && this._entries.Contains(above)),
|
|
() => "The provided entry for `above` is not present in the Overlay.");
|
|
D.assert(below == null || (below._overlay == this && this._entries.Contains(below)),
|
|
() => "The provided entry for `below` is not present in the Overlay.");
|
|
D.assert(entries.All(entry => !this._entries.Contains(entry)),
|
|
() => "One or more of the specified entries are already present in the Overlay.");
|
|
D.assert(entries.All(entry => entry._overlay == null),
|
|
() => "One or more of the specified entries are already present in another Overlay.");
|
|
if (entries.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
foreach (OverlayEntry entry in entries) {
|
|
D.assert(entry._overlay == null);
|
|
entry._overlay = this;
|
|
}
|
|
|
|
this.setState(() => {
|
|
this._entries.InsertRange(this._insertionIndex(below, above), entries);
|
|
});
|
|
}
|
|
|
|
public void rearrange(IEnumerable<OverlayEntry> newEntries, OverlayEntry below = null, OverlayEntry above = null) {
|
|
List<OverlayEntry> newEntriesList =
|
|
newEntries is List<OverlayEntry> ?(newEntries as List<OverlayEntry>) : newEntries.ToList();
|
|
D.assert(above == null || below == null, () => "Only one of `above` and `below` may be specified.");
|
|
D.assert(above == null || (above._overlay == this && this._entries.Contains(above)),
|
|
() => "The provided entry for `above` is not present in the Overlay.");
|
|
D.assert(below == null || (below._overlay == this && this._entries.Contains(below)),
|
|
() => "The provided entry for `below` is not present in the Overlay.");
|
|
D.assert(newEntriesList.All(entry => !this._entries.Contains(entry)),
|
|
() => "One or more of the specified entries are already present in the Overlay.");
|
|
D.assert(newEntriesList.All(entry => entry._overlay == null),
|
|
() => "One or more of the specified entries are already present in another Overlay.");
|
|
if (newEntriesList.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
if (this._entries.SequenceEqual(newEntriesList)) {
|
|
return;
|
|
}
|
|
|
|
HashSet<OverlayEntry> old = new HashSet<OverlayEntry>(this._entries);
|
|
foreach(OverlayEntry entry in newEntriesList) {
|
|
entry._overlay = entry._overlay ?? this;
|
|
}
|
|
this.setState(() => {
|
|
this._entries.Clear();
|
|
this._entries.AddRange(newEntriesList);
|
|
foreach (OverlayEntry entry in newEntriesList) {
|
|
old.Remove(entry);
|
|
}
|
|
|
|
this._entries.InsertRange(this._insertionIndex(below, above), old);
|
|
});
|
|
}
|
|
|
|
internal void _remove(OverlayEntry entry) {
|
|
if (this.mounted) {
|
|
this.setState(() => { this._entries.Remove(entry); });
|
|
}
|
|
}
|
|
|
|
public bool debugIsVisible(OverlayEntry entry) {
|
|
bool result = false;
|
|
D.assert(this._entries.Contains(entry));
|
|
D.assert(() => {
|
|
for (int i = this._entries.Count - 1; i > 0; i -= 1) {
|
|
// todo why not including 0?
|
|
OverlayEntry candidate = this._entries[i];
|
|
if (candidate == entry) {
|
|
result = true;
|
|
break;
|
|
}
|
|
|
|
if (candidate.opaque) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
internal void _didChangeEntryOpacity() {
|
|
this.setState(() => { });
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
var onstageChildren = new List<Widget>();
|
|
var offstageChildren = new List<Widget>();
|
|
var onstage = true;
|
|
for (var i = this._entries.Count - 1; i >= 0; i -= 1) {
|
|
var entry = this._entries[i];
|
|
if (onstage) {
|
|
onstageChildren.Add(new _OverlayEntry(entry));
|
|
if (entry.opaque) {
|
|
onstage = false;
|
|
}
|
|
}
|
|
else if (entry.maintainState) {
|
|
offstageChildren.Add(new TickerMode(enabled: false, child: new _OverlayEntry(entry)));
|
|
}
|
|
}
|
|
|
|
onstageChildren.Reverse();
|
|
return new _Theatre(
|
|
onstage: new Stack(
|
|
fit: StackFit.expand,
|
|
children: onstageChildren
|
|
),
|
|
offstage: offstageChildren
|
|
);
|
|
}
|
|
}
|
|
|
|
class _Theatre : RenderObjectWidget {
|
|
internal _Theatre(Stack onstage = null, List<Widget> offstage = null) {
|
|
D.assert(offstage != null);
|
|
D.assert(!offstage.Any((child) => child == null));
|
|
this.onstage = onstage;
|
|
this.offstage = offstage;
|
|
}
|
|
|
|
public readonly Stack onstage;
|
|
|
|
public readonly List<Widget> offstage;
|
|
|
|
public override Element createElement() {
|
|
return new _TheatreElement(this);
|
|
}
|
|
|
|
public override RenderObject createRenderObject(BuildContext context) {
|
|
return new _RenderTheatre();
|
|
}
|
|
}
|
|
|
|
class _TheatreElement : RenderObjectElement {
|
|
public _TheatreElement(RenderObjectWidget widget) : base(widget) {
|
|
D.assert(!WidgetsD.debugChildrenHaveDuplicateKeys(widget, ((_Theatre) widget).offstage));
|
|
}
|
|
|
|
public new _Theatre widget {
|
|
get { return (_Theatre) base.widget; }
|
|
}
|
|
|
|
public new _RenderTheatre renderObject {
|
|
get { return (_RenderTheatre) base.renderObject; }
|
|
}
|
|
|
|
Element _onstage;
|
|
static readonly object _onstageSlot = new object();
|
|
|
|
List<Element> _offstage;
|
|
readonly HashSet<Element> _forgottenOffstageChildren = new HashSet<Element>();
|
|
|
|
|
|
protected override void insertChildRenderObject(RenderObject child, object slot) {
|
|
D.assert(this.renderObject.debugValidateChild(child));
|
|
if (slot == _onstageSlot) {
|
|
D.assert(child is RenderStack);
|
|
this.renderObject.child = (RenderStack) child;
|
|
}
|
|
else {
|
|
D.assert(slot == null || slot is Element);
|
|
this.renderObject.insert((RenderBox) child, after: (RenderBox) ((Element) slot)?.renderObject);
|
|
}
|
|
}
|
|
|
|
protected override void moveChildRenderObject(RenderObject child, object slot) {
|
|
if (slot == _onstageSlot) {
|
|
this.renderObject.remove((RenderBox) child);
|
|
D.assert(child is RenderStack);
|
|
this.renderObject.child = (RenderStack) child;
|
|
}
|
|
else {
|
|
D.assert(slot == null || slot is Element);
|
|
if (this.renderObject.child == child) {
|
|
this.renderObject.child = null;
|
|
this.renderObject.insert((RenderBox) child, after: (RenderBox) ((Element) slot)?.renderObject);
|
|
}
|
|
else {
|
|
this.renderObject.move((RenderBox) child, after: (RenderBox) ((Element) slot)?.renderObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void removeChildRenderObject(RenderObject child) {
|
|
if (this.renderObject.child == child) {
|
|
this.renderObject.child = null;
|
|
}
|
|
else {
|
|
this.renderObject.remove((RenderBox) child);
|
|
}
|
|
}
|
|
|
|
public override void visitChildren(ElementVisitor visitor) {
|
|
if (this._onstage != null) {
|
|
visitor(this._onstage);
|
|
}
|
|
|
|
foreach (var child in this._offstage) {
|
|
if (!this._forgottenOffstageChildren.Contains(child)) {
|
|
visitor(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void debugVisitOnstageChildren(ElementVisitor visitor) {
|
|
if (this._onstage != null) {
|
|
visitor(this._onstage);
|
|
}
|
|
}
|
|
|
|
|
|
protected override void forgetChild(Element child) {
|
|
if (child == this._onstage) {
|
|
this._onstage = null;
|
|
}
|
|
else {
|
|
D.assert(this._offstage.Contains(child));
|
|
D.assert(!this._forgottenOffstageChildren.Contains(child));
|
|
this._forgottenOffstageChildren.Add(child);
|
|
}
|
|
}
|
|
|
|
public override void mount(Element parent, object newSlot) {
|
|
base.mount(parent, newSlot);
|
|
this._onstage = this.updateChild(this._onstage, this.widget.onstage, _onstageSlot);
|
|
this._offstage = new List<Element>(this.widget.offstage.Count);
|
|
Element previousChild = null;
|
|
for (int i = 0; i < this._offstage.Count; i += 1) {
|
|
var newChild = this.inflateWidget(this.widget.offstage[i], previousChild);
|
|
this._offstage[i] = newChild;
|
|
previousChild = newChild;
|
|
}
|
|
}
|
|
|
|
public override void update(Widget newWidget) {
|
|
base.update(newWidget);
|
|
D.assert(Equals(this.widget, newWidget));
|
|
this._onstage = this.updateChild(this._onstage, this.widget.onstage, _onstageSlot);
|
|
this._offstage = this.updateChildren(this._offstage, this.widget.offstage,
|
|
forgottenChildren: this._forgottenOffstageChildren);
|
|
this._forgottenOffstageChildren.Clear();
|
|
}
|
|
}
|
|
|
|
class _RenderTheatre :
|
|
ContainerRenderObjectMixinRenderProxyBoxMixinRenderObjectWithChildMixinRenderBoxRenderStack<
|
|
RenderBox, StackParentData> {
|
|
public override void setupParentData(RenderObject child) {
|
|
if (!(child.parentData is StackParentData)) {
|
|
child.parentData = new StackParentData();
|
|
}
|
|
}
|
|
|
|
public override void redepthChildren() {
|
|
if (this.child != null) {
|
|
this.redepthChild(this.child);
|
|
}
|
|
|
|
base.redepthChildren();
|
|
}
|
|
|
|
public override void visitChildren(RenderObjectVisitor visitor) {
|
|
if (this.child != null) {
|
|
visitor(this.child);
|
|
}
|
|
|
|
base.visitChildren(visitor);
|
|
}
|
|
|
|
public override List<DiagnosticsNode> debugDescribeChildren() {
|
|
var children = new List<DiagnosticsNode>();
|
|
|
|
if (this.child != null) {
|
|
children.Add(this.child.toDiagnosticsNode(name: "onstage"));
|
|
}
|
|
|
|
if (this.firstChild != null) {
|
|
var child = this.firstChild;
|
|
|
|
int count = 1;
|
|
while (true) {
|
|
children.Add(
|
|
child.toDiagnosticsNode(
|
|
name: $"offstage {count}",
|
|
style: DiagnosticsTreeStyle.offstage
|
|
)
|
|
);
|
|
if (child == this.lastChild) {
|
|
break;
|
|
}
|
|
|
|
var childParentData = (StackParentData) child.parentData;
|
|
child = childParentData.nextSibling;
|
|
count += 1;
|
|
}
|
|
}
|
|
else {
|
|
children.Add(
|
|
DiagnosticsNode.message(
|
|
"no offstage children",
|
|
style: DiagnosticsTreeStyle.offstage
|
|
)
|
|
);
|
|
}
|
|
|
|
return children;
|
|
}
|
|
}
|
|
}
|