Line data Source code
1 : import 'package:flutter/material.dart';
2 :
3 : /// This class helps you find element within your app
4 : /// use [searchChildElement] with a String containing key value
5 : /// result is available in [result] and returns an [Element]
6 : class ElementFinder {
7 :
8 : // Prefer use the navigatorContext to get full context tree
9 : final BuildContext context;
10 :
11 6 : ElementFinder(this.context);
12 :
13 : // this method scan all child recursively to get all widget bounds we could select for an helper
14 3 : Map<String, ElementModel> scan({Key omitChildsOf, bool debugMode = false}) {
15 3 : Map<String, ElementModel> results = Map<String, ElementModel>();
16 12 : context.visitChildElements((element) => _scanChildElement(
17 6 : context.findRenderObject(),
18 : element,
19 : results,
20 : omitChildsOf: omitChildsOf,
21 : debugMode: debugMode
22 : ));
23 : return results;
24 : }
25 :
26 : // List all pages from this context
27 0 : List<PageElement> scanPages() {
28 0 : var pages = List<PageElement>();
29 0 : context.visitChildElements((element) => _scanPageChildElement(element, pages));
30 : return pages;
31 : }
32 :
33 : // this method scan all child recursively to find a widget having a key == searchedKey
34 3 : ElementModel searchChildElement(String key) {
35 3 : ElementModel result = ElementModel.empty();
36 12 : context.visitChildElements((element) => _searchChildElement(element, key, result));
37 3 : if(result.element != null) {
38 12 : return _createElementModel(context.findRenderObject(), result.element);
39 : }
40 : return result;
41 : }
42 :
43 : /// This functions search for the maximum rect available space
44 : /// We use it for example to find the most available space to write a text in our anchored helper
45 1 : Rect getLargestAvailableSpace(ElementModel elementModel) {
46 2 : var parentObject = context.findRenderObject();
47 1 : var element = elementModel.element;
48 3 : var translation = element.renderObject.getTransformTo(parentObject).getTranslation();
49 1 : var objectX = translation.x;
50 3 : var objectEndX = objectX + element.size.width;
51 1 : var objectY = translation.y;
52 3 : var objectEndY = objectY + element.size.height;
53 1 : var layerRect = parentObject.paintBounds;
54 :
55 : Rect availableHSpace;
56 : Rect availableWSpace;
57 3 : if(objectY > layerRect.height - objectEndY) {
58 0 : availableHSpace = Rect.fromLTWH(0, 0, layerRect.width, objectY);
59 : } else {
60 4 : availableHSpace = Rect.fromLTWH(0, objectEndY, layerRect.width, layerRect.height - objectEndY);
61 : }
62 3 : if(objectX > layerRect.width - objectEndX) {
63 0 : availableWSpace = Rect.fromLTWH(0, 0, objectX, layerRect.height);
64 : } else {
65 4 : availableWSpace = Rect.fromLTWH(objectEndX, 0, layerRect.width - objectEndX, layerRect.height);
66 : }
67 : // check area to use the largest
68 5 : var availableHSpaceArea = availableHSpace.size.width * availableHSpace.size.height;
69 5 : var availableWSpaceArea = availableWSpace.size.width * availableWSpace.size.height;
70 : const MIN_WRITABLE_SPACE = 100;
71 1 : if(availableWSpaceArea > availableHSpaceArea && availableWSpace.width > MIN_WRITABLE_SPACE) {
72 : return availableWSpace;
73 : }
74 : return availableHSpace;
75 : }
76 :
77 : // -----------------------------------------------------------
78 : // private
79 : // -----------------------------------------------------------
80 3 : _searchChildElement(Element element, String key, ElementModel result, {int n = 0}) {
81 21 : if(result.element == null && element.widget.key != null && element.widget.key.toString().contains(key)) {
82 3 : result.element = element;
83 : }
84 3 : if(result.element != null) {
85 : return;
86 : }
87 12 : element.visitChildElements((visitor) => _searchChildElement(visitor, key, result, n: n + 1));
88 : }
89 :
90 : // omits elements with key starting with anything other than [<
91 : // flutter makes key with "[<_myKey_>]" for our keys
92 : // scan all elements in the current page tree and add their bounds to the results map
93 3 : _scanChildElement(RenderObject parentObject, Element element, Map<String, ElementModel> results, {int n = 0, Key omitChildsOf, bool debugMode = true}) {
94 : if(debugMode) {
95 0 : var nbChilds = element.debugDescribeChildren().length;
96 0 : var pre = StringBuffer();
97 0 : for(int i = 0; i<n ; i++) {
98 0 : pre.write(" ");
99 : }
100 0 : print("$pre ${element?.widget.runtimeType} $n => $nbChilds ");
101 : }
102 6 : if(element.widget.key != null && omitChildsOf !=null && element.widget.key.toString() == omitChildsOf.toString()) {
103 : return;
104 : }
105 30 : if(element.widget.key != null && element.widget.key.toString().startsWith("[<") && !results.containsKey(element.widget.key.toString())) {
106 : if(debugMode) {
107 0 : print(" added ${element?.widget?.key.toString()} : $n");
108 : }
109 3 : var model = _createElementModel(parentObject, element);
110 30 : if(results.values.firstWhere((element) => element.bounds == model.bounds && element.offset == model.offset, orElse: () => null) == null) {
111 15 : results.putIfAbsent(element.widget.key.toString(), () => model);
112 : }
113 : }
114 12 : element.visitChildElements((visitor) => _scanChildElement(parentObject, visitor, results, n: n + 1, omitChildsOf: omitChildsOf, debugMode: debugMode));
115 : }
116 :
117 : // search first entries that contains our pages
118 0 : _scanPageChildElement(Element element, List<PageElement> pages) {
119 0 : if(element.runtimeType.toString() == "_Theatre") {
120 0 : element.visitChildElements((visitor) => pages.add(PageElement(element)));
121 : } else {
122 0 : element.visitChildElements((visitor) => _scanPageChildElement(element, pages));
123 : }
124 : }
125 :
126 3 : ElementModel _createElementModel(RenderObject parentObject, Element element) {
127 3 : var renderObject = element.findRenderObject();
128 6 : var bounds = element.findRenderObject().paintBounds;
129 6 : var translation = renderObject.getTransformTo(parentObject).getTranslation();
130 9 : var offset = Offset(translation.x, translation.y);
131 3 : return ElementModel(
132 9 : element.widget.key.toString(),
133 : bounds,
134 : offset,
135 6 : element.widget.runtimeType,
136 : element: element
137 : );
138 : }
139 : }
140 :
141 : class PageElement {
142 :
143 : Element element;
144 :
145 0 : PageElement(this.element);
146 : }
147 :
148 : class ElementModel {
149 :
150 : String key;
151 :
152 : Rect bounds;
153 :
154 : Offset offset;
155 :
156 : Element element;
157 :
158 : Type runtimeType;
159 :
160 3 : ElementModel(this.key, this.bounds, this.offset, this.runtimeType, {this.element});
161 :
162 6 : factory ElementModel.empty() => ElementModel(null, null, null, null);
163 : }
164 :
|