guanghuispark
4 年前
当前提交
4e38a562
共有 34 个文件被更改,包括 9026 次插入 和 12 次删除
-
5com.unity.uiwidgets.devtools/Editor/Unity.UIWidgets.DevTools.Editor.asmdef
-
974com.unity.uiwidgets.devtools/Editor/inspector/inspector_controller.cs
-
12com.unity.uiwidgets.devtools/Editor/inspector/inspector_text_style.cs
-
27com.unity.uiwidgets.devtools/Editor/theme.cs
-
8com.unity.uiwidgets.devtools/Editor/ui/icons.cs
-
2com.unity.uiwidgets/Runtime/foundation/key.cs
-
548com.unity.uiwidgets.devtools/Editor/app.cs
-
3com.unity.uiwidgets.devtools/Editor/config_specific.meta
-
52com.unity.uiwidgets.devtools/Editor/globals.cs
-
3com.unity.uiwidgets.devtools/Editor/globals.cs.meta
-
260com.unity.uiwidgets.devtools/Editor/inspector/diagnostics.cs
-
1001com.unity.uiwidgets.devtools/Editor/inspector/diagnostics_node.cs
-
152com.unity.uiwidgets.devtools/Editor/inspector/flutter_widget.cs
-
1001com.unity.uiwidgets.devtools/Editor/inspector/inspector_data_models.cs
-
408com.unity.uiwidgets.devtools/Editor/inspector/inspector_screen.cs
-
144com.unity.uiwidgets.devtools/Editor/inspector/inspector_screen_details_tab.cs
-
1001com.unity.uiwidgets.devtools/Editor/inspector/inspector_service.cs
-
873com.unity.uiwidgets.devtools/Editor/inspector/inspector_tree.cs
-
93com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/layout_explorer.cs
-
345com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/ui/layout_explorer_widget.cs
-
569com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/ui/utils.cs
-
21com.unity.uiwidgets.devtools/Editor/listenable.cs
-
20com.unity.uiwidgets.devtools/Editor/main.cs
-
290com.unity.uiwidgets.devtools/Editor/screen.cs
-
59com.unity.uiwidgets.devtools/Editor/utils.cs
-
23com.unity.uiwidgets.devtools/Editor/analytics/provider.cs
-
53com.unity.uiwidgets.devtools/Editor/analytics/stub_provider.cs
-
18com.unity.uiwidgets.devtools/Editor/config_specific/ide_theme/ide_theme.cs
-
807com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/flex/flex.cs
-
266com.unity.uiwidgets.devtools/Editor/inspector/layout_explorer/flex/utils.cs
|
|||
{ |
|||
"name": "Unity.UIWidgets.DevTools.Editor", |
|||
"references": [], |
|||
"references": [ |
|||
"Unity.UIWidgets", |
|||
"Unity.UIWidgets.Editor" |
|||
], |
|||
"includePlatforms": [ |
|||
"Editor" |
|||
], |
|
|||
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 |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
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, |
|||
], |
|||
), |
|||
); |
|||
}*/ |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|
|||
fileFormatVersion: 2 |
|||
guid: ec20b4818fa849a88ade34bed3bc361d |
|||
timeCreated: 1615531810 |
|
|||
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;
|
|||
// }
|
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 2cd9c39bad944654afbb4b9928bf19b9 |
|||
timeCreated: 1615544477 |
|
|||
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
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
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
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
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); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
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
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
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); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|
|||
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; |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
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 |
|||
) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
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;
|
|||
} |
|||
} |
|
|||
|
|||
|
|||
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)); |
|||
} |
|||
} |
|||
} |
|
|||
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(); |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
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"; |
|||
} |
|||
} |
|||
|
|||
} |
|
|||
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(); |
|||
} |
|||
} |
|
|||
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() {} |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|
|||
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; |
|||
} |
|||
} |
|
|||
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 |
|||
) |
|||
) |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|
|||
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; } |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
|
撰写
预览
正在加载...
取消
保存
Reference in new issue