Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Sources/Visto/App/VistoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {

@objc private func openNewWindow(_ sender: Any?) {
let session = BrowserSession()
let window = NSWindow(
let window = VistoWindow(
contentRect: NSRect(origin: .zero, size: windowSize),
styleMask: [.borderless],
styleMask: [.titled, .closable, .miniaturizable, .utilityWindow],
backing: .buffered,
defer: false
)
Expand All @@ -40,6 +40,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
window.backgroundColor = .white
window.isOpaque = true
window.hasShadow = true
window.isFloatingPanel = false
window.hidesOnDeactivate = false
window.isMovableByWindowBackground = false
window.contentMinSize = windowSize
window.contentMaxSize = windowSize
window.isReleasedWhenClosed = false
Expand Down Expand Up @@ -140,6 +143,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
editMenu.addItem(menuItem(title: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x"))
editMenu.addItem(menuItem(title: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c"))
editMenu.addItem(menuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"))
editMenu.addItem(menuItem(title: "Delete", action: #selector(NSText.delete(_:)), keyEquivalent: ""))
editMenu.addItem(menuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"))
editMenuItem.submenu = editMenu
mainMenu.addItem(editMenuItem)
Expand Down
8 changes: 8 additions & 0 deletions Sources/Visto/Support/VistoWindow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import AppKit

/// Utility-style panel so Visto gets the standard compact title bar
/// (traffic lights + title) used by macOS utility apps.
final class VistoWindow: NSPanel {
override var canBecomeKey: Bool { true }
override var canBecomeMain: Bool { true }
}
51 changes: 5 additions & 46 deletions Sources/Visto/Views/BrowserChromeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,6 @@ struct BrowserChromeView: NSViewRepresentable {
}

final class BrowserChromeNSView: NSView, NSTextFieldDelegate {
private let closeButton = NSWindow.standardWindowButton(
.closeButton,
for: [.titled, .closable]
)!
private let minimizeButton = NSWindow.standardWindowButton(
.miniaturizeButton,
for: [.titled, .miniaturizable]
)!
private let zoomButton = NSWindow.standardWindowButton(
.zoomButton,
for: [.titled, .resizable]
)!
private let addressField = NSTextField()
private let refreshButton = NSButton()
private let separator = NSBox()
Expand All @@ -41,7 +29,6 @@ final class BrowserChromeNSView: NSView, NSTextFieldDelegate {
wantsLayer = true
layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor

configureWindowButtons()
configureAddressField()
configureRefreshButton()

Expand All @@ -60,33 +47,23 @@ final class BrowserChromeNSView: NSView, NSTextFieldDelegate {
override func layout() {
super.layout()

let buttonSize = NSSize(width: 14, height: 14)
let buttonY = floor((bounds.height - buttonSize.height) / 2)
closeButton.frame = NSRect(x: 12, y: buttonY, width: buttonSize.width, height: buttonSize.height)
minimizeButton.frame = NSRect(x: 32, y: buttonY, width: buttonSize.width, height: buttonSize.height)
zoomButton.frame = NSRect(x: 52, y: buttonY, width: buttonSize.width, height: buttonSize.height)

let controlHeight: CGFloat = 22
let controlY = floor((bounds.height - controlHeight) / 2)
let addressX: CGFloat = 78
let leading: CGFloat = 10
let refreshWidth: CGFloat = 24
let trailing: CGFloat = 7
let gap: CGFloat = 5
let refreshX = bounds.width - trailing - refreshWidth
addressField.frame = NSRect(
x: addressX,
x: leading,
y: controlY,
width: max(80, refreshX - gap - addressX),
width: max(80, refreshX - gap - leading),
height: controlHeight
)
refreshButton.frame = NSRect(x: refreshX, y: controlY, width: refreshWidth, height: controlHeight)
separator.frame = NSRect(x: 0, y: 0, width: bounds.width, height: 1)
}

override func mouseDown(with event: NSEvent) {
window?.performDrag(with: event)
}

func updateSession(_ session: BrowserSession) {
guard self.session !== session else {
return
Expand All @@ -101,22 +78,12 @@ final class BrowserChromeNSView: NSView, NSTextFieldDelegate {
session.inputURL = addressField.stringValue
}

private func configureWindowButtons() {
closeButton.target = self
closeButton.action = #selector(closeWindow)
minimizeButton.target = self
minimizeButton.action = #selector(minimizeWindow)
zoomButton.isEnabled = false

addSubview(closeButton)
addSubview(minimizeButton)
addSubview(zoomButton)
}

private func configureAddressField() {
addressField.placeholderString = "localhost:3000"
addressField.controlSize = .small
addressField.font = .systemFont(ofSize: 12)
addressField.isEditable = true
addressField.isSelectable = true
addressField.delegate = self
addressField.target = self
addressField.action = #selector(loadAddress)
Expand Down Expand Up @@ -161,14 +128,6 @@ final class BrowserChromeNSView: NSView, NSTextFieldDelegate {
}
}

@objc private func closeWindow() {
window?.close()
}

@objc private func minimizeWindow() {
window?.miniaturize(nil)
}

@objc private func loadAddress() {
session.inputURL = addressField.stringValue
session.loadInput()
Expand Down
14 changes: 13 additions & 1 deletion Sources/Visto/Views/WebViewContainer.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AppKit
import SwiftUI
import WebKit

Expand All @@ -17,7 +18,7 @@ struct WebViewContainer: NSViewRepresentable {
configuration.preferences.javaScriptCanOpenWindowsAutomatically = true
configuration.defaultWebpagePreferences.preferredContentMode = .mobile

let webView = WKWebView(frame: .zero, configuration: configuration)
let webView = FocusableWebView(frame: .zero, configuration: configuration)
webView.customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 26_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1"
webView.allowsBackForwardNavigationGestures = true
webView.navigationDelegate = context.coordinator
Expand Down Expand Up @@ -93,3 +94,14 @@ struct WebViewContainer: NSViewRepresentable {
var hardReloadToken = 0
}
}

/// Ensures clicks in the preview claim first responder so Delete and
/// Command-C/V reach page inputs through the AppKit responder chain.
private final class FocusableWebView: WKWebView {
override var acceptsFirstResponder: Bool { true }

override func mouseDown(with event: NSEvent) {
window?.makeFirstResponder(self)
super.mouseDown(with: event)
}
}