blob: d595a228a9290910a0aefc4f4f194318bc218f40 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 'dart:math' as math;
import 'package:fidl_fuchsia_modular/fidl_async.dart';
import 'package:fuchsia_scenic_flutter/child_view_connection.dart'
show ChildViewConnection;
import 'package:fuchsia_logger/logger.dart';
import 'package:lib.widgets/model.dart';
import '../../models/surface/surface_transition.dart';
import '../tree/tree.dart';
import 'surface_graph.dart';
import 'surface_properties.dart';
import 'surface_relation_util.dart';
/// The parentId that means no parent
const String kNoParent = '';
/// Details of a surface child view
class Surface extends Model {
/// Public constructor
Surface(this._graph, this.node, this.properties, this.relation,
this.compositionPattern, this.placeholderColor) {
transitionModel = SurfaceTransitionModel()
// notify listeners of Surface model to changes that happen in
// surface_transition, so we can use the same model in builders
/// ignore: unnecessary_lambdas
..addListener(() => notifyListeners());
}
Surface.fromJson(Map<String, dynamic> json, this._graph)
: node = Tree<String>(value: json['id']),
compositionPattern = json['compositionPattern'],
properties = SurfaceProperties.fromJson(
json['surfaceProperties'].cast<String, dynamic>()),
relation = SurfaceRelationUtil.decode(
json['surfaceRelation'].cast<String, String>()),
childIds = json['children'].cast<String>(),
isParentRoot = json['parentId'] == null,
placeholderColor = json['placeholderColor'];
final SurfaceGraph _graph;
final Tree<String> node;
/// The transitionModel handling placeholder timinggit
SurfaceTransitionModel transitionModel;
/// Connection to underlying view
ChildViewConnection connection;
/// The properties of this surface
final SurfaceProperties properties;
/// The relationship this node has with its parent
final SurfaceRelation relation;
/// The pattern with which to compose this node with its parent
final String compositionPattern;
/// The placeholder color to use if the surface is focused before the
/// module is ready to display. This comes in as a hex string on the
/// module manifest.
final String placeholderColor;
// Used to track whether this node is attached to the root of the graph
bool isParentRoot = false;
// Used for constructing the surface and its associated graph from json.
// Note: these ids will not stay up to date with what's in the node.
// Use the _children method below after the graph has been updated.
List<String> childIds;
/// Whether or not this surface is currently dismissed
bool get dismissed => _graph.isDismissed(node.value);
/// Return the min width of this Surface
double minWidth({double min = 0.0}) => math.max(0.0, min);
/// Return the absolute emphasis given some root displayed Surface
double absoluteEmphasis(Surface relative) {
assert(root == relative.root);
Iterable<Surface> relativeAncestors = relative.ancestors;
Surface ancestor = this;
double emphasis = 1.0;
while (ancestor != relative && !relativeAncestors.contains(ancestor)) {
emphasis *= ancestor.relation.emphasis;
ancestor = ancestor.parent;
}
Surface aRelative = relative;
while (ancestor != aRelative) {
emphasis /= aRelative.relation.emphasis;
aRelative = aRelative.parent;
}
return emphasis;
}
/// The parent of this node
Surface get parent => _surface(node.parent);
/// The parentId of this node
String get parentId => node.parent.value;
/// The root surface
Surface get root {
List<Tree<String>> nodeAncestors = node.ancestors;
return _surface(nodeAncestors.length > 1
? nodeAncestors[nodeAncestors.length - 2]
: node);
}
/// The children of this node
Iterable<Surface> get children => _surfaces(node.children);
/// The siblings of this node
Iterable<Surface> get siblings => _surfaces(node.siblings);
/// The ancestors of this node
Iterable<Surface> get ancestors => _surfaces(node.ancestors);
/// This node and its descendents flattened into an Iterable
Iterable<Surface> get flattened => _surfaces(node);
/// Returns a Tree for this surface
Tree<Surface> get tree {
Tree<Surface> t = Tree<Surface>(value: this);
for (Surface child in children) {
t.add(child.tree);
}
return t;
}
/// Dismiss this node hiding it from layouts
bool dismiss() => _graph.dismissSurface(node.value);
/// Returns true if this surface can be dismissed
bool canDismiss() => _graph.canDismissSurface(node.value);
/// Remove this node from graph
/// Returns true if this was removed
bool remove() {
// Only allow non-root surfaces to be removed
if (node.parent?.value != null) {
_graph.removeSurface(node.value);
return true;
}
return false;
}
// Get the surface for this node
Surface _surface(Tree<String> node) =>
(node == null || node.value == null) ? null : _graph.getNode(node.value);
Iterable<Surface> _surfaces(Iterable<Tree<String>> nodes) => nodes
.where((Tree<String> node) => node != null && node.value != null)
.map(_surface);
@override
String toString() {
String edgeLabel = relation?.arrangement?.toString() ?? '';
String edgeArrow = '$edgeLabel->'.padLeft(6, '-');
String disconnected = connection == null ? '[DISCONNECTED]' : '';
return '${edgeArrow}Surface ${node.value} $disconnected';
}
List<String> _children() {
List<String> ids = [];
for (Tree<String> child in node.children) {
ids.add(child.value);
}
return ids;
}
Map<String, dynamic> toJson() {
return {
'id': node.value,
'parentId': parentId,
'surfaceRelation': SurfaceRelationUtil.toMap(relation),
'surfaceProperties': properties,
'compositionPattern': compositionPattern,
'isDismissed': dismissed ? 'true' : 'false',
'children': _children(),
'placeholderColor': placeholderColor,
};
}
}
/// Defines a Container root in the [Surface Graph], holds the layout description
class SurfaceContainer extends Surface {
SurfaceContainer(
SurfaceGraph graph,
Tree<String> node,
SurfaceProperties properties,
SurfaceRelation relation,
String compositionPattern)
: super(graph, node, properties, relation, compositionPattern, '') {
super.connection = null;
}
@override
set connection(ChildViewConnection value) {
log.warning('Cannot set a child view connection on a Container');
}
}