浏览代码

add some files

/zgh-devtools
guanghuispark 4 年前
当前提交
4e38a562
共有 34 个文件被更改,包括 9026 次插入12 次删除
  1. 5
      com.unity.uiwidgets.devtools/Editor/Unity.UIWidgets.DevTools.Editor.asmdef
  2. 974
      com.unity.uiwidgets.devtools/Editor/inspector/inspector_controller.cs
  3. 12
      com.unity.uiwidgets.devtools/Editor/inspector/inspector_text_style.cs
  4. 27
      com.unity.uiwidgets.devtools/Editor/theme.cs
  5. 8
      com.unity.uiwidgets.devtools/Editor/ui/icons.cs
  6. 2
      com.unity.uiwidgets/Runtime/foundation/key.cs
  7. 548
      com.unity.uiwidgets.devtools/Editor/app.cs
  8. 3
      com.unity.uiwidgets.devtools/Editor/config_specific.meta
  9. 52
      com.unity.uiwidgets.devtools/Editor/globals.cs
  10. 3
      com.unity.uiwidgets.devtools/Editor/globals.cs.meta
  11. 260
      com.unity.uiwidgets.devtools/Editor/inspector/diagnostics.cs
  12. 1001
      com.unity.uiwidgets.devtools/Editor/inspector/diagnostics_node.cs
  13. 152
      com.unity.uiwidgets.devtools/Editor/inspector/flutter_widget.cs
  14. 1001
      com.unity.uiwidgets.devtools/Editor/inspector/inspector_data_models.cs
  15. 408
      com.unity.uiwidgets.devtools/Editor/inspector/inspector_screen.cs
  16. 144
      com.unity.uiwidgets.devtools/Editor/inspector/inspector_screen_details_tab.cs
  17. 1001
      com.unity.uiwidgets.devtools/Editor/inspector/inspector_service.cs
  18. 873
      com.unity.uiwidgets.devtools/Editor/inspector/inspector_tree.cs
  19. 93
      com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/layout_explorer.cs
  20. 345
      com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/ui/layout_explorer_widget.cs
  21. 569
      com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/ui/utils.cs
  22. 21
      com.unity.uiwidgets.devtools/Editor/listenable.cs
  23. 20
      com.unity.uiwidgets.devtools/Editor/main.cs
  24. 290
      com.unity.uiwidgets.devtools/Editor/screen.cs
  25. 59
      com.unity.uiwidgets.devtools/Editor/utils.cs
  26. 23
      com.unity.uiwidgets.devtools/Editor/analytics/provider.cs
  27. 53
      com.unity.uiwidgets.devtools/Editor/analytics/stub_provider.cs
  28. 18
      com.unity.uiwidgets.devtools/Editor/config_specific/ide_theme/ide_theme.cs
  29. 807
      com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/flex/flex.cs
  30. 266
      com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/flex/utils.cs

5
com.unity.uiwidgets.devtools/Editor/Unity.UIWidgets.DevTools.Editor.asmdef


{
"name": "Unity.UIWidgets.DevTools.Editor",
"references": [],
"references": [
"Unity.UIWidgets",
"Unity.UIWidgets.Editor"
],
"includePlatforms": [
"Editor"
],

974
com.unity.uiwidgets.devtools/Editor/inspector/inspector_controller.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.async2;
using Unity.UIWidgets.DevTools.inspector;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.ui;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
namespace Unity.UIWidgets.DevTools.inspector
{
public class InspectorControllerUtils
{
public const string inspectorRefQueryParam = "inspectorRef";
public static TextStyle textStyleForLevel(DiagnosticLevel level, ColorScheme colorScheme) {
switch (level) {
case DiagnosticLevel.hidden:
return inspector_text_styles.unimportant(colorScheme);
case DiagnosticLevel.warning:
return inspector_text_styles.warning(colorScheme);
case DiagnosticLevel.error:
return inspector_text_styles.error(colorScheme);
case DiagnosticLevel.debug:
case DiagnosticLevel.info:
case DiagnosticLevel.fine:
default:
return inspector_text_styles.regular;
}
}
}
public class InspectorSettingsController {
public readonly ValueNotifier<bool> showOnlyUserDefined = new ValueNotifier<bool>(false);
public readonly ValueNotifier<bool> expandSelectedBuildMethod = new ValueNotifier<bool>(true);
}
/// This class is based on the InspectorPanel class from the Flutter IntelliJ
/// plugin with some refactors to make it more of a true controller than a view.
public class InspectorController : DisposableController, AutoDisposeControllerMixin, InspectorServiceClient {
public InspectorController(
InspectorService inspectorService,
InspectorTreeController inspectorTree,
InspectorTreeController detailsTree,
FlutterTreeType treeType,
InspectorController parent,
bool isSummaryTree = true,
VoidCallback onExpandCollapseSupported = null,
VoidCallback onLayoutExplorerSupported = null
)
{
_treeGroups = new InspectorObjectGroupManager(inspectorService, "tree");
_selectionGroups =
new InspectorObjectGroupManager(inspectorService, "selection");
_refreshRateLimiter = RateLimiter(refreshFramesPerSecond, refresh);
assert(inspectorTree != null);
inspectorTree.config = InspectorTreeConfig(
summaryTree: isSummaryTree,
treeType: treeType,
onNodeAdded: _onNodeAdded,
onHover: highlightShowNode,
onSelectionChange: selectionChanged,
onExpand: _onExpand,
onClientActiveChange: _onClientChange,
);
if (isSummaryTree) {
details = inspector.InspectorController(
inspectorService: inspectorService,
inspectorTree: detailsTree,
treeType: treeType,
parent: this,
isSummaryTree: false,
);
} else {
details = null;
}
flutterIsolateSubscription = serviceManager.isolateManager
.getSelectedIsolate((IsolateRef flutterIsolate) {
// TODO(jacobr): listen for real isolate stopped events.
// Only send an isolate stopped event if there was a previous isolate or
// the isolate has actually changed.
if (_activeIsolate != null && _activeIsolate != flutterIsolate) {
onIsolateStopped();
}
_activeIsolate = flutterIsolate;
});
_checkForExpandCollapseSupport();
_checkForLayoutExplorerSupport();
// This logic only needs to be run once so run it in the outermost
// controller.
if (parent == null) {
// If select mode is available, enable the on device inspector as it
// won't interfere with users.
addAutoDisposeListener(_supportsToggleSelectWidgetMode, () => {
if (_supportsToggleSelectWidgetMode.value) {
serviceManager.serviceExtensionManager.setServiceExtensionState(
extensions.enableOnDeviceInspector.extension,
true,
true
);
}
});
}
}
ValueListenable<bool> get _supportsToggleSelectWidgetMode =>
serviceManager.serviceExtensionManager
.hasServiceExtension(extensions.toggleSelectWidgetMode.extension);
void _onClientChange(bool added) {
_clientCount += added ? 1 : -1;
assert(_clientCount >= 0);
if (_clientCount == 1) {
setVisibleToUser(true);
setActivate(true);
} else if (_clientCount == 0) {
setVisibleToUser(false);
}
}
int _clientCount = 0;
/// Maximum frame rate to refresh the inspector at to avoid taxing the
/// physical device with too many requests to recompute properties and trees.
///
/// A value up to around 30 frames per second could be reasonable for
/// debugging highly interactive cases particularly when the user is on a
/// simulator or high powered native device. The frame rate is set low
/// for now mainly to minimize risk.
public static readonly float refreshFramesPerSecond = 5.0f;
public readonly bool isSummaryTree;
public readonly VoidCallback onExpandCollapseSupported;
public readonly VoidCallback onLayoutExplorerSupported;
/// Parent InspectorController if this is a details subtree.
InspectorController parent;
InspectorController details;
InspectorTreeController inspectorTree;
public readonly FlutterTreeType treeType;
public readonly InspectorService inspectorService;
StreamSubscription<IsolateRef> flutterIsolateSubscription;
IsolateRef _activeIsolate;
bool _disposed = false;
RateLimiter _refreshRateLimiter;
/// Groups used to manage and cancel requests to load data to display directly
/// in the tree.
InspectorObjectGroupManager _treeGroups;
/// Groups used to manage and cancel requests to determine what the current
/// selection is.
///
/// This group needs to be kept separate from treeGroups as the selection is
/// shared more with the details subtree.
/// TODO(jacobr): is there a way we can unify the selection and tree groups?
InspectorObjectGroupManager _selectionGroups;
/// Node being highlighted due to the current hover.
InspectorTreeNode currentShowNode
{
get
{
return inspectorTree.hover;
}
set
{
inspectorTree.hover = value;
}
}
bool flutterAppFrameReady = false;
bool treeLoadStarted = false;
RemoteDiagnosticsNode subtreeRoot;
bool programaticSelectionChangeInProgress = false;
public ValueListenable<InspectorTreeNode> selectedNode
{
get
{
return _selectedNode;
}
}
public readonly ValueNotifier<InspectorTreeNode> _selectedNode = new ValueNotifier((InspectorTreeNode)null);
InspectorTreeNode lastExpanded;
bool isActive = false;
public readonly Dictionary<InspectorInstanceRef, InspectorTreeNode> valueToInspectorTreeNode = new Dictionary<InspectorInstanceRef, InspectorTreeNode>();
/// When visibleToUser is false we should dispose all allocated objects and
/// not perform any actions.
bool visibleToUser = false;
bool highlightNodesShownInBothTrees = false;
bool detailsSubtree
{
get
{
return parent != null;
}
}
RemoteDiagnosticsNode selectedDiagnostic
{
get
{
return selectedNode.value?.diagnostic;
}
}
public readonly ValueNotifier<int> _selectedErrorIndex = new ValueNotifier<int>(null);
ValueListenable<int> selectedErrorIndex
{
get
{
return _selectedErrorIndex;
}
}
FlutterTreeType getTreeType() {
return treeType;
}
void setVisibleToUser(bool visible) {
if (visibleToUser == visible) {
return;
}
visibleToUser = visible;
details?.setVisibleToUser(visible);
if (visibleToUser) {
if (parent == null) {
maybeLoadUI();
}
} else {
shutdownTree(false);
}
}
bool hasDiagnosticsValue(InspectorInstanceRef _ref) {
return valueToInspectorTreeNode.ContainsKey(_ref);
}
RemoteDiagnosticsNode findDiagnosticsValue(InspectorInstanceRef _ref) {
return valueToInspectorTreeNode[_ref]?.diagnostic;
}
void endShowNode() {
highlightShowNode(null);
}
bool highlightShowFromNodeInstanceRef(InspectorInstanceRef _ref) {
return highlightShowNode(valueToInspectorTreeNode[_ref]);
}
bool highlightShowNode(InspectorTreeNode node) {
if (node == null && parent != null) {
// If nothing is highlighted, highlight the node selected in the parent
// tree so user has context of where the node selected in the parent is
// in the details tree.
node = findMatchingInspectorTreeNode(parent.selectedDiagnostic);
}
currentShowNode = node;
return true;
}
InspectorTreeNode findMatchingInspectorTreeNode(RemoteDiagnosticsNode node) {
if (node?.valueRef == null) {
return null;
}
return valueToInspectorTreeNode[node.valueRef];
}
Future<void> getPendingUpdateDone() async {
// Wait for the selection to be resolved followed by waiting for the tree to be computed.
await _selectionGroups?.pendingUpdateDone;
await _treeGroups?.pendingUpdateDone;
// TODO(jacobr): are there race conditions we need to think mroe carefully about here?
}
Future<void> refresh() {
if (!visibleToUser) {
// We will refresh again once we are visible.
// There is a risk a refresh got triggered before the view was visble.
return Future.value();
}
// TODO(jacobr): refresh the tree as well as just the properties.
if (details != null) {
return Future.wait(
[getPendingUpdateDone(), details.getPendingUpdateDone()]);
} else {
return getPendingUpdateDone();
}
}
// Note that this may be called after the controller is disposed. We need to handle nulls in the fields.
void shutdownTree(bool isolateStopped) {
// It is critical we clear all data that is kept alive by inspector object
// references in this method as that stale data will trigger inspector
// exceptions.
programaticSelectionChangeInProgress = true;
_treeGroups?.clear(isolateStopped);
_selectionGroups?.clear(isolateStopped);
currentShowNode = null;
_selectedNode.value = null;
lastExpanded = null;
subtreeRoot = null;
inspectorTree?.root = inspectorTree?.createNode();
details?.shutdownTree(isolateStopped);
programaticSelectionChangeInProgress = false;
valueToInspectorTreeNode?.clear();
}
void onIsolateStopped() {
flutterAppFrameReady = false;
treeLoadStarted = false;
shutdownTree(true);
}
@override
Future<void> onForceRefresh() async {
assert(!_disposed);
if (!visibleToUser || _disposed) {
return Future.value();
}
await recomputeTreeRoot(null, null, false);
filterErrors();
return getPendingUpdateDone();
}
void filterErrors() {
if (isSummaryTree) {
serviceManager.errorBadgeManager.filterErrors(InspectorScreen.id,
(id) => hasDiagnosticsValue(new InspectorInstanceRef(id)));
}
}
void setActivate(bool enabled) {
if (!enabled) {
onIsolateStopped();
isActive = false;
return;
}
if (isActive) {
// Already activated.
return;
}
isActive = true;
inspectorService.addClient(this);
maybeLoadUI();
}
List<String> get rootDirectories =>
_rootDirectories ?? parent.rootDirectories;
List<String> _rootDirectories;
Future<void> maybeLoadUI() async {
if (parent != null) {
// The parent controller will drive loading the UI.
return;
}
if (!visibleToUser || !isActive) {
return;
}
if (flutterAppFrameReady) {
_rootDirectories = await inspectorService.inferPubRootDirectoryIfNeeded();
// We need to start by querying the inspector service to find out the
// current state of the UI.
final queryParams = loadQueryParams();
final inspectorRef = queryParams.containsKey(inspectorRefQueryParam)
? queryParams[inspectorRefQueryParam]
: null;
await updateSelectionFromService(
firstFrame: true, inspectorRef: inspectorRef);
} else {
final ready = await inspectorService.isWidgetTreeReady();
flutterAppFrameReady = ready;
if (isActive && ready) {
await maybeLoadUI();
}
}
}
Future<void> recomputeTreeRoot(
RemoteDiagnosticsNode newSelection,
RemoteDiagnosticsNode detailsSelection,
bool setSubtreeRoot, {
int subtreeDepth = 2,
}) async {
assert(!_disposed);
if (_disposed) {
return;
}
_treeGroups.cancelNext();
try {
final group = _treeGroups.next;
final node = await (detailsSubtree
? group.getDetailsSubtree(subtreeRoot, subtreeDepth: subtreeDepth)
: group.getRoot(treeType));
if (node == null || group.disposed) {
return;
}
// TODO(jacobr): as a performance optimization we should check if the
// new tree is identical to the existing tree in which case we should
// dispose the new tree and keep the old tree.
_treeGroups.promoteNext();
clearValueToInspectorTreeNodeMapping();
if (node != null) {
final InspectorTreeNode rootNode = inspectorTree.setupInspectorTreeNode(
inspectorTree.createNode(),
node,
expandChildren: true,
expandProperties: false,
);
inspectorTree.root = rootNode;
} else {
inspectorTree.root = inspectorTree.createNode();
}
refreshSelection(newSelection, detailsSelection, setSubtreeRoot);
} catch (error) {
log(error.toString(), LogLevel.error);
_treeGroups.cancelNext();
return;
}
}
void clearValueToInspectorTreeNodeMapping() {
valueToInspectorTreeNode.clear();
}
/// Show the details subtree starting with node subtreeRoot highlighting
/// node subtreeSelection.
void showDetailSubtrees(
RemoteDiagnosticsNode subtreeRoot,
RemoteDiagnosticsNode subtreeSelection
) {
this.subtreeRoot = subtreeRoot;
details?.setSubtreeRoot(subtreeRoot, subtreeSelection);
}
InspectorInstanceRef getSubtreeRootValue() {
return subtreeRoot?.valueRef;
}
void setSubtreeRoot(
RemoteDiagnosticsNode node,
RemoteDiagnosticsNode selection,
) {
assert(detailsSubtree);
selection ??= node;
if (node != null && node == subtreeRoot) {
// Select the new node in the existing subtree.
applyNewSelection(selection, null, false);
return;
}
subtreeRoot = node;
if (node == null) {
// Passing in a null node indicates we should clear the subtree and free any memory allocated.
shutdownTree(false);
return;
}
// Clear now to eliminate frame of highlighted nodes flicker.
clearValueToInspectorTreeNodeMapping();
recomputeTreeRoot(selection, null, false);
}
InspectorTreeNode getSubtreeRootNode() {
if (subtreeRoot == null) {
return null;
}
return valueToInspectorTreeNode[subtreeRoot.valueRef];
}
void refreshSelection(RemoteDiagnosticsNode newSelection,
RemoteDiagnosticsNode detailsSelection, bool setSubtreeRoot) {
newSelection ??= selectedDiagnostic;
setSelectedNode(findMatchingInspectorTreeNode(newSelection));
syncSelectionHelper(
maybeRerootDetailsTree: setSubtreeRoot,
selection: newSelection,
detailsSelection: detailsSelection,
);
if (details != null) {
if (subtreeRoot != null && getSubtreeRootNode() == null) {
subtreeRoot = newSelection;
details.setSubtreeRoot(newSelection, detailsSelection);
}
}
syncTreeSelection();
}
void syncTreeSelection() {
programaticSelectionChangeInProgress = true;
inspectorTree.selection = selectedNode.value;
inspectorTree.expandPath(selectedNode.value);
programaticSelectionChangeInProgress = false;
animateTo(selectedNode.value);
}
void selectAndShowNode(RemoteDiagnosticsNode node) {
if (node == null) {
return;
}
selectAndShowInspectorInstanceRef(node.valueRef);
}
void selectAndShowInspectorInstanceRef(InspectorInstanceRef ref) {
final node = valueToInspectorTreeNode[ref];
if (node == null) {
return;
}
setSelectedNode(node);
syncTreeSelection();
}
InspectorTreeNode getTreeNode(RemoteDiagnosticsNode node) {
if (node == null) {
return null;
}
return valueToInspectorTreeNode[node.valueRef];
}
void maybeUpdateValueUI(InspectorInstanceRef valueRef) {
var node = valueToInspectorTreeNode[valueRef];
if (node == null) {
// The value isn't shown in the parent tree. Nothing to do.
return;
}
inspectorTree.nodeChanged(node);
}
public override void onFlutterFrame() {
flutterAppFrameReady = true;
if (!visibleToUser) {
return;
}
if (!treeLoadStarted) {
treeLoadStarted = true;
// This was the first frame.
maybeLoadUI();
}
_refreshRateLimiter.scheduleRequest();
}
bool identicalDiagnosticsNodes(
RemoteDiagnosticsNode a,
RemoteDiagnosticsNode b
) {
if (a == b) {
return true;
}
if (a == null || b == null) {
return false;
}
return a.dartDiagnosticRef == b.dartDiagnosticRef;
}
@override
void onInspectorSelectionChanged() {
if (!visibleToUser) {
// Don't do anything. We will update the view once it is visible again.
return;
}
if (detailsSubtree) {
// Wait for the master to update.
return;
}
updateSelectionFromService(firstFrame: false);
}
Future<void> updateSelectionFromService(
{@required bool firstFrame, String inspectorRef}) async {
if (parent != null) {
// If we have a parent controller we should wait for the parent to update
// our selection rather than updating it our self.
return;
}
if (_selectionGroups == null) {
// Already disposed. Ignore this requested to update selection.
return;
}
treeLoadStarted = true;
_selectionGroups.cancelNext();
final group = _selectionGroups.next;
if (inspectorRef != null) {
await group.setSelectionInspector(
InspectorInstanceRef(inspectorRef),
false,
);
}
final pendingSelectionFuture = group.getSelection(
selectedDiagnostic,
treeType,
isSummaryTree: isSummaryTree,
);
final Future<RemoteDiagnosticsNode> pendingDetailsFuture = isSummaryTree
? group.getSelection(selectedDiagnostic, treeType, isSummaryTree: false)
: null;
try {
final RemoteDiagnosticsNode newSelection = await pendingSelectionFuture;
if (group.disposed) return;
RemoteDiagnosticsNode detailsSelection;
if (pendingDetailsFuture != null) {
detailsSelection = await pendingDetailsFuture;
if (group.disposed) return;
}
if (!firstFrame &&
detailsSelection?.valueRef == details?.selectedDiagnostic?.valueRef &&
newSelection?.valueRef == selectedDiagnostic?.valueRef) {
// No need to change the selection as it didn't actually change.
_selectionGroups.cancelNext();
return;
}
_selectionGroups.promoteNext();
subtreeRoot = newSelection;
applyNewSelection(newSelection, detailsSelection, true);
} catch (error) {
if (_selectionGroups.next == group) {
log(error.toString(), LogLevel.error);
_selectionGroups.cancelNext();
}
}
}
void applyNewSelection(
RemoteDiagnosticsNode newSelection,
RemoteDiagnosticsNode detailsSelection,
bool setSubtreeRoot,
) {
final InspectorTreeNode nodeInTree =
findMatchingInspectorTreeNode(newSelection);
if (nodeInTree == null) {
// The tree has probably changed since we last updated. Do a full refresh
// so that the tree includes the new node we care about.
recomputeTreeRoot(newSelection, detailsSelection, setSubtreeRoot);
}
refreshSelection(newSelection, detailsSelection, setSubtreeRoot);
}
void animateTo(InspectorTreeNode node) {
if (node == null) {
return;
}
final List<InspectorTreeNode> targets = [node];
// Backtrack to the the first non-property parent so that all properties
// for the node are visible if one property is animated to. This is helpful
// as typically users want to view the properties of a node as a chunk.
while (node.parent != null && node.diagnostic?.isProperty == true) {
node = node.parent;
}
// Make sure we scroll so that immediate un-expanded children
// are also in view. There is no risk in including these children as
// the amount of space they take up is bounded. This also ensures that if
// a node is selected, its properties will also be selected as by
// convention properties are the first children of a node and properties
// typically do not have children and are never expanded by default.
for (InspectorTreeNode child in node.children) {
final RemoteDiagnosticsNode diagnosticsNode = child.diagnostic;
targets.add(child);
if (!child.isLeaf && child.isExpanded) {
// Stop if we get to expanded children as they might be too large
// to try to scroll into view.
break;
}
if (diagnosticsNode != null && !diagnosticsNode.isProperty) {
break;
}
}
inspectorTree.animateToTargets(targets);
}
void setSelectedNode(InspectorTreeNode newSelection) {
if (newSelection == selectedNode.value) {
return;
}
_selectedNode.value = newSelection;
lastExpanded = null; // New selected node takes precedence.
endShowNode();
if (details != null) {
details.endShowNode();
} else if (parent != null) {
parent.endShowNode();
}
_updateSelectedErrorFromNode(_selectedNode.value);
animateTo(selectedNode.value);
}
/// Update the index of the selected error based on a node that has been
/// selected in the tree.
void _updateSelectedErrorFromNode(InspectorTreeNode node) {
final inspectorRef = node?.diagnostic?.valueRef?.id;
final errors = serviceManager.errorBadgeManager
.erroredItemsForPage(InspectorScreen.id)
.value;
// Check whether the node that was just selected has any errors associated
// with it.
var errorIndex = inspectorRef != null
? errors.keys.toList().indexOf(inspectorRef)
: null;
if (errorIndex == -1) {
errorIndex = null;
}
_selectedErrorIndex.value = errorIndex;
if (errorIndex != null) {
// Mark the error as "seen" as this will render slightly differently
// so the user can track which errored nodes they've viewed.
serviceManager.errorBadgeManager
.markErrorAsRead(InspectorScreen.id, errors[inspectorRef]);
// Also clear the error badge since new errors may have arrived while
// the inspector was visible (normally they're cleared when visiting
// the screen) and visiting an errored node seems an appropriate
// acknowledgement of the errors.
serviceManager.errorBadgeManager.clearErrors(InspectorScreen.id);
}
}
/// Updates the index of the selected error and selects its node in the tree.
void selectErrorByIndex(int index) {
_selectedErrorIndex.value = index;
if (index == null) return;
final errors = serviceManager.errorBadgeManager
.erroredItemsForPage(InspectorScreen.id)
.value;
updateSelectionFromService(
firstFrame: false, inspectorRef: errors.keys.elementAt(index));
}
void _onExpand(InspectorTreeNode node) {
inspectorTree.maybePopulateChildren(node);
}
void selectionChanged() {
if (visibleToUser == false) {
return;
}
InspectorTreeNode node = inspectorTree.selection;
if (node != null) {
inspectorTree.maybePopulateChildren(node);
}
if (programaticSelectionChangeInProgress) {
return;
}
if (node != null) {
setSelectedNode(node);
bool maybeReroot = isSummaryTree &&
details != null &&
selectedDiagnostic != null &&
!details.hasDiagnosticsValue(selectedDiagnostic.valueRef);
syncSelectionHelper(
maybeRerootDetailsTree: maybeReroot,
selection: selectedDiagnostic,
detailsSelection: selectedDiagnostic,
);
if (!maybeReroot) {
if (isSummaryTree && details != null) {
details.selectAndShowNode(selectedDiagnostic);
} else if (parent != null) {
parent
.selectAndShowNode(firstAncestorInParentTree(selectedNode.value));
}
}
}
}
RemoteDiagnosticsNode firstAncestorInParentTree(InspectorTreeNode node) {
if (parent == null) {
return node.diagnostic;
}
while (node != null) {
var diagnostic = node.diagnostic;
if (diagnostic != null &&
parent.hasDiagnosticsValue(diagnostic.valueRef)) {
return parent.findDiagnosticsValue(diagnostic.valueRef);
}
node = node.parent;
}
return null;
}
void syncSelectionHelper(
bool maybeRerootDetailsTree = true,
RemoteDiagnosticsNode selection = null,
RemoteDiagnosticsNode detailsSelection = null
) {
if (selection != null) {
if (selection.isCreatedByLocalProject) {
_navigateTo(selection);
}
}
if (detailsSubtree || details == null) {
if (selection != null) {
var toSelect = selectedNode.value;
while (toSelect != null && toSelect.diagnostic.isProperty) {
toSelect = toSelect.parent;
}
if (toSelect != null) {
var diagnosticToSelect = toSelect.diagnostic;
diagnosticToSelect.setSelectionInspector(true);
}
}
}
if (maybeRerootDetailsTree) {
showDetailSubtrees(selection, detailsSelection);
} else if (selection != null) {
// We can't rely on the details tree to update the selection on the server in this case.
selection.setSelectionInspector(true);
}
}
void _navigateTo(RemoteDiagnosticsNode diagnostic) {
// TODO(jacobr): dispatch an event over the inspectorService requesting a
// navigate operation.
}
public override void dispose() {
D.assert(!_disposed);
_disposed = true;
flutterIsolateSubscription.cancel();
if (inspectorService != null) {
shutdownTree(false);
}
_treeGroups?.clear(false);
_treeGroups = null;
_selectionGroups?.clear(false);
_selectionGroups = null;
details?.dispose();
base.dispose();
}
static string treeTypeDisplayName(FlutterTreeType treeType) {
switch (treeType) {
case FlutterTreeType.widget:
return "Widget";
case FlutterTreeType.renderObject:
return "Render Objects";
default:
return null;
}
}
void _onNodeAdded(
InspectorTreeNode node,
RemoteDiagnosticsNode diagnosticsNode
) {
InspectorInstanceRef valueRef = diagnosticsNode.valueRef;
// Properties do not have unique values so should not go in the valueToInspectorTreeNode map.
if (valueRef.id != null && !diagnosticsNode.isProperty) {
valueToInspectorTreeNode[valueRef] = node;
}
}
Future expandAllNodesInDetailsTree() {
details.recomputeTreeRoot(
inspectorTree.selection?.diagnostic,
details.inspectorTree.selection?.diagnostic ??
details.inspectorTree.root?.diagnostic,
false,
subtreeDepth: maxJsInt
);
}
Future collapseDetailsToSelected() {
details.inspectorTree.collapseToSelected();
details.animateTo(details.inspectorTree.selection);
return null;
}
/// execute given [callback] when minimum Flutter [version] is met.
void _onVersionSupported(
SemanticVersion version,
VoidCallback callback
) {
final flutterVersionServiceListenable = serviceManager
.registeredServiceListenable(registrations.flutterVersion.service);
addAutoDisposeListener(flutterVersionServiceListenable, () async {
final registered = flutterVersionServiceListenable.value;
if (registered) {
final flutterVersion =
FlutterVersion.parse((await serviceManager.flutterVersion).json);
if (flutterVersion.isSupported(supportedVersion: version)) {
callback();
}
}
});
}
void _checkForExpandCollapseSupport() {
if (onExpandCollapseSupported == null) return;
// Configurable subtree depth is available in versions of Flutter
// greater than or equal to 1.9.7, but the flutterVersion service is
// not available until 1.10.1, so we will check for 1.10.1 here.
_onVersionSupported(
SemanticVersion(major: 1, minor: 10, patch: 1),
onExpandCollapseSupported
);
}
void _checkForLayoutExplorerSupport() {
if (onLayoutExplorerSupported == null) return;
_onVersionSupported(
SemanticVersion(major: 1, minor: 13, patch: 1),
onLayoutExplorerSupported
);
}
}
}

12
com.unity.uiwidgets.devtools/Editor/inspector/inspector_text_style.cs


public static TextStyle warning(ColorScheme colorScheme) => new TextStyle(
color:
CommonThemeUtils.isLight ? Colors.orange.shade900 : Colors.orange.shade400);
public static TextStyle error(ColorScheme colorScheme) => new TextStyle(
public static TextStyle error(ColorScheme colorScheme) => new TextStyle(
TextStyle link(ColorScheme colorScheme) => new TextStyle(
public static TextStyle link(ColorScheme colorScheme) => new TextStyle(
TextStyle regularBold(ColorScheme colorScheme) => new TextStyle(
public static TextStyle regularBold(ColorScheme colorScheme) => new TextStyle(
TextStyle regularItalic(ColorScheme colorScheme) => new TextStyle(
public static TextStyle regularItalic(ColorScheme colorScheme) => new TextStyle(
TextStyle unimportantItalic(ColorScheme colorScheme) =>
public static TextStyle unimportantItalic(ColorScheme colorScheme) =>
));
));
}
}

27
com.unity.uiwidgets.devtools/Editor/theme.cs


using System;
using uiwidgets;
using Unity.UIWidgets.DevTools.config_specific.ide_theme;
using Unity.UIWidgets.material;
using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.DevTools
{

public static readonly float defaultIconSize = 16.0f;
public static readonly TimeSpan defaultDuration = TimeSpan.FromMilliseconds(200);
public static bool isLight = false;
public static readonly NeverScrollableScrollPhysics defaultTabBarViewPhysics = new NeverScrollableScrollPhysics();
public static readonly Color devtoolsError = new Color(0xFFAF4054);
public static Color defaultBackground
{

return isLight ? Colors.black : Color.fromARGB(255, 187, 187, 187);
}
}
// ThemeData themeFor(
// bool isDarkTheme,
// IdeTheme ideTheme
// ) {
// // If the theme specifies a background color, use it to infer a theme.
// if (isValidDarkColor(ideTheme?.backgroundColor)) {
// return _darkTheme(ideTheme);
// } else if (isValidLightColor(ideTheme?.backgroundColor)) {
// return _lightTheme(ideTheme);
// }
//
// return isDarkTheme ? _darkTheme(ideTheme) : _lightTheme(ideTheme);
// }
// bool isValidDarkColor(Color color) {
// if (color == null) {
// return false;
// }
// return color.computeLuminance() <= _lightDarkLuminanceThreshold;
// }
}
}

8
com.unity.uiwidgets.devtools/Editor/ui/icons.cs


});
}
CustomIcon fromWidgetName(string name) {
public CustomIcon fromWidgetName(string name) {
if (name == null) {
return null;
}

);
}
CustomIcon fromInfo(String name) {
public CustomIcon fromInfo(String name) {
if (name == null) {
return null;
}

public class ColorIconMaker {
public readonly Dictionary<Color, ColorIcon> iconCache = new Dictionary<Color, ColorIcon>();
ColorIcon getCustomIcon(Color color) {
public ColorIcon getCustomIcon(Color color) {
return iconCache.putIfAbsent(color, () => new ColorIcon(color));
}
}

public class FlutterMaterialIcons {
public FlutterMaterialIcons(){}
static Icon getIconForCodePoint(int charCode, ColorScheme colorScheme) {
public static Icon getIconForCodePoint(int charCode, ColorScheme colorScheme) {
return new Icon(new IconData(charCode), color: CommonThemeUtils.defaultForeground);
}
}

2
com.unity.uiwidgets/Runtime/foundation/key.cs


#pragma warning disable 0660
#pragma warning disable 0661
public abstract class Key {
protected Key() {
public Key() {
}
public static Key key(string value) {

548
com.unity.uiwidgets.devtools/Editor/app.cs


using System;
using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.async2;
using Unity.UIWidgets.DevTools;
using Unity.UIWidgets.DevTools.analytics;
using Unity.UIWidgets.DevTools.config_specific.ide_theme;
using Unity.UIWidgets.DevTools.inspector;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Screen = Unity.UIWidgets.DevTools.Screen;
namespace Unity.UIWidgets.DevTools
{
public class appUtils
{
public static readonly bool showVmDeveloperMode = false;
/// Whether this DevTools build is external.
public bool isExternalBuild = true;
public static List<DevToolsScreen<object>> defaultScreens {
get
{
return new List<DevToolsScreen<object>>
{
new DevToolsScreen<object>(
new InspectorScreen(),
createController: () => new InspectorSettingsController()
)
};
}
}
}
public class DevToolsApp : StatefulWidget {
public DevToolsApp(
List<DevToolsScreen<object>> screens,
IdeTheme ideTheme,
AnalyticsProvider analyticsProvider
)
{
this.screens = screens;
this.ideTheme = ideTheme;
this.analyticsProvider = analyticsProvider;
}
public readonly List<DevToolsScreen<object>> screens;
public readonly IdeTheme ideTheme;
public readonly AnalyticsProvider analyticsProvider;
public override State createState()
{
return new DevToolsAppState();
}
}
// TODO(https://github.com/flutter/devtools/issues/1146): Introduce tests that
// navigate the full app.
public class DevToolsAppState : State<DevToolsApp> {
List<Screen> _screens
{
get
{
List<Screen> screensList = new List<Screen>();
foreach (var s in widget.screens)
{
screensList.Add(s.screen);
}
return screensList;
}
}
IdeTheme ideTheme
{
get
{
return widget.ideTheme;
}
}
bool isDarkThemeEnabled
{
get
{
return _isDarkThemeEnabled;
}
}
bool _isDarkThemeEnabled;
bool vmDeveloperModeEnabled
{
get
{
return _vmDeveloperModeEnabled;
}
}
bool _vmDeveloperModeEnabled;
public override void initState() {
base.initState();
// ga.setupDimensions();
//
// serviceManager.isolateManager.onSelectedIsolateChanged.listen((_) => {
// setState(() => {
// _clearCachedRoutes();
// });
// });
//
// _isDarkThemeEnabled = preferences.darkModeTheme.value;
// preferences.darkModeTheme.addListener(() => {
// setState(() => {
// _isDarkThemeEnabled = preferences.darkModeTheme.value;
// });
// });
//
// _vmDeveloperModeEnabled = preferences.vmDeveloperModeEnabled.value;
// preferences.vmDeveloperModeEnabled.addListener(() => {
// setState(() => {
// _vmDeveloperModeEnabled = preferences.vmDeveloperModeEnabled.value;
// });
// });
}
public void didUpdateWidget(DevToolsApp oldWidget) {
base.didUpdateWidget(oldWidget);
//_clearCachedRoutes();
}
// Gets the page for a given page/path and args.
Page _getPage(BuildContext context2, string page, Dictionary<string, string> args) {
// Provide the appropriate page route.
if (pages.ContainsKey(page)) {
Widget widget = pages[page](
context2,
page,
args
);
D.assert(() => {
widget = new _AlternateCheckedModeBanner(
builder: (context) => pages[page](
context,
page,
args
)
);
return true;
});
return MaterialPage(child: widget);
}
// Return a page not found.
return MaterialPage(
child: DevToolsScaffold.withChild(
key: Key.key("not-found"),
child: CenteredMessage("'$page' not found."),
ideTheme: ideTheme,
analyticsProvider: widget.analyticsProvider
)
);
}
Widget _buildTabbedPage(
BuildContext context,
string page,
Dictionary<string, string> _params
) {
var vmServiceUri = _params["uri"];
// Always return the landing screen if there's no VM service URI.
if (vmServiceUri?.isEmpty() ?? true) {
return DevToolsScaffold.withChild(
key: Key.key("landing"),
child: LandingScreenBody(),
ideTheme: ideTheme,
analyticsProvider: widget.analyticsProvider,
actions: new List<Widget>{
new OpenSettingsAction(),
new OpenAboutAction()
}
);
}
// TODO(dantup): We should be able simplify this a little, removing params['page']
// and only supporting /inspector (etc.) instead of also &page=inspector if
// all IDEs switch over to those URLs.
if (page?.isEmpty() ?? true) {
page = _params["page"];
}
var embed = _params["embed"] == "true";
var hide = _params["hide"]?.Split(',');
return Initializer(
url: vmServiceUri,
allowConnectionScreenOnDisconnect: !embed,
builder: (_) => {
return new ValueListenableBuilder<bool>(
valueListenable: preferences.vmDeveloperModeEnabled,
builder: (_, __, ___) => {
var tabs = _visibleScreens()
.where((p) => embed && page != null ? p.screenId == page : true)
.where((p) => !hide.Contains(p.screenId))
.toList();
if (tabs.isEmpty) {
return DevToolsScaffold.withChild(
child: CenteredMessage(
$"The \"{page}\" screen is not available for this application."),
ideTheme: ideTheme,
analyticsProvider: widget.analyticsProvider
);
}
List<Widget> widgets = new List<Widget>();
if (serviceManager.connectedApp.isFlutterAppNow)
{
widgets.Add(HotReloadButton());
widgets.Add(HotRestartButton());
}
widgets.Add(new OpenSettingsAction());
widgets.Add(new OpenAboutAction());
return _providedControllers(
child: DevToolsScaffold(
embed: embed,
ideTheme: ideTheme,
page: page,
tabs: tabs,
analyticsProvider: widget.analyticsProvider,
actions: widgets
)
);
}
);
}
);
}
// The pages that the app exposes.
Dictionary<string, UrlParametersBuilder> pages {
get
{
if (_routes == null)
{
return null;
}
return _routes ?? {
homePageId: _buildTabbedPage,
foreach (Screen screen in widget.screens)
screen.screen.screenId: _buildTabbedPage,
snapshotPageId: (_, __, args) => {
final snapshotArgs = SnapshotArguments.fromArgs(args);
return DevToolsScaffold.withChild(
key: new UniqueKey(),
analyticsProvider: widget.analyticsProvider,
child: _providedControllers(
offline: true,
child: SnapshotScreenBody(snapshotArgs, _screens),
),
ideTheme: ideTheme,
);
},
appSizePageId: (_, __, ___) =>{
return DevToolsScaffold.withChild(
key: Key.key("appsize"),
analyticsProvider: widget.analyticsProvider,
child: _providedControllers(
child: AppSizeBody()
),
ideTheme: ideTheme,
actions: [
OpenSettingsAction(),
OpenAboutAction(),
],
);
},
};
}
}
Dictionary<string, UrlParametersBuilder> _routes;
void _clearCachedRoutes() {
_routes = null;
}
List<Screen> _visibleScreens() => _screens.where(shouldShowScreen).toList();
Widget _providedControllers({@required Widget child, bool offline = false}) {
var _providers = widget.screens
.where((s) =>
s.createController != null && (offline ? s.supportsOffline : true))
.map((s) => s.controllerProvider)
.toList();
return MultiProvider(
providers: _providers,
child: child,
);
}
public override Widget build(BuildContext context) {
return new MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.light(),//themeFor(isDarkTheme: isDarkThemeEnabled, ideTheme: ideTheme),
builder: (context2, child) => null//Notifications(child: child),
// routerDelegate: DevToolsRouterDelegate(_getPage),
// routeInformationParser: DevToolsRouteInformationParser()
);
}
}
public class DevToolsScreen<C> {
public delegate C CreateController();
public DevToolsScreen(
Screen screen,
CreateController createController = null,
bool supportsOffline = false
)
{
this.screen = screen;
this.createController = createController;
this.supportsOffline = supportsOffline;
}
public readonly Screen screen;
public readonly CreateController createController;
public readonly bool supportsOffline;
Provider<C> controllerProvider {
get
{
D.assert(createController != null);
return Provider<C>(create: (_) => createController());
}
}
}
public delegate Widget UrlParametersBuilder(
BuildContext buildContext,
string s,
Dictionary<string, string> dictionary
);
public class _AlternateCheckedModeBanner : StatelessWidget {
public _AlternateCheckedModeBanner(Key key = null, WidgetBuilder builder = null) : base(key: key)
{
this.builder = builder;
}
public readonly WidgetBuilder builder;
public override Widget build(BuildContext context) {
return new Banner(
message: "DEBUG",
textDirection: TextDirection.ltr,
location: BannerLocation.topStart,
child: new Builder(
builder: builder
)
);
}
}
/*public class OpenAboutAction : StatelessWidget {
public override Widget build(BuildContext context) {
return DevToolsTooltip(
tooltip: "About DevTools",
child: new InkWell(
onTap: () => {
unawaited(showDialog(
context: context,
builder: (context2) => new DevToolsAboutDialog()
));
},
child: new Container(
width: DevToolsScaffold.actionWidgetSize,
height: DevToolsScaffold.actionWidgetSize,
alignment: Alignment.center,
child: new Icon(
Icons.help_outline,
size: actionsIconSize
)
)
)
);
}
}
public class OpenSettingsAction : StatelessWidget {
public override Widget build(BuildContext context) {
return DevToolsTooltip(
tooltip: "Settings",
child: new InkWell(
onTap: () => {
unawaited(showDialog(
context: context,
builder: (context2) => new SettingsDialog()
));
},
child: new Container(
width: DevToolsScaffold.actionWidgetSize,
height: DevToolsScaffold.actionWidgetSize,
alignment: Alignment.center,
child: new Icon(
Icons.settings,
size: actionsIconSize
)
)
)
);
}
}
public class DevToolsAboutDialog : StatelessWidget {
public override Widget build(BuildContext context) {
var theme = Theme.of(context);
List<Widget> widgets = new List<Widget>();
widgets.Add(new SizedBox(height: defaultSpacing));
List<Widget> temp = dialogSubHeader(theme, "Feedback");
foreach (var widget in temp)
{
widgets.Add(widget);
}
widgets.Add(new Wrap(
children: new List<Widget>{
new Text("Encountered an issue? Let us know at "),
_createFeedbackLink(context),
new Text(".")
}
));
return DevToolsDialog(
title: dialogTitleText(theme, "About DevTools"),
content: new Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: widgets
),
actions: [
DialogCloseButton(),
],
);
}
Widget _aboutDevTools(BuildContext context) {
return const SelectableText('DevTools version ${devtools.version}');
}
Widget _createFeedbackLink(BuildContext context) {
const urlPath = 'github.com/flutter/devtools/issues';
final colorScheme = Theme.of(context).colorScheme;
return InkWell(
onTap: () async {
ga.select(devToolsMain, feedback);
const reportIssuesUrl = 'https://$urlPath';
await launchUrl(reportIssuesUrl, context);
},
child: Text(urlPath, style: linkTextStyle(colorScheme)),
);
}
}
TODO(kenz): merge the checkbox functionality here with [NotifierCheckbox]
class SettingsDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DevToolsDialog(
title: dialogTitleText(Theme.of(context), 'Settings'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildOption(
label: const Text('Use a dark theme'),
listenable: preferences.darkModeTheme,
toggle: preferences.toggleDarkModeTheme,
),
if (isExternalBuild && isDevToolsServerAvailable)
_buildOption(
label: const Text('Enable analytics'),
listenable: ga.gaEnabledNotifier,
toggle: ga.setAnalyticsEnabled,
),
_buildOption(
label: const Text('Enable VM developer mode'),
listenable: preferences.vmDeveloperModeEnabled,
toggle: preferences.toggleVmDeveloperMode,
),
],
),
actions: [
DialogCloseButton(),
],
);
}
Widget _buildOption({
Text label,
ValueListenable<bool> listenable,
Function(bool) toggle,
}) {
return InkWell(
onTap: () => toggle(!listenable.value),
child: Row(
children: [
ValueListenableBuilder<bool>(
valueListenable: listenable,
builder: (context, value, _) {
return Checkbox(
value: value,
onChanged: toggle,
);
},
),
label,
],
),
);
}*/
}

3
com.unity.uiwidgets.devtools/Editor/config_specific.meta


fileFormatVersion: 2
guid: ec20b4818fa849a88ade34bed3bc361d
timeCreated: 1615531810

52
com.unity.uiwidgets.devtools/Editor/globals.cs


using System;
using System.Collections.Generic;
namespace Unity.UIWidgets.DevTools
{
public class globals
{
public static bool offlineMode = false;
// TODO(kenz): store this data in an inherited widget.
Dictionary<string, object> offlineDataJson = new Dictionary<string, object>();
public static readonly Dictionary<Type, object> _globals = new Dictionary<Type, object>();
public static ServiceConnectionManager serviceManager
{
get
{
return _globals[ServiceConnectionManager];
}
}
// MessageBus get messageBus => globals[MessageBus];
//
// FrameworkController get frameworkController => globals[FrameworkController];
//
// Storage get storage => globals[Storage];
//
// SurveyService get surveyService => globals[SurveyService];
//
public static PreferencesController preferences
{
get
{
return _globals[PreferencesController];
}
}
// string generateDevToolsTitle() {
// if (!serviceManager.hasConnection) return " ";
// return serviceManager.connectedApp.isFlutterAppNow
// ? "Flutter DevTools"
// : "Dart DevTools";
// }
//
// void setGlobal(Type clazz, dynamic instance) {
// globals[clazz] = instance;
// }
}
}

3
com.unity.uiwidgets.devtools/Editor/globals.cs.meta


fileFormatVersion: 2
guid: 2cd9c39bad944654afbb4b9928bf19b9
timeCreated: 1615544477

260
com.unity.uiwidgets.devtools/Editor/inspector/diagnostics.cs


using System;
using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.DevTools.inspector;
using Unity.UIWidgets.DevTools.ui;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
namespace Unity.UIWidgets.DevTools.inspector
{
public class diagnosticsUtils
{
public static ColorIconMaker _colorIconMaker = new ColorIconMaker();
public static CustomIconMaker _customIconMaker = new CustomIconMaker();
public static CustomIcon defaultIcon = _customIconMaker.fromInfo("Default");
public static readonly bool _showRenderObjectPropertiesAsLinks = false;
}
public class DiagnosticsNodeDescription : StatelessWidget {
public DiagnosticsNodeDescription(
RemoteDiagnosticsNode diagnostic,
bool isSelected = false,
string errorText = null
)
{
this.diagnostic = diagnostic;
this.isSelected = isSelected;
this.errorText = errorText;
}
public readonly RemoteDiagnosticsNode diagnostic;
public readonly bool isSelected;
public readonly string errorText;
Widget _paddedIcon(Widget icon) {
return new Padding(
padding: EdgeInsets.only(right: iconPadding),
child: icon
);
}
IEnumerable<TextSpan> _buildDescriptionTextSpans(
String description,
TextStyle textStyle,
ColorScheme colorScheme
) {
if (diagnostic.isDiagnosticableValue) {
var match = treeNodePrimaryDescriptionPattern.firstMatch(description);
if (match != null) {
yield return new TextSpan(text: match.group(1), style: textStyle);
if (match.group(2).isNotEmpty()) {
yield return new TextSpan(
text: match.group(2),
style: inspector_text_styles.unimportant(colorScheme)
);
}
//return new List<TextSpan>();
}
} else if (diagnostic.type == "ErrorDescription") {
var match = assertionThrownBuildingError.firstMatch(description);
if (match != null) {
yield return new TextSpan(text: match.group(1), style: textStyle);
yield return new TextSpan(text: match.group(3), style: textStyle);
// return;
}
}
if (description?.isNotEmpty() == true) {
yield return new TextSpan(text: description, style: textStyle);
}
}
Widget buildDescription(
string description,
TextStyle textStyle,
ColorScheme colorScheme,
bool isProperty = false
) {
return new RichText(
overflow: TextOverflow.ellipsis,
text: new TextSpan(
children: _buildDescriptionTextSpans(
description,
textStyle,
colorScheme
).ToList()
)
);
}
public override Widget build(BuildContext context) {
if (diagnostic == null) {
return new SizedBox();
}
var colorScheme = Theme.of(context).colorScheme;
Widget icon = diagnostic.icon;
var children = new List<Widget>();
if (icon != null) {
children.Add(_paddedIcon(icon));
}
string name = diagnostic.name;
TextStyle textStyle = DefaultTextStyle.of(context)
.style
.merge(InspectorControllerUtils.textStyleForLevel(diagnostic.level, colorScheme));
if (diagnostic.isProperty) {
// Display of inline properties.
string propertyType = diagnostic.propertyType;
Dictionary<string, object> properties = diagnostic.valuePropertiesJson;
if (name?.isNotEmpty() == true && diagnostic.showName) {
children.Add(new Text($"{name}{diagnostic.separator} ", style: textStyle));
}
if (diagnostic.isCreatedByLocalProject) {
textStyle =
textStyle.merge(inspector_text_styles.regularBold(colorScheme));
}
string description = diagnostic.description;
if (propertyType != null && properties != null) {
switch (propertyType) {
case "Color":
{
int alpha = JsonUtils.getIntMember(properties, "alpha");
int red = JsonUtils.getIntMember(properties, "red");
int green = JsonUtils.getIntMember(properties, "green");
int blue = JsonUtils.getIntMember(properties, "blue");
string radix(int chan) => Convert.ToString(chan,16).PadLeft(2, '0');
if (alpha == 255) {
description = $"#{radix(red)}{radix(green)}{radix(blue)}";
} else {
description =
$"#{radix(alpha)}{radix(red)}{radix(green)}{radix(blue)}";
}
Color color = Color.fromARGB(alpha, red, green, blue);
children.Add(_paddedIcon(diagnosticsUtils._colorIconMaker.getCustomIcon(color)));
break;
}
case "IconData":
{
int codePoint =
JsonUtils.getIntMember(properties, "codePoint");
if (codePoint > 0) {
icon = FlutterMaterialIcons.getIconForCodePoint(
codePoint,
colorScheme
);
if (icon != null) {
children.Add(_paddedIcon(icon));
}
}
break;
}
}
}
if (diagnosticsUtils._showRenderObjectPropertiesAsLinks &&
propertyType == "RenderObject") {
textStyle = textStyle.merge(inspector_text_styles.link(colorScheme));
}
// TODO(jacobr): custom display for units, iterables, and padding.
children.Add(new Flexible(
child: buildDescription(
description,
textStyle,
colorScheme,
isProperty: true
)
));
if (diagnostic.level == DiagnosticLevel.fine &&
diagnostic.hasDefaultValue) {
children.Add(new Text(" "));
children.Add(_paddedIcon(diagnosticsUtils.defaultIcon));
}
} else {
// Non property, regular node case.
if (name?.isNotEmpty() == true && diagnostic.showName && name != "child") {
if (name.StartsWith("child ")) {
children.Add(new Text(
name,
style: inspector_text_styles.unimportant(colorScheme)
));
} else {
children.Add(new Text(name, style: textStyle));
}
if (diagnostic.showSeparator) {
children.Add(new Text(
diagnostic.separator,
style: inspector_text_styles.unimportant(colorScheme)
));
if (diagnostic.separator != " " &&
diagnostic.description.isNotEmpty()) {
children.Add(new Text(
" ",
style: inspector_text_styles.unimportant(colorScheme)
));
}
}
}
if (!diagnostic.isSummaryTree && diagnostic.isCreatedByLocalProject) {
textStyle =
textStyle.merge(inspector_text_styles.regularBold(colorScheme));
}
var diagnosticDescription = buildDescription(
diagnostic.description,
textStyle,
colorScheme,
isProperty: false
);
if (errorText != null) {
// TODO(dantup): Find if there's a way to achieve this without
// the nested row.
diagnosticDescription = new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: new List<Widget>{
diagnosticDescription,
_buildErrorText(colorScheme),
}
);
}
children.Add(new Expanded(child: diagnosticDescription));
}
return new Row(mainAxisSize: MainAxisSize.min, children: children);
}
Flexible _buildErrorText(ColorScheme colorScheme) {
return new Flexible(
child: new RichText(
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
text: new TextSpan(
text: errorText,
style: isSelected
? inspector_text_styles.regular
: inspector_text_styles.error(colorScheme)
)
)
);
}
}
}

1001
com.unity.uiwidgets.devtools/Editor/inspector/diagnostics_node.cs
文件差异内容过多而无法显示
查看文件

152
com.unity.uiwidgets.devtools/Editor/inspector/flutter_widget.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.DevTools.ui;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.DevTools.inspector
{
public class Category {
public Category(string label, Image icon)
{
this.label = label;
this.icon = icon;
}
public static Category accessibility = new Category(
"Accessibility",
IconsUtils.createImageIcon("icons/inspector/balloonInformation.png")
);
public static Category animationAndMotion = new Category(
"Animation and Motion",
IconsUtils.createImageIcon("icons/inspector/resume.png")
);
public static Category assetsImagesAndIcons = new Category(
"Assets, Images, and Icons",
IconsUtils.createImageIcon("icons/inspector/any_type.png")
);
public static Category asyncCategory = new Category(
"Async",
IconsUtils.createImageIcon("icons/inspector/threads.png")
);
public static readonly Category basics = new Category(
"Basics",
null // TODO(jacobr): add an icon.
);
public static readonly Category cupertino = new Category(
"Cupertino (iOS-style widgets)",
null // TODO(jacobr): add an icon.
);
public static Category input = new Category(
"Input",
IconsUtils.createImageIcon("icons/inspector/renderer.png")
);
public static Category paintingAndEffects = new Category(
"Painting and effects",
IconsUtils.createImageIcon("icons/inspector/colors.png")
);
public static Category scrolling = new Category(
"Scrolling",
IconsUtils.createImageIcon("icons/inspector/scrollbar.png")
);
public static Category stack = new Category(
"Stack",
IconsUtils.createImageIcon("icons/inspector/value.png")
);
public static Category styling = new Category(
"Styling",
IconsUtils.createImageIcon("icons/inspector/atrule.png")
);
public static Category text = new Category(
"Text",
IconsUtils.createImageIcon("icons/inspector/textArea.png")
);
public static List<Category> values = new List<Category> {
accessibility,
animationAndMotion,
assetsImagesAndIcons,
asyncCategory,
basics,
cupertino,
input,
paintingAndEffects,
scrolling,
stack,
styling,
text,
};
public readonly string label;
public readonly Image icon;
static Dictionary<string, Category> _categories;
public static Category forLabel(string label) {
if (_categories == null) {
_categories = new Dictionary<string, Category>();
foreach (var category in values) {
_categories[category.label] = category;
}
}
return _categories[label];
}
}
public class FlutterWidget {
public FlutterWidget(Dictionary<string, object> json)
{
this.json = json;
icon = initIcon(json);
}
public readonly Dictionary<string, object> json;
public static Image icon;
//[!!!] may has error
static Image initIcon(Dictionary<string, object> json)
{
List<object> categories = new List<object>();
categories.Add(json.getOrDefault("categories"));
if (categories != null) {
// TODO(pq): consider priority over first match.
foreach (string label in categories) {
Category category = Category.forLabel(label);
if (category != null) {
icon = category.icon;
if (icon != null) return icon;
}
}
}
return null;
}
string name
{
get
{
return JsonUtils.getStringMember(json, "name");
}
}
List<string> categories
{
get
{
return JsonUtils.getValues(json, "categories");
}
}
List<string> subCategories
{
get
{
return JsonUtils.getValues(json, "subcategories");
}
}
}
}

1001
com.unity.uiwidgets.devtools/Editor/inspector/inspector_data_models.cs
文件差异内容过多而无法显示
查看文件

408
com.unity.uiwidgets.devtools/Editor/inspector/inspector_screen.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.DevTools.inspector;
using Unity.UIWidgets.DevTools.ui;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.widgets;
using UnityEngine;
namespace Unity.UIWidgets.DevTools.inspector
{
public class InspectorScreen : Screen {
public InspectorScreen():base(
id: id,
requiresLibrary: null,//flutterLibraryUri,
requiresDebugBuild: true,
title: "Flutter Inspector",
icon: Octicons.deviceMobile
)
{
}
public static readonly string id = "inspector";
public new string docPageId
{
get
{
return screenId;
}
}
public override Widget build(BuildContext context)
{
return new InspectorScreenBody();
}
}
public class InspectorScreenBody : StatefulWidget {
public InspectorScreenBody(){}
public override State createState()
{
return new InspectorScreenBodyState();
}
}
public class InspectorScreenBodyState : State<InspectorScreenBody>, BlockingActionMixin, AutoDisposeMixin
{
bool _expandCollapseSupported = false;
bool _layoutExplorerSupported = false;
bool connectionInProgress = false;
InspectorService inspectorService;
InspectorController inspectorController;
InspectorTreeControllerFlutter summaryTreeController;
InspectorTreeControllerFlutter detailsTreeController;
bool displayedWidgetTrackingNotice = false;
// bool enableButtons
// {
// get
// {
// return actionInProgress == false && connectionInProgress == false;
// }
// }
public static readonly Key summaryTreeKey = Key.key("Summary Tree");
public static readonly Key detailsTreeKey = Key.key("Details Tree");
public override void initState() {
base.initState();
// ga.screen(InspectorScreen.id);
// autoDispose(
// serviceManager.onConnectionAvailable.listen(_handleConnectionStart));
// if (serviceManager.hasConnection) {
// _handleConnectionStart(serviceManager.service);
// }
// autoDispose(
// serviceManager.onConnectionClosed.listen(_handleConnectionStop));
}
public override void dispose() {
inspectorService?.dispose();
inspectorController?.dispose();
base.dispose();
}
// void _onExpandClick() {
// blockWhileInProgress(inspectorController.expandAllNodesInDetailsTree);
// }
// void _onResetClick() {
// blockWhileInProgress(inspectorController.collapseDetailsToSelected);
// }
public override Widget build(BuildContext context) {
var summaryTree = _buildSummaryTreeColumn();
var detailsTree = InspectorTree(
key: detailsTreeKey,
controller: detailsTreeController
);
var splitAxis = Split.axisFor(context, 0.85);
return new Column(
children: new List<Widget>{
new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: new List<Widget>{
new ValueListenableBuilder(
valueListenable: serviceManager.serviceExtensionManager
.hasServiceExtension(
extensions.toggleSelectWidgetMode.extension),
builder: (_, selectModeSupported, __) => {
return ServiceExtensionButtonGroup(
extensions:
selectModeSupported
? extensions.toggleSelectWidgetMode
: extensions.toggleOnDeviceWidgetInspector,
minIncludeTextWidth: 650
);
}
),
new SizedBox(width: denseSpacing),
IconLabelButton(
onPressed: _refreshInspector,
icon: Icons.refresh,
label: "Refresh Tree",
includeTextWidth: 750
),
new Spacer(),
new Row(children: getServiceExtensionWidgets()),
}
),
new SizedBox(height: denseRowSpacing),
new Expanded(
child: Split(
axis: splitAxis,
initialFractions: const [0.33, 0.67],
children: [
summaryTree,
InspectorDetailsTabController(
detailsTree: detailsTree,
controller: inspectorController,
actionButtons: _expandCollapseButtons(),
layoutExplorerSupported: _layoutExplorerSupported,
),
],
),
),
},
);
}
Widget _buildSummaryTreeColumn() => OutlineDecoration(
child: ValueListenableBuilder(
valueListenable: serviceManager.errorBadgeManager
.erroredItemsForPage(InspectorScreen.id),
builder: (_, LinkedHashMap<String, DevToolsError> errors, __) {
final inspectableErrors = errors.map(
(key, value) => MapEntry(key, value as InspectableWidgetError));
return Stack(
children: [
InspectorTree(
key: summaryTreeKey,
controller: summaryTreeController,
isSummaryTree: true,
widgetErrors: inspectableErrors,
),
if (errors.isNotEmpty && inspectorController != null)
ValueListenableBuilder(
valueListenable: inspectorController.selectedErrorIndex,
builder: (_, selectedErrorIndex, __) => Positioned(
top: 0,
right: 0,
child: ErrorNavigator(
errors: inspectableErrors,
errorIndex: selectedErrorIndex,
onSelectError: inspectorController.selectErrorByIndex,
),
),
)
],
);
},
),
);
List<Widget> getServiceExtensionWidgets() {
return [
ServiceExtensionButtonGroup(
minIncludeTextWidth: 1050,
extensions: [extensions.slowAnimations],
),
const SizedBox(width: denseSpacing),
ServiceExtensionButtonGroup(
minIncludeTextWidth: 1050,
extensions: [extensions.debugPaint, extensions.debugPaintBaselines],
),
const SizedBox(width: denseSpacing),
ServiceExtensionButtonGroup(
minIncludeTextWidth: 1250,
extensions: [
extensions.repaintRainbow,
extensions.invertOversizedImages,
],
),
// TODO(jacobr): implement TogglePlatformSelector.
// TogglePlatformSelector().selector
];
}
Widget _expandCollapseButtons() {
if (!_expandCollapseSupported) return null;
return Align(
alignment: Alignment.centerRight,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: FixedHeightOutlinedButton(
onPressed: enableButtons ? _onExpandClick : null,
child: const Text(
'Expand all',
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(width: denseSpacing),
Flexible(
child: FixedHeightOutlinedButton(
onPressed: enableButtons ? _onResetClick : null,
child: const Text(
'Collapse to selected',
overflow: TextOverflow.ellipsis,
),
),
)
],
),
);
}
void _onExpandCollapseSupported() {
setState(() {
_expandCollapseSupported = true;
});
}
void _onLayoutExplorerSupported() {
setState(() {
_layoutExplorerSupported = true;
});
}
void _handleConnectionStart(VmService service) async {
setState(() {
connectionInProgress = true;
});
try {
// Init the inspector service, or return null.
await ensureInspectorServiceDependencies();
inspectorService =
await InspectorService.create(service).catchError((e) => null);
} finally {
setState(() {
connectionInProgress = false;
});
}
if (inspectorService == null) {
return;
}
setState(() {
inspectorController?.dispose();
summaryTreeController = InspectorTreeControllerFlutter();
detailsTreeController = InspectorTreeControllerFlutter();
inspectorController = InspectorController(
inspectorTree: summaryTreeController,
detailsTree: detailsTreeController,
inspectorService: inspectorService,
treeType: FlutterTreeType.widget,
onExpandCollapseSupported: _onExpandCollapseSupported,
onLayoutExplorerSupported: _onLayoutExplorerSupported,
);
// Clear any existing badge/errors for older errors that were collected.
serviceManager.errorBadgeManager.clearErrors(InspectorScreen.id);
inspectorController.filterErrors();
// TODO(jacobr): move this notice display to once a day.
if (!displayedWidgetTrackingNotice) {
// ignore: unawaited_futures
inspectorService.isWidgetCreationTracked().then((bool value) {
if (value) {
return;
}
displayedWidgetTrackingNotice = true;
// TODO(jacobr): implement showMessage.
// framework.showMessage(
// message: trackWidgetCreationWarning,
// screenId: inspectorScreenId,
//);
});
}
});
}
void _handleConnectionStop(dynamic event) {
inspectorController?.setActivate(false);
inspectorController?.dispose();
setState(() {
inspectorController = null;
});
}
void _refreshInspector() {
ga.select(inspector, refresh);
blockWhileInProgress(() async {
await inspectorController?.onForceRefresh();
});
}
}
class ErrorNavigator extends StatelessWidget {
const ErrorNavigator({
Key key,
@required this.errors,
@required this.errorIndex,
@required this.onSelectError,
}) : super(key: key);
final LinkedHashMap<String, InspectableWidgetError> errors;
final int errorIndex;
final Function(int) onSelectError;
@override
Widget build(BuildContext context) {
final label = errorIndex != null
? 'Error ${errorIndex + 1}/${errors.length}'
: 'Errors: ${errors.length}';
return Container(
color: devtoolsError,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: defaultSpacing,
vertical: denseSpacing,
),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: denseSpacing),
child: Text(label),
),
IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
splashRadius: defaultIconSize,
icon: const Icon(Icons.keyboard_arrow_up),
onPressed: _previousError,
),
IconButton(
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
splashRadius: defaultIconSize,
icon: const Icon(Icons.keyboard_arrow_down),
onPressed: _nextError,
),
],
),
),
);
}
void _previousError() {
var newIndex = errorIndex == null ? errors.length - 1 : errorIndex - 1;
while (newIndex < 0) {
newIndex += errors.length;
}
onSelectError(newIndex);
}
void _nextError() {
final newIndex = errorIndex == null ? 0 : (errorIndex + 1) % errors.length;
onSelectError(newIndex);
}
}
}

144
com.unity.uiwidgets.devtools/Editor/inspector/inspector_screen_details_tab.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.widgets;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.DevTools.inspector.layout_explorer;
namespace Unity.UIWidgets.DevTools.inspector
{
public class inspector_screen_details_tab
{
}
public class InspectorDetailsTabController : StatefulWidget {
public InspectorDetailsTabController(
Widget detailsTree = null,
Widget actionButtons = null,
InspectorController controller = null,
bool layoutExplorerSupported = false,
Key key = null
) : base(key: key)
{
this.detailsTree = detailsTree;
this.actionButtons = actionButtons;
this.controller = controller;
this.layoutExplorerSupported = layoutExplorerSupported;
}
public readonly Widget detailsTree;
public readonly Widget actionButtons;
public readonly InspectorController controller;
public readonly bool layoutExplorerSupported;
public override State createState()
{
return new _InspectorDetailsTabControllerState();
}
}
public class _InspectorDetailsTabControllerState : State<InspectorDetailsTabController>, TickerProviderStateMixin, AutoDisposeMixin
{
public static readonly int _detailsTreeTabIndex = 1;
public static readonly int _tabsLengthWithLayoutExplorer = 2;
public static readonly int _tabsLengthWithoutLayoutExplorer = 1;
TabController _tabControllerWithLayoutExplorer;
TabController _tabControllerWithoutLayoutExplorer;
public override void initState() {
base.initState();
addAutoDisposeListener(
_tabControllerWithLayoutExplorer =
new TabController(length: _tabsLengthWithLayoutExplorer, vsync: this)
);
addAutoDisposeListener(
_tabControllerWithoutLayoutExplorer =
new TabController(length: _tabsLengthWithoutLayoutExplorer, vsync: this)
);
}
public override Widget build(BuildContext context)
{
List<Widget> tabs = new List<Widget>();
if (widget.layoutExplorerSupported)
tabs.Add(_buildTab("Layout Explorer"));
tabs.Add(_buildTab("Details Tree"));
List<Widget> tabViews = new List<Widget>();
if (widget.layoutExplorerSupported)
tabViews.Add(new LayoutExplorerTab(controller: widget.controller));
tabViews.Add(widget.detailsTree);
var _tabController = widget.layoutExplorerSupported
? _tabControllerWithLayoutExplorer
: _tabControllerWithoutLayoutExplorer;
var theme = Theme.of(context);
var focusColor = theme.focusColor;
var borderSide = new BorderSide(color: focusColor);
var hasActionButtons = widget.actionButtons != null &&
_tabController.index == _detailsTreeTabIndex;
return new Column(
children: new List<Widget>{
new SizedBox(
height: 50.0f,
child: new Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: new List<Widget>{
new Container(
color: focusColor,
child: new TabBar(
controller: _tabController,
labelColor: theme.textTheme.bodyText1.color,
tabs: tabs,
isScrollable: true
)
),
new Expanded(
child: new Container(
decoration: new BoxDecoration(border: new Border(bottom: borderSide)),
child: hasActionButtons
? widget.actionButtons
: new SizedBox()
)
)
}
)
),
new Expanded(
child: new Container(
decoration: new BoxDecoration(
border: new Border(
left: borderSide,
bottom: borderSide,
right: borderSide
)
),
child: new TabBarView(
physics: CommonThemeUtils.defaultTabBarViewPhysics,
controller: _tabController,
children: tabViews
)
)
),
}
);
}
Widget _buildTab(string tabName) {
return new Tab(
child: new Text(
tabName,
overflow: TextOverflow.ellipsis
)
);
}
}
}

1001
com.unity.uiwidgets.devtools/Editor/inspector/inspector_service.cs
文件差异内容过多而无法显示
查看文件

873
com.unity.uiwidgets.devtools/Editor/inspector/inspector_tree.cs


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using uiwidgets;
using Unity.UIWidgets.async2;
using Unity.UIWidgets.DevTools.inspector;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Color = Unity.UIWidgets.ui.Color;
using Rect = Unity.UIWidgets.ui.Rect;
namespace Unity.UIWidgets.DevTools.inspector
{
public class InspectorTreeUtils
{
public static readonly Regex treeNodePrimaryDescriptionPattern = new Regex(@"^([\w ]+)(.*)$");
// TODO(jacobr): temporary workaround for missing structure from assertion thrown building
// widget errors.
public static readonly Regex assertionThrownBuildingError = new Regex(
@"^(The following assertion was thrown building [a-zA-Z]+)(\(.*\))(:)$");
public delegate void TreeEventCallback(InspectorTreeNode node);
public static readonly float iconPadding = 5.0f;
public static readonly float chartLineStrokeWidth = 1.0f;
public static readonly float columnWidth = 16.0f;
public static readonly float verticalPadding = 10.0f;
public static readonly float rowHeight = 24.0f;
// TODO(jacobr): merge this scheme with other color schemes in DevTools.
public static Color selectedRowBackgroundColor
{
get
{
return CommonThemeUtils.isLight
? Color.fromARGB(255, 202, 191, 69)
: Color.fromARGB(255, 99, 101, 103);
}
}
public static Color hoverColor
{
get
{
return CommonThemeUtils.isLight ? Colors.yellowAccent : Color.fromARGB(255, 70, 73, 76);
}
}
}
// TODO(kenz): extend TreeNode class to share tree logic.
public class InspectorTreeNode {
public InspectorTreeNode(
InspectorTreeNode parent = null,
bool expandChildren = true
)
{
_children = new List<InspectorTreeNode>();
_parent = parent;
_isExpanded = expandChildren;
}
bool showLinesToChildren {
get
{
return _children.Count > 1 && !_children.Last().isProperty;
}
}
public bool isDirty
{
get
{
return _isDirty;
}
set
{
if (value) {
_isDirty = true;
_shouldShow = null;
if (_childrenCount == null) {
// Already dirty.
return;
}
_childrenCount = null;
if (parent != null) {
parent.isDirty = true;
}
} else {
_isDirty = false;
}
}
}
bool _isDirty = true;
/// Returns whether the node is currently visible in the tree.
void updateShouldShow(bool value) {
if (value != _shouldShow) {
_shouldShow = value;
foreach (var child in children) {
child.updateShouldShow(value);
}
}
}
bool? shouldShow {
get
{
_shouldShow = _shouldShow ?? parent == null || parent.isExpanded && parent.shouldShow.Value;
return _shouldShow;
}
}
bool? _shouldShow;
public bool selected = false;
RemoteDiagnosticsNode _diagnostic;
public readonly List<InspectorTreeNode> _children;
public IEnumerable<InspectorTreeNode> children
{
get
{
return _children;
}
}
bool isCreatedByLocalProject
{
get
{
return _diagnostic.isCreatedByLocalProject;
}
}
bool isProperty
{
get
{
return diagnostic == null || diagnostic.isProperty;
}
}
public bool isExpanded
{
get
{
return _isExpanded;
}
set
{
if (value != _isExpanded) {
_isExpanded = value;
isDirty = true;
if (_shouldShow ?? false) {
foreach (var child in children) {
child.updateShouldShow(value);
}
}
}
}
}
bool _isExpanded;
bool allowExpandCollapse = true;
bool showExpandCollapse {
get
{
return (diagnostic?.hasChildren == true || children.Any()) &&
allowExpandCollapse;
}
}
public InspectorTreeNode parent
{
get
{
return _parent;
}
set
{
_parent = value;
_parent.isDirty = true;
}
}
InspectorTreeNode _parent;
public RemoteDiagnosticsNode diagnostic
{
get
{
return _diagnostic;
}
set
{
_diagnostic = value;
_isExpanded = value.childrenReady;
//isDirty = true;
}
}
public int? childrenCount {
get
{
if (!isExpanded) {
_childrenCount = 0;
}
if (_childrenCount != null) {
return _childrenCount;
}
int count = 0;
foreach (InspectorTreeNode child in _children) {
count += (child.subtreeSize?? 0);
}
_childrenCount = count;
return _childrenCount;
}
}
public bool hasPlaceholderChildren {
get
{
return children.Count() == 1 && children.First().diagnostic == null;
}
}
int? _childrenCount;
public int? subtreeSize
{
get
{
return childrenCount + 1;
}
}
bool isLeaf
{
get
{
return _children.isEmpty();
}
}
// TODO(jacobr): move getRowIndex to the InspectorTree class.
public int getRowIndex(InspectorTreeNode node) {
int index = 0;
while (true) {
InspectorTreeNode parent = node.parent;
if (parent == null) {
break;
}
foreach (InspectorTreeNode sibling in parent._children) {
if (sibling == node) {
break;
}
index += (sibling.subtreeSize?? 0);
}
index += 1;
node = parent;
}
return index;
}
// TODO(jacobr): move this method to the InspectorTree class.
// TODO: optimize this method.
/// Use [getCachedRow] wherever possible, as [getRow] is slow and can cause
/// performance problems.
public InspectorTreeRow getRow(int index) {
List<int> ticks = new List<int>();
InspectorTreeNode node = this;
if (subtreeSize <= index) {
return null;
}
int current = 0;
int depth = 0;
while (node != null) {
var style = node.diagnostic?.style;
bool indented = style != DiagnosticsTreeStyle.flat &&
style != DiagnosticsTreeStyle.error;
if (current == index) {
return new InspectorTreeRow(
node: node,
index: index,
ticks: ticks,
depth: depth,
lineToParent:
!node.isProperty && index != 0 && node.parent.showLinesToChildren
);
}
D.assert(index > current);
current++;
List<InspectorTreeNode> children = node._children;
int i;
for (i = 0; i < children.Count; ++i) {
var child = children[i];
var subtreeSize = child.subtreeSize;
if (current + subtreeSize > index) {
node = child;
if (children.Count > 1 &&
i + 1 != children.Count &&
!children.Last().isProperty) {
if (indented) {
ticks.Add(depth);
}
}
break;
}
current += (subtreeSize?? 0);
}
D.assert(i < children.Count);
if (indented) {
depth++;
}
}
D.assert(false); // internal error.
return null;
}
public void removeChild(InspectorTreeNode child) {
child.parent = null;
var removed = _children.Remove(child);
D.assert(removed != null);
isDirty = true;
}
public void appendChild(InspectorTreeNode child) {
_children.Add(child);
child.parent = this;
isDirty = true;
}
public void clearChildren() {
_children.Clear();
isDirty = true;
}
}
/// A row in the tree with all information required to render it.
public class InspectorTreeRow {
public InspectorTreeRow(
InspectorTreeNode node,
int? index = null,
List<int> ticks = null,
int? depth = null,
bool? lineToParent = null
)
{
this.node = node;
this.index = index;
this.ticks = ticks;
this.depth = depth;
this.lineToParent = lineToParent;
}
public readonly InspectorTreeNode node;
/// Column indexes of ticks to draw lines from parents to children.
public readonly List<int> ticks;
public readonly int? depth;
public readonly int? index;
public readonly bool? lineToParent;
bool isSelected
{
get
{
return node.selected;
}
}
}
public delegate void NodeAddedCallback(InspectorTreeNode node, RemoteDiagnosticsNode diagnosticsNode);
public class InspectorTreeConfig {
public delegate void OnClientActiveChange(bool added);
public InspectorTreeConfig(
bool? summaryTree = null,
FlutterTreeType? treeType = null,
NodeAddedCallback onNodeAdded = null,
OnClientActiveChange onClientActiveChange = null,
VoidCallback onSelectionChange = null,
InspectorTreeUtils.TreeEventCallback onExpand = null,
InspectorTreeUtils.TreeEventCallback onHover = null
)
{
this.summaryTree = summaryTree;
this.treeType = treeType;
this.onNodeAdded = onNodeAdded;
this.onSelectionChange = onSelectionChange;
this.onExpand = onExpand;
this.onHover = onHover;
}
public readonly bool? summaryTree;
public readonly FlutterTreeType? treeType;
public readonly NodeAddedCallback onNodeAdded;
public readonly VoidCallback onSelectionChange;
public readonly OnClientActiveChange ONClientActiveChange;
public readonly InspectorTreeUtils.TreeEventCallback onExpand;
public readonly InspectorTreeUtils.TreeEventCallback onHover;
}
public abstract class InspectorTreeController
{
protected abstract void setState(VoidCallback fn);
public InspectorTreeNode root
{
get
{
return _root;
}
set
{
setState(() => {
_root = value;
});
}
}
InspectorTreeNode _root;
RemoteDiagnosticsNode subtreeRoot; // Optional.
public InspectorTreeNode selection
{
get
{
return _selection;
}
set
{
if (value == _selection) return;
setState(() => {
_selection.selected = false;
_selection = value;
_selection.selected = true;
if (config.onSelectionChange != null) {
config.onSelectionChange();
}
});
}
}
InspectorTreeNode _selection;
InspectorTreeConfig config
{
get
{
return _config;
}
set
{
// Only allow setting config once.
D.assert(_config == null);
_config = value;
}
}
InspectorTreeConfig _config;
InspectorTreeNode hover
{
get
{
return _hover;
}
set
{
if (value == _hover) {
return;
}
setState(() => {
_hover = value;
// TODO(jacobr): we could choose to repaint only a portion of the UI
});
}
}
InspectorTreeNode _hover;
float? lastContentWidth;
public abstract InspectorTreeNode createNode();
public readonly List<InspectorTreeRow> cachedRows = new List<InspectorTreeRow>();
// TODO: we should add a listener instead that clears the cache when the
// root is marked as dirty.
void _maybeClearCache() {
if (root.isDirty) {
cachedRows.Clear();
root.isDirty = false;
lastContentWidth = null;
}
}
public InspectorTreeRow getCachedRow(int index) {
_maybeClearCache();
while (cachedRows.Count <= index) {
cachedRows.Add(null);
}
cachedRows[index] = cachedRows[index] ??root.getRow(index);
return cachedRows[index];
}
double getRowOffset(int index) {
return (getCachedRow(index)?.depth ?? 0) * InspectorTreeUtils.columnWidth;
}
RemoteDiagnosticsNode currentHoverDiagnostic;
void navigateUp() {
_navigateHelper(-1);
}
void navigateDown() {
_navigateHelper(1);
}
void navigateLeft() {
// This logic is consistent with how IntelliJ handles tree navigation on
// on left arrow key press.
if (selection == null) {
_navigateHelper(-1);
return;
}
if (selection.isExpanded) {
setState(() => {
selection.isExpanded = false;
});
return;
}
if (selection.parent != null) {
selection = selection.parent;
}
}
void navigateRight() {
// This logic is consistent with how IntelliJ handles tree navigation on
// on right arrow key press.
if (selection == null || selection.isExpanded) {
_navigateHelper(1);
return;
}
setState(() => {
selection.isExpanded = true;
});
}
void _navigateHelper(int indexOffset) {
if (numRows == 0) return;
if (selection == null) {
selection = root;
return;
}
selection = root
.getRow(
(root.getRowIndex(selection) + indexOffset).clamp(0, numRows.Value - 1))
?.node;
}
float horizontalPadding
{
get
{
return 10.0f;
}
}
double getDepthIndent(int depth) {
return (depth + 1) * InspectorTreeUtils.columnWidth + horizontalPadding;
}
double getRowY(int index) {
return InspectorTreeUtils.rowHeight * index + InspectorTreeUtils.verticalPadding;
}
void nodeChanged(InspectorTreeNode node) {
if (node == null) return;
setState(() => {
node.isDirty = true;
});
}
void removeNodeFromParent(InspectorTreeNode node) {
setState(() => {
node.parent?.removeChild(node);
});
}
void appendChild(InspectorTreeNode node, InspectorTreeNode child) {
setState(() => {
node.appendChild(child);
});
}
void expandPath(InspectorTreeNode node) {
setState(() => {
_expandPath(node);
});
}
void _expandPath(InspectorTreeNode node) {
while (node != null) {
if (!node.isExpanded) {
node.isExpanded = true;
}
node = node.parent;
}
}
public void collapseToSelected() {
setState(() => {
_collapseAllNodes(root);
if (selection == null) return;
_expandPath(selection);
});
}
void _collapseAllNodes(InspectorTreeNode root) {
root.isExpanded = false;
foreach (var child in root.children)
{
_collapseAllNodes(child);
}
}
int? numRows
{
get
{
return root != null ? root.subtreeSize : 0;
}
}
int getRowIndex(float y) => (int)((y - InspectorTreeUtils.verticalPadding) / InspectorTreeUtils.rowHeight);
public InspectorTreeRow getRowForNode(InspectorTreeNode node) {
return getCachedRow(root.getRowIndex(node));
}
InspectorTreeRow getRow(Offset offset) {
if (root == null) return null;
int row = getRowIndex(offset.dy);
return row < root.subtreeSize ? getCachedRow(row) : null;
}
public abstract void animateToTargets(List<InspectorTreeNode> targets);
void onExpandRow(InspectorTreeRow row) {
setState(() => {
row.node.isExpanded = true;
if (config.onExpand != null) {
config.onExpand(row.node);
}
});
}
void onCollapseRow(InspectorTreeRow row) {
setState(() => {
row.node.isExpanded = false;
});
}
void onSelectRow(InspectorTreeRow row) {
selection = row.node;
expandPath(row.node);
}
bool expandPropertiesByDefault(DiagnosticsTreeStyle style) {
// This code matches the text style defaults for which styles are
// by default and which aren't.
switch (style) {
case DiagnosticsTreeStyle.none:
case DiagnosticsTreeStyle.singleLine:
case DiagnosticsTreeStyle.errorProperty:
return false;
case DiagnosticsTreeStyle.sparse:
case DiagnosticsTreeStyle.offstage:
case DiagnosticsTreeStyle.dense:
case DiagnosticsTreeStyle.transition:
case DiagnosticsTreeStyle.error:
case DiagnosticsTreeStyle.whitespace:
case DiagnosticsTreeStyle.flat:
case DiagnosticsTreeStyle.shallow:
case DiagnosticsTreeStyle.truncateChildren:
return true;
}
return true;
}
InspectorTreeNode setupInspectorTreeNode(
InspectorTreeNode node,
RemoteDiagnosticsNode diagnosticsNode,
bool expandChildren = true,
bool expandProperties = true
) {
D.assert(expandChildren != null);
D.assert(expandProperties != null);
node.diagnostic = diagnosticsNode;
if (config.onNodeAdded != null) {
config.onNodeAdded(node, diagnosticsNode);
}
if (diagnosticsNode.hasChildren ||
diagnosticsNode.inlineProperties.isNotEmpty()) {
if (diagnosticsNode.childrenReady || !diagnosticsNode.hasChildren) {
bool styleIsMultiline =
expandPropertiesByDefault(diagnosticsNode.style.Value);
setupChildren(
diagnosticsNode,
node,
node.diagnostic.childrenNow,
expandChildren: expandChildren && styleIsMultiline,
expandProperties: expandProperties && styleIsMultiline
);
} else {
node.clearChildren();
node.appendChild(createNode());
}
}
return node;
}
void setupChildren(
RemoteDiagnosticsNode parent,
InspectorTreeNode treeNode,
List<RemoteDiagnosticsNode> children,
bool expandChildren = true,
bool expandProperties = true
) {
D.assert(expandChildren != null);
D.assert(expandProperties != null);
treeNode.isExpanded = expandChildren;
if (treeNode.children.Any()) {
// Only case supported is this is the loading node.
D.assert(treeNode.children.Count() == 1);
removeNodeFromParent(treeNode.children.First());
}
var inlineProperties = parent.inlineProperties;
if (inlineProperties != null) {
foreach (RemoteDiagnosticsNode property in inlineProperties) {
appendChild(
treeNode,
setupInspectorTreeNode(
createNode(),
property,
expandChildren: expandProperties,
expandProperties: expandProperties
)
);
}
}
if (children != null) {
foreach (RemoteDiagnosticsNode child in children) {
appendChild(
treeNode,
setupInspectorTreeNode(
createNode(),
child,
expandChildren: expandChildren,
expandProperties: expandProperties
)
);
}
}
}
public Future maybePopulateChildren(InspectorTreeNode treeNode) {
RemoteDiagnosticsNode diagnostic = treeNode.diagnostic;
if (diagnostic != null &&
diagnostic.hasChildren &&
(treeNode.hasPlaceholderChildren || !treeNode.children.Any())) {
try
{
diagnostic.children.then((children) =>
{
if (treeNode.hasPlaceholderChildren || !treeNode.children.Any()) {
setupChildren(
diagnostic,
treeNode,
children as List<RemoteDiagnosticsNode>,
expandChildren: true,
expandProperties: false
);
nodeChanged(treeNode);
if (treeNode == selection) {
expandPath(treeNode);
}
}
});
} catch (Exception e) {
Debug.Log(e.ToString());
}
}
return null;
}
}
mixin InspectorTreeFixedRowHeightController on InspectorTreeController {
Rect getBoundingBox(InspectorTreeRow row);
void scrollToRect(Rect targetRect);
public override void animateToTargets(List<InspectorTreeNode> targets) {
Rect targetRect;
foreach (InspectorTreeNode target in targets) {
var row = InspectorTreeController.getRowForNode(target);
if (row != null) {
var rowRect = getBoundingBox(row);
targetRect =
targetRect == null ? rowRect : targetRect.expandToInclude(rowRect);
}
}
if (targetRect == null || targetRect.isEmpty) return;
targetRect = targetRect.inflate(20.0f);
scrollToRect(targetRect);
}
}
}

93
com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/layout_explorer.cs


using Unity.UIWidgets.DevTools.inspector.layout_explorer.box;
using Unity.UIWidgets.DevTools.inspector.layout_explorer.flex;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.DevTools.inspector.layout_explorer
{
public class LayoutExplorerTab : StatefulWidget {
public LayoutExplorerTab(Key key = null, InspectorController controller = null) : base(key: key)
{
this.controller = controller;
}
public readonly InspectorController controller;
public override State createState()
{
return new _LayoutExplorerTabState();
}
}
public class _LayoutExplorerTabState : State<LayoutExplorerTab> //, AutomaticKeepAliveClientMixin<LayoutExplorerTab>, AutoDisposeMixin
{
InspectorController controller
{
get
{
return widget.controller;
}
}
RemoteDiagnosticsNode selected
{
get
{
return null; //controller?.selectedNode?.value?.diagnostic;
}
}
RemoteDiagnosticsNode previousSelection;
Widget rootWidget(RemoteDiagnosticsNode node) {
if (FlexLayoutExplorerWidget.shouldDisplay(node)) {
return new FlexLayoutExplorerWidget(controller);
}
if (BoxLayoutExplorerWidget.shouldDisplay(node)) {
return new BoxLayoutExplorerWidget(controller);
}
return new Center(
child: new Text(
node != null
? "Currently, Layout Explorer only supports Box and Flex-based widgets."
: "Select a widget to view its layout.",
textAlign: TextAlign.center,
overflow: TextOverflow.clip
)
);
}
void onSelectionChanged() {
if (rootWidget(previousSelection).GetType() !=
rootWidget(selected).GetType()) {
setState(() => previousSelection = selected);
}
}
public override void initState() {
base.initState();
//addAutoDisposeListener(controller.selectedNode, onSelectionChanged);
}
public override Widget build(BuildContext context) {
//base.build(context);
return rootWidget(selected);
}
public new bool wantKeepAlive
{
get
{
return true;
}
}
}
}

345
com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/ui/layout_explorer_widget.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.async2;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.DevTools.inspector.layout_explorer.ui
{
public delegate Future OnSelectionChangedCallback();
public class LayoutExplorerWidgetUtils
{
const float maxRequestsPerSecond = 3.0f;
}
public abstract class LayoutExplorerWidget : StatefulWidget {
public LayoutExplorerWidget(
InspectorController inspectorController,
Key key
) : base(key: key)
{
this.inspectorController = inspectorController;
}
public readonly InspectorController inspectorController;
}
public abstract class LayoutExplorerWidgetState<W, L> :
InspectorServiceClient where W : LayoutExplorerWidget where L : LayoutProperties // TickerProviderStateMixin, InspectorServiceClient where W : LayoutExplorerWidget where L : LayoutProperties
{
public LayoutExplorerWidgetState() {
_onSelectionChangedCallback = onSelectionChanged;
}
public AnimationController entranceController;
public CurvedAnimation entranceCurve;
public AnimationController changeController;
public CurvedAnimation changeAnimation;
L _previousProperties;
L _properties;
InspectorObjectGroupManager objectGroupManager;
public AnimatedLayoutProperties<L> animatedProperties
{
get
{
return _animatedProperties;
}
}
AnimatedLayoutProperties<L> _animatedProperties;
public L properties
{
get
{
return _previousProperties ?? _animatedProperties ?? _properties;
}
}
public RemoteDiagnosticsNode selectedNode
{
get
{
return null; //inspectorController?.selectedNode?.value?.diagnostic;
}
}
public InspectorController inspectorController
{
get
{
return null; //widget.inspectorController;
}
}
public InspectorService inspectorService
{
get
{
return inspectorController?.inspectorService;
}
}
//RateLimiter rateLimiter;
public OnSelectionChangedCallback _onSelectionChangedCallback;
Future onSelectionChanged() {
if (!mounted) return async2.Future.value(); //if (!mounted) return async2.Future.value();
if (!shouldDisplay(selectedNode)) {
return async2.Future.value();
}
var prevRootId = id(_properties?.node);
var newRootId = id(getRoot(selectedNode));
var shouldFetch = prevRootId != newRootId;
if (shouldFetch) {
_dirty = false;
fetchLayoutProperties().then((newSelection) =>
{
_setProperties(newSelection);
});
} else {
updateHighlighted(_properties);
}
return async2.Future.value();
}
/// Whether this layout explorer can work with this kind of node.
public abstract bool shouldDisplay(RemoteDiagnosticsNode node);
public Size size
{
get
{
return properties.size;
}
}
public List<LayoutProperties> children
{
get
{
return properties.displayChildren;
}
}
public LayoutProperties highlighted;
/// Returns the root widget to show.
///
/// For cases such as Flex widgets or in the future ListView widgets we may
/// want to show the layout for all widgets under a root that is the parent
/// of the current widget.
public abstract RemoteDiagnosticsNode getRoot(RemoteDiagnosticsNode node);
Future<L> fetchLayoutProperties() {
objectGroupManager?.cancelNext();
var nextObjectGroup = objectGroupManager.next;
var res = nextObjectGroup.getLayoutExplorerNode(
getRoot(selectedNode)
).then((node)=>{computeLayoutProperties(node);});
if (!nextObjectGroup.disposed) {
D.assert(objectGroupManager.next == nextObjectGroup);
objectGroupManager.promoteNext();
}
return res;
}
public abstract L computeLayoutProperties(RemoteDiagnosticsNode node);
public abstract AnimatedLayoutProperties<L> computeAnimatedProperties(L nextProperties);
public abstract void updateHighlighted(L newProperties);
string id(RemoteDiagnosticsNode node) => node?.dartDiagnosticRef?.id;
void _registerInspectorControllerService() {
inspectorController?.selectedNode?.addListener(_onSelectionChangedCallback);
inspectorService?.addClient(this);
}
void _unregisterInspectorControllerService() {
inspectorController?.selectedNode
?.removeListener(_onSelectionChangedCallback);
inspectorService?.removeClient(this);
}
public override void initState() {
base.initState();
rateLimiter = RateLimiter(maxRequestsPerSecond, refresh);
_registerInspectorControllerService();
_initAnimationStates();
_updateObjectGroupManager();
// TODO(jacobr): put inspector controller in Controllers and
// update on didChangeDependencies.
_animateProperties();
}
public override void didUpdateWidget(W oldWidget) {
base.didUpdateWidget(oldWidget);
_updateObjectGroupManager();
_animateProperties();
if (oldWidget.inspectorController != inspectorController) {
_unregisterInspectorControllerService();
_registerInspectorControllerService();
}
}
public override void dispose() {
entranceController.dispose();
changeController.dispose();
_unregisterInspectorControllerService();
base.dispose();
}
void _animateProperties() {
if (_animatedProperties != null) {
changeController.forward();
}
if (_previousProperties != null) {
entranceController.reverse();
} else {
entranceController.forward();
}
}
Future setSelectionInspector(RemoteDiagnosticsNode node) {
node.inspectorService.then((service) =>
{
service.setSelectionInspector(node.valueRef, false);
});
return null;
}
// update selected widget and trigger selection listener event to change focus.
public void refreshSelection(RemoteDiagnosticsNode node) {
inspectorController.refreshSelection(node, node, true);
}
public Future onTap(LayoutProperties properties) {
setState(() => highlighted = properties);
setSelectionInspector(properties.node);
return null;
}
public void onDoubleTap(LayoutProperties properties) {
refreshSelection(properties.node);
}
Future refresh() {
if (!_dirty) return null;
_dirty = false;
fetchLayoutProperties().then((updatedProperties) =>
{
if (updatedProperties != null) _changeProperties(updatedProperties);
});
return null;
}
void _changeProperties(L nextProperties) {
if (!mounted || nextProperties == null) return;
updateHighlighted(nextProperties);
setState(() => {
_animatedProperties = computeAnimatedProperties(nextProperties);
changeController.forward(from: 0.0f);
});
}
void _setProperties(L newProperties) {
if (!mounted) return;
updateHighlighted(newProperties);
if (_properties == newProperties) {
return;
}
setState(() => {
_previousProperties = _previousProperties ?? _properties;
_properties = newProperties;
});
_animateProperties();
}
void _initAnimationStates()
{
entranceController = longAnimationController(
this
);
entranceController.addStatusListener((status) => {
if (status == AnimationStatus.dismissed) {
setState(() => {
_previousProperties = null;
entranceController.forward();
});
}
});
entranceCurve = defaultCurvedAnimation(entranceController);
changeController = longAnimationController(this);
changeController.addStatusListener((status) => {
if (status == AnimationStatus.completed) {
setState(() => {
_properties = _animatedProperties.end;
_animatedProperties = null;
changeController._value = 0.0f;
});
}
});
changeAnimation = defaultCurvedAnimation(changeController);
}
void _updateObjectGroupManager() {
var service = inspectorController.inspectorService;
if (service != objectGroupManager?.inspectorService) {
objectGroupManager = InspectorObjectGroupManager(
service,
"flex-layout"
);
}
onSelectionChanged();
}
bool _dirty = false;
public override void onFlutterFrame() {
if (!mounted) return;
if (_dirty) {
rateLimiter.scheduleRequest();
}
}
// TODO(albertusangga): Investigate why onForceRefresh is not getting called.
public override Future<Object> onForceRefresh()
{
fetchLayoutProperties().then((v) =>
{
_setProperties(v);
});
return null;
}
public override Future onInspectorSelectionChanged() {
return null;
}
/// Register callback to be executed once Flutter frame is ready.
public void markAsDirty() {
_dirty = true;
}
}
}

569
com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/ui/utils.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.DevTools;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using Image = Unity.UIWidgets.widgets.Image;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
namespace Unity.UIWidgets.DevTools.inspector.layout_explorer.ui
{
public class BorderLayout : StatelessWidget {
public BorderLayout(
Key key = null,
Widget left= null,
float? leftWidth = null,
Widget top= null,
float? topHeight = null,
Widget right= null,
float? rightWidth = null,
Widget bottom = null,
float? bottomHeight = null,
Widget center = null
) : base(key: key)
{
D.assert(left != null ||
top != null ||
right != null ||
bottom != null ||
center != null);
this.left = left;
this.leftWidth = leftWidth;
this.top = top;
this.topHeight = topHeight;
this.right = right;
this.rightWidth = rightWidth;
this.bottom = bottom;
this.bottomHeight = bottomHeight;
this.center = center;
}
public readonly Widget center;
public readonly Widget top;
public readonly Widget left;
public readonly Widget right;
public readonly Widget bottom;
public readonly float? leftWidth;
public readonly float? rightWidth;
public readonly float? topHeight;
public readonly float? bottomHeight;
CrossAxisAlignment crossAxisAlignment {
get
{
if (left != null && right != null) {
return CrossAxisAlignment.center;
} else if (left == null && right != null) {
return CrossAxisAlignment.start;
} else if (left != null && right == null) {
return CrossAxisAlignment.end;
} else {
return CrossAxisAlignment.start;
}
}
}
public override Widget build(BuildContext context)
{
List<Widget> widgets = new List<Widget>();
widgets.Add(new Center(
child: new Container(
margin: EdgeInsets.only(
left: leftWidth ?? 0,
right: rightWidth ?? 0,
top: topHeight ?? 0,
bottom: bottomHeight ?? 0
),
child: center
)
));
if (top != null)
{
widgets.Add(new Align(
alignment: Alignment.topCenter,
child: new Container(height: topHeight, child: top)
));
}
if (left != null)
{
widgets.Add(new Align(
alignment: Alignment.centerLeft,
child: new Container(width: leftWidth, child: left)
));
}
if (right != null)
{
widgets.Add(new Align(
alignment: Alignment.centerRight,
child: new Container(width: rightWidth, child: right)
));
}
if (bottom != null)
{
widgets.Add(new Align(
alignment: Alignment.bottomCenter,
child: new Container(height: bottomHeight, child: bottom)
));
}
return new Stack(
children: widgets
);
}
}
public class Truncateable : StatelessWidget {
public Truncateable(Key key = null , bool truncate = false, Widget child = null) : base(key: key)
{
this.truncate = truncate;
this.child = child;
}
public readonly Widget child;
public readonly bool truncate;
public override Widget build(BuildContext context) {
return new Flexible(flex: truncate ? 1 : 0, child: child);
}
}
public class WidgetVisualizer : StatelessWidget {
public WidgetVisualizer(
Key key = null,
bool isSelected = false,
string title = null,
Widget hint = null,
LayoutProperties layoutProperties = null,
Widget child = null,
OverflowSide? overflowSide = null,
bool largeTitle = false
) : base(key: key)
{
D.assert(title != null);
this.child = child;
this.isSelected = isSelected;
this.title = title;
this.hint = hint;
this.layoutProperties = layoutProperties;
this.overflowSide = overflowSide;
this.largeTitle = largeTitle;
}
public readonly LayoutProperties layoutProperties;
public readonly string title;
public readonly Widget child;
public readonly Widget hint;
public readonly bool isSelected;
public readonly bool largeTitle;
public readonly OverflowSide? overflowSide;
public static readonly float overflowIndicatorSize = 20.0f;
bool drawOverflow
{
get
{
return overflowSide != null;
}
}
public override Widget build(BuildContext context) {
var theme = Theme.of(context);
var colorScheme = theme.colorScheme;
var properties = layoutProperties;
Color borderColor = ThemeUtils.regularWidgetColor;
if (properties is FlexLayoutProperties) {
borderColor =
((FlexLayoutProperties)properties)?.direction == Axis.horizontal ? ThemeUtils.rowColor : ThemeUtils.columnColor;
}
if (isSelected) {
borderColor = ThemeUtils.selectedWidgetColor;
}
List<Widget> widgets1 = new List<Widget>();
widgets1.Add(new Flexible(
child: new Container(
constraints: new BoxConstraints(
maxWidth: largeTitle
? ThemeUtils.defaultMaxRenderWidth
: ThemeUtils.minRenderWidth *
ThemeUtils.widgetTitleMaxWidthPercentage),
child: new Center(
child: new Text(
title,
style:
new TextStyle(color: ThemeUtils.widgetNameColor),
overflow: TextOverflow.ellipsis
)
),
decoration: new BoxDecoration(color: borderColor),
padding: EdgeInsets.all(4.0f)
)
));
if (hint != null)
{
widgets1.Add(new Flexible(child: hint));
}
List<Widget> widgets2 = new List<Widget>();
widgets2.Add(new IntrinsicHeight(
child: new Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: widgets1
)
));
if (child != null) widgets2.Add(new Expanded(child: child));
List<Widget> widgets3 = new List<Widget>();
if (drawOverflow)
{
widgets3.Add(Positioned.fill(
child: new CustomPaint(
painter: new OverflowIndicatorPainter(
overflowSide.Value,
overflowIndicatorSize
)
)
));
widgets3.Add(new Container(
margin: EdgeInsets.only(
right: overflowSide == OverflowSide.right
? overflowIndicatorSize
: 0.0f,
bottom: overflowSide == OverflowSide.bottom
? overflowIndicatorSize
: 0.0f
),
color: isSelected ? ThemeUtils.backgroundColorSelected : null,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: widgets2
)
));
}
return new Container(
child: new Stack(
children: widgets3
),
decoration: new BoxDecoration(
border: Border.all(
color: borderColor
),
color: Color.black //theme.canvasColor.darken()
)
);
}
}
public class AnimatedLayoutProperties<T> : LayoutProperties where T : LayoutProperties
{
public AnimatedLayoutProperties(T begin, T end, Animation<float> animation)
{
D.assert(begin != null);
D.assert(end != null);
D.assert(begin.children?.Count == end.children?.Count);
for (var i = 0; i < begin.children.Count; i++)
_children.Add(new AnimatedLayoutProperties<LayoutProperties>(
begin.children[i],
end.children[i],
animation
));
this.begin = begin;
this.end = end;
this.animation = animation;
}
public readonly T begin;
public readonly T end;
public readonly Animation<float> animation;
public readonly List<LayoutProperties> _children;
public new LayoutProperties parent
{
get
{
return end.parent;
}
set
{
end.parent = value;
}
}
public new List<LayoutProperties> children {
get
{
return _children;
}
}
List<float?> _lerpList(List<float?> l1, List<float?> l2) {
D.assert(l1.Count == l2.Count);
List<float?> result = new List<float?>();
for (var i = 0; i < children.Count; i++)
{
result.Add(utils.lerpFloat(l1[i], l2[i], animation.value));
}
return result;
}
public List<float?> childrenDimensions(Axis axis) { // public override List<float?> childrenDimensions(Axis axis)
var beginDimensions = begin.childrenDimensions(axis);
var endDimensions = end.childrenDimensions(axis);
return _lerpList(beginDimensions, endDimensions);
}
public new List<float?> childrenHeights
{
get
{
return _lerpList(begin.childrenHeights, end.childrenHeights);
}
}
public new List<float?> childrenWidths
{
get
{
return _lerpList(begin.childrenWidths, end.childrenWidths);
}
}
public new BoxConstraints constraints {
get
{
try {
return BoxConstraints.lerp(
begin.constraints, end.constraints, animation.value);
} catch (Exception e) {
return end.constraints;
}
}
}
public string describeWidthConstraints() { // public override string describeWidthConstraints()
return constraints.hasBoundedWidth
? LayoutProperties.describeAxis(
constraints.minWidth, constraints.maxWidth, "w")
: "w=unconstrained";
}
public string describeHeightConstraints() { // public override string describeHeightConstraints()
return constraints.hasBoundedHeight
? LayoutProperties.describeAxis(
constraints.minHeight, constraints.maxHeight, "h")
: "h=unconstrained";
}
public string describeWidth() //public override string describeWidth()
{
return $"w={size.width}";
}
public new string describeHeight()
{
return $"h={size.height}";
}
public new string description
{
get
{
return end.description;
}
}
public float? dimension(Axis axis) { // public override float? dimension(Axis axis)
return utils.lerpFloat(
begin.dimension(axis),
end.dimension(axis),
animation.value
);
}
public new float? flexFactor
{
get
{
return utils.lerpFloat(begin.flexFactor, end.flexFactor, animation.value);
}
}
public new bool hasChildren
{
get
{
return children.isNotEmpty();
}
}
public new float? height
{
get
{
return size.height;
}
}
public new bool isFlex
{
get
{
return begin.isFlex.Value && end.isFlex.Value;
}
}
public new RemoteDiagnosticsNode node
{
get
{
return end.node;
}
}
public new Size size
{
get
{
return Size.lerp(begin.size, end.size, animation.value);
}
}
public new int totalChildren
{
get
{
return end.totalChildren;
}
}
public new float? width
{
get
{
return size.width;
}
}
public new bool hasFlexFactor
{
get
{
return begin.hasFlexFactor && end.hasFlexFactor;
}
}
public LayoutProperties copyWith( //public override LayoutProperties copyWith
List<LayoutProperties> children = null,
BoxConstraints constraints = null,
string description = null,
float? flexFactor = null,
FlexFit? flexFit = null,
bool? isFlex = null,
Size size = null
)
{
return new LayoutProperties();
// return LayoutProperties.values(
// node: node,
// children: children ?? this.children,
// constraints: constraints ?? this.constraints,
// description: description ?? this.description,
// flexFactor: flexFactor ?? this.flexFactor,
// flexFit: flexFit ?? this.flexFit,
// isFlex: isFlex ?? this.isFlex,
// size: size ?? this.size
// );
}
public new bool isOverflowWidth
{
get
{
return end.isOverflowWidth;
}
}
public new bool isOverflowHeight
{
get
{
return end.isOverflowHeight;
}
}
public new FlexFit? flexFit
{
get
{
return end.flexFit;
}
}
public new List<LayoutProperties> displayChildren
{
get
{
return end.displayChildren;
}
}
}
public class LayoutExplorerBackground : StatelessWidget {
public LayoutExplorerBackground(
Key key = null,
ColorScheme colorScheme = null
) : base(key: key)
{
this.colorScheme = colorScheme;
}
public readonly ColorScheme colorScheme;
public override Widget build(BuildContext context) {
return Positioned.fill(
child: new Opacity(
opacity: CommonThemeUtils.isLight ? 0.3f : 0.2f,
child: Image.asset(
CommonThemeUtils.isLight
? ThemeUtils.negativeSpaceLightAssetName
: ThemeUtils.negativeSpaceDarkAssetName,
fit: BoxFit.none,
repeat: ImageRepeat.repeat,
alignment: Alignment.topLeft
)
)
);
}
}
}

21
com.unity.uiwidgets.devtools/Editor/listenable.cs


using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.DevTools
{
public class FixedValueListenable<T> : ValueListenable<T> {
public FixedValueListenable(T _value)
{
this._value = _value;
}
public readonly T _value;
public void addListener(VoidCallback listener){} //public override void addListener(VoidCallback listener){}
public void removeListener(VoidCallback listener){} //public override void removeListener(VoidCallback listener){}
public T value => _value; // public override T value => _value;
}
}

20
com.unity.uiwidgets.devtools/Editor/main.cs


using Unity.UIWidgets.DevTools.analytics;
using Unity.UIWidgets.DevTools.config_specific.ide_theme;
using Unity.UIWidgets.Editor;
using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.DevTools
{
public class Devetool : UIWidgetsEditorPanel
{
public IdeTheme ideTheme = null;
protected override void main()
{
ui_.runApp(new DevToolsApp(appUtils.defaultScreens, ideTheme, stub_provider.analyticsProvider));
}
}
}

290
com.unity.uiwidgets.devtools/Editor/screen.cs


using System;
using System.Collections.Generic;
using uiwidgets;
using Unity.UIWidgets.async2;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
using Canvas = Unity.UIWidgets.ui.Canvas;
using Rect = Unity.UIWidgets.ui.Rect;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
namespace Unity.UIWidgets.DevTools
{
public class ScreenUtils
{
public static bool shouldShowScreen(Screen screen) {
if (globals.offlineMode) {
return screen.worksOffline;
}
if (screen.requiresLibrary != null) {
if (!globals.serviceManager.isServiceAvailable ||
!globals.serviceManager.isolateManager.selectedIsolateAvailable.isCompleted ||
!globals.serviceManager.libraryUriAvailableNow(screen.requiresLibrary)) {
return false;
}
}
if (screen.requiresDartVm) {
if (!globals.serviceManager.isServiceAvailable ||
!globals.serviceManager.connectedApp.isRunningOnDartVM) {
return false;
}
}
if (screen.requiresDebugBuild) {
if (!globals.serviceManager.isServiceAvailable ||
globals.serviceManager.connectedApp.isProfileBuildNow) {
return false;
}
}
if (screen.requiresVmDeveloperMode) {
if (!globals.preferences.vmDeveloperModeEnabled.value) {
return false;
}
}
return true;
}
}
public abstract class Screen {
public Screen(
string screenId,
string title = null,
IconData icon = null,
Key tabKey = null,
string requiresLibrary = null,
bool requiresDartVm = false,
bool requiresDebugBuild = false,
bool requiresVmDeveloperMode = false,
bool worksOffline = false
)
{
this.screenId = screenId;
this.title = title;
this.icon = icon;
this.tabKey = tabKey;
this.requiresLibrary = requiresLibrary;
this.requiresDartVm = requiresDartVm;
this.requiresDebugBuild = requiresDebugBuild;
this.requiresVmDeveloperMode = requiresVmDeveloperMode;
this.worksOffline = worksOffline;
}
public Screen(
string id = null,
string requiresLibrary = null,
bool requiresDartVm = false,
bool requiresDebugBuild = false,
bool requiresVmDeveloperMode = false,
bool worksOffline = false,
string title = null,
IconData icon = null,
Key tabKey = null
)
{
this.screenId = id;
this.requiresLibrary = requiresLibrary;
this.requiresDartVm = requiresDartVm;
this.requiresDebugBuild = requiresDebugBuild;
this.requiresVmDeveloperMode = requiresVmDeveloperMode;
this.worksOffline = worksOffline;
this.title = title;
this.icon = icon;
this.tabKey = tabKey;
}
public readonly string screenId;
public readonly string title;
public readonly IconData icon;
public readonly Key tabKey;
public readonly string requiresLibrary;
public readonly bool requiresDartVm;
public readonly bool requiresDebugBuild;
public readonly bool requiresVmDeveloperMode;
public readonly bool worksOffline;
// ValueListenable<bool> showIsolateSelector
// {
// get
// {
// return FixedValueListenable<bool>(false);
// }
// }
string docPageId
{
get
{
return null;
}
}
int badgeCount
{
get
{
return 0;
}
}
Widget buildTab(BuildContext context) {
return new ValueListenableBuilder<int>(
valueListenable:
globals.serviceManager.errorBadgeManager.errorCountNotifier(screenId),
builder: (context2, count, _) => {
var tab = new Tab(
key: tabKey,
child: new Row(
children: new List<Widget>{
new Icon(icon, size: CommonThemeUtils.defaultIconSize),
new Padding(
padding: EdgeInsets.only(left: CommonThemeUtils.denseSpacing),
child: new Text(title)
),
}
)
);
if (count > 0) {
var painter = new TextPainter(
text: new TextSpan(
text: title
),
textDirection: TextDirection.ltr
);
var titleWidth = painter.width;
return new LayoutBuilder(
builder: (context3, constraints) =>{
return new Stack(
children: new List<Widget>{
new CustomPaint(
size: new Size(CommonThemeUtils.defaultIconSize + CommonThemeUtils.denseSpacing + titleWidth, 0),
painter: new BadgePainter(number: count)
),
tab,
}
);
}
);
}
return tab;
}
);
}
public abstract Widget build(BuildContext context);
Widget buildStatus(BuildContext context, TextTheme textTheme) {
return null;
}
}
// mixin OfflineScreenMixin<T extends StatefulWidget, U> on State<T> {
// bool loadingOfflineData
// {
// get
// {
// return _loadingOfflineData;
// }
// }
//
// bool _loadingOfflineData = false;
//
// bool shouldLoadOfflineData();
//
// FutureOr processOfflineData(U offlineData);
//
// Future loadOfflineData(U offlineData) {
// setState(() => {
// _loadingOfflineData = true;
// });
// processOfflineData(offlineData).then(() =>
// {
// setState(() => {
// _loadingOfflineData = false;
// });
// });
// }
// }
public class BadgePainter : CustomPainter {
public BadgePainter(int? number = null)
{
this.number = number;
}
public readonly int? number;
public void paint(Canvas canvas, Size size)
{
Paint paint = new Paint();
paint.color = CommonThemeUtils.devtoolsError;
paint.style = PaintingStyle.fill;
TextPainter countPainter = new TextPainter(
text: new TextSpan(
text: $"{number}",
style: new TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold
)
),
textDirection: TextDirection.ltr
);
countPainter.layout();
var badgeWidth = Mathf.Max(
CommonThemeUtils.defaultIconSize,
countPainter.width + CommonThemeUtils.denseSpacing
);
canvas.drawOval(
Rect.fromLTWH(size.width, 0, badgeWidth, CommonThemeUtils.defaultIconSize),
paint
);
countPainter.paint(
canvas,
new Offset(size.width + (badgeWidth - countPainter.width) / 2, 0)
);
}
public bool shouldRepaint(CustomPainter oldDelegate) {
if (oldDelegate is BadgePainter) {
return number != ((BadgePainter)oldDelegate).number;
}
return true;
}
public bool? hitTest(Offset position)
{
throw new NotImplementedException();
}
public void addListener(VoidCallback listener)
{
throw new NotImplementedException();
}
public void removeListener(VoidCallback listener)
{
throw new NotImplementedException();
}
}
}

59
com.unity.uiwidgets.devtools/Editor/utils.cs


using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
using UnityEngine;
namespace Unity.UIWidgets.DevTools
{
public class utils
{
public static float safePositiveFloat(float value) {
if (value.isNaN()) return 0.0f;
return Mathf.Max(value, 0.0f);
}
public static float? lerpFloat(float? a, float? b, float t) {
if (a == b || (a?.isNaN() == true) && (b?.isNaN() == true))
return a;
a = a?? 0.0f;
b = b?? 0.0f;
D.assert(a.Value.isFinite(), ()=>"Cannot interpolate between finite and non-finite values");
D.assert(b.Value.isFinite(), ()=>"Cannot interpolate between finite and non-finite values");
D.assert(t.isFinite(), ()=>"t must be finite when interpolating between values");
return a * (1.0f - t) + b * t;
}
}
public class JsonUtils {
public JsonUtils(){}
public static string getStringMember(Dictionary<string, object> json, string memberName) {
// TODO(jacobr): should we handle non-string values with a reasonable
// toString differently?
return (string)json[memberName];
}
public static int getIntMember(Dictionary<string, object> json, string memberName)
{
if (!json.ContainsKey(memberName)) return -1;
return (int)(json.getOrDefault(memberName));
}
public static List<string> getValues(Dictionary<string, object> json, string member) {
List<object> values = json[member] as List<object>;
if (values == null || values.isEmpty()) {
return new List<string>();
}
return values.Cast<string>().ToList();
}
public static bool hasJsonData(string data) {
return data != null && data.isNotEmpty() && data != "null";
}
}
}

23
com.unity.uiwidgets.devtools/Editor/analytics/provider.cs


namespace Unity.UIWidgets.DevTools.analytics
{
public abstract class AnalyticsProvider {
private bool isGtagsEnabled
{
get;
}
private bool shouldPrompt
{
get;
}
private bool isEnabled
{
get;
}
public abstract void setUpAnalytics();
public abstract void setAllowAnalytics();
public abstract void setDontAllowAnalytics();
}
}

53
com.unity.uiwidgets.devtools/Editor/analytics/stub_provider.cs


using Unity.UIWidgets.async2;
namespace Unity.UIWidgets.DevTools.analytics
{
public class stub_provider
{
public static AnalyticsProvider analyticsProvider
{
get
{
return _provider;
}
}
public static AnalyticsProvider _provider = new _StubProvider();
}
public class _StubProvider : AnalyticsProvider {
public new bool isEnabled
{
get
{
return false;
}
}
public new bool shouldPrompt
{
get
{
return false;
}
}
public new bool isGtagsEnabled
{
get
{
return false;
}
}
public override void setAllowAnalytics() {}
public override void setDontAllowAnalytics() {}
public override void setUpAnalytics() {}
}
}

18
com.unity.uiwidgets.devtools/Editor/config_specific/ide_theme/ide_theme.cs


using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.DevTools.config_specific.ide_theme
{
public class IdeTheme {
public IdeTheme(Color backgroundColor = null, Color foregroundColor = null, float? fontSize = null)
{
this.backgroundColor = backgroundColor;
this.foregroundColor = foregroundColor;
this.fontSize = fontSize;
}
public readonly Color backgroundColor;
public readonly Color foregroundColor;
public readonly float? fontSize;
}
}

807
com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/flex/flex.cs


using System;
using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.async2;
using Unity.UIWidgets.DevTools.inspector.layout_explorer.ui;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using UnityEngine;
using UnityEngine.UIElements;
using Align = Unity.UIWidgets.widgets.Align;
using Color = Unity.UIWidgets.ui.Color;
using FontStyle = Unity.UIWidgets.ui.FontStyle;
using Image = Unity.UIWidgets.widgets.Image;
using Object = System.Object;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
using ThemeUtils = Unity.UIWidgets.DevTools.inspector.layout_explorer.ui.ThemeUtils;
namespace Unity.UIWidgets.DevTools.inspector.layout_explorer.flex
{
public class FlexLayoutExplorerWidget : LayoutExplorerWidget {
public FlexLayoutExplorerWidget(
InspectorController inspectorController,
Key key = null
) : base(inspectorController, key: key){}
public static bool shouldDisplay(RemoteDiagnosticsNode node) {
return (node?.isFlex ?? false) || (node?.parent?.isFlex ?? false);
}
public override State createState()
{
return new _FlexLayoutExplorerWidgetState();
}
}
public class _FlexLayoutExplorerWidgetState : LayoutExplorerWidgetState<FlexLayoutExplorerWidget, FlexLayoutProperties> {
ScrollController scrollController = new ScrollController();
public Axis? direction
{
get
{
return properties.direction;
}
}
Color horizontalColor(ColorScheme colorScheme)
{
return properties.isMainAxisHorizontal
? ThemeUtils.mainAxisColor
: ThemeUtils.crossAxisColor;
}
Color verticalColor(ColorScheme colorScheme)
{
return properties.isMainAxisVertical
? ThemeUtils.mainAxisColor
: ThemeUtils.crossAxisColor;
}
Color horizontalTextColor(ColorScheme colorScheme)
{
return properties.isMainAxisHorizontal
? ThemeUtils.mainAxisTextColor
: ThemeUtils.crossAxisTextColor;
}
Color verticalTextColor(ColorScheme colorScheme)
{
return properties.isMainAxisVertical
? ThemeUtils.mainAxisTextColor
: ThemeUtils.crossAxisTextColor;
}
string flexType
{
get
{
return properties.type;
}
}
public override RemoteDiagnosticsNode getRoot(RemoteDiagnosticsNode node) {
if (!shouldDisplay(node)) return null;
if (node.isFlex) return node;
return node.parent;
}
public override bool shouldDisplay(RemoteDiagnosticsNode node) {
return FlexLayoutExplorerWidget.shouldDisplay(selectedNode);
}
public override AnimatedLayoutProperties<FlexLayoutProperties> computeAnimatedProperties(
FlexLayoutProperties nextProperties) {
return new AnimatedFlexLayoutProperties(
(FlexLayoutProperties)animatedProperties?.copyWith() ?? properties,
nextProperties,
changeAnimation
);
}
public override FlexLayoutProperties computeLayoutProperties(RemoteDiagnosticsNode node)
{
return FlexLayoutProperties.fromDiagnostics(node);
}
public override void updateHighlighted(FlexLayoutProperties newProperties) {
setState(() => {
if (selectedNode.isFlex) {
highlighted = newProperties;
} else {
var idx = selectedNode.parent.childrenNow.IndexOf(selectedNode);
if (newProperties == null || newProperties.children == null) return;
if (idx != -1) highlighted = newProperties.children[idx];
}
});
}
Widget _buildAxisAlignmentDropdown(Axis axis, ColorScheme colorScheme) {
Color color = axis == direction
? ThemeUtils.mainAxisTextColor
: ThemeUtils.crossAxisTextColor;
List<object> alignmentEnumEntries;
Object selected;
if (axis == direction)
{
alignmentEnumEntries = new List<object>
{
Enum.GetValues(typeof(MainAxisAlignment))
.Cast<MainAxisAlignment>().ToList()
};
selected = properties.mainAxisAlignment;
} else {
alignmentEnumEntries = alignmentEnumEntries = new List<object>
{
Enum.GetValues(typeof(CrossAxisAlignment))
.Cast<CrossAxisAlignment>().ToList()
};
if (properties.textBaseline == null) {
// TODO(albertusangga): Look for ways to visualize baseline when it is null
alignmentEnumEntries.Remove(CrossAxisAlignment.baseline);
}
selected = properties.crossAxisAlignment;
}
List<DropdownMenuItem<object>> dropdownMenuItems = new List<DropdownMenuItem<object>>();
foreach (var alignment in alignmentEnumEntries)
{
dropdownMenuItems.Add(new DropdownMenuItem<object>(
value: alignment,
child: new Container(
padding: EdgeInsets.symmetric(vertical: ThemeUtils.margin),
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: new List<Widget>{
new Expanded(
child: new Text(
alignment.ToString(),
style: new TextStyle(color: color),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis
)
),
new Flexible(
child: Image.asset(
(axis == direction)
? FlexUtils.mainAxisAssetImageUrl(direction.Value, (MainAxisAlignment)alignment)
: FlexUtils.crossAxisAssetImageUrl(direction.Value, (CrossAxisAlignment)alignment),
fit: BoxFit.fitHeight,
color: color
)
)
}
)
)
));
}
return new RotatedBox(
quarterTurns: axis == Axis.vertical ? 3 : 0,
child: new Container(
constraints: new BoxConstraints(
maxWidth: ThemeUtils.dropdownMaxSize,
maxHeight: ThemeUtils.dropdownMaxSize
),
child: new DropdownButton<object>(
value: selected,
isExpanded: true,
// Avoid showing an underline for the main axis and cross-axis drop downs.
underline: new SizedBox(),
iconEnabledColor: axis == properties.direction
? ThemeUtils.mainAxisColor
: ThemeUtils.crossAxisColor,
selectedItemBuilder: (context) =>
{
List<Widget> widgets = new List<Widget>();
foreach (var alignment in alignmentEnumEntries)
{
widgets.Add(new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: new List<Widget>{
new Expanded(
flex: 2,
child: new Text(
alignment.ToString(),
style: new TextStyle(color: color),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis
)
),
new Flexible(
child: Image.asset(
(axis == direction)
? FlexUtils.mainAxisAssetImageUrl(direction.Value, (MainAxisAlignment)alignment)
: FlexUtils.crossAxisAssetImageUrl(direction.Value, (CrossAxisAlignment)alignment),
height: ThemeUtils.axisAlignmentAssetImageHeight,
fit: BoxFit.fitHeight,
color: color
)
)
}
));
}
return widgets;
},
items: dropdownMenuItems,
onChanged: (object newSelection) => {
FlexLayoutProperties changedProperties;
if (axis == direction) {
changedProperties =
properties.copyWith(mainAxisAlignment: (MainAxisAlignment)newSelection);
} else {
changedProperties =
properties.copyWith(crossAxisAlignment: (CrossAxisAlignment)newSelection);
}
//[!!!] not sure about this
var service = properties.node.inspectorService;
var valueRef = properties.node.valueRef;
markAsDirty();
// service.invokeSetFlexProperties(
// valueRef,
// changedProperties.mainAxisAlignment,
// changedProperties.crossAxisAlignment
// );
}
)
)
);
}
public override Widget build(BuildContext context) {
if (properties == null) return new SizedBox();
return new Container(
margin: EdgeInsets.all(ThemeUtils.margin),
padding: EdgeInsets.only(bottom: ThemeUtils.margin, right: ThemeUtils.margin),
child: new AnimatedBuilder(
animation: changeController,
builder: (context2, _) => {
return new LayoutBuilder(builder: _buildLayout);
}
)
);
}
Widget _buildLayout(BuildContext context, BoxConstraints constraints) {
var colorScheme = Theme.of(context).colorScheme;
var maxHeight = constraints.maxHeight;
var maxWidth = constraints.maxWidth;
var flexDescription = new Align(
alignment: Alignment.centerLeft,
child: new Container(
margin: EdgeInsets.only(
top: ThemeUtils.mainAxisArrowIndicatorSize,
left: ThemeUtils.crossAxisArrowIndicatorSize + ThemeUtils.margin
),
child: new InkWell(
onTap: () => onTap(properties),
child: new WidgetVisualizer(
title: flexType,
layoutProperties: properties,
isSelected: highlighted == properties,
overflowSide: properties.overflowSide,
hint: new Container(
padding: EdgeInsets.all(4.0f),
child: new Text(
$"Total Flex Factor: {properties?.totalFlex}",
textScaleFactor: ThemeUtils.largeTextScaleFactor,
style: new TextStyle(
color: ThemeUtils.emphasizedTextColor,
fontWeight: FontWeight.bold
),
overflow: TextOverflow.ellipsis
)
),
child: new VisualizeFlexChildren(
state: this,
properties: properties,
children: children,
highlighted: highlighted,
scrollController: scrollController,
direction: direction
)
)
)
)
);
var verticalAxisDescription = new Align(
alignment: Alignment.bottomLeft,
child: new Container(
margin: EdgeInsets.only(top: ThemeUtils.mainAxisArrowIndicatorSize + ThemeUtils.margin),
width: ThemeUtils.crossAxisArrowIndicatorSize,
child: new Column(
children: new List<Widget>{
new Expanded(
child: new ArrowWrapper(
arrowColor: verticalColor(colorScheme),
child: new Truncateable(
truncate: maxHeight <= ThemeUtils.minHeightToAllowTruncating,
child: new RotatedBox(
quarterTurns: 3,
child: new Text(
properties.verticalDirectionDescription,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
textScaleFactor: ThemeUtils.largeTextScaleFactor,
style: new TextStyle(
color: verticalTextColor(colorScheme)
)
)
)
),
type: ArrowType.down
)
),
new Truncateable(
truncate: maxHeight <= ThemeUtils.minHeightToAllowTruncating,
child: _buildAxisAlignmentDropdown(Axis.vertical, colorScheme)
),
}
)
)
);
var horizontalAxisDescription = new Align(
alignment: Alignment.topRight,
child: new Container(
margin: EdgeInsets.only(left: ThemeUtils.crossAxisArrowIndicatorSize + ThemeUtils.margin),
height: ThemeUtils.mainAxisArrowIndicatorSize,
child: new Row(
children: new List<Widget>{
new Expanded(
child: new ArrowWrapper(
arrowColor: horizontalColor(colorScheme),
child: new Truncateable(
truncate: maxWidth <= ThemeUtils.minWidthToAllowTruncating,
child: new Text(
properties.horizontalDirectionDescription,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
textScaleFactor: ThemeUtils.largeTextScaleFactor,
style: new TextStyle(color: horizontalTextColor(colorScheme))
)
),
type: ArrowType.right
)
),
new Truncateable(
truncate: maxWidth <= ThemeUtils.minWidthToAllowTruncating,
child: _buildAxisAlignmentDropdown(Axis.horizontal, colorScheme)
),
}
)
)
);
return new Container(
constraints: new BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight),
child: new Stack(
children: new List<Widget>{
flexDescription,
verticalAxisDescription,
horizontalAxisDescription
}
)
);
}
}
public class VisualizeFlexChildren : StatefulWidget {
public VisualizeFlexChildren(
Key key = null,
_FlexLayoutExplorerWidgetState state = null,
FlexLayoutProperties properties = null,
List<LayoutProperties> children = null,
LayoutProperties highlighted = null,
ScrollController scrollController = null,
Axis? direction = null
) : base(key: key)
{
this.state = state;
this.properties = properties;
this.children = children;
this.highlighted = highlighted;
this.scrollController = scrollController;
this.direction = direction;
}
public readonly FlexLayoutProperties properties;
public readonly List<LayoutProperties> children;
public readonly LayoutProperties highlighted;
public readonly ScrollController scrollController;
public readonly Axis? direction;
public readonly _FlexLayoutExplorerWidgetState state;
public override State createState()
{
return new _VisualizeFlexChildrenState();
}
}
public class _VisualizeFlexChildrenState : State<VisualizeFlexChildren> {
LayoutProperties lastHighlighted;
public static readonly GlobalKey selectedChildKey = GlobalKey.key(debugLabel: "selectedChild");
public override Widget build(BuildContext context) {
if (lastHighlighted != widget.highlighted) {
lastHighlighted = widget.highlighted;
if (widget.highlighted != null) {
WidgetsBinding.instance.addPostFrameCallback((_) => {
var selectedRenderObject =
selectedChildKey.currentContext?.findRenderObject();
if (selectedRenderObject != null &&
widget.scrollController.hasClients) {
widget.scrollController.position.ensureVisible(
selectedRenderObject,
alignment: 0.5f,
duration: CommonThemeUtils.defaultDuration
);
}
});
}
}
if (!widget.properties.hasChildren) {
return new Center(child: new Text("No Children"));
}
var theme = Theme.of(context);
var colorScheme = theme.colorScheme;
var contents = new Container(
decoration: new BoxDecoration(
border: Border.all(
color: theme.primaryColorLight
)
),
margin: EdgeInsets.only(top: ThemeUtils.margin, left: ThemeUtils.margin),
child: new LayoutBuilder(builder: (context2, constraints) => {
var maxWidth = constraints.maxWidth;
var maxHeight = constraints.maxHeight;
float maxSizeAvailable(Axis axis) {
return axis == Axis.horizontal ? maxWidth : maxHeight;
}
var childrenAndMainAxisSpacesRenderProps =
widget.properties.childrenRenderProperties(
smallestRenderWidth: ThemeUtils.minRenderWidth,
largestRenderWidth: ThemeUtils.defaultMaxRenderWidth,
smallestRenderHeight: ThemeUtils.minRenderHeight,
largestRenderHeight: ThemeUtils.defaultMaxRenderHeight,
maxSizeAvailable: maxSizeAvailable
);
List<RenderProperties> renderProperties = new List<RenderProperties>();
List<RenderProperties> mainAxisSpaces = new List<RenderProperties>();
foreach (var prop in childrenAndMainAxisSpacesRenderProps)
{
if (!prop.isFreeSpace)
{
renderProperties.Add(prop);
}
else
{
mainAxisSpaces.Add(prop);
}
}
var crossAxisSpaces = widget.properties.crossAxisSpaces(
childrenRenderProperties: renderProperties,
maxSizeAvailable: maxSizeAvailable
);
var childrenRenderWidgets = new List<Widget>();
for (var i = 0; i < widget.children.Count; i++) {
var child = widget.children[i];
var isSelected = widget.highlighted == child;
childrenRenderWidgets.Add( new FlexChildVisualizer(
key: isSelected ? selectedChildKey : null,
state: widget.state,
layoutProperties: child,
isSelected: isSelected,
renderProperties: renderProperties[i]
));
}
List<Widget> freeSpacesWidgets = new List<Widget>();
var propertiesList = new List<RenderProperties>(mainAxisSpaces.Union(crossAxisSpaces));
foreach (var property in propertiesList)
{
freeSpacesWidgets.Add(new FreeSpaceVisualizerWidget(property));
}
List<Widget> widgets = new List<Widget>();
widgets.Add(new LayoutExplorerBackground(colorScheme: colorScheme));
widgets = widgets.Union(freeSpacesWidgets).Union(childrenRenderWidgets).ToList();
float sum_width = 0;
float sum_height = 0;
foreach (var prop in childrenAndMainAxisSpacesRenderProps)
{
sum_width += prop.width;
sum_height += prop.height;
}
return new Scrollbar(
isAlwaysShown: true,
controller: widget.scrollController,
child: new SingleChildScrollView(
scrollDirection: widget.properties.direction.Value,
controller: widget.scrollController,
child: new ConstrainedBox(
constraints: new BoxConstraints(
minWidth: maxWidth,
minHeight: maxHeight,
maxWidth: widget.direction == Axis.horizontal
? sum_width
: maxWidth,
maxHeight: widget.direction == Axis.vertical
? sum_height
: maxHeight
).normalize(),
child: new Stack(
children: widgets
)
)
)
);
})
);
return new VisualizeWidthAndHeightWithConstraints(
child: contents,
properties: widget.properties
);
}
}
/// Widget that represents and visualize a direct child of Flex widget.
public class FlexChildVisualizer : StatelessWidget {
public readonly int maximumFlexFactorOptions = 5;
public FlexChildVisualizer(
Key key = null,
_FlexLayoutExplorerWidgetState state = null,
LayoutProperties layoutProperties = null,
RenderProperties renderProperties = null,
bool? isSelected = null
) : base(key: key)
{
this.state = state;
this.layoutProperties = layoutProperties;
this.renderProperties = renderProperties;
this.isSelected = isSelected;
}
public readonly _FlexLayoutExplorerWidgetState state;
public readonly bool? isSelected;
public readonly LayoutProperties layoutProperties;
public readonly RenderProperties renderProperties;
FlexLayoutProperties root
{
get
{
return state.properties;
}
}
LayoutProperties properties
{
get
{
return renderProperties.layoutProperties;
}
}
void onChangeFlexFactor(int newFlexFactor) {
// var node = properties.node;
// var inspectorService = await node.inspectorService;
// state.markAsDirty();
// await inspectorService.invokeSetFlexFactor(
// node.valueRef,
// newFlexFactor
// );
}
void onChangeFlexFit(FlexFit newFlexFit) {
// var node = properties.node;
// var inspectorService = node.inspectorService;
// state.markAsDirty();
// inspectorService.invokeSetFlexFit(
// node.valueRef,
// newFlexFit
// );
}
Widget _buildFlexFactorChangerDropdown(int maximumFlexFactor) {
Widget buildMenuitemChild(int flexFactor) {
return new Text(
$"flex: {flexFactor}",
style: flexFactor == properties.flexFactor
? new TextStyle(
fontWeight: FontWeight.bold,
color: ThemeUtils.emphasizedTextColor
)
: new TextStyle(color: ThemeUtils.emphasizedTextColor)
);
}
DropdownMenuItem<int> buildMenuItem(int flexFactor) {
return new DropdownMenuItem<int>(
value: flexFactor,
child: buildMenuitemChild(flexFactor)
);
}
List<DropdownMenuItem<int>> items = new List<DropdownMenuItem<int>>();
items.Add(buildMenuItem(default)); // May has porblems
for (var i = 0; i <= maximumFlexFactor; ++i)
{
items.Add(buildMenuItem(i));
}
return new DropdownButton<int>(
value: (int)properties.flexFactor?.clamp(0, maximumFlexFactor),
onChanged: onChangeFlexFactor,
iconEnabledColor: ThemeUtils.textColor,
underline: ThemeUtils.buildUnderline(),
items: items
);
}
Widget _buildFlexFitChangerDropdown() {
Widget flexFitDescription(FlexFit flexFit)
{
return new Text(
$"fit: {flexFit.ToString()}",
style: new TextStyle(color: ThemeUtils.emphasizedTextColor)
);
}
// Disable FlexFit changer if widget is Expanded.
if (properties.description == "Expanded") {
return flexFitDescription(FlexFit.tight);
}
DropdownMenuItem<FlexFit> buildMenuItem(FlexFit flexFit) {
return new DropdownMenuItem<FlexFit>(
value: flexFit,
child: flexFitDescription(flexFit)
);
}
List<DropdownMenuItem<FlexFit>> items = new List<DropdownMenuItem<FlexFit>>();
items.Add(buildMenuItem(FlexFit.loose));
if (properties.description != "Expanded") items.Add(buildMenuItem(FlexFit.tight));
return new DropdownButton<FlexFit>(
value: properties.flexFit.Value,
onChanged: onChangeFlexFit,
underline: ThemeUtils.buildUnderline(),
iconEnabledColor: ThemeUtils.emphasizedTextColor,
items: items
);
}
Widget _buildContent(ColorScheme colorScheme)
{
List<Widget> widgets = new List<Widget>();
widgets.Add(new Flexible(
child: _buildFlexFactorChangerDropdown(maximumFlexFactorOptions)
));
if (!properties.hasFlexFactor)
{
widgets.Add(new Text(
root.isMainAxisHorizontal ?"unconstrained horizontal" : "unconstrained vertical",
style: new TextStyle(
color: ThemeUtils.unconstrainedColor,
fontStyle: FontStyle.italic
),
maxLines: 2,
softWrap: true,
overflow: TextOverflow.ellipsis,
textScaleFactor: ThemeUtils.smallTextScaleFactor,
textAlign: TextAlign.center
));
}
widgets.Add(_buildFlexFitChangerDropdown());
return new Container(
margin: EdgeInsets.only(
top: ThemeUtils.margin,
left: ThemeUtils.margin
),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: widgets
)
);
}
public override Widget build(BuildContext context) {
var renderSize = renderProperties.size;
var renderOffset = renderProperties.offset;
Widget buildEntranceAnimation(BuildContext context2, Widget child) {
var vertical = root.isMainAxisVertical;
var horizontal = root.isMainAxisHorizontal;
Size size = renderSize;
if (properties.hasFlexFactor) {
size = new SizeTween(
begin: new Size(
horizontal ? ThemeUtils.minRenderWidth - ThemeUtils.entranceMargin : renderSize.width,
vertical ? ThemeUtils.minRenderHeight - ThemeUtils.entranceMargin : renderSize.height
),
end: renderSize
).evaluate(state.entranceCurve);
}
// Not-expanded widgets enter much faster.
return new Opacity(
opacity: Mathf.Min(state.entranceCurve.value * 5, 1.0f),
child: new Padding(
padding: EdgeInsets.symmetric(
horizontal: Mathf.Max(0.0f, (renderSize.width - size.width) / 2),
vertical: Mathf.Max(0.0f, (renderSize.height - size.height) / 2)
),
child: child
)
);
}
var colorScheme = Theme.of(context).colorScheme;
return new Positioned(
top: renderOffset.dy,
left: renderOffset.dx,
child: new InkWell(
onTap: () => state.onTap(properties),
onDoubleTap: () => state.onDoubleTap(properties),
onLongPress: () => state.onDoubleTap(properties),
child: new SizedBox(
width: renderSize.width,
height: renderSize.height,
child: new AnimatedBuilder(
animation: state.entranceController,
builder: buildEntranceAnimation,
child: new WidgetVisualizer(
isSelected: isSelected.Value,
layoutProperties: layoutProperties,
title: properties.description,
overflowSide: properties.overflowSide,
child: new VisualizeWidthAndHeightWithConstraints(
arrowHeadSize: ThemeUtils.arrowHeadSize,
child: new Align(
alignment: Alignment.topRight,
child: _buildContent(colorScheme)
),
properties: properties
)
)
)
)
)
);
}
}
}

266
com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/flex/utils.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.DevTools.inspector.layout_explorer.ui;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.DevTools.inspector.layout_explorer.flex
{
public class FlexUtils
{
public static string crossAxisAssetImageUrl(Axis direction, CrossAxisAlignment alignment)
{
return "assets/img/layout_explorer/cross_axis_alignment/" +
$"{flexType(direction)}_{alignment.ToString()}.png";
}
public static string mainAxisAssetImageUrl(Axis direction, MainAxisAlignment alignment)
{
return "assets/img/layout_explorer/main_axis_alignment/" +
$"{flexType(direction)}_{alignment.ToString()}.png";
}
public static string flexType(Axis direction)
{
switch (direction)
{
case Axis.horizontal:
return "row";
case Axis.vertical:
default:
return "column";
}
}
}
public delegate float MaxSizeAvailable(Axis axis);
public class AnimatedFlexLayoutProperties
: AnimatedLayoutProperties<FlexLayoutProperties>//, FlexLayoutProperties
{
public AnimatedFlexLayoutProperties(FlexLayoutProperties begin,
FlexLayoutProperties end, Animation<float> animation)
: base(begin, end, animation) { }
public new CrossAxisAlignment? crossAxisAlignment
{
get { return end.crossAxisAlignment; }
}
public new MainAxisAlignment? mainAxisAlignment
{
get { return end.mainAxisAlignment; }
}
public List<RenderProperties> childrenRenderProperties( // public override List<RenderProperties> childrenRenderProperties
float smallestRenderWidth,
float largestRenderWidth,
float smallestRenderHeight,
float largestRenderHeight,
MaxSizeAvailable maxSizeAvailable
)
{
var beginRenderProperties = begin.childrenRenderProperties(
smallestRenderHeight: smallestRenderHeight,
smallestRenderWidth: smallestRenderWidth,
largestRenderHeight: largestRenderHeight,
largestRenderWidth: largestRenderWidth,
maxSizeAvailable: maxSizeAvailable
);
var endRenderProperties = end.childrenRenderProperties(
smallestRenderHeight: smallestRenderHeight,
smallestRenderWidth: smallestRenderWidth,
largestRenderHeight: largestRenderHeight,
largestRenderWidth: largestRenderWidth,
maxSizeAvailable: maxSizeAvailable
);
var result = new List<RenderProperties>();
for (var i = 0; i < children?.Count; i++)
{
var beginProps = beginRenderProperties[i];
var endProps = endRenderProperties[i];
var t = animation.value;
result.Add(
new RenderProperties(
axis: endProps.axis,
offset: Offset.lerp(beginProps.offset, endProps.offset, t),
size: Size.lerp(beginProps.size, endProps.size, t),
realSize: Size.lerp(beginProps.realSize, endProps.realSize, t),
layoutProperties: new AnimatedLayoutProperties<LayoutProperties>(
beginProps.layoutProperties,
endProps.layoutProperties,
animation
)
)
);
}
// Add in the free space from the end.
// TODO(djshuckerow): We should make free space a part of
// RenderProperties so that we can animate between those.
foreach (var property in endRenderProperties)
{
if (property.isFreeSpace)
{
result.Add(property);
}
}
return result;
}
public new float? crossAxisDimension
{
get
{
return utils.lerpFloat(
begin.crossAxisDimension,
end.crossAxisDimension,
animation.value
);
}
}
public new Axis crossAxisDirection
{
get { return end.crossAxisDirection; }
}
public List<RenderProperties> crossAxisSpaces( // public override List<RenderProperties> crossAxisSpaces
List<RenderProperties> childrenRenderProperties,
MaxSizeAvailable maxSizeAvailable
) {
return end.crossAxisSpaces(
childrenRenderProperties: childrenRenderProperties,
maxSizeAvailable: maxSizeAvailable
);
}
public new Axis? direction
{
get { return end.direction; }
}
public new string horizontalDirectionDescription
{
get { return end.horizontalDirectionDescription; }
}
public new bool isMainAxisHorizontal
{
get { return end.isMainAxisHorizontal; }
}
public new bool isMainAxisVertical
{
get { return end.isMainAxisVertical; }
}
public new float? mainAxisDimension
{
get
{
return utils.lerpFloat(
begin.mainAxisDimension,
end.mainAxisDimension,
animation.value
);
}
}
public new MainAxisSize? mainAxisSize
{
get { return end.mainAxisSize; }
}
public new TextBaseline? textBaseline
{
get { return end.textBaseline; }
}
public new TextDirection? textDirection
{
get { return end.textDirection; }
}
public new float? totalFlex
{
get { return utils.lerpFloat(begin.totalFlex, end.totalFlex, animation.value); }
}
public new string type
{
get { return end.type; }
}
public new VerticalDirection? verticalDirection
{
get { return end.verticalDirection; }
}
public new string verticalDirectionDescription
{
get { return end.verticalDirectionDescription; }
}
public new FlexLayoutProperties copyWith(
Size size = null,
List<LayoutProperties> children = null,
BoxConstraints constraints = null,
bool? isFlex = null,
string description = null,
float? flexFactor = null,
FlexFit? flexFit = null,
Axis? direction = null,
MainAxisAlignment? mainAxisAlignment = null,
MainAxisSize? mainAxisSize = null,
CrossAxisAlignment? crossAxisAlignment = null,
TextDirection? textDirection = null,
VerticalDirection? verticalDirection = null,
TextBaseline? textBaseline = null
)
{
return new FlexLayoutProperties(
size: size ?? this.size,
children: children ?? this.children,
node: node,
constraints: constraints ?? this.constraints,
isFlex: isFlex ?? this.isFlex,
description: description ?? this.description,
flexFactor: flexFactor ?? this.flexFactor,
direction: direction ?? this.direction,
mainAxisAlignment: mainAxisAlignment ?? this.mainAxisAlignment,
mainAxisSize: mainAxisSize ?? this.mainAxisSize,
crossAxisAlignment: crossAxisAlignment ?? this.crossAxisAlignment,
textDirection: textDirection ?? this.textDirection,
verticalDirection: verticalDirection ?? this.verticalDirection,
textBaseline: textBaseline ?? this.textBaseline
);
}
public new bool startIsTopLeft
{
get { return end.startIsTopLeft; }
}
}
}
正在加载...
取消
保存