Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ public enum TerminalInputAccessoryAction: Int, CaseIterable, Sendable {
return String(localized: "terminal.input_accessory.zoom_in", defaultValue: "Zoom In")
case .paste:
return String(localized: "terminal.input_accessory.paste", defaultValue: "Paste")
case .upArrow:
return String(localized: "terminal.input_accessory.up_arrow", defaultValue: "Up Arrow")
case .downArrow:
return String(localized: "terminal.input_accessory.down_arrow", defaultValue: "Down Arrow")
case .leftArrow:
return String(localized: "terminal.input_accessory.left_arrow", defaultValue: "Left Arrow")
case .rightArrow:
return String(localized: "terminal.input_accessory.right_arrow", defaultValue: "Right Arrow")
default:
return nil
}
Expand All @@ -387,6 +395,14 @@ public enum TerminalInputAccessoryAction: Int, CaseIterable, Sendable {
return "plus.magnifyingglass"
case .paste:
return "doc.on.clipboard"
case .upArrow:
return "arrow.up"
case .downArrow:
return "arrow.down"
case .leftArrow:
return "arrow.left"
case .rightArrow:
return "arrow.right"
default:
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,21 @@ final class TerminalInputTextView: UITextView {
private static let monokaiBarColor = UIColor(red: 0x27/255.0, green: 0x28/255.0, blue: 0x22/255.0, alpha: 1)
private static let accessoryHorizontalInset: CGFloat = 16
private static let accessoryButtonFont = UIFont.systemFont(ofSize: 14, weight: .medium)
private static let accessoryButtonSymbolConfig = UIImage.SymbolConfiguration(pointSize: 14, weight: .medium)
private static let accessoryButtonContentInsets = NSDirectionalEdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10)
/// One shared SF Symbol config for every icon on the bar (paste, zoom,
/// arrows, settings, keyboard toggle) so all glyphs render at one size.
/// The point size sits just under the 14pt text font because an SF Symbol's
/// bounding box reads larger than text at the same size; 13pt keeps the
/// icons visually in line with the text keys instead of looming over them.
private static let accessoryButtonSymbolConfig = UIImage.SymbolConfiguration(pointSize: 13, weight: .medium)
/// One compact horizontal inset applied to every button so the bar reads
/// tight and uniform. Each button then hugs its label/icon plus this inset.
private static let accessoryButtonContentInsets = NSDirectionalEdgeInsets(top: 5, leading: 8, bottom: 5, trailing: 8)
private static let accessoryButtonCornerRadius: CGFloat = 6
private static let accessoryButtonHeight: CGFloat = 28
private static let accessoryButtonMinWidth: CGFloat = 44
/// Minimum (not fixed) button width. Buttons size to their intrinsic
/// content width and only floor here, which keeps a usable tap target while
/// letting short labels (Tab, Esc, ^C, ^D) and icons hug their content.
private static let accessoryButtonMinWidth: CGFloat = 34
private static let accessoryButtonNormalBackground = UIColor(white: 0.35, alpha: 1)
private var accessoryBackgroundLeadingConstraint: NSLayoutConstraint?
private var accessoryBackgroundTrailingConstraint: NSLayoutConstraint?
Expand All @@ -77,8 +87,7 @@ final class TerminalInputTextView: UITextView {

// Pinned keyboard dismiss button on the left
let dismissButton = UIButton(type: .system)
let dismissConfig = UIImage.SymbolConfiguration(pointSize: 16, weight: .medium)
dismissButton.setImage(UIImage(systemName: "keyboard.chevron.compact.down", withConfiguration: dismissConfig), for: .normal)
dismissButton.setImage(UIImage(systemName: "keyboard.chevron.compact.down", withConfiguration: Self.accessoryButtonSymbolConfig), for: .normal)
dismissButton.tintColor = UIColor(white: 0.7, alpha: 1)
dismissButton.addTarget(self, action: #selector(handleHideKeyboard), for: .touchUpInside)
dismissButton.accessibilityIdentifier = "terminal.inputAccessory.hideKeyboard"
Expand Down Expand Up @@ -422,9 +431,8 @@ final class TerminalInputTextView: UITextView {
/// glyphs, cross-dissolved, so it reads as a single keyboard toggle.
func setKeyboardShown(_ shown: Bool) {
guard let dismissButton else { return }
let config = UIImage.SymbolConfiguration(pointSize: 16, weight: .medium)
let symbol = shown ? "keyboard.chevron.compact.down" : "keyboard"
let image = UIImage(systemName: symbol, withConfiguration: config)
let image = UIImage(systemName: symbol, withConfiguration: Self.accessoryButtonSymbolConfig)
UIView.transition(with: dismissButton, duration: 0.2, options: .transitionCrossDissolve) {
dismissButton.setImage(image, for: .normal)
}
Expand Down Expand Up @@ -475,15 +483,10 @@ final class TerminalInputTextView: UITextView {
button.accessibilityLabel = action.accessibilityLabel
applyAccessoryButtonStyle(button, item: .builtin(action), armed: false, sticky: false)
button.heightAnchor.constraint(equalToConstant: Self.accessoryButtonHeight).isActive = true
if action.isModifier || action.symbolName != nil {
// Single-glyph modifiers (⌃⌥⌘⇧) and icon buttons (zoom) get a fixed
// width so they stay uniform — their glyph metrics differ, and a
// greater-than-or-equal min-width let some (e.g. the glass capsule)
// grow wider than others. Variable-text buttons keep growing.
button.widthAnchor.constraint(equalToConstant: Self.accessoryButtonMinWidth).isActive = true
} else {
button.widthAnchor.constraint(greaterThanOrEqualToConstant: Self.accessoryButtonMinWidth).isActive = true
}
// Every button hugs its intrinsic content (label/icon + the shared
// compact inset) and only floors at the tap-target minimum, so Tab/Esc/
// ^C/^D and the icon keys are all as narrow as their glyph allows.
button.widthAnchor.constraint(greaterThanOrEqualToConstant: Self.accessoryButtonMinWidth).isActive = true
return button
}

Expand All @@ -496,15 +499,9 @@ final class TerminalInputTextView: UITextView {
// Custom actions never arm; they always render in the resting style.
applyAccessoryButtonStyle(button, item: .custom(custom), armed: false, sticky: false)
button.heightAnchor.constraint(equalToConstant: Self.accessoryButtonHeight).isActive = true
if let symbolName = custom.symbolName,
!symbolName.isEmpty,
UIImage(systemName: symbolName) != nil {
// Icon-only custom actions match the fixed-width modifier/zoom keys.
button.widthAnchor.constraint(equalToConstant: Self.accessoryButtonMinWidth).isActive = true
} else {
// Text custom actions (e.g. "Claude") grow with their title.
button.widthAnchor.constraint(greaterThanOrEqualToConstant: Self.accessoryButtonMinWidth).isActive = true
}
// Custom actions hug their content like every other button: icon-only
// ones stay compact, text ones (e.g. "Claude") grow with their title.
button.widthAnchor.constraint(greaterThanOrEqualToConstant: Self.accessoryButtonMinWidth).isActive = true
return button
}

Expand All @@ -531,7 +528,8 @@ final class TerminalInputTextView: UITextView {
button.configuration = config
button.tintColor = UIColor(white: 0.7, alpha: 1)
button.heightAnchor.constraint(equalToConstant: Self.accessoryButtonHeight).isActive = true
button.widthAnchor.constraint(equalToConstant: Self.accessoryButtonMinWidth).isActive = true
// Hugs its icon plus the shared compact inset, floored at the tap target.
button.widthAnchor.constraint(greaterThanOrEqualToConstant: Self.accessoryButtonMinWidth).isActive = true
return button
}

Expand Down
68 changes: 68 additions & 0 deletions ios/cmux/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -3638,6 +3638,74 @@
}
}
}
},
"terminal.input_accessory.up_arrow": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Up Arrow"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "上矢印"
}
}
}
},
"terminal.input_accessory.down_arrow": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Down Arrow"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "下矢印"
}
}
}
},
"terminal.input_accessory.left_arrow": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Left Arrow"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "左矢印"
}
}
}
},
"terminal.input_accessory.right_arrow": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Right Arrow"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "右矢印"
}
}
}
}
},
"version": "1.0"
Expand Down
Loading