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
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,19 @@ struct EditorJumpBarComponent: View {
@State var selection: CEWorkspaceFile
@State var isHovering: Bool = false
@State var button = NSPopUpButton()
@Binding var truncatedCrumbWidth: CGFloat?

init(
fileItem: CEWorkspaceFile,
tappedOpenFile: @escaping (CEWorkspaceFile) -> Void,
isLastItem: Bool
isLastItem: Bool,
isTruncated: Binding<CGFloat?>
) {
self.fileItem = fileItem
self._selection = .init(wrappedValue: fileItem)
self.tappedOpenFile = tappedOpenFile
self.isLastItem = isLastItem
self._truncatedCrumbWidth = isTruncated
}

var siblings: [CEWorkspaceFile] {
Expand Down Expand Up @@ -65,6 +68,28 @@ struct EditorJumpBarComponent: View {

return button
}
.frame(
maxWidth: isHovering || isLastItem ? nil : truncatedCrumbWidth,
alignment: .leading
)
.mask(
LinearGradient(
gradient: Gradient(
stops: truncatedCrumbWidth == nil || isHovering ?
[
.init(color: .black, location: 0),
.init(color: .black, location: 1)
] : [
.init(color: .black, location: 0),
.init(color: .black, location: 0.8),
.init(color: .clear, location: 1)
]
),
startPoint: .leading,
endPoint: .trailing
)
)
.clipped()
.padding(.trailing, 11)
.background {
Color(nsColor: colorScheme == .dark ? .white : .black)
Expand All @@ -83,7 +108,9 @@ struct EditorJumpBarComponent: View {
}
.padding(.vertical, 3)
.onHover { hover in
isHovering = hover
withAnimation(.easeInOut(duration: 0.2)) {
isHovering = hover
}
}
.onLongPressGesture(minimumDuration: 0) {
button.performClick(nil)
Expand Down
112 changes: 95 additions & 17 deletions CodeEdit/Features/Editor/JumpBar/Views/EditorJumpBarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ struct EditorJumpBarView: View {

static let height = 28.0

@State private var textWidth: CGFloat = 0
@State private var containerWidth: CGFloat = 0
@State private var isTruncated: Bool = false
@State private var crumbWidth: CGFloat?
@State private var firstCrumbWidth: CGFloat?

init(
file: CEWorkspaceFile?,
shouldShowTabBar: Bool,
Expand All @@ -50,25 +56,61 @@ struct EditorJumpBarView: View {
}

var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
if file == nil {
Text("No Selection")
.font(.system(size: 11, weight: .regular))
.foregroundColor(
activeState != .inactive
? isActiveEditor ? .primary : .secondary
: Color(nsColor: .tertiaryLabelColor)
)
} else {
ForEach(fileItems, id: \.self) { fileItem in
EditorJumpBarComponent(
fileItem: fileItem,
tappedOpenFile: tappedOpenFile,
isLastItem: fileItems.last == fileItem
)
GeometryReader { containerProxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
if file == nil {
Text("No Selection")
.font(.system(size: 11, weight: .regular))
.foregroundColor(
activeState != .inactive
? isActiveEditor ? .primary : .secondary
: Color(nsColor: .tertiaryLabelColor)
)
.frame(maxHeight: .infinity)
} else {
ForEach(fileItems, id: \.self) { fileItem in
EditorJumpBarComponent(
fileItem: fileItem,
tappedOpenFile: tappedOpenFile,
isLastItem: fileItems.last == fileItem,
isTruncated: fileItems.first == fileItem ? $firstCrumbWidth : $crumbWidth
)
}

}
}
.background(
GeometryReader { proxy in
Color.clear
.onAppear {
if crumbWidth == nil {
textWidth = proxy.size.width
}
}
.onChange(of: proxy.size.width) { newValue in
if crumbWidth == nil {
textWidth = newValue
}
}
}
)
}
.onAppear {
containerWidth = containerProxy.size.width
}
.onChange(of: containerProxy.size.width) { newValue in
containerWidth = newValue
}
.onChange(of: textWidth) { _ in
withAnimation(.easeInOut(duration: 0.2)) {
resize()
}
}
.onChange(of: containerWidth) { _ in
withAnimation(.easeInOut(duration: 0.2)) {
resize()
}
}
}
.padding(.horizontal, shouldShowTabBar ? (file == nil ? 10 : 4) : 0)
Expand All @@ -86,4 +128,40 @@ struct EditorJumpBarView: View {
.opacity(activeState == .inactive ? 0.8 : 1.0)
.grayscale(isActiveEditor ? 0.0 : 1.0)
}

private func resize() {
let minWidth: CGFloat = 20
let snapThreshold: CGFloat = 30
let maxWidth: CGFloat = textWidth / CGFloat(fileItems.count)
let exponent: CGFloat = 5.0
var betweenWidth: CGFloat = 0.0

if textWidth >= containerWidth {
let scale = max(0, min(1, containerWidth / textWidth))
betweenWidth = floor((minWidth + (maxWidth - minWidth) * pow(scale, exponent)))
if betweenWidth < snapThreshold {
betweenWidth = minWidth
}
crumbWidth = betweenWidth
} else {
crumbWidth = nil
}

if betweenWidth > snapThreshold || crumbWidth == nil {
firstCrumbWidth = nil
} else {
let otherCrumbs = CGFloat(max(fileItems.count - 1, 1))
let usedWidth = otherCrumbs * snapThreshold

// Multiplier to reserve extra space for other crumbs in the jump bar.
// Increasing this value causes the first crumb to truncate sooner.
let crumbSpacingMultiplier: CGFloat = 1.5
let availableForFirst = containerWidth - usedWidth * crumbSpacingMultiplier
if availableForFirst < snapThreshold {
firstCrumbWidth = minWidth
} else {
firstCrumbWidth = availableForFirst
}
}
}
}