From b28b97975c11b0f515e358350a045cd5d708a53e Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 25 Jun 2025 22:32:43 +0200 Subject: [PATCH] tab tree initial scroll --- .../presentation/dialogs/tab_tree.dart | 104 ++++++++++++++---- 1 file changed, 80 insertions(+), 24 deletions(-) diff --git a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart b/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart index ba5d70f1..6a976ac7 100644 --- a/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart +++ b/app/lib/features/geckoview/features/browser/presentation/dialogs/tab_tree.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:fast_equatable/fast_equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -11,6 +12,55 @@ import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart import 'package:weblibre/features/geckoview/domain/providers/selected_tab.dart'; import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/tab_preview.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; +import 'package:weblibre/utils/ui_helper.dart'; + +class BuchheimWalkerAlgorithmWithFocus extends BuchheimWalkerAlgorithm { + bool _hasScrolled = false; + + final Size childSize; + final TransformationController transforamtionController; + final dynamic initialNodeId; + + BuchheimWalkerAlgorithmWithFocus( + super.configuration, + super.renderer, { + required this.childSize, + required this.transforamtionController, + this.initialNodeId, + }); + + @override + void setFocusedNode(Node node) { + final scale = transforamtionController.value.getMaxScaleOnAxis(); + final newPosition = + (node.position - Offset(childSize.width / 2, childSize.height / 2)) * + scale; + + transforamtionController.value = transforamtionController.value.clone() + ..setTranslation(Vector3(-newPosition.dx, -newPosition.dy, 0)); + } + + @override + Size run(Graph? graph, double shiftX, double shiftY) { + final size = super.run(graph, shiftX, shiftY); + + if (initialNodeId != null && !_hasScrolled) { + final node = graph?.nodes.firstWhereOrNull( + (element) => element.key?.value == initialNodeId, + ); + + if (node != null && node.position != Offset.zero) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setFocusedNode(node); + }); + + _hasScrolled = true; + } + } + + return size; + } +} class TabTreeDialog extends HookConsumerWidget { final String tabId; @@ -26,22 +76,11 @@ class TabTreeDialog extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final transforamtionController = useTransformationController(); - final builder = useMemoized(() { - final config = BuchheimWalkerConfiguration() - ..siblingSeparation = 100 - ..levelSeparation = 150 - ..subtreeSeparation = 150 - ..orientation = BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM; - - return BuchheimWalkerAlgorithm(config, TreeEdgeRenderer(config)); - }); - final selectedTabId = ref.watch(selectedTabProvider); final tabs = ref.watch(tabDescendantsProvider(tabId)); final graph = useMemoized(() { - final graph = Graph(); - graph.isTree = true; + final graph = Graph()..isTree = true; if (tabs.hasValue) { for (final MapEntry(:key, :value) in tabs.valueOrNull!.entries) { @@ -55,7 +94,23 @@ class TabTreeDialog extends HookConsumerWidget { } return graph; - }, [EquatableValue(tabs.valueOrNull)]); + }, [EquatableValue(tabs.valueOrNull), selectedTabId]); + + final graphAlgo = useMemoized(() { + final config = BuchheimWalkerConfiguration() + ..siblingSeparation = 100 + ..levelSeparation = 150 + ..subtreeSeparation = 150 + ..orientation = BuchheimWalkerConfiguration.ORIENTATION_TOP_BOTTOM; + + return BuchheimWalkerAlgorithmWithFocus( + config, + TreeEdgeRenderer(config), + childSize: childSize, + transforamtionController: transforamtionController, + initialNodeId: selectedTabId, + ); + }, [selectedTabId]); return Dialog.fullscreen( child: Scaffold( @@ -72,17 +127,18 @@ class TabTreeDialog extends HookConsumerWidget { floatingActionButton: FloatingActionButton( child: const Icon(MdiIcons.target), onPressed: () { - final node = graph.getNodeUsingId(selectedTabId); - final scale = transforamtionController.value.getMaxScaleOnAxis(); - final newPosition = - (node.position - - Offset(childSize.width / 2, childSize.height / 2)) * - scale; + final node = graph.nodes.firstWhereOrNull( + (element) => element.key?.value == selectedTabId, + ); - transforamtionController.value = - transforamtionController.value.clone()..setTranslation( - Vector3(-newPosition.dx, -newPosition.dy, 0), - ); + if (node != null) { + graphAlgo.setFocusedNode(node); + } else { + showErrorMessage( + context, + 'The current tab is not part of this tree', + ); + } }, ), extendBodyBehindAppBar: true, @@ -98,7 +154,7 @@ class TabTreeDialog extends HookConsumerWidget { replacement: const Bone.square(), child: GraphView( graph: graph, - algorithm: builder, + algorithm: graphAlgo, paint: Paint() ..color = Theme.of(context).colorScheme.outline ..strokeWidth = 1