Port the remaining tabs to the macOS client
This commit is contained in:
@@ -227,13 +227,14 @@ private struct WebButton: View {
|
||||
@ViewState private var hover = false
|
||||
|
||||
var body: some View {
|
||||
let fill = danger ? Palette.danger : Palette.accent
|
||||
configuration.label
|
||||
.font(Fonts.font(13, weight: primary ? 700 : 400))
|
||||
.foregroundStyle(primary ? Palette.bg : danger ? Palette.danger : Palette.text)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 4)
|
||||
.background(primary ? Palette.accent : Palette.raised, in: RoundedRectangle(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).stroke(primary ? Palette.accent : hover && enabled ? Palette.muted : Palette.border))
|
||||
.background(primary ? fill : Palette.raised, in: RoundedRectangle(cornerRadius: 6))
|
||||
.overlay(RoundedRectangle(cornerRadius: 6).stroke(primary ? fill : hover && enabled ? Palette.muted : Palette.border))
|
||||
.opacity(enabled ? (configuration.isPressed ? 0.8 : 1) : 0.45)
|
||||
.contentShape(Rectangle())
|
||||
.onHover { hover = $0 }
|
||||
@@ -269,6 +270,142 @@ struct FieldStyle: ViewModifier {
|
||||
}
|
||||
}
|
||||
|
||||
struct HeaderBar: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 8)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.overlay(alignment: .bottom) {
|
||||
Rectangle().fill(Palette.border).frame(height: 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RangeTabButton: View {
|
||||
let label: String
|
||||
let active: Bool
|
||||
var help = ""
|
||||
let action: () -> Void
|
||||
@ViewState private var hover = false
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
Text(label)
|
||||
.font(Fonts.font(12))
|
||||
.foregroundStyle(active || hover ? Palette.text : Palette.muted)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.top, 2)
|
||||
.padding(.bottom, 3)
|
||||
.overlay(alignment: .bottom) {
|
||||
Rectangle().fill(active ? Palette.accent : .clear).frame(height: 2)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help(help)
|
||||
.onHover { hover = $0 }
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyText: View {
|
||||
let text: String
|
||||
var error = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(text)
|
||||
.foregroundStyle(error ? Palette.danger : Palette.muted)
|
||||
.multilineTextAlignment(.center)
|
||||
.textSelection(.enabled)
|
||||
.padding(32)
|
||||
.frame(maxWidth: .infinity)
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
struct StateDot: View {
|
||||
let filled: Color?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let filled {
|
||||
Circle().fill(filled)
|
||||
} else {
|
||||
Circle().strokeBorder(Palette.muted, lineWidth: 1.5)
|
||||
}
|
||||
}
|
||||
.frame(width: 8, height: 8)
|
||||
}
|
||||
}
|
||||
|
||||
struct StateMark: View {
|
||||
let state: TaskState
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch state.kind {
|
||||
case .working, .merging:
|
||||
Spinner(color: color)
|
||||
case .answer:
|
||||
StateDot(filled: Palette.warn)
|
||||
case .error:
|
||||
StateDot(filled: Palette.danger)
|
||||
default:
|
||||
StateDot(filled: nil)
|
||||
}
|
||||
}
|
||||
.help(state.title)
|
||||
}
|
||||
}
|
||||
|
||||
enum PatchLine {
|
||||
case hunk, add, del, meta, ctx
|
||||
|
||||
init(_ l: String) {
|
||||
self = l.hasPrefix("@@") ? .hunk : l.hasPrefix("+") ? .add : l.hasPrefix("-") ? .del : l.hasPrefix("\\") ? .meta : .ctx
|
||||
}
|
||||
|
||||
var background: Color {
|
||||
switch self {
|
||||
case .hunk: Palette.surface
|
||||
case .add: Palette.ok.opacity(0.14)
|
||||
case .del: Palette.danger.opacity(0.14)
|
||||
default: .clear
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
self == .hunk || self == .meta ? Palette.muted : Palette.text
|
||||
}
|
||||
}
|
||||
|
||||
struct StretchColumn: Layout {
|
||||
var minWidth: CGFloat = 0
|
||||
|
||||
private func width(_ proposal: ProposedViewSize, _ subviews: Subviews) -> CGFloat {
|
||||
max(minWidth, proposal.width ?? 0, subviews.map { $0.sizeThatFits(.unspecified).width }.max() ?? 0)
|
||||
}
|
||||
|
||||
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
|
||||
let w = width(ProposedViewSize(width: nil, height: nil), subviews)
|
||||
let h = subviews.reduce(0) { $0 + $1.sizeThatFits(ProposedViewSize(width: w, height: nil)).height }
|
||||
return CGSize(width: w, height: h)
|
||||
}
|
||||
|
||||
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
||||
var y = bounds.minY
|
||||
for sub in subviews {
|
||||
let h = sub.sizeThatFits(ProposedViewSize(width: bounds.width, height: nil)).height
|
||||
sub.place(at: CGPoint(x: bounds.minX, y: y), proposal: ProposedViewSize(width: bounds.width, height: h))
|
||||
y += h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TrailingBubble: Layout {
|
||||
var fraction: CGFloat = 0.85
|
||||
|
||||
|
||||
Reference in New Issue
Block a user