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
8 changes: 8 additions & 0 deletions Leader Key.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/* Begin PBXBuildFile section */
115AA5BF2DA521C600C17E18 /* ActionIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115AA5BE2DA521C200C17E18 /* ActionIcon.swift */; };
6D9B9C042DBA000000000002 /* KeyCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9B9C032DBA000000000002 /* KeyCapture.swift */; };
6D9B9C062DBA000000000003 /* ConfigEditorShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9B9C052DBA000000000003 /* ConfigEditorShared.swift */; };
115AA5C22DA546D500C17E18 /* SymbolPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 115AA5C12DA546D500C17E18 /* SymbolPicker */; };
130196C62D73B3DE0093148B /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 130196C52D73B3DC0093148B /* Breadcrumbs.swift */; };
423632222D68CA6500878D92 /* MysteryBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 423632212D68CA6500878D92 /* MysteryBox.swift */; };
Expand Down Expand Up @@ -106,6 +108,8 @@
605385A22D523CAD00BEDB4B /* Pulsate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pulsate.swift; sourceTree = "<group>"; };
606C56EE2DAB875A00198B9F /* Cheater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cheater.swift; sourceTree = "<group>"; };
6D9B9C002DBA000000000001 /* ConfigOutlineEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigOutlineEditorView.swift; sourceTree = "<group>"; };
6D9B9C032DBA000000000002 /* KeyCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCapture.swift; sourceTree = "<group>"; };
6D9B9C052DBA000000000003 /* ConfigEditorShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigEditorShared.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -241,6 +245,8 @@
6D9B9C002DBA000000000001 /* ConfigOutlineEditorView.swift */,
427C18372BD3262100955B98 /* VisualEffectBackground.swift */,
42F4CDCC2D45B13600D0DD76 /* KeyButton.swift */,
6D9B9C032DBA000000000002 /* KeyCapture.swift */,
6D9B9C052DBA000000000003 /* ConfigEditorShared.swift */,
115AA5BE2DA521C200C17E18 /* ActionIcon.swift */,
);
path = Views;
Expand Down Expand Up @@ -420,6 +426,8 @@
427C18282BD31E2E00955B98 /* GeneralPane.swift in Sources */,
423632282D6A806700878D92 /* Theme.swift in Sources */,
6D9B9C012DBA000000000001 /* ConfigOutlineEditorView.swift in Sources */,
6D9B9C042DBA000000000002 /* KeyCapture.swift in Sources */,
6D9B9C062DBA000000000003 /* ConfigEditorShared.swift in Sources */,
42F4CDD12D48C52400D0DD76 /* Extensions.swift in Sources */,
427C182F2BD3206200955B98 /* UserState.swift in Sources */,
427C18202BD31C3D00955B98 /* AppDelegate.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions Leader Key/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class AppDelegate: NSObject, NSApplicationDelegate,
AdvancedPane().environmentObject(self.config)
}),
],
style: .segmentedControl
style: .segmentedControl,
)

func applicationDidFinishLaunching(_: Notification) {
Expand Down Expand Up @@ -147,7 +147,7 @@ class AppDelegate: NSObject, NSApplicationDelegate,

// MARK: - Sparkle Gentle Reminders

var supportsGentleScheduledUpdateReminders: Bool {
@objc var supportsGentleScheduledUpdateReminders: Bool {
return true
}

Expand Down
18 changes: 16 additions & 2 deletions Leader Key/Controller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,15 +220,29 @@ class Controller {
return englishGlyph(for: event)
}

// 2. Use the system-translated character first.
// 2. For special keys like Enter, always use the mapped glyph
if let entry = KeyMaps.entry(for: event.keyCode) {
// For Enter, Space, Tab, arrows, etc. - use the glyph representation
if event.keyCode == KeyHelpers.enter.rawValue || event.keyCode == KeyHelpers.space.rawValue
|| event.keyCode == KeyHelpers.tab.rawValue
|| event.keyCode == KeyHelpers.leftArrow.rawValue
|| event.keyCode == KeyHelpers.rightArrow.rawValue
|| event.keyCode == KeyHelpers.upArrow.rawValue
|| event.keyCode == KeyHelpers.downArrow.rawValue
{
return entry.glyph
}
}

// 3. Use the system-translated character for regular keys.
if let printable = event.charactersIgnoringModifiers,
!printable.isEmpty,
printable.unicodeScalars.first?.isASCII ?? false
{
return printable // already contains correct case
}

// 3. For arrows, ␣, ⌫ … use map as last resort.
// 4. For arrows, ␣, ⌫ … use map as last resort.
return englishGlyph(for: event)
}

Expand Down
141 changes: 141 additions & 0 deletions Leader Key/Views/ConfigEditorShared.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import AppKit
import ObjectiveC

enum ConfigEditorUI {
static func setButtonTitle(_ button: NSButton, text: String, placeholder: Bool) {
let attr = NSMutableAttributedString(string: text)
let color: NSColor = placeholder ? .secondaryLabelColor : .labelColor
attr.addAttribute(
.foregroundColor, value: color, range: NSRange(location: 0, length: attr.length))
button.title = text
button.attributedTitle = attr
}

static func presentMoreMenu(
anchor: NSView?,
onDuplicate: @escaping () -> Void,
onDelete: @escaping () -> Void
) {
guard let anchor else { return }
let menu = NSMenu()
menu.addItem(
withTitle: "Duplicate",
action: #selector(MenuHandler.duplicate),
keyEquivalent: ""
)
menu.addItem(
withTitle: "Delete",
action: #selector(MenuHandler.delete),
keyEquivalent: ""
)
let handler = MenuHandler(onDuplicate: onDuplicate, onDelete: onDelete)
for item in menu.items { item.target = handler }
objc_setAssociatedObject(
menu,
&handlerAssociationKey,
handler,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
let point = NSPoint(x: 0, y: anchor.bounds.height)
menu.popUp(positioning: nil, at: point, in: anchor)
}

static func presentIconMenu(
anchor: NSView?,
onPickAppIcon: @escaping () -> Void,
onPickSymbol: @escaping () -> Void,
onClear: @escaping () -> Void
) {
guard let anchor else { return }
let menu = NSMenu()
menu.addItem(
withTitle: "App Icon…",
action: #selector(MenuHandler.pickAppIcon),
keyEquivalent: ""
)
menu.addItem(
withTitle: "Symbol…",
action: #selector(MenuHandler.pickSymbol),
keyEquivalent: ""
)
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: "Clear", action: #selector(MenuHandler.clearIcon), keyEquivalent: "")
let handler = MenuHandler(
onPickAppIcon: onPickAppIcon,
onPickSymbol: onPickSymbol,
onClearIcon: onClear,
onDuplicate: {},
onDelete: {}
)
for item in menu.items { item.target = handler }
objc_setAssociatedObject(
menu,
&handlerAssociationKey,
handler,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
let point = NSPoint(x: 0, y: anchor.bounds.height)
menu.popUp(positioning: nil, at: point, in: anchor)
}

private static var handlerAssociationKey: UInt8 = 0

private final class MenuHandler: NSObject {
let onPickAppIcon: (() -> Void)?
let onPickSymbol: (() -> Void)?
let onClearIcon: (() -> Void)?
let onDuplicate: () -> Void
let onDelete: () -> Void

init(
onPickAppIcon: (() -> Void)? = nil,
onPickSymbol: (() -> Void)? = nil,
onClearIcon: (() -> Void)? = nil,
onDuplicate: @escaping () -> Void,
onDelete: @escaping () -> Void
) {
self.onPickAppIcon = onPickAppIcon
self.onPickSymbol = onPickSymbol
self.onClearIcon = onClearIcon
self.onDuplicate = onDuplicate
self.onDelete = onDelete
}

@objc func pickAppIcon() { onPickAppIcon?() }
@objc func pickSymbol() { onPickSymbol?() }
@objc func clearIcon() { onClearIcon?() }
@objc func duplicate() { onDuplicate() }
@objc func delete() { onDelete() }
}
}

extension Action {
func resolvedIcon() -> NSImage? {
if let iconPath = iconPath, !iconPath.isEmpty {
if iconPath.hasSuffix(".app") { return NSWorkspace.shared.icon(forFile: iconPath) }
if let img = NSImage(systemSymbolName: iconPath, accessibilityDescription: nil) { return img }
}
switch type {
case .application:
return NSWorkspace.shared.icon(forFile: value)
case .url:
return NSImage(systemSymbolName: "link", accessibilityDescription: nil)
case .command:
return NSImage(systemSymbolName: "terminal", accessibilityDescription: nil)
case .folder:
return NSImage(systemSymbolName: "folder", accessibilityDescription: nil)
default:
return NSImage(systemSymbolName: "questionmark", accessibilityDescription: nil)
}
}
}

extension Group {
func resolvedIcon() -> NSImage? {
if let iconPath = iconPath, !iconPath.isEmpty {
if iconPath.hasSuffix(".app") { return NSWorkspace.shared.icon(forFile: iconPath) }
if let img = NSImage(systemSymbolName: iconPath, accessibilityDescription: nil) { return img }
}
return NSImage(systemSymbolName: "folder", accessibilityDescription: nil)
}
}
Loading