layout.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. import 'dart:math' as math;
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/rendering.dart';
  4. import './popover.dart';
  5. class PopoverLayoutDelegate extends SingleChildLayoutDelegate {
  6. PopoverLink link;
  7. PopoverDirection direction;
  8. final Offset offset;
  9. final EdgeInsets windowPadding;
  10. PopoverLayoutDelegate({
  11. required this.link,
  12. required this.direction,
  13. required this.offset,
  14. required this.windowPadding,
  15. });
  16. @override
  17. bool shouldRelayout(PopoverLayoutDelegate oldDelegate) {
  18. if (direction != oldDelegate.direction) {
  19. return true;
  20. }
  21. if (link != oldDelegate.link) {
  22. return true;
  23. }
  24. if (link.leaderOffset != oldDelegate.link.leaderOffset) {
  25. return true;
  26. }
  27. if (link.leaderSize != oldDelegate.link.leaderSize) {
  28. return true;
  29. }
  30. return false;
  31. }
  32. @override
  33. Size getSize(BoxConstraints constraints) {
  34. return Size(
  35. constraints.maxWidth - windowPadding.left - windowPadding.right,
  36. constraints.maxHeight - windowPadding.top - windowPadding.bottom,
  37. );
  38. }
  39. @override
  40. BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
  41. return BoxConstraints(
  42. maxWidth: constraints.maxWidth - windowPadding.left - windowPadding.right,
  43. maxHeight:
  44. constraints.maxHeight - windowPadding.top - windowPadding.bottom,
  45. );
  46. // assert(link.leaderSize != null);
  47. // // if (link.leaderSize == null) {
  48. // // return constraints.loosen();
  49. // // }
  50. // final anchorRect = Rect.fromLTWH(
  51. // link.leaderOffset!.dx,
  52. // link.leaderOffset!.dy,
  53. // link.leaderSize!.width,
  54. // link.leaderSize!.height,
  55. // );
  56. // BoxConstraints childConstraints;
  57. // switch (direction) {
  58. // case PopoverDirection.topLeft:
  59. // childConstraints = BoxConstraints.loose(Size(
  60. // anchorRect.left,
  61. // anchorRect.top,
  62. // ));
  63. // break;
  64. // case PopoverDirection.topRight:
  65. // childConstraints = BoxConstraints.loose(Size(
  66. // constraints.maxWidth - anchorRect.right,
  67. // anchorRect.top,
  68. // ));
  69. // break;
  70. // case PopoverDirection.bottomLeft:
  71. // childConstraints = BoxConstraints.loose(Size(
  72. // anchorRect.left,
  73. // constraints.maxHeight - anchorRect.bottom,
  74. // ));
  75. // break;
  76. // case PopoverDirection.bottomRight:
  77. // childConstraints = BoxConstraints.loose(Size(
  78. // constraints.maxWidth - anchorRect.right,
  79. // constraints.maxHeight - anchorRect.bottom,
  80. // ));
  81. // break;
  82. // case PopoverDirection.center:
  83. // childConstraints = BoxConstraints.loose(Size(
  84. // constraints.maxWidth,
  85. // constraints.maxHeight,
  86. // ));
  87. // break;
  88. // case PopoverDirection.topWithLeftAligned:
  89. // childConstraints = BoxConstraints.loose(Size(
  90. // constraints.maxWidth - anchorRect.left,
  91. // anchorRect.top,
  92. // ));
  93. // break;
  94. // case PopoverDirection.topWithCenterAligned:
  95. // childConstraints = BoxConstraints.loose(Size(
  96. // constraints.maxWidth,
  97. // anchorRect.top,
  98. // ));
  99. // break;
  100. // case PopoverDirection.topWithRightAligned:
  101. // childConstraints = BoxConstraints.loose(Size(
  102. // anchorRect.right,
  103. // anchorRect.top,
  104. // ));
  105. // break;
  106. // case PopoverDirection.rightWithTopAligned:
  107. // childConstraints = BoxConstraints.loose(Size(
  108. // constraints.maxWidth - anchorRect.right,
  109. // constraints.maxHeight - anchorRect.top,
  110. // ));
  111. // break;
  112. // case PopoverDirection.rightWithCenterAligned:
  113. // childConstraints = BoxConstraints.loose(Size(
  114. // constraints.maxWidth - anchorRect.right,
  115. // constraints.maxHeight,
  116. // ));
  117. // break;
  118. // case PopoverDirection.rightWithBottomAligned:
  119. // childConstraints = BoxConstraints.loose(Size(
  120. // constraints.maxWidth - anchorRect.right,
  121. // anchorRect.bottom,
  122. // ));
  123. // break;
  124. // case PopoverDirection.bottomWithLeftAligned:
  125. // childConstraints = BoxConstraints.loose(Size(
  126. // anchorRect.left,
  127. // constraints.maxHeight - anchorRect.bottom,
  128. // ));
  129. // break;
  130. // case PopoverDirection.bottomWithCenterAligned:
  131. // childConstraints = BoxConstraints.loose(Size(
  132. // constraints.maxWidth,
  133. // constraints.maxHeight - anchorRect.bottom,
  134. // ));
  135. // break;
  136. // case PopoverDirection.bottomWithRightAligned:
  137. // childConstraints = BoxConstraints.loose(Size(
  138. // anchorRect.right,
  139. // constraints.maxHeight - anchorRect.bottom,
  140. // ));
  141. // break;
  142. // case PopoverDirection.leftWithTopAligned:
  143. // childConstraints = BoxConstraints.loose(Size(
  144. // anchorRect.left,
  145. // constraints.maxHeight - anchorRect.top,
  146. // ));
  147. // break;
  148. // case PopoverDirection.leftWithCenterAligned:
  149. // childConstraints = BoxConstraints.loose(Size(
  150. // anchorRect.left,
  151. // constraints.maxHeight,
  152. // ));
  153. // break;
  154. // case PopoverDirection.leftWithBottomAligned:
  155. // childConstraints = BoxConstraints.loose(Size(
  156. // anchorRect.left,
  157. // anchorRect.bottom,
  158. // ));
  159. // break;
  160. // case PopoverDirection.custom:
  161. // childConstraints = constraints.loosen();
  162. // break;
  163. // default:
  164. // throw UnimplementedError();
  165. // }
  166. // return childConstraints;
  167. }
  168. @override
  169. Offset getPositionForChild(Size size, Size childSize) {
  170. if (link.leaderSize == null) {
  171. return Offset.zero;
  172. }
  173. final anchorRect = Rect.fromLTWH(
  174. link.leaderOffset!.dx + offset.dx,
  175. link.leaderOffset!.dy + offset.dy,
  176. link.leaderSize!.width,
  177. link.leaderSize!.height,
  178. );
  179. Offset position;
  180. switch (direction) {
  181. case PopoverDirection.topLeft:
  182. position = Offset(
  183. anchorRect.left - childSize.width,
  184. anchorRect.top - childSize.height,
  185. );
  186. break;
  187. case PopoverDirection.topRight:
  188. position = Offset(
  189. anchorRect.right,
  190. anchorRect.top - childSize.height,
  191. );
  192. break;
  193. case PopoverDirection.bottomLeft:
  194. position = Offset(
  195. anchorRect.left - childSize.width,
  196. anchorRect.bottom,
  197. );
  198. break;
  199. case PopoverDirection.bottomRight:
  200. position = Offset(
  201. anchorRect.right,
  202. anchorRect.bottom,
  203. );
  204. break;
  205. case PopoverDirection.center:
  206. position = anchorRect.center;
  207. break;
  208. case PopoverDirection.topWithLeftAligned:
  209. position = Offset(
  210. anchorRect.left,
  211. anchorRect.top - childSize.height,
  212. );
  213. break;
  214. case PopoverDirection.topWithCenterAligned:
  215. position = Offset(
  216. anchorRect.left + anchorRect.width / 2.0 - childSize.width / 2.0,
  217. anchorRect.top - childSize.height,
  218. );
  219. break;
  220. case PopoverDirection.topWithRightAligned:
  221. position = Offset(
  222. anchorRect.right - childSize.width,
  223. anchorRect.top - childSize.height,
  224. );
  225. break;
  226. case PopoverDirection.rightWithTopAligned:
  227. position = Offset(anchorRect.right, anchorRect.top);
  228. break;
  229. case PopoverDirection.rightWithCenterAligned:
  230. position = Offset(
  231. anchorRect.right,
  232. anchorRect.top + anchorRect.height / 2.0 - childSize.height / 2.0,
  233. );
  234. break;
  235. case PopoverDirection.rightWithBottomAligned:
  236. position = Offset(
  237. anchorRect.right,
  238. anchorRect.bottom - childSize.height,
  239. );
  240. break;
  241. case PopoverDirection.bottomWithLeftAligned:
  242. position = Offset(
  243. anchorRect.left,
  244. anchorRect.bottom,
  245. );
  246. break;
  247. case PopoverDirection.bottomWithCenterAligned:
  248. position = Offset(
  249. anchorRect.left + anchorRect.width / 2.0 - childSize.width / 2.0,
  250. anchorRect.bottom,
  251. );
  252. break;
  253. case PopoverDirection.bottomWithRightAligned:
  254. position = Offset(
  255. anchorRect.right - childSize.width,
  256. anchorRect.bottom,
  257. );
  258. break;
  259. case PopoverDirection.leftWithTopAligned:
  260. position = Offset(
  261. anchorRect.left - childSize.width,
  262. anchorRect.top,
  263. );
  264. break;
  265. case PopoverDirection.leftWithCenterAligned:
  266. position = Offset(
  267. anchorRect.left - childSize.width,
  268. anchorRect.top + anchorRect.height / 2.0 - childSize.height / 2.0,
  269. );
  270. break;
  271. case PopoverDirection.leftWithBottomAligned:
  272. position = Offset(
  273. anchorRect.left - childSize.width,
  274. anchorRect.bottom - childSize.height,
  275. );
  276. break;
  277. default:
  278. throw UnimplementedError();
  279. }
  280. return Offset(
  281. math.max(
  282. windowPadding.left,
  283. math.min(
  284. windowPadding.left + size.width - childSize.width, position.dx)),
  285. math.max(
  286. windowPadding.top,
  287. math.min(
  288. windowPadding.top + size.height - childSize.height, position.dy)),
  289. );
  290. }
  291. }
  292. class PopoverTarget extends SingleChildRenderObjectWidget {
  293. final PopoverLink link;
  294. const PopoverTarget({
  295. super.key,
  296. super.child,
  297. required this.link,
  298. });
  299. @override
  300. PopoverTargetRenderBox createRenderObject(BuildContext context) {
  301. return PopoverTargetRenderBox(
  302. link: link,
  303. );
  304. }
  305. @override
  306. void updateRenderObject(
  307. BuildContext context, PopoverTargetRenderBox renderObject) {
  308. renderObject.link = link;
  309. }
  310. }
  311. class PopoverTargetRenderBox extends RenderProxyBox {
  312. PopoverLink link;
  313. PopoverTargetRenderBox({required this.link, RenderBox? child}) : super(child);
  314. @override
  315. bool get alwaysNeedsCompositing => true;
  316. @override
  317. void performLayout() {
  318. super.performLayout();
  319. link.leaderSize = size;
  320. }
  321. @override
  322. void paint(PaintingContext context, Offset offset) {
  323. link.leaderOffset = localToGlobal(Offset.zero);
  324. super.paint(context, offset);
  325. }
  326. @override
  327. void detach() {
  328. super.detach();
  329. link.leaderOffset = null;
  330. link.leaderSize = null;
  331. }
  332. @override
  333. void attach(covariant PipelineOwner owner) {
  334. super.attach(owner);
  335. if (hasSize) {
  336. // The leaderSize was set after [performLayout], but was
  337. // set to null when [detach] get called.
  338. //
  339. // set the leaderSize when attach get called
  340. link.leaderSize = size;
  341. }
  342. }
  343. @override
  344. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  345. super.debugFillProperties(properties);
  346. properties.add(DiagnosticsProperty<PopoverLink>('link', link));
  347. }
  348. }
  349. class PopoverLink {
  350. Offset? leaderOffset;
  351. Size? leaderSize;
  352. }