Skip to content
Merged
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
22 changes: 11 additions & 11 deletions Plugins/AutoInput/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -692,17 +692,17 @@
"settings.hud.position.at-pointer.help" : {
"extractionState" : "manual",
"localizations" : {
"ar" : { "stringUnit" : { "state" : "translated", "value" : "يضع شاشة العرض مباشرةً تحت مؤشر الماوس للنقر والتبديل فورًا." } },
"de" : { "stringUnit" : { "state" : "translated", "value" : "Platziert das HUD direkt unter dem Zeiger, um sofort per Klick zu wechseln." } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Places the HUD directly beneath the pointer for immediate click-to-switch." } },
"es" : { "stringUnit" : { "state" : "translated", "value" : "Coloca el indicador justo debajo del puntero para cambiar con un clic inmediato." } },
"fr" : { "stringUnit" : { "state" : "translated", "value" : "Place l’indicateur directement sous le pointeur pour changer immédiatement d’un clic." } },
"ja" : { "stringUnit" : { "state" : "translated", "value" : "すぐクリックして切り替えられるよう、ポインタの直下に表示します。" } },
"ko" : { "stringUnit" : { "state" : "translated", "value" : "바로 클릭해 전환할 수 있도록 포인터 바로 아래에 표시합니다." } },
"pt" : { "stringUnit" : { "state" : "translated", "value" : "Posiciona o indicador diretamente sob o cursor para alternar imediatamente com um clique." } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Размещает индикатор прямо под указателем для мгновенного переключения нажатием." } },
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "将提示直接放在指针下方,便于立即点击切换。" } },
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "將提示直接放在指標下方,便於立即按一下切換。" } }
"ar" : { "stringUnit" : { "state" : "translated", "value" : "يضع شاشة العرض أعلى المؤشر وإلى يساره، بحيث يبقى المؤشر عند الزاوية السفلية اليمنى ويظل النص ظاهرًا." } },
"de" : { "stringUnit" : { "state" : "translated", "value" : "Platziert das HUD oberhalb und links vom Zeiger, sodass der Zeiger an der unteren rechten Ecke liegt und der Text sichtbar bleibt." } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Places the HUD above and to the left of the pointer, with the pointer at its bottom-right corner so the text remains visible." } },
"es" : { "stringUnit" : { "state" : "translated", "value" : "Coloca el indicador arriba y a la izquierda del puntero, con el puntero en la esquina inferior derecha para que el texto siga visible." } },
"fr" : { "stringUnit" : { "state" : "translated", "value" : "Place l’indicateur au-dessus et à gauche du pointeur, avec le pointeur dans son coin inférieur droit pour que le texte reste visible." } },
"ja" : { "stringUnit" : { "state" : "translated", "value" : "ポインタの左上に表示し、ポインタが右下隅に来るようにしてテキストを見やすくします。" } },
"ko" : { "stringUnit" : { "state" : "translated", "value" : "포인터의 왼쪽 위에 표시하고 포인터가 오른쪽 아래 모서리에 오도록 하여 텍스트가 잘 보이게 합니다." } },
"pt" : { "stringUnit" : { "state" : "translated", "value" : "Posiciona o indicador acima e à esquerda do cursor, com o cursor no canto inferior direito para manter o texto visível." } },
"ru" : { "stringUnit" : { "state" : "translated", "value" : "Размещает индикатор выше и левее указателя, оставляя указатель в правом нижнем углу, чтобы текст был виден." } },
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "将提示放在指针左上方,指针停在右下角,避免遮挡文字。" } },
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "將提示放在指標左上方,指標停在右下角,避免遮擋文字。" } }
}
},
"settings.hud.position.at-pointer.selected-help" : {
Expand Down
2 changes: 1 addition & 1 deletion Plugins/AutoInput/Sources/AutoInputSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ struct AutoInputSettingsView: View {
private var atPointerPlacementHelp: String {
localization.string(
"settings.hud.position.at-pointer.help",
defaultValue: "将提示直接放在指针下方,便于立即点击切换。"
defaultValue: "将提示放在指针左上方,指针停在右下角,避免遮挡文字。"
)
}

Expand Down
30 changes: 21 additions & 9 deletions Plugins/AutoInput/Sources/InputSourceHUDController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ final class InputSourceHUDController: InputSourceHUDPresenting {
panelSize: panelSize,
visibleFrames: placementFrames,
position: effectivePosition,
pointerLocation: capturedPointerLocation
pointerLocation: capturedPointerLocation,
pointerInset: Self.pointerHitInset(for: configuration.size)
)
panel.setFrame(frame, display: true)
panel.setAccessibilityLabel(
Expand Down Expand Up @@ -363,13 +364,15 @@ final class InputSourceHUDController: InputSourceHUDPresenting {
panelSize: CGSize,
visibleFrames: [CGRect],
position: AutoInputHUDPosition = .automatic,
pointerLocation: CGPoint? = nil
pointerLocation: CGPoint? = nil,
pointerInset: CGFloat = 12
) -> CGRect {
if position == .atPointer, let pointerLocation {
return panelFrame(
at: pointerLocation,
panelSize: panelSize,
displayFrames: visibleFrames
displayFrames: visibleFrames,
pointerInset: pointerInset
)
}

Expand Down Expand Up @@ -468,30 +471,31 @@ final class InputSourceHUDController: InputSourceHUDPresenting {
static func panelFrame(
at pointerLocation: CGPoint,
panelSize: CGSize,
displayFrames: [CGRect]
displayFrames: [CGRect],
pointerInset: CGFloat = 12
) -> CGRect {
guard let displayFrame = matchingVisibleFrame(
for: CGRect(origin: pointerLocation, size: CGSize(width: 1, height: 1)),
visibleFrames: displayFrames
) else {
return CGRect(
x: pointerLocation.x - panelSize.width / 2,
y: pointerLocation.y - panelSize.height + pointerHitInset,
x: pointerLocation.x - panelSize.width + pointerInset,
y: pointerLocation.y - pointerInset,
width: panelSize.width,
height: panelSize.height
).integral
}

let minX = displayFrame.minX
let maxX = displayFrame.maxX - panelSize.width
let preferredX = pointerLocation.x - panelSize.width / 2
let preferredX = pointerLocation.x - panelSize.width + pointerInset
let x = maxX >= minX
? min(max(preferredX, minX), maxX)
: displayFrame.midX - panelSize.width / 2

let minY = displayFrame.minY
let maxY = displayFrame.maxY - panelSize.height
let preferredY = pointerLocation.y - panelSize.height + pointerHitInset
let preferredY = pointerLocation.y - pointerInset
let y = maxY >= minY
? min(max(preferredY, minY), maxY)
: displayFrame.midY - panelSize.height / 2
Expand Down Expand Up @@ -579,7 +583,15 @@ final class InputSourceHUDController: InputSourceHUDPresenting {
}

private static let displayMargin: CGFloat = 10
private static let pointerHitInset: CGFloat = 8

private static func pointerHitInset(for size: AutoInputHUDSize) -> CGFloat {
switch size {
case .compact:
10
case .standard, .large:
12
}
}

private static func matchingVisibleFrame(
for focusedFrame: CGRect,
Expand Down
38 changes: 24 additions & 14 deletions Plugins/AutoInput/Tests/AutoInputPluginTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2067,21 +2067,31 @@ final class InputSourceHUDControllerTests: XCTestCase {
defer { controller.dismiss() }
let focusedFrame = CGRect(x: 100, y: 200, width: 300, height: 24)

controller.show(
label: InputSourceHUDLabel(title: "ABC", modeIndicator: nil),
near: focusedFrame,
avoiding: focusedFrame,
configuration: AutoInputHUDConfiguration(
size: .standard,
position: .atPointer,
isInteractive: true
),
presentationID: AutoInputHUDPresentationID()
)
let sizeInsets: [(AutoInputHUDSize, CGFloat)] = [
(.compact, 10),
(.standard, 12),
(.large, 12),
]
var presentedFrame = CGRect.zero

for (size, expectedInset) in sizeInsets {
controller.show(
label: InputSourceHUDLabel(title: "ABC", modeIndicator: nil),
near: focusedFrame,
avoiding: focusedFrame,
configuration: AutoInputHUDConfiguration(
size: size,
position: .atPointer,
isInteractive: true
),
presentationID: AutoInputHUDPresentationID()
)

let presentedFrame = try XCTUnwrap(controller.presentedPanelForTests).frame
XCTAssertTrue(presentedFrame.contains(pointerLocation))
XCTAssertEqual(presentedFrame.maxY, pointerLocation.y + 8)
presentedFrame = try XCTUnwrap(controller.presentedPanelForTests).frame
XCTAssertTrue(presentedFrame.contains(pointerLocation))
XCTAssertEqual(presentedFrame.maxX, pointerLocation.x + expectedInset)
XCTAssertEqual(presentedFrame.minY, pointerLocation.y - expectedInset)
}

pointerLocation = CGPoint(x: 900, y: 200)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
| Saved Scripts | Keep AppleScript, zsh, Bash, and POSIX shell scripts in a searchable library with direct Run/Stop controls, a slider or numeric timeout editor, captured output, and running/recent-completion Feature Panel indicators, or run the first scripts without configuring Automation or Action Grid. Every script also becomes a stable MacTools action for optional shortcuts, gestures, grids, workflows, and Run Links; a per-script switch controls whether other MacTools features ask before running it. Scripts use explicit interpreters, bounded timeouts and output, no implicit `sudo`, and opt-in source backup. |
| Apple Shortcuts | Discover every current Apple Shortcut through macOS's supported Shortcuts interfaces. Each discovered shortcut is immediately available to Unified Search, global shortcuts, Action Grid, Trackpad Gestures, Automation, and confirmed Run Links. MacTools shows the shortcut's native color and the icon supplied by Shortcuts when Automation permission is available, provides bounded Run/Stop controls and captured text output, and never reads or modifies private Shortcuts data. |
| Run Links | Copy stable `mactools://app/actions/...` links for eligible actions and workflows to use from launchers, Apple Shortcuts, scripts, or documents with normal safety checks. Parameter presets remain an internal compatibility detail instead of a second user-facing link type. |
| Auto Input | Remember and restore input sources per app, assign fixed sources, switch directly with global shortcuts, and optionally show the current source whenever an editable field gains focus—including fixed-source apps, accessory palettes such as Raycast, and MacTools' own Settings and global command searches. The transient HUD anchors near the caret without covering the full input control or, when interactive, directly beneath the pointer; it stays visible while hovered for repeated-click source cycling. An optional quieter mode keeps first-focus and input-source-change prompts immediate, then reminds again after either a configurable interval or number of app switches. |
| Auto Input | Remember and restore input sources per app, assign fixed sources, switch directly with global shortcuts, and optionally show the current source whenever an editable field gains focus—including fixed-source apps, accessory palettes such as Raycast, and MacTools' own Settings and global command searches. The transient HUD anchors near the caret without covering the full input control or, when interactive, above and to the left of the pointer so the pointer rests at its bottom-right corner; it stays visible while hovered for repeated-click source cycling. An optional quieter mode keeps first-focus and input-source-change prompts immediate, then reminds again after either a configurable interval or number of app switches. |
| Launchpad | Summon an app grid in fullscreen or a compact window, with instant search, horizontal paging, keyboard navigation, drag-to-stack folders with inline rename (click an open folder's title, or right-click a folder to rename/dissolve), an adjustable glass background (clear/standard/deep presets or a custom material + dimming, with a live preview in settings), adjustable appearance (icon size 48–96 pt with rows/columns adapting, optional icons-only mode that hides app names, label appearance presets (color: automatic/light/dark/accent, weight, and a size tier that scales with the icon — shared by app names and the open-folder title), and a compact-window size slider — the compact panel now scales with the screen instead of capping at 960×680, so any display whose usable area exceeds ~1333×829 pt renders a larger panel than before, including modern built-in laptop screens (~13% on a 14″ MacBook Pro); all previewed live by a layout thumbnail in settings that mirrors the real grid math), a global shortcut, and IME-composition safety. |
| Finder Right Click | Add Finder context menu actions: new folder, new empty files for common text, data, web, and script extensions (.txt, .md, .csv, .json, .yaml, .yml, .xml, .html, .css, .js, .ts, .sh, and .py), open in Terminal, open with a configurable app list, copy selected item names, and copy absolute / relative / shell-escaped paths / file:// URLs for selected items or the current folder when clicking the background — each item toggleable in Settings. |
| Lock Screen | Lock the screen immediately, equivalent to Cmd+Ctrl+Q. |
Expand Down
2 changes: 1 addition & 1 deletion changes/unreleased/auto-input-at-pointer-hud.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ release: plugin
type: added
---

Auto Input's interactive HUD can now appear beneath the pointer for immediate click-to-switch access.
Auto Input's interactive HUD can now appear above and to the left of the pointer, keeping its text visible while preserving immediate click-to-switch access.
Loading