fix(mac): show critter menubar icon
This commit is contained in:
@@ -358,7 +358,7 @@ struct ClawdisApp: App {
|
|||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
SettingsRootView(state: state)
|
SettingsRootView(state: state)
|
||||||
.frame(minWidth: 520, minHeight: 420)
|
.frame(minWidth: 520, minHeight: 460)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,60 +456,71 @@ struct CritterGlyph: View {
|
|||||||
let w = geo.size.width
|
let w = geo.size.width
|
||||||
let h = geo.size.height
|
let h = geo.size.height
|
||||||
|
|
||||||
let bodyWidth = w * 0.78
|
let bodyWidth = w * 0.8
|
||||||
let bodyHeight = h * 0.58
|
let bodyHeight = h * 0.56
|
||||||
let bodyRect = CGRect(x: (w - bodyWidth) / 2, y: h * 0.18, width: bodyWidth, height: bodyHeight)
|
let bodyCorner = w * 0.08
|
||||||
|
let bodyY = h * 0.18
|
||||||
|
|
||||||
let armWidth = w * 0.2
|
let earWidth = w * 0.22
|
||||||
let armHeight = bodyHeight * 0.6
|
let earHeight = bodyHeight * 0.7
|
||||||
let armCorner = armWidth * 0.24
|
let earCorner = earWidth * 0.22
|
||||||
|
|
||||||
let legWidth = w * 0.11
|
let legWidth = w * 0.12
|
||||||
let legHeight = h * 0.26
|
let legHeight = h * 0.28
|
||||||
let legSpacing = w * 0.08
|
let legSpacing = w * 0.08
|
||||||
let legStartX = bodyRect.minX + w * 0.05
|
let legStartX = (w - (4 * legWidth + 3 * legSpacing)) / 2
|
||||||
let legY = bodyRect.maxY - legHeight * 0.2
|
let legY = bodyY + bodyHeight - legHeight * 0.18
|
||||||
|
|
||||||
let eyeOpen = max(0.02, 1 - blinkAmount)
|
let eyeOpen = max(0.08, 1 - blinkAmount)
|
||||||
let eyeWidth = bodyWidth * 0.18
|
let eyeWidth = bodyWidth * 0.18
|
||||||
let eyeHeight = bodyHeight * 0.22 * eyeOpen
|
let eyeHeight = bodyHeight * 0.24 * eyeOpen
|
||||||
let eyeY = bodyRect.midY - bodyHeight * 0.08
|
let eyeY = bodyY + bodyHeight * 0.45
|
||||||
let eyeOffset = bodyWidth * 0.2
|
let eyeOffset = bodyWidth * 0.22
|
||||||
|
|
||||||
Path { path in
|
ZStack {
|
||||||
path.addRoundedRect(in: bodyRect, cornerSize: CGSize(width: w * 0.08, height: w * 0.08))
|
RoundedRectangle(cornerRadius: bodyCorner, style: .continuous)
|
||||||
|
.frame(width: bodyWidth, height: bodyHeight)
|
||||||
|
.offset(y: bodyY)
|
||||||
|
|
||||||
path.addRoundedRect(
|
RoundedRectangle(cornerRadius: earCorner, style: .continuous)
|
||||||
in: CGRect(x: bodyRect.minX - armWidth * 0.65, y: bodyRect.midY - armHeight / 2, width: armWidth, height: armHeight),
|
.frame(width: earWidth, height: earHeight)
|
||||||
cornerSize: CGSize(width: armCorner, height: armCorner)
|
.offset(x: -bodyWidth * 0.5 + earWidth * 0.35, y: bodyY + bodyHeight * 0.02)
|
||||||
)
|
|
||||||
|
|
||||||
path.addRoundedRect(
|
RoundedRectangle(cornerRadius: earCorner, style: .continuous)
|
||||||
in: CGRect(x: bodyRect.maxX - armWidth * 0.35, y: bodyRect.midY - armHeight / 2, width: armWidth, height: armHeight),
|
.frame(width: earWidth, height: earHeight)
|
||||||
cornerSize: CGSize(width: armCorner, height: armCorner)
|
.offset(x: bodyWidth * 0.5 - earWidth * 0.35, y: bodyY + bodyHeight * 0.02)
|
||||||
)
|
|
||||||
|
|
||||||
for i in 0 ..< 4 {
|
Path { path in
|
||||||
let x = legStartX + CGFloat(i) * (legWidth + legSpacing)
|
for i in 0 ..< 4 {
|
||||||
path.addRoundedRect(
|
let x = legStartX + CGFloat(i) * (legWidth + legSpacing)
|
||||||
in: CGRect(x: x, y: legY, width: legWidth, height: legHeight),
|
path.addRoundedRect(
|
||||||
cornerSize: CGSize(width: legWidth * 0.35, height: legWidth * 0.35)
|
in: CGRect(x: x, y: legY, width: legWidth, height: legHeight),
|
||||||
)
|
cornerSize: CGSize(width: legWidth * 0.36, height: legWidth * 0.36)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.fill()
|
||||||
|
|
||||||
let leftEyeX = bodyRect.midX - eyeOffset
|
ZStack {
|
||||||
path.move(to: CGPoint(x: leftEyeX - eyeWidth / 2, y: eyeY - eyeHeight))
|
Path { path in
|
||||||
path.addLine(to: CGPoint(x: leftEyeX + eyeWidth / 2, y: eyeY))
|
let centerX = w / 2 - eyeOffset
|
||||||
path.addLine(to: CGPoint(x: leftEyeX - eyeWidth / 2, y: eyeY + eyeHeight))
|
path.move(to: CGPoint(x: centerX - eyeWidth / 2, y: eyeY - eyeHeight))
|
||||||
path.closeSubpath()
|
path.addLine(to: CGPoint(x: centerX + eyeWidth / 2, y: eyeY))
|
||||||
|
path.addLine(to: CGPoint(x: centerX - eyeWidth / 2, y: eyeY + eyeHeight))
|
||||||
let rightEyeX = bodyRect.midX + eyeOffset
|
path.closeSubpath()
|
||||||
path.move(to: CGPoint(x: rightEyeX + eyeWidth / 2, y: eyeY - eyeHeight))
|
}
|
||||||
path.addLine(to: CGPoint(x: rightEyeX - eyeWidth / 2, y: eyeY))
|
Path { path in
|
||||||
path.addLine(to: CGPoint(x: rightEyeX + eyeWidth / 2, y: eyeY + eyeHeight))
|
let centerX = w / 2 + eyeOffset
|
||||||
path.closeSubpath()
|
path.move(to: CGPoint(x: centerX + eyeWidth / 2, y: eyeY - eyeHeight))
|
||||||
|
path.addLine(to: CGPoint(x: centerX - eyeWidth / 2, y: eyeY))
|
||||||
|
path.addLine(to: CGPoint(x: centerX + eyeWidth / 2, y: eyeY + eyeHeight))
|
||||||
|
path.closeSubpath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.compositingGroup()
|
||||||
|
.blendMode(.destinationOut)
|
||||||
}
|
}
|
||||||
.fill(style: FillStyle(eoFill: true, antialiased: true))
|
.compositingGroup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,31 +563,24 @@ struct SettingsRootView: View {
|
|||||||
@State private var selectedTab: SettingsTab = .general
|
@State private var selectedTab: SettingsTab = .general
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
TabView(selection: $selectedTab) {
|
||||||
Picker("", selection: $selectedTab) {
|
GeneralSettings(state: state)
|
||||||
ForEach(SettingsTab.allCases, id: \.self) { tab in
|
.tabItem { Label("General", systemImage: "gearshape") }
|
||||||
Text(tab.title).tag(tab)
|
.tag(SettingsTab.general)
|
||||||
}
|
|
||||||
}
|
|
||||||
.pickerStyle(.segmented)
|
|
||||||
.padding([.top, .horizontal])
|
|
||||||
|
|
||||||
Divider()
|
PermissionsSettings(status: permStatus, refresh: refreshPerms, showOnboarding: { OnboardingController.shared.show() })
|
||||||
|
.tabItem { Label("Permissions", systemImage: "lock.shield") }
|
||||||
|
.tag(SettingsTab.permissions)
|
||||||
|
|
||||||
Group {
|
DebugSettings()
|
||||||
switch selectedTab {
|
.tabItem { Label("Debug", systemImage: "ant") }
|
||||||
case .general:
|
.tag(SettingsTab.debug)
|
||||||
GeneralSettings(state: state)
|
|
||||||
case .permissions:
|
AboutSettings()
|
||||||
PermissionsSettings(status: permStatus, refresh: refreshPerms, showOnboarding: { OnboardingController.shared.show() })
|
.tabItem { Label("About", systemImage: "info.circle") }
|
||||||
case .debug:
|
.tag(SettingsTab.about)
|
||||||
DebugSettings()
|
|
||||||
case .about:
|
|
||||||
AboutSettings()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(16)
|
|
||||||
}
|
}
|
||||||
|
.padding(12)
|
||||||
.task { await refreshPerms() }
|
.task { await refreshPerms() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,6 +607,8 @@ enum SettingsTab: CaseIterable {
|
|||||||
|
|
||||||
struct GeneralSettings: View {
|
struct GeneralSettings: View {
|
||||||
@ObservedObject var state: AppState
|
@ObservedObject var state: AppState
|
||||||
|
@State private var isInstallingCLI = false
|
||||||
|
@State private var cliStatus: String?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
@@ -624,7 +630,69 @@ struct GeneralSettings: View {
|
|||||||
.labelsHidden()
|
.labelsHidden()
|
||||||
.frame(width: 140)
|
.frame(width: 140)
|
||||||
}
|
}
|
||||||
|
Divider().padding(.vertical, 6)
|
||||||
|
cliInstaller
|
||||||
Spacer()
|
Spacer()
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Button("Quit Clawdis") { NSApp.terminate(nil) }
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cliInstaller: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
Button {
|
||||||
|
Task { await installCLI() }
|
||||||
|
} label: {
|
||||||
|
if isInstallingCLI {
|
||||||
|
ProgressView().controlSize(.small)
|
||||||
|
} else {
|
||||||
|
Text("Install CLI helper")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(isInstallingCLI)
|
||||||
|
|
||||||
|
if let status = cliStatus {
|
||||||
|
Text(status)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.lineLimit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text("Symlink \"clawdis-mac\" into /usr/local/bin and /opt/homebrew/bin for scripts.")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func installCLI() async {
|
||||||
|
guard !isInstallingCLI else { return }
|
||||||
|
isInstallingCLI = true
|
||||||
|
defer { isInstallingCLI = false }
|
||||||
|
|
||||||
|
let helper = Bundle.main.bundleURL.appendingPathComponent("Contents/MacOS/ClawdisCLI")
|
||||||
|
guard FileManager.default.isExecutableFile(atPath: helper.path) else {
|
||||||
|
await MainActor.run { cliStatus = "Helper missing in bundle; rebuild via scripts/package-mac-app.sh" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let targets = ["/usr/local/bin/clawdis-mac", "/opt/homebrew/bin/clawdis-mac"]
|
||||||
|
var messages: [String] = []
|
||||||
|
for target in targets {
|
||||||
|
do {
|
||||||
|
try FileManager.default.createDirectory(atPath: (target as NSString).deletingLastPathComponent, withIntermediateDirectories: true)
|
||||||
|
try? FileManager.default.removeItem(atPath: target)
|
||||||
|
try FileManager.default.createSymbolicLink(atPath: target, withDestinationPath: helper.path)
|
||||||
|
messages.append("Linked \(target)")
|
||||||
|
} catch {
|
||||||
|
messages.append("Failed \(target): \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await MainActor.run {
|
||||||
|
cliStatus = messages.joined(separator: "; ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user