From c13180a92948d624a1b62c3c306b4b4446912fa3 Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Wed, 7 Jun 2023 06:20:13 -0700 Subject: [PATCH] Draw selected and hovered node borders in overlay layer Summary: The previous approach of setting some of the borders to be thicker and different colours was flakey, sometimes parts of the border would be cut off by a parent With this approach we figure out the offset relative to the root of the visualiser, and draw a box that is definatley on top. It works much more reliably Also fixed a couple of other niggles: 1. Can unselect when clicking again 2. Going into focus mode clears selection since your selection may not be in the focused area and there is a phantom box Reviewed By: lblasa Differential Revision: D46224034 fbshipit-source-id: 24bed8db38cddab796f786e7e0a4acfe7c6a9089 --- .../components/Visualization2D.tsx | 107 +++++++++++------- desktop/plugins/public/ui-debugger/index.tsx | 11 +- 2 files changed, 78 insertions(+), 40 deletions(-) diff --git a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx index e8e6bb056..034eaaecc 100644 --- a/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx +++ b/desktop/plugins/public/ui-debugger/components/Visualization2D.tsx @@ -7,7 +7,7 @@ * @format */ -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useEffect, useMemo, useRef, useState} from 'react'; import {Bounds, Coordinate, Id, NestedNode, Tag, UINode} from '../types'; import {produce, styled, theme, usePlugin, useValue} from 'flipper-plugin'; @@ -15,7 +15,6 @@ import {plugin} from '../index'; import {head, isEqual, throttle} from 'lodash'; import {Dropdown, Menu, Tooltip} from 'antd'; import {UIDebuggerMenuItem} from './util/UIDebuggerMenuItem'; -import {useFilteredValue} from '../hooks/usefilteredValue'; export const Visualization2D: React.FC< { @@ -31,6 +30,8 @@ export const Visualization2D: React.FC< const snapshotNode = snapshot && nodes.get(snapshot.nodeId); const focusedNodeId = useValue(instance.uiState.focusedNode); + const selectedNodeId = useValue(instance.uiState.selectedNode); + const hoveredNodeId = head(useValue(instance.uiState.hoveredNodes)); const focusState = useMemo(() => { //use the snapshot node as root since we cant realistically visualise any node above this const rootNode = snapshot && toNestedNode(snapshot.nodeId, nodes); @@ -107,6 +108,16 @@ export const Visualization2D: React.FC< height: toPx(focusState.actualRoot.bounds.height), } as React.CSSProperties }> + {hoveredNodeId && ( + + )} + {selectedNodeId && ( + + )}
{ @@ -170,19 +181,7 @@ function Visualization2DNode({ }) { const instance = usePlugin(plugin); - const wasOrIsSelected = useCallback( - (curValue?: Id, prevValue?: Id) => - curValue === node.id || prevValue === node.id, - [node.id], - ); - const selectedNode = useFilteredValue( - instance.uiState.selectedNode, - wasOrIsSelected, - ); - - const isSelected = selectedNode === node.id; - - const {isHovered, isLongHovered} = useHoverStates(node.id); + const {isLongHovered} = useHoverStates(node.id); const ref = useRef(null); let nestedChildren: NestedNode[]; @@ -234,22 +233,62 @@ function Visualization2DNode({ e.stopPropagation(); const hoveredNodes = instance.uiState.hoveredNodes.get(); - if (hoveredNodes[0] === selectedNode) { - onSelectNode(undefined); - } else { - onSelectNode(hoveredNodes[0]); - } + + onSelectNode(hoveredNodes[0]); }}> - + + {children}
); } +const OverlayBorder = styled.div<{ + type: 'selected' | 'hovered'; + nodeId: Id; + nodes: Map; +}>(({type, nodeId, nodes}) => { + const offset = getTotalOffset(nodeId, nodes); + const node = nodes.get(nodeId); + return { + zIndex: 100, + pointerEvents: 'none', + cursor: 'pointer', + position: 'absolute', + top: toPx(offset.y), + left: toPx(offset.x), + width: toPx(node?.bounds?.width ?? 0), + height: toPx(node?.bounds?.height ?? 0), + boxSizing: 'border-box', + borderWidth: 2, + borderStyle: 'solid', + color: 'transparent', + borderColor: + type === 'selected' ? theme.primaryColor : theme.selectionBackgroundColor, + }; +}); + +/** + * computes the x,y offset of a given node from the root of the visualization + * in node coordinates + */ +function getTotalOffset(id: Id, nodes: Map): Coordinate { + const offset = {x: 0, y: 0}; + let curId: Id | undefined = id; + + while (curId != null) { + const cur = nodes.get(curId); + if (cur != null) { + offset.x += cur.bounds.x; + offset.y += cur.bounds.y; + } + curId = cur?.parent; + } + + return offset; +} + function useHoverStates(nodeId: Id) { const instance = usePlugin(plugin); const [isHovered, setIsHovered] = useState(false); @@ -309,7 +348,7 @@ const ContextMenu: React.FC<{nodes: Map}> = ({children}) => { key="focus" text={`Focus ${hoveredNode?.name}`} onClick={() => { - instance.uiState.focusedNode.set(hoveredNode?.id); + instance.uiActions.onFocusNode(hoveredNode?.id); }} /> )} @@ -335,33 +374,25 @@ const ContextMenu: React.FC<{nodes: Map}> = ({children}) => { * node itself so that it has the same size but the border doesnt affect the sizing of its children * as border is part of the box model */ -const NodeBorder = styled.div<{ - tags: Tag[]; - hovered: boolean; - selected: boolean; -}>((props) => ({ +const NodeBorder = styled.div({ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, boxSizing: 'border-box', - borderWidth: props.selected || props.hovered ? '2px' : '1px', + borderWidth: '1px', borderStyle: 'solid', color: 'transparent', - borderColor: props.selected - ? theme.primaryColor - : props.hovered - ? theme.selectionBackgroundColor - : theme.disabledColor, -})); + borderColor: theme.disabledColor, +}); const longHoverDelay = 200; const pxScaleFactorCssVar = '--pxScaleFactor'; const MouseThrottle = 32; function toPx(n: number) { - return `calc(${n}px / var(${pxScaleFactorCssVar})`; + return `calc(${n}px / var(${pxScaleFactorCssVar}))`; } function toNestedNode( diff --git a/desktop/plugins/public/ui-debugger/index.tsx b/desktop/plugins/public/ui-debugger/index.tsx index 742975a60..5f949bcd3 100644 --- a/desktop/plugins/public/ui-debugger/index.tsx +++ b/desktop/plugins/public/ui-debugger/index.tsx @@ -375,7 +375,12 @@ function uiActions(uiState: UIState, nodes: Atom>): UIActions { }); }; const onSelectNode = (node?: Id) => { - uiState.selectedNode.set(node); + if (uiState.selectedNode.get() === node) { + uiState.selectedNode.set(undefined); + } else { + uiState.selectedNode.set(node); + } + if (node) { const selectedNode = nodes.get().get(node); const tags = selectedNode?.tags; @@ -410,12 +415,14 @@ function uiActions(uiState: UIState, nodes: Atom>): UIActions { }; const onFocusNode = (node?: Id) => { - if (node) { + if (node != null) { const focusedNode = nodes.get().get(node); const tags = focusedNode?.tags; if (tags) { tracker.track('node-focused', {name: focusedNode.name, tags}); } + + uiState.selectedNode.set(undefined); } uiState.focusedNode.set(node);