// Copyright (C) 2024 Rubén Beltrán del Río // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see https://map.tranquil.systems. import AppKit class HoverView: NSView { weak var controller: MapTextEditorController? private let action: () -> Void init( message: String, action: @escaping () -> Void, actionLabel: String = String(localized: "map_editor.add_missing_component"), controller: MapTextEditorController ) { self.controller = controller self.action = action super.init(frame: .zero) setupView(message: message, actionLabel: actionLabel) setupTrackingArea() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupView(message: String, actionLabel: String) { let box = NSBox() box.boxType = .custom box.cornerRadius = 8 box.borderColor = NSColor.separatorColor box.fillColor = NSColor.controlBackgroundColor addSubview(box) box.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ box.topAnchor.constraint(equalTo: topAnchor), box.leadingAnchor.constraint(equalTo: leadingAnchor), box.trailingAnchor.constraint(equalTo: trailingAnchor), box.bottomAnchor.constraint(equalTo: bottomAnchor) ]) let label = NSTextField(labelWithString: message) label.font = NSFont.Theme.SmallControl.regular label.textColor = NSColor.labelColor label.backgroundColor = .clear let button = NSButton(title: actionLabel, target: self, action: #selector(buttonClicked)) button.bezelStyle = .accessoryBarAction button.font = NSFont.Theme.SmallControl.emphasized button.controlSize = .mini // Configure button for hover and active states button.wantsLayer = true button.layer?.cornerRadius = 4 // Set up tracking area for button hover effects let buttonTrackingArea = NSTrackingArea( rect: button.bounds, options: [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect], owner: button, userInfo: nil ) button.addTrackingArea(buttonTrackingArea) addSubview(label) addSubview(button) label.translatesAutoresizingMaskIntoConstraints = false button.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ label.centerYAnchor.constraint(equalTo: centerYAnchor), label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8), button.firstBaselineAnchor.constraint(equalTo: label.firstBaselineAnchor), button.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 8), button.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), heightAnchor.constraint(equalToConstant: 32) ]) } private func setupTrackingArea() { let trackingArea = NSTrackingArea( rect: bounds, options: [.mouseEnteredAndExited, .mouseMoved, .activeInKeyWindow, .inVisibleRect], owner: self, userInfo: nil ) addTrackingArea(trackingArea) } @objc private func buttonClicked() { action() } override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) controller?.isHoveringHoverView = true controller?.hoverTimer?.invalidate() } override func mouseExited(with event: NSEvent) { super.mouseExited(with: event) controller?.isHoveringHoverView = false controller?.scheduleHideHoverView() } }