From 8aacd1f753c7ef21a7e1173cc08c5b0a85a94c6b Mon Sep 17 00:00:00 2001 From: Tommaso Negri Date: Wed, 14 Oct 2026 22:05:54 +0200 Subject: fix block interaction capture warning --- Sources/bar/Views/BlockView.swift | 78 +++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 23 deletions(-) (limited to 'Sources') diff --git a/Sources/bar/Views/BlockView.swift b/Sources/bar/Views/BlockView.swift index 00d36aa..a97c776 100644 --- a/Sources/bar/Views/BlockView.swift +++ b/Sources/bar/Views/BlockView.swift @@ -3,41 +3,73 @@ import SwiftUI struct BlockView: View { @ObservedObject var block: Block - @State private var hoveredCell: Cell? - @State private var eventsMonitor: Any? + @State private var interaction = BlockInteraction() var body: some View { HStack(spacing: 0) { - ForEach(block.cells) { cell in - CellView(cell: cell) + ForEach(block.cells.indices, id: \.self) { index in + CellView(cell: block.cells[index]) .onHover { hovering in if hovering { - hoveredCell = cell - } else if hoveredCell?.id == cell.id { - hoveredCell = nil + interaction.hoveredIndex = index + } else if interaction.hoveredIndex == index { + interaction.hoveredIndex = nil } } } } - .onAppear { - let monitor = NSEvent.addLocalMonitorForEvents(matching: [ - .leftMouseDown, - .rightMouseDown, - .otherMouseDown, - .scrollWheel, - ]) { [weak block] event in - guard let block else { return event } - return self.handle(event: event, block: block) - } - self.eventsMonitor = monitor as Any + .onAppear { [weak block, weak interaction] in + guard let block else { return } + interaction?.start(block: block) + } + .onDisappear { [weak interaction] in + interaction?.stop() } - .onDisappear { - if let eventsMonitor { - NSEvent.removeMonitor(eventsMonitor) - self.eventsMonitor = nil + .onChange(of: block.cells.count) { _, count in + if let index = interaction.hoveredIndex, index >= count { + interaction.hoveredIndex = nil } } } +} + +/// Per-view state: the application-owned monitor must not retain its owner +/// or the block. Disappearance removes it; destruction is a fallback when +/// a hosting view is discarded without a disappearance callback. +@MainActor +final class BlockInteraction { + var hoveredIndex: Int? + private(set) var eventsMonitor: Any? + + func start(block: Block) { + guard eventsMonitor == nil else { return } + eventsMonitor = NSEvent.addLocalMonitorForEvents(matching: [ + .leftMouseDown, + .rightMouseDown, + .otherMouseDown, + .scrollWheel, + ]) { [weak block, weak self] event in + guard let block, let self else { return event } + return self.handle(event: event, block: block) + } + } + + func stop() { + if let eventsMonitor { + NSEvent.removeMonitor(eventsMonitor) + self.eventsMonitor = nil + } + hoveredIndex = nil + } + + isolated deinit { + if let eventsMonitor { NSEvent.removeMonitor(eventsMonitor) } + } + + func hoveredCell(in block: Block) -> Cell? { + guard let index = hoveredIndex, block.cells.indices.contains(index) else { return nil } + return block.cells[index] + } /// Processes an event when a cell is hovered, re-running the script /// with the appropriate environment variable and the hovered cell's @@ -45,7 +77,7 @@ struct BlockView: View { /// /// - Returns: The original event so the system continues normal delivery. private func handle(event: NSEvent, block: Block) -> NSEvent { - guard let cell = hoveredCell else { return event } + guard let cell = hoveredCell(in: block) else { return event } switch event.type { case .leftMouseDown, .rightMouseDown, .otherMouseDown: -- cgit v1.2.3