blob: 4d322d62a48f680ed3ffec45a4bf6e7902429739 [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';
import 'package:fidl_fuchsia_modular/fidl_async.dart';
import 'package:flutter/widgets.dart';
import '../models/layout_model.dart';
import '../models/surface/positioned_surface.dart';
import '../models/surface/surface.dart';
import '../models/surface/surface_graph.dart';
import '../models/tree/spanning_tree.dart';
import '../models/tree/tree.dart';
// Convenience comparator used to ensure more focused items get higher priority
int _compareByOtherList(Surface l, Surface r, List<Surface> otherList) {
int li = otherList.indexOf(l);
int ri = otherList.indexOf(r);
if (li < 0) {
li = otherList.length;
}
if (ri < 0) {
ri = otherList.length;
}
return ri - li;
}
/// Returns in the order they should be stacked
List<PositionedSurface> layoutSurfaces(
BuildContext context,
SurfaceGraph graph,
List<Surface> focusStack,
LayoutModel layoutModel,
) {
Surface focused = focusStack.lastWhere((Surface surface) {
return surface.connection != null;
}, orElse: () => null);
if (focusStack.isEmpty || focused == null) {
return <PositionedSurface>[];
}
SurfaceArrangement focusedArrangement = focused.relation.arrangement;
Tree<Surface> copresTree = getCopresentSpanningTree(focused);
// Ontop only applies if the currently focused mod has a parent. If there's
// only one sutface in the stack, fall through to the logic below.
if (focusedArrangement == SurfaceArrangement.ontop && focusStack.length > 1) {
// Determine the parent's position and then place the focused surface on top.
List<PositionedSurface> surfaces = layoutSurfaces(context, graph,
focusStack.sublist(0, focusStack.length - 1), layoutModel);
PositionedSurface parentSurface =
surfaces.firstWhere((PositionedSurface positioned) {
return positioned.surface == focused.parent;
});
PositionedSurface ontopSurface = PositionedSurface(
surface: focused,
position: parentSurface.position,
);
surfaces.add(ontopSurface);
return surfaces;
}
int focusOrder(Tree<Surface> l, Tree<Surface> r) =>
_compareByOtherList(l.value, r.value, focusStack);
// Remove dismissed surfaces and surfaces without views and collapse tree
for (Tree<Surface> node in copresTree) {
if (node.value.dismissed || node.value.connection == null) {
node.children.forEach(node.parent.add);
node.detach();
}
}
// Prune less focused surfaces where their min constraints do not fit
double totalMinWidth = 0.0;
for (Tree<Surface> node
in copresTree.flatten(orderChildren: focusOrder).skipWhile(
(Tree<Surface> node) {
double minWidth = node.value.minWidth(min: layoutModel.minScreenRatio);
if (totalMinWidth + minWidth > 1.0) {
return false;
}
totalMinWidth += minWidth;
return true;
},
)) {
node.detach();
}
// Prune less focused surfaces where emphasis values cannot be respected
double totalEmphasis = 0.0;
Surface top = focused;
Surface tightestFit = focused;
for (Tree<Surface> node
in copresTree.flatten(orderChildren: focusOrder).skipWhile(
(Tree<Surface> node) {
Surface prevTop = top;
double prevTotalEmphasis = totalEmphasis;
// Update top
if (top.ancestors.contains(node.value)) {
top = node.value;
totalEmphasis *= prevTop.absoluteEmphasis(top);
}
double emphasis = node.value.absoluteEmphasis(top);
totalEmphasis += emphasis;
// Calculate min width available
double tightestFitEmphasis = tightestFit.absoluteEmphasis(top);
double extraWidth = emphasis / totalEmphasis -
node.value.minWidth(min: layoutModel.minScreenRatio);
double tightestFitExtraWidth = tightestFitEmphasis / totalEmphasis -
tightestFit.minWidth(min: layoutModel.minScreenRatio);
// Break if smallest or this doesn't fit
if (min(tightestFitExtraWidth, extraWidth) < 0.0) {
// Restore previous values
top = prevTop;
totalEmphasis = prevTotalEmphasis;
return false;
}
// Update tightest fit
if (extraWidth < tightestFitExtraWidth) {
tightestFit = node.value;
}
return true;
},
)) {
node.detach();
}
List<Surface> surfacesToDisplay =
copresTree.map((Tree<Surface> t) => t.value).toList(growable: false);
Iterable<Surface> arrangement =
top.flattened.where((Surface s) => surfacesToDisplay.contains(s));
// Layout rects for arrangement
final List<PositionedSurface> layout = <PositionedSurface>[];
double fractionalWidthOffset = 0.0;
for (Surface surface in arrangement) {
double fractionalWidth = surface.absoluteEmphasis(top) / totalEmphasis;
double fractionalHeight = 1.0;
layout.add(
PositionedSurface(
surface: surface,
position: Rect.fromLTWH(
fractionalWidthOffset,
0.0,
fractionalWidth,
fractionalHeight,
),
),
);
fractionalWidthOffset += fractionalWidth;
}
return layout;
}