| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file is hand-formatted. |
| |
| // This file must not import `dart:ui`, directly or indirectly, as it is |
| // intended to function even in pure Dart server or CLI environments. |
| import 'package:meta/meta.dart'; |
| |
| /// A map whose keys are strings and whose values are [DynamicMap], |
| /// [DynamicList], int, double, bool, string, and [BlobNode] objects. |
| /// |
| /// Part of the data type for [DynamicContent] objects. |
| typedef DynamicMap = Map<String, Object?>; |
| |
| /// A list whose values are [DynamicMap], [DynamicList], int, double, bool, |
| /// string, and [BlobNode] objects. |
| /// |
| /// Part of the data type for [DynamicContent] objects. |
| typedef DynamicList = List<Object?>; |
| |
| /// Base class of nodes that appear in the output of [decodeDataBlob] and |
| /// [decodeLibraryBlob]. |
| /// |
| /// In addition to this, the following types can be found in that output: |
| /// |
| /// * [DynamicMap] |
| /// * [DynamicList] |
| /// * [int] |
| /// * [double] |
| /// * [bool] |
| /// * [String] |
| abstract class BlobNode { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| const BlobNode(); |
| } |
| |
| bool _listEquals<T>(List<T>? a, List<T>? b) { |
| if (identical(a, b)) { |
| return true; |
| } |
| if (a == null || b == null || a.length != b.length) { |
| return false; |
| } |
| for (int index = 0; index < a.length; index += 1) { |
| if (a[index] != b[index]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /// The name of a widgets library in the RFW package. |
| /// |
| /// Libraries are typically referred to with names like "core.widgets" or |
| /// "com.example.shopping.cart". This class represents these names as lists of |
| /// tokens, in those cases `['core', 'widgets']` and `['com', 'example', |
| /// 'shopping', 'cart']`, for example. |
| @immutable |
| class LibraryName implements Comparable<LibraryName> { |
| /// Wrap the given list as a [LibraryName]. |
| /// |
| /// The given list is not copied; it is an error to modify it after creating |
| /// the [LibraryName]. |
| const LibraryName(this.parts); |
| |
| /// The components of the structured library name. |
| final List<String> parts; |
| |
| @override |
| bool operator ==(Object other) { |
| if (other.runtimeType != runtimeType) { |
| return false; |
| } |
| return other is LibraryName |
| && _listEquals<String>(parts, other.parts); |
| } |
| |
| @override |
| int get hashCode => Object.hashAll(parts); |
| |
| @override |
| String toString() => parts.join('.'); |
| |
| @override |
| int compareTo(LibraryName other) { |
| for (int index = 0; index < parts.length; index += 1) { |
| if (other.parts.length <= index) { |
| return 1; |
| } |
| final int result = parts[index].compareTo(other.parts[index]); |
| if (result != 0) { |
| return result; |
| } |
| } |
| assert(other.parts.length >= parts.length); |
| return parts.length - other.parts.length; |
| } |
| } |
| |
| /// The name of a widget used by the RFW package, including its library name. |
| /// |
| /// This can be used to identify both local widgets and remote widgets. |
| @immutable |
| class FullyQualifiedWidgetName implements Comparable<FullyQualifiedWidgetName> { |
| /// Wrap the given library name and widget name in a [FullyQualifiedWidgetName]. |
| const FullyQualifiedWidgetName(this.library, this.widget); |
| |
| /// The name of the library in which [widget] can be found. |
| final LibraryName library; |
| |
| /// The name of the widget, which should be in the specified [library]. |
| final String widget; |
| |
| @override |
| bool operator ==(Object other) { |
| if (other.runtimeType != runtimeType) { |
| return false; |
| } |
| return other is FullyQualifiedWidgetName |
| && library == other.library && widget == other.widget; |
| } |
| |
| @override |
| int get hashCode => Object.hash(library, widget); |
| |
| @override |
| String toString() => '$library:$widget'; |
| |
| @override |
| int compareTo(FullyQualifiedWidgetName other) { |
| final int result = library.compareTo(other.library); |
| if (result != 0) { |
| return result; |
| } |
| return widget.compareTo(other.widget); |
| } |
| } |
| |
| /// The type of the [missing] value. |
| /// |
| /// This is used internally by the RFW package to avoid needing to use nullable |
| /// types. |
| class Missing extends BlobNode { |
| const Missing._(); |
| |
| @override |
| String toString() => '<missing>'; |
| } |
| |
| /// The value used by [DynamicContent] to represent missing data. |
| /// |
| /// This is return from [DynamicContent.subscribe] when the specified key is not |
| /// present. |
| /// |
| /// Content in a [DynamicContent] should not contain [missing] values. |
| const Missing missing = Missing._(); |
| |
| /// Representation of the `...for` construct in Remote Flutter Widgets library |
| /// blobs. |
| class Loop extends BlobNode { |
| /// Creates a [Loop] with the given [input] and [output]. |
| /// |
| /// The provided objects must not be mutated after being given to the |
| /// constructor (e.g. the ownership of any lists and maps passes to this |
| /// object). |
| const Loop(this.input, this.output); |
| |
| /// The list on which to iterate. |
| /// |
| /// This is typically some sort of [Reference], but could be a [DynamicList]. |
| /// |
| /// It is an error for this to be a value that does not resolve to a list. |
| final Object input; |
| |
| /// The template to apply for each value on [input]. |
| final Object output; |
| |
| @override |
| String toString() => '...for loop in $input: $output'; |
| } |
| |
| /// Representation of the `switch` construct in Remote Flutter Widgets library |
| /// blobs. |
| class Switch extends BlobNode { |
| /// Creates a [Switch] with the given [input] and [outputs]. |
| /// |
| /// The provided objects must not be mutated after being given to the |
| /// constructor. In particular, changing the [outputs] map after creating the |
| /// [Switch] is an error. |
| const Switch(this.input, this.outputs); |
| |
| /// The value to switch on (after resolution). |
| final Object input; |
| |
| /// The cases for this switch. Keys correspond to values to compare with |
| /// [input]. The null value is used as the default case. |
| /// |
| /// At runtime, if none of the keys match [input] and there is no null key, |
| /// the [Switch] as a whole is treated as if it was [missing]. If the [Switch] |
| /// is used where a [ConstructorCall] was expected, the result is an |
| /// [ErrorWidget]. |
| final Map<Object?, Object> outputs; |
| |
| @override |
| String toString() => 'switch $input $outputs'; |
| } |
| |
| /// Representation of references to widgets in Remote Flutter Widgets library |
| /// blobs. |
| class ConstructorCall extends BlobNode { |
| /// Creates a [ConstructorCall] for a widget of the given name in the current |
| /// library's scope, with the given [arguments]. |
| /// |
| /// The [arguments] must not be mutated after the object is created. |
| const ConstructorCall(this.name, this.arguments); |
| |
| /// The name of the widget to create. |
| /// |
| /// The name is looked up in the current library, or, failing that, in a |
| /// depth-first search of this library's dependencies. |
| final String name; |
| |
| /// The arguments to pass to the constructor. |
| /// |
| /// Constructors in RFW only have named arguments. This differs from Dart |
| /// (where arguments can also be positional.) |
| final DynamicMap arguments; |
| |
| @override |
| String toString() => '$name($arguments)'; |
| } |
| |
| /// Base class for various kinds of references in the RFW data structures. |
| abstract class Reference extends BlobNode { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| /// |
| /// The [parts] must not be mutated after the object is created. |
| const Reference(this.parts); |
| |
| /// The components of the reference. Each entry must be either a String (to |
| /// index into a [DynamicMap]) an integer (to index into a [DynamicList]). |
| /// |
| /// It is an error for any of the parts to be of any other type. |
| final List<Object> parts; |
| } |
| |
| /// Unbound reference to arguments. |
| /// |
| /// This class is used to represent references of the form "args.foo.bar" after |
| /// parsing, before the arguments are bound. |
| class ArgsReference extends Reference { |
| /// Wraps the given [parts] as an [ArgsReference]. |
| /// |
| /// The [parts] must not be mutated after the object is created. |
| const ArgsReference(List<Object> parts): super(parts); |
| |
| /// Binds the arguments reference to a specific set of arguments. |
| /// |
| /// Returns a [BoundArgsReference] with the same [parts] and whose |
| /// [BoundArgsReference.arguments] is given by `arguments`. |
| BoundArgsReference bind(Object arguments) { |
| return BoundArgsReference(arguments, parts); |
| } |
| |
| @override |
| String toString() => 'args.${parts.join(".")}'; |
| } |
| |
| /// Bound reference to arguments. |
| /// |
| /// This class is used to represent references of the form "args.foo.bar" after |
| /// a widget declaration has been bound to specific arguments via a constructor |
| /// call. The [arguments] property is a reference to the |
| /// [ConstructorCall.arguments] object (or, more typically, a clone of that |
| /// object that itself has had references within it bound). |
| /// |
| /// This class is an internal detail of the RFW [Runtime] and is generally not |
| /// used directly. |
| class BoundArgsReference extends Reference { |
| /// Wraps the given [parts] and [arguments] as a [BoundArgsReference]. |
| /// |
| /// The parameters must not be mutated after the object is created. |
| /// |
| /// Generally this class is created using [ArgsReference.bind]. |
| const BoundArgsReference(this.arguments, List<Object> parts): super(parts); |
| |
| /// The object into which [parts] will be indexed. |
| /// |
| /// This could contain [Loop]s, which is why it cannot be indexed immediately |
| /// upon creation. |
| final Object arguments; |
| |
| @override |
| String toString() => 'args($arguments).${parts.join(".")}'; |
| } |
| |
| /// Reference to the [DynamicContent] data that is passed into the widget (see |
| /// [Runtime.build]'s `data` argument). |
| class DataReference extends Reference { |
| /// Wraps the given [parts] as a [DataReference]. |
| /// |
| /// The [parts] must not be mutated after the object is created. |
| const DataReference(List<Object> parts): super(parts); |
| |
| /// Creates a new [DataRefererence] that indexes even deeper than this one. |
| /// |
| /// For example, suppose a widget's arguments consisted of a map with one key, |
| /// "a", whose value was a [DataRefererence] referencing "data.foo.bar". Now |
| /// suppose that the widget itself has an [ArgsReference] that references |
| /// "args.a.baz". The "args.a" part identifies the aforementioned |
| /// [DataReference], and so the resulting reference is actually to |
| /// "data.foo.bar.baz". |
| /// |
| /// In this example, the [DataReference] to "data.foo.bar" would have its |
| /// [constructReference] method invoked by the runtime, with `["baz"]` as the |
| /// `moreParts` argument, so that the resulting [DataReference]'s [parts] is a |
| /// combination of the original's (`["foo", "bar"]`) and the additional parts |
| /// provided to the method. |
| DataReference constructReference(List<Object> moreParts) { |
| return DataReference(parts + moreParts); |
| } |
| |
| @override |
| String toString() => 'data.${parts.join(".")}'; |
| } |
| |
| /// Unbound reference to a [Loop]. |
| class LoopReference extends Reference { |
| /// Wraps the given [loop] and [parts] as a [LoopReference]. |
| /// |
| /// The [parts] must not be mutated after the object is created. |
| const LoopReference(this.loop, List<Object> parts): super(parts); |
| |
| /// The index to the referenced loop. |
| /// |
| /// Loop indices count up, so the nearest loop ancestor of the reference has |
| /// index zero, with indices counting up when going up the tree towards the |
| /// root. |
| final int loop; // this is basically a De Bruijn index |
| |
| /// Creates a new [LoopRefererence] that indexes even deeper than this one. |
| /// |
| /// For example, suppose a widget's arguments consisted of a map with one key, |
| /// "a", whose value was a [LoopRefererence] referencing "loop0.foo.bar". Now |
| /// suppose that the widget itself has an [ArgsReference] that references |
| /// "args.a.baz". The "args.a" part identifies the aforementioned |
| /// [LoopReference], and so the resulting reference is actually to |
| /// "loop0.foo.bar.baz". |
| /// |
| /// In this example, the [LoopReference] to "loop0.foo.bar" would have its |
| /// [constructReference] method invoked by the runtime, with `["baz"]` as the |
| /// `moreParts` argument, so that the resulting [LoopReference]'s [parts] is a |
| /// combination of the original's (`["foo", "bar"]`) and the additional parts |
| /// provided to the method. |
| /// |
| /// The [loop] index is maintained in the new object. |
| LoopReference constructReference(List<Object> moreParts) { |
| return LoopReference(loop, parts + moreParts); |
| } |
| |
| /// Binds the loop reference to a specific value. |
| /// |
| /// Returns a [BoundLoopReference] with the same [parts] and whose |
| /// [BoundLoopReference.value] is given by `value`. The [loop] index is |
| /// dropped in the process. |
| BoundLoopReference bind(Object value) { |
| return BoundLoopReference(value, parts); |
| } |
| |
| @override |
| String toString() => 'loop$loop.${parts.join(".")}'; |
| } |
| |
| /// Bound reference to a [Loop]. |
| /// |
| /// This class is used to represent references of the form "loopvar.foo.bar" |
| /// after the list containing the relevant loop has been dereferenced so that |
| /// the loop variable refers to a specific value in the list. The [value] is |
| /// that resolved value. |
| /// |
| /// This class is an internal detail of the RFW [Runtime] and is generally not |
| /// used directly. |
| class BoundLoopReference extends Reference { |
| /// Wraps the given [value] and [parts] as a [BoundLoopReference]. |
| /// |
| /// The [parts] must not be mutated after the object is created. |
| /// |
| /// Generally this class is created using [LoopReference.bind]. |
| const BoundLoopReference(this.value, List<Object> parts): super(parts); |
| |
| /// The object into which [parts] will index. |
| /// |
| /// This could contain further [Loop]s or unbound [LoopReference]s, which is |
| /// why it cannot be indexed immediately upon creation. |
| final Object value; |
| |
| /// Creates a new [BoundLoopRefererence] that indexes even deeper than this |
| /// one. |
| /// |
| /// For example, suppose a widget's arguments consisted of a map with one key, |
| /// "a", whose value was a [BoundLoopRefererence] referencing "loop0.foo.bar". |
| /// Now suppose that the widget itself has an [ArgsReference] that references |
| /// "args.a.baz". The "args.a" part identifies the aforementioned |
| /// [BoundLoopReference], and so the resulting reference is actually to |
| /// "loop0.foo.bar.baz". |
| /// |
| /// In this example, the [BoundLoopReference] to "loop0.foo.bar" would have |
| /// its [constructReference] method invoked by the runtime, with `["baz"]` as |
| /// the `moreParts` argument, so that the resulting [BoundLoopReference]'s |
| /// [parts] is a combination of the original's (`["foo", "bar"]`) and the |
| /// additional parts provided to the method. |
| /// |
| /// The resolved [value] (which is what the [parts] will eventually index |
| /// into) is maintained in the new object. |
| BoundLoopReference constructReference(List<Object> moreParts) { |
| return BoundLoopReference(value, parts + moreParts); |
| } |
| |
| @override |
| String toString() => 'loop($value).${parts.join(".")}'; |
| } |
| |
| /// Base class for [StateReference] and [BoundStateReference]. |
| /// |
| /// This is used to ensure [SetStateHandler]'s [SetStateHandler.stateReference] |
| /// property can only hold a state reference. |
| abstract class AnyStateReference extends Reference { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| /// |
| /// The [parts] must not be mutated after the object is created. |
| const AnyStateReference(List<Object> parts): super(parts); |
| } |
| |
| /// Unbound reference to remote widget's state. |
| /// |
| /// This class is used to represent references of the form "state.foo.bar". |
| class StateReference extends AnyStateReference { |
| /// Wraps the given [parts] as a [StateReference]. |
| /// |
| /// The [parts] must not be mutated after the object is created. |
| const StateReference(List<Object> parts): super(parts); |
| |
| /// Binds the state reference to a specific widget (identified by depth). |
| /// |
| /// Returns a [BoundStateReference] with the same [parts] and whose |
| /// [BoundLoopReference.depth] is given by `depth`. |
| BoundStateReference bind(int depth) { |
| return BoundStateReference(depth, parts); |
| } |
| |
| @override |
| String toString() => 'state.${parts.join(".")}'; |
| } |
| |
| /// Bound reference to a remote widget's state. |
| /// |
| /// This class is used to represent references of the form "state.foo.bar" after |
| /// the widgets have been constructed, so that the right state can be |
| /// identified. |
| /// |
| /// This class is an internal detail of the RFW [Runtime] and is generally not |
| /// used directly. |
| class BoundStateReference extends AnyStateReference { |
| /// Wraps the given [depth] and [parts] as a [BoundStateReference]. |
| /// |
| /// The [parts] must not be mutated after the object is created. |
| /// |
| /// Generally this class is created using [StateReference.bind]. |
| const BoundStateReference(this.depth, List<Object> parts): super(parts); |
| |
| /// The widget to whose state the state reference refers. |
| /// |
| /// This identifies the widget by depth starting at the widget that was |
| /// created by [Runtime.build] (or a [RemoteWidget], which uses that method). |
| /// |
| /// Since state references always go up the tree, this is an unambiguous way |
| /// to reference state, even though in practice in the entire tree multiple |
| /// widgets may be stateful at the same depth. |
| final int depth; |
| |
| /// Creates a new [BoundStateRefererence] that indexes even deeper than this |
| /// one (deeper into the specified widget's state, not into a deeper widget!). |
| /// |
| /// For example, suppose a widget's arguments consisted of a map with one key, |
| /// "a", whose value was a [BoundStateRefererence] referencing "state.foo.bar". |
| /// Now suppose that the widget itself has an [ArgsReference] that references |
| /// "args.a.baz". The "args.a" part identifies the aforementioned |
| /// [BoundStateReference], and so the resulting reference is actually to |
| /// "state.foo.bar.baz". |
| /// |
| /// In this example, the [BoundStateReference] to "state.foo.bar" would have |
| /// its [constructReference] method invoked by the runtime, with `["baz"]` as |
| /// the `moreParts` argument, so that the resulting [BoundStateReference]'s |
| /// [parts] is a combination of the original's (`["foo", "bar"]`) and the |
| /// additional parts provided to the method. |
| /// |
| /// The [depth] is maintained in the new object. |
| BoundStateReference constructReference(List<Object> moreParts) { |
| return BoundStateReference(depth, parts + moreParts); |
| } |
| |
| @override |
| String toString() => 'state^$depth.${parts.join(".")}'; |
| } |
| |
| /// Base class for [EventHandler] and [SetStateHandler]. |
| /// |
| /// This is used by the [Runtime] to quickly filter out objects that are not |
| /// event handlers of any kind. |
| abstract class AnyEventHandler extends BlobNode { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| const AnyEventHandler(); |
| } |
| |
| /// Description of a callback in an RFW widget declaration. |
| /// |
| /// This represents a signal to send to the application using the RFW package. |
| /// Typically applications either handle such messages locally, or forward them |
| /// to a server for further processing. |
| class EventHandler extends AnyEventHandler { |
| /// Wraps the given event name and arguments in an [EventHandler] object. |
| /// |
| /// The [eventArguments] must not be mutated after the object is created. |
| const EventHandler(this.eventName, this.eventArguments); |
| |
| /// A string to identify the event. This provides an unambiguous identifier |
| /// for the event, avoiding the need to establish a convention in the |
| /// [eventArguments]. |
| final String eventName; |
| |
| /// The payload to provide with the event. |
| final DynamicMap eventArguments; |
| |
| @override |
| String toString() => 'event $eventName $eventArguments'; |
| } |
| |
| /// Description of a state setter in an RFW widget declaration. |
| /// |
| /// This event handler is handled by the RFW [Runtime] itself by setting the |
| /// state referenced by [stateReference] to the value represented by [value] |
| /// when the event handler would be invoked. |
| class SetStateHandler extends AnyEventHandler { |
| /// Wraps the given [stateReference] and [value] in a [SetStateHandler] object. |
| /// |
| /// The [value] must not be mutated after the object is created (e.g. in the |
| /// event that it is a [DynamicMap] or [DynamicList]). |
| const SetStateHandler(this.stateReference, this.value); |
| |
| /// Identifies the member in the widget's state to mutate. |
| final AnyStateReference stateReference; |
| |
| /// The value to which the specified state will be set. |
| final Object value; |
| |
| @override |
| String toString() => 'set $stateReference = $value'; |
| } |
| |
| /// A library import. |
| /// |
| /// Used to describe which libraries a remote widget libraries depends on. The |
| /// identified libraries can be local or remote. Import loops are invalid. |
| // TODO(ianh): eventually people will probably want a way to disambiguate imports |
| // with a prefix. |
| class Import extends BlobNode { |
| /// Wraps the given library [name] in an [Import] object. |
| const Import(this.name); |
| |
| /// The name of the library to import. |
| final LibraryName name; |
| |
| @override |
| String toString() => 'import $name;'; |
| } |
| |
| /// A description of a widget in a remote widget library. |
| /// |
| /// The [root] must be either a [ConstructorCall] or a [Switch] that evaluates |
| /// to a [ConstructorCall]. (In principle one can imagine that an |
| /// [ArgsReference] that evaluates to a [ConstructorCall] would also be valid, |
| /// but such a construct would be redundant and would not provide any additional |
| /// expressivity, so it is disallowed.) |
| /// |
| /// The tree rooted at [root] must not contain (directly or indirectly) a |
| /// [ConstructorCall] that references the widget declared by this |
| /// [WidgetDeclaration]: widget loops, even indirect loops or loops that would |
| /// in principle be terminated by use of a [Switch], are not allowed. |
| class WidgetDeclaration extends BlobNode { |
| /// Binds the given [name] to the definition given by [root]. |
| /// |
| /// The [initialState] may be null. If it is not, this represents a stateful widget. |
| const WidgetDeclaration(this.name, this.initialState, this.root) : assert(root is ConstructorCall || root is Switch); |
| |
| /// The name of the widget that this declaration represents. |
| /// |
| /// This is the left hand side of a widget declaration. |
| final String name; |
| |
| /// If non-null, this is a stateful widget; the value is used to create the |
| /// initial copy of the state when the widget is created. |
| final DynamicMap? initialState; |
| |
| /// The widget to return when this widget is used. |
| /// |
| /// This is usually a [ConstructorCall], but may be a [Switch] (so long as |
| /// that [Switch] resolves to a [ConstructorCall]. Other values (or a [Switch] |
| /// that does not resolve to a constructor call) will result in an |
| /// [ErrorWidget] being used. |
| final BlobNode root; // ConstructorCall or Switch |
| |
| @override |
| String toString() => 'widget $name = $root;'; |
| } |
| |
| /// Base class for widget libraries. |
| abstract class WidgetLibrary { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| const WidgetLibrary(); |
| } |
| |
| /// The in-memory representation of the output of [parseTextLibraryFile] or |
| /// [decodeLibraryBlob]. |
| class RemoteWidgetLibrary extends WidgetLibrary { |
| /// Wraps a set of [imports] and [widgets] (widget declarations) in a |
| /// [RemoteWidgetLibrary] object. |
| /// |
| /// The provided lists must not be mutated once the library is created. |
| const RemoteWidgetLibrary(this.imports, this.widgets); |
| |
| /// The list of libraries that this library depends on. |
| /// |
| /// This must not be empty, since at least one local widget library must be in |
| /// scope in order for the remote widget library to be useful. |
| final List<Import> imports; |
| |
| /// The list of widgets declared by this library. |
| /// |
| /// This can be empty. |
| final List<WidgetDeclaration> widgets; |
| |
| @override |
| String toString() => const Iterable<Object>.empty().followedBy(imports).followedBy(widgets).join('\n'); |
| } |