From f629bc5621dd7977d3123de8b508a50067d0ddd7 Mon Sep 17 00:00:00 2001 From: Chris Clampitt Date: Thu, 12 Dec 2024 13:57:01 -0700 Subject: [PATCH] iPad, upon project first open: better re-rendering when a Group Layer's Scroll Enabled input is set true (#640) * Remove some log statements * Use @Bindable var when passing down LayerInputObservers in LayerInspectorView --- .../LayerInspector/LayerInspectorView.swift | 84 +++++++++++++------ .../Node/Model/RowData/LayerInputType.swift | 2 +- .../Frame/View/NativeScrollGestureView.swift | 6 +- .../PreviewAbsoluteShapeLayerModifier.swift | 4 - .../PreviewCommonModifierWithoutFrame.swift | 4 - .../Layer/View/Type/PreviewGroup.swift | 23 +---- .../SidebarItemSelectionActions.swift | 24 +++--- 7 files changed, 74 insertions(+), 73 deletions(-) diff --git a/Stitch/Graph/LayerInspector/LayerInspectorView.swift b/Stitch/Graph/LayerInspector/LayerInspectorView.swift index 43c1c1dc3..90324c442 100644 --- a/Stitch/Graph/LayerInspector/LayerInspectorView.swift +++ b/Stitch/Graph/LayerInspector/LayerInspectorView.swift @@ -115,7 +115,7 @@ struct LayerInspectorView: View { if !filteredInputs.isEmpty { LayerInspectorInputsSectionView( sectionName: sectionName, - layerInputs: filteredInputs, + layerInputs: .init(layerInputs: filteredInputs), graph: graph, nodeId: node ) @@ -193,13 +193,59 @@ enum LayerInspectorSectionName: String, Equatable, Hashable { typography = "Typography", stroke = "Stroke", rotation = "Rotation", -// shadow = "Shadow", layerEffects = "Layer Effects" } -// Named Tuple -typealias LayerInputAndObserver = (layerInput: LayerInputPort, - portObserver: LayerInputObserver) +@Observable +final class LayerInputAndObserver { + let layerInput: LayerInputPort + let portObserver: LayerInputObserver + + init(layerInput: LayerInputPort, + portObserver: LayerInputObserver) { + self.layerInput = layerInput + self.portObserver = portObserver + } +} + +@Observable +final class LayerInputsAndObservers { + let layerInputs: [LayerInputAndObserver] + + init(layerInputs: [LayerInputAndObserver]) { + self.layerInputs = layerInputs + } +} + +struct LayerInspectorInputView: View { + + // `@Bindable var` (vs. `let`) seems to improve a strange issue where toggling scroll-enabled input on iPad would update the LayerInputObserver's blockedFields set but not re-render the view. + @Bindable var layerInput: LayerInputAndObserver + @Bindable var graph: GraphState + let nodeId: NodeId + + var body: some View { + let layerInputObserver: LayerInputObserver = layerInput.portObserver + + let blockedFields = layerInputObserver.blockedFields + + let allFieldsBlockedOut = layerInputObserver + .fieldValueTypes.first? + .fieldObservers.allSatisfy({ $0.isBlocked(blockedFields)}) + ?? false + + if !allFieldsBlockedOut { + LayerInspectorInputPortView(layerInputObserver: layerInputObserver, + graph: graph, + nodeId: nodeId) + .modifier(LayerPropertyRowOriginReader(graph: graph, + layerInput: layerInput.layerInput)) + } else { + EmptyView() + } + } + +} // This view now needs to receive the inputs it will be listing, // rather than receiving the entire layer node. @@ -208,7 +254,7 @@ struct LayerInspectorInputsSectionView: View { let sectionName: LayerInspectorSectionName // This section's layer inputs, filtered to excluded any not supported by this specific layer. - let layerInputs: [LayerInputAndObserver] + @Bindable var layerInputs: LayerInputsAndObservers @Bindable var graph: GraphState let nodeId: NodeId @@ -217,23 +263,10 @@ struct LayerInspectorInputsSectionView: View { var body: some View { Section(isExpanded: $expanded) { - ForEach(layerInputs, id: \.layerInput) { layerInput in - let layerInputObserver: LayerInputObserver = layerInput.portObserver - - let blockedFields = layerInputObserver.blockedFields - - let allFieldsBlockedOut = layerInputObserver - .fieldValueTypes.first? - .fieldObservers.allSatisfy({ $0.isBlocked(blockedFields)}) - ?? false - - if !allFieldsBlockedOut { - LayerInspectorInputPortView(layerInputObserver: layerInputObserver, - graph: graph, - nodeId: nodeId) - .modifier(LayerPropertyRowOriginReader(graph: graph, - layerInput: layerInput.layerInput)) - } + ForEach(layerInputs.layerInputs, id: \.layerInput) { (layerInput: LayerInputAndObserver) in + LayerInspectorInputView(layerInput: layerInput, + graph: graph, + nodeId: nodeId) } .transition(.slideInAndOut(edge: .top)) } header: { @@ -267,7 +300,7 @@ struct LayerInspectorInputsSectionView: View { self.expanded.toggle() dispatch(LayerInspectorSectionToggled(section: sectionName)) - layerInputs.forEach { layerInput in + layerInputs.layerInputs.forEach { layerInput in if case let .layerInput(x) = graph.graphUI.propertySidebar.selectedProperty, x.layerInput == layerInput.layerInput { graph.graphUI.propertySidebar.selectedProperty = nil @@ -328,6 +361,7 @@ extension GraphState { node: NodeId, inputs: LayerInputObserverDict, outputs: [OutputLayerNodeRowData])? { + // log("getLayerInspectorData called") // Any time orderedSidebarLayers changes, that will retrigger LayerInspector guard !self.orderedSidebarLayers.isEmpty else { @@ -336,7 +370,7 @@ extension GraphState { var inspectorFocusedLayers = self.layersSidebarViewModel.selectionState.primary - log("getLayerInspectorData: inspectorFocusedLayers: \(inspectorFocusedLayers)") + // log("getLayerInspectorData: inspectorFocusedLayers: \(inspectorFocusedLayers)") #if DEV_DEBUG // For debug diff --git a/Stitch/Graph/Node/Model/RowData/LayerInputType.swift b/Stitch/Graph/Node/Model/RowData/LayerInputType.swift index 9de491b9f..444ea4120 100644 --- a/Stitch/Graph/Node/Model/RowData/LayerInputType.swift +++ b/Stitch/Graph/Node/Model/RowData/LayerInputType.swift @@ -1570,7 +1570,7 @@ extension LayerInputPort { case .deviceAppearance: return useShortLabel ? "Appearance" : "Device Appearance" case .scrollContentSize: - return "Content Size" + return useShortLabel ? "Content" : "Content Size" case .scrollXEnabled: return "Scroll X Enabled" case .scrollJumpToXStyle: diff --git a/Stitch/Graph/PrototypePreview/Frame/View/NativeScrollGestureView.swift b/Stitch/Graph/PrototypePreview/Frame/View/NativeScrollGestureView.swift index d97c52ae5..1d69dfcbd 100644 --- a/Stitch/Graph/PrototypePreview/Frame/View/NativeScrollGestureView.swift +++ b/Stitch/Graph/PrototypePreview/Frame/View/NativeScrollGestureView.swift @@ -51,12 +51,11 @@ struct NativeScrollGestureView: View { @MainActor var hasScrollInteraction: Bool { let _hasScrollInteraction = layerViewModel.isScrollXEnabled || layerViewModel.isScrollYEnabled - log("NativeScrollGestureView: hasScrollInteraction: _hasScrollInteraction: \(_hasScrollInteraction)") + // log("NativeScrollGestureView: hasScrollInteraction: _hasScrollInteraction: \(_hasScrollInteraction)") return _hasScrollInteraction } var body: some View { - // logInView("NativeScrollGestureView: var body") if hasScrollInteraction { view() .modifier(NativeScrollGestureViewInner( @@ -127,7 +126,6 @@ struct NativeScrollGestureViewInner: ViewModifier { @State var viewId: UUID = .init() func body(content: Content) -> some View { - // logInView("NativeScrollGestureViewInner: var body") ScrollView(self.scrollAxes) { @@ -138,8 +136,6 @@ struct NativeScrollGestureViewInner: ViewModifier { .frame(height: self.customContentHeight) // factor out parent-scroll's offset, so that view does not move unless we explicitly connect scroll interaction node's output to the layer's position input -// .offset(x: self.scrollOffset.x, -// y: self.scrollOffset.y) .offset(x: self.finalScrollOffset.x, y: self.finalScrollOffset.y) } diff --git a/Stitch/Graph/PrototypePreview/Layer/View/CommonModifier/PreviewAbsoluteShapeLayerModifier.swift b/Stitch/Graph/PrototypePreview/Layer/View/CommonModifier/PreviewAbsoluteShapeLayerModifier.swift index ed28a9afb..4edf7fcf0 100644 --- a/Stitch/Graph/PrototypePreview/Layer/View/CommonModifier/PreviewAbsoluteShapeLayerModifier.swift +++ b/Stitch/Graph/PrototypePreview/Layer/View/CommonModifier/PreviewAbsoluteShapeLayerModifier.swift @@ -97,9 +97,5 @@ struct PreviewAbsoluteShapeLayerModifier: ViewModifier { size: size, parentSize: parentSize, minimumDragDistance: DEFAULT_MINIMUM_DRAG_DISTANCE)) - -// .modifier(NativeScrollGestureView( -// layerViewModel: viewModel, -// graph: graph)) } } diff --git a/Stitch/Graph/PrototypePreview/Layer/View/CommonModifier/PreviewCommonModifierWithoutFrame.swift b/Stitch/Graph/PrototypePreview/Layer/View/CommonModifier/PreviewCommonModifierWithoutFrame.swift index 32e706528..dcf41baf0 100644 --- a/Stitch/Graph/PrototypePreview/Layer/View/CommonModifier/PreviewCommonModifierWithoutFrame.swift +++ b/Stitch/Graph/PrototypePreview/Layer/View/CommonModifier/PreviewCommonModifierWithoutFrame.swift @@ -143,10 +143,6 @@ struct PreviewCommonModifierWithoutFrame: ViewModifier { size: sizeForAnchoringAndGestures, parentSize: parentSize, minimumDragDistance: minimumDragDistance)) - -// .modifier(NativeScrollGestureView( -// layerViewModel: layerViewModel, -// graph: graph)) } } diff --git a/Stitch/Graph/PrototypePreview/Layer/View/Type/PreviewGroup.swift b/Stitch/Graph/PrototypePreview/Layer/View/Type/PreviewGroup.swift index aecdf9c6a..9eb14cf91 100644 --- a/Stitch/Graph/PrototypePreview/Layer/View/Type/PreviewGroup.swift +++ b/Stitch/Graph/PrototypePreview/Layer/View/Type/PreviewGroup.swift @@ -121,8 +121,6 @@ struct PreviewGroupLayer: View { var body: some View { - logInView("PreviewGroupLayer: var body") - groupLayer // TODO: add "child alignment" input on Group Layer node? or find some other solution for how a group with an orientation can position children that have static sizes @@ -242,30 +240,11 @@ struct PreviewGroupLayer: View { size: _size, parentSize: parentSize, minimumDragDistance: DEFAULT_MINIMUM_DRAG_DISTANCE)) - -// .modifier(NativeScrollGestureView( -// layerViewModel: layerViewModel, -// graph: graph)) } @ViewBuilder private var groupLayer: some View { - logInView("PreviewGroupLayer: groupLayer") -// PreviewLayersView(document: document, -// graph: graph, -// layers: layersInGroup, -// // This Group's size will be the `parentSize` for the `layersInGroup` -// parentSize: _size, -// parentId: interactiveLayer.id.layerNodeId, -// parentOrientation: orientation, -// parentSpacing: spacing, -// parentCornerRadius: cornerRadius, -// // i.e. if this view (a LayerGroup) uses .hug, then its children will not use their own .position values. -// parentUsesHug: usesHug, -// noFixedSizeForLayerGroup: noFixedSizeForLayerGroup, -// parentGridData: gridData, -// isGhostView: !isPinnedViewRendering) - + NativeScrollGestureView(layerViewModel: layerViewModel, graph: graph, isClipped: isClipped, diff --git a/Stitch/Graph/Sidebar/Util/ListModification/SidebarItemSelectionActions.swift b/Stitch/Graph/Sidebar/Util/ListModification/SidebarItemSelectionActions.swift index ad7c19e1f..618492f1d 100644 --- a/Stitch/Graph/Sidebar/Util/ListModification/SidebarItemSelectionActions.swift +++ b/Stitch/Graph/Sidebar/Util/ListModification/SidebarItemSelectionActions.swift @@ -15,15 +15,15 @@ extension ProjectSidebarObservable { func sidebarItemTapped(id: Self.ItemID, shiftHeld: Bool, commandHeld: Bool) { - log("sidebarItemTapped: id: \(id)") - log("sidebarItemTapped: shiftHeld: \(shiftHeld)") + // log("sidebarItemTapped: id: \(id)") + // log("sidebarItemTapped: shiftHeld: \(shiftHeld)") let originalSelections = self.selectionState.primary // Set sidebar to be focused: self.graphDelegate?.graphUI.isSidebarFocused = true - log("sidebarItemTapped: originalSelections: \(originalSelections)") + // log("sidebarItemTapped: originalSelections: \(originalSelections)") if shiftHeld, originalSelections.isEmpty { // Special case: if no current selections, shift-click just selects from the top to the clicked item; and the shift-clicked item counts as the 'last selected item' @@ -49,7 +49,7 @@ extension ProjectSidebarObservable { !originalSelections.isEmpty, let lastClickedItemId = self.selectionState.lastFocused { - log("sidebarItemTapped: shift select") + // log("sidebarItemTapped: shift select") guard let clickedItem = self.retrieveItem(id), let lastClickedItem = self.retrieveItem(lastClickedItemId) else { @@ -58,7 +58,7 @@ extension ProjectSidebarObservable { return } - log("sidebarItemTapped: lastClickedItemId: \(lastClickedItemId)") + // log("sidebarItemTapped: lastClickedItemId: \(lastClickedItemId)") let flatList = self.items.flattenedItems @@ -84,12 +84,12 @@ extension ProjectSidebarObservable { // If we ended up selecting the exact same as the original, // then we actually DE-SELECTED the range. let newSelections = self.selectionState.primary - log("sidebarItemTapped: selected range: newSelections: \(newSelections)") + // log("sidebarItemTapped: selected range: newSelections: \(newSelections)") if newSelections == originalSelections { - log("sidebarItemTapped: selected range; will wipe inspectorFocusedLayers") + // log("sidebarItemTapped: selected range; will wipe inspectorFocusedLayers") itemsBetween.forEach { itemBetween in - log("sidebarItemTapped: will remove item Between \(itemBetween)") + // log("sidebarItemTapped: will remove item Between \(itemBetween)") self.selectionState.primary.remove(itemBetween.id) } } @@ -99,11 +99,11 @@ extension ProjectSidebarObservable { self.graphDelegate?.deselectAllCanvasItems() } else { - log("sidebarItemTapped: did not have itemsBetween") + // log("sidebarItemTapped: did not have itemsBetween") // TODO: this can happen when just-clicked == last-clicked, but some apps do not any deselection etc. // If we shift click the last-clicked item, then remove everything in the island? if clickedItem.id == lastClickedItem.id { - log("clicked the same item as the last clicked; will deselect original island and select only last selected") + // log("clicked the same item as the last clicked; will deselect original island and select only last selected") originalIsland.forEach { self.selectionState.primary.remove($0.id) } @@ -122,7 +122,7 @@ extension ProjectSidebarObservable { else if commandHeld { - log("sidebarItemTapped: command select") + // log("sidebarItemTapped: command select") let alreadySelected = self.selectionState.primary.contains(id) @@ -141,7 +141,7 @@ extension ProjectSidebarObservable { } } else { - log("sidebarItemTapped: normal select") + // log("sidebarItemTapped: normal select") self.selectionState.resetEditModeSelections()