From 40e805a785f257c2d5b21dd98ac7eccc327b1ca9 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sat, 7 Feb 2026 23:42:06 +0530 Subject: [PATCH] Hopefully fixed the Positioning --- DynamicIsland/Localizable.xcstrings | 12 ++ .../LockScreen/LockScreenWeatherWidget.swift | 129 +++++++++++++++++- .../LockScreenWeatherPanelManager.swift | 13 +- 3 files changed, 145 insertions(+), 9 deletions(-) diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index 98d2d64..43742e8 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -827,6 +827,18 @@ } } }, + "%@. Focus active: %@" : { + "comment" : "A row that combines a calendar event with a focus indicator. The first argument is the text of the calendar event. The second argument is the name of the focus.", + "isCommentAutoGenerated" : true, + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@. Focus active: %2$@" + } + } + } + }, "%@°" : { "comment" : "A label inside the temperature gauge that shows the current temperature. The value is shown in degrees Celsius (or Fahrenheit, if specified in the snapshot).", "isCommentAutoGenerated" : true, diff --git a/DynamicIsland/components/LockScreen/LockScreenWeatherWidget.swift b/DynamicIsland/components/LockScreen/LockScreenWeatherWidget.swift index a0726b2..5a31718 100644 --- a/DynamicIsland/components/LockScreen/LockScreenWeatherWidget.swift +++ b/DynamicIsland/components/LockScreen/LockScreenWeatherWidget.swift @@ -393,6 +393,14 @@ struct LockScreenWeatherWidget: View { lockScreenShowCalendarEvent && nextCalendarEvent != nil } + private var shouldShowFocusInCalendarSlot: Bool { + shouldShowFocusWidget && !shouldShowCalendarRow + } + + private var shouldCombineFocusAndCalendar: Bool { + shouldShowFocusWidget && shouldShowCalendarRow + } + private enum RowKind { case weather case focus @@ -422,6 +430,8 @@ struct LockScreenWeatherWidget: View { var enabled: Set = [.weather] if lockScreenShowCalendarEvent { enabled.insert(.calendar) } if shouldShowFocusWidget { enabled.insert(.focus) } + if shouldShowFocusInCalendarSlot { enabled.insert(.calendar); enabled.remove(.focus) } + if shouldCombineFocusAndCalendar { enabled.remove(.focus) } return orderedRowKinds.filter { enabled.contains($0) } } @@ -462,8 +472,13 @@ struct LockScreenWeatherWidget: View { private var isInline: Bool { snapshot.widgetStyle == .inline } private var stackAlignment: VerticalAlignment { isInline ? .firstTextBaseline : .top } private var stackSpacing: CGFloat { isInline ? 14 : 22 } - private var mainRowAlignment: Alignment { isInline ? .leading : .center } - private var secondaryRowAlignment: Alignment { isInline ? .leading : .center } + private var mainRowAlignment: Alignment { + if isInline { + return shouldCombineFocusAndCalendar ? .center : .leading + } + return .center + } + private var secondaryRowAlignment: Alignment { .center } private var focusRowAlignment: Alignment { .center } private var gaugeDiameter: CGFloat { 64 } private var topPadding: CGFloat { isInline ? 6 : 22 } @@ -570,15 +585,44 @@ struct LockScreenWeatherWidget: View { case .weather: mainWidgetRow case .calendar: - nextEventRow + if shouldCombineFocusAndCalendar { + combinedFocusCalendarRow + } else if shouldShowFocusInCalendarSlot { + focusRowInCalendarSlot + } else { + nextEventRow + } case .focus: - focusWidget - .opacity(shouldShowFocusWidget ? 1 : 0) - .accessibilityHidden(!shouldShowFocusWidget) - .allowsHitTesting(false) + if shouldCombineFocusAndCalendar { + EmptyView() + } else { + focusWidget + .opacity(shouldShowFocusWidget ? 1 : 0) + .accessibilityHidden(!shouldShowFocusWidget) + .allowsHitTesting(false) + } } } + private var focusRowInCalendarSlot: some View { + HStack(alignment: .center, spacing: 8) { + focusIcon + .font(.system(size: 20, weight: .semibold)) + .frame(width: 26, height: 26) + + Text(focusDisplayName) + .font(inlinePrimaryFont) + .lineLimit(1) + .minimumScaleFactor(0.85) + .allowsTightening(true) + } + .frame(maxWidth: .infinity, alignment: secondaryRowAlignment) + .padding(.horizontal, 2) + .opacity(shouldShowFocusWidget ? 1 : 0) + .accessibilityLabel("Focus active: \(focusDisplayName)") + .allowsHitTesting(false) + } + private var mainWidgetRow: some View { HStack(alignment: stackAlignment, spacing: stackSpacing) { if let charging = snapshot.charging { @@ -627,6 +671,77 @@ struct LockScreenWeatherWidget: View { ) } + private var combinedFocusCalendarRow: some View { + let event = nextCalendarEvent + let line = shouldShowCalendarRow ? eventLineText(for: event) : lastCalendarLine + let iconConfig = calendarRowIconConfig(for: event) + + return HStack(alignment: .center, spacing: 10) { + HStack(alignment: .center, spacing: 6) { + calendarIconView(iconConfig) + .frame(width: 26, height: 26) + + Text(line) + .font(inlinePrimaryFont) + .lineLimit(1) + .minimumScaleFactor(0.85) + .allowsTightening(true) + } + .layoutPriority(2) + + Text("•") + .font(inlinePrimaryFont) + .foregroundStyle(secondaryLabelColor) + .accessibilityHidden(true) + + HStack(alignment: .center, spacing: 8) { + focusIcon + .font(.system(size: 20, weight: .semibold)) + .frame(width: 26, height: 26) + + Text(focusDisplayName) + .font(inlinePrimaryFont) + .lineLimit(1) + .minimumScaleFactor(0.85) + .allowsTightening(true) + } + .layoutPriority(1) + } + .frame(maxWidth: .infinity, alignment: .center) + .padding(.horizontal, 2) + .id(calendarRowRenderToken) + .opacity(shouldShowFocusWidget && shouldShowCalendarRow ? 1 : 0) + .accessibilityLabel("\(line). Focus active: \(focusDisplayName)") + .allowsHitTesting(false) + } + + @ViewBuilder + private func calendarIconView(_ iconConfig: (name: String, renderingMode: SymbolRenderingMode, colors: [Color])) -> some View { + let image = Image(systemName: iconConfig.name) + .font(.system(size: 20, weight: .semibold)) + + let isPalette = String(describing: iconConfig.renderingMode) + .lowercased() + .contains("palette") + + if isPalette, iconConfig.colors.count >= 2 { + image + .symbolRenderingMode(.palette) + .foregroundStyle(iconConfig.colors[0], iconConfig.colors[1]) + } else if iconConfig.colors.count >= 2 { + image + .symbolRenderingMode(iconConfig.renderingMode) + .foregroundStyle(iconConfig.colors[0], iconConfig.colors[1]) + } else if iconConfig.colors.count == 1 { + image + .symbolRenderingMode(iconConfig.renderingMode) + .foregroundStyle(iconConfig.colors[0]) + } else { + image + .symbolRenderingMode(iconConfig.renderingMode) + } + } + private func calendarRowIconConfig(for event: EventModel?) -> (name: String, renderingMode: SymbolRenderingMode, colors: [Color]) { guard let event else { return (name: "calendar.badge", renderingMode: .hierarchical, colors: [Color.white.opacity(0.9)]) diff --git a/DynamicIsland/managers/LockScreenWeatherPanelManager.swift b/DynamicIsland/managers/LockScreenWeatherPanelManager.swift index 8d5cf8b..9b6b5b1 100644 --- a/DynamicIsland/managers/LockScreenWeatherPanelManager.swift +++ b/DynamicIsland/managers/LockScreenWeatherPanelManager.swift @@ -31,6 +31,7 @@ final class LockScreenWeatherPanelManager { private(set) var latestFrame: NSRect? private var lastSnapshot: LockScreenWeatherSnapshot? private var lastContentSize: CGSize? + private var lastInlineBaselineHeight: CGFloat = 0 private var screenChangeObserver: NSObjectProtocol? private var workspaceObservers: [NSObjectProtocol] = [] @@ -64,6 +65,9 @@ final class LockScreenWeatherPanelManager { let view = LockScreenWeatherWidget(snapshot: snapshot) let hostingView = NSHostingView(rootView: view) let fittingSize = hostingView.fittingSize + if snapshot.widgetStyle == .inline { + lastInlineBaselineHeight = max(lastInlineBaselineHeight, fittingSize.height) + } hostingView.frame = NSRect(origin: .zero, size: fittingSize) let targetFrame = frame(for: fittingSize, snapshot: snapshot, on: screen) @@ -116,13 +120,18 @@ final class LockScreenWeatherPanelManager { let verticalOffset = screenFrame.height * 0.15 let isCircular = snapshot.widgetStyle == .circular let topMargin: CGFloat = isCircular ? 120 : 48 - let maxY = screenFrame.maxY - size.height - topMargin + let inlineBaselineHeight: CGFloat = max(lastInlineBaselineHeight, 80) + let positionHeight = snapshot.widgetStyle == .inline + ? max(size.height, inlineBaselineHeight) + : size.height + let maxY = screenFrame.maxY - positionHeight - topMargin let baseY = min(maxY, screenFrame.midY + verticalOffset) let loweredY = baseY - 36 let inlineLift: CGFloat = snapshot.widgetStyle == .inline ? 44 : 0 let circularDrop: CGFloat = isCircular ? 28 : 0 - let sizeDrop = max(0, size.height - 80) * 0.35 + let sizeDropHeight = positionHeight + let sizeDrop = max(0, sizeDropHeight - 80) * 0.35 let userOffset = CGFloat(Defaults[.lockScreenWeatherVerticalOffset]) let clampedOffset = min(max(userOffset, -160), 160) let adjustedY = loweredY + inlineLift + clampedOffset - circularDrop - sizeDrop