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
16 changes: 8 additions & 8 deletions NotchIA.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1277,15 +1277,15 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 20800;
CURRENT_PROJECT_VERSION = 20801;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NotchIAXPCHelper/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotchIAXPCHelper;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 2.8.0;
MARKETING_VERSION = 2.8.1;
PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia.NotchIAXPCHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand All @@ -1303,15 +1303,15 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 20800;
CURRENT_PROJECT_VERSION = 20801;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NotchIAXPCHelper/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NotchIAXPCHelper;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 2.8.0;
MARKETING_VERSION = 2.8.1;
PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia.NotchIAXPCHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
Expand Down Expand Up @@ -1462,7 +1462,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 20800;
CURRENT_PROJECT_VERSION = 20801;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"NotchIA/Preview Content\"";
DEVELOPMENT_TEAM = "";
Expand Down Expand Up @@ -1490,7 +1490,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 2.8.0;
MARKETING_VERSION = 2.8.1;
PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -1518,7 +1518,7 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = YES;
CURRENT_PROJECT_VERSION = 20800;
CURRENT_PROJECT_VERSION = 20801;
DEAD_CODE_STRIPPING = YES;
DEPLOYMENT_POSTPROCESSING = YES;
DEVELOPMENT_ASSET_PATHS = "\"NotchIA/Preview Content\"";
Expand Down Expand Up @@ -1546,7 +1546,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 2.8.0;
MARKETING_VERSION = 2.8.1;
PRODUCT_BUNDLE_IDENTIFIER = com.coaxel2.notchia;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
2 changes: 1 addition & 1 deletion NotchIA/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ struct ContentView: View {

private func updateShelfNotchSizeForSummary() {
guard vm.notchState == .open, coordinator.currentView == .shelf else { return }
let target = openNotchSize(for: .shelf)
let target = openNotchSize(for: .shelf, screenUUID: vm.screenUUID)
guard vm.notchSize != target else { return }

withAnimation(.smooth) {
Expand Down
10 changes: 7 additions & 3 deletions NotchIA/NotchIAApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,10 +279,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {

private func setupDragDetectorForScreen(_ screen: NSScreen) {
guard let uuid = screen.displayUUID else { return }

let screenFrame = screen.frame
let notchHeight = openNotchSize.height
let notchWidth = openNotchSize.width
// Détection drag basée sur l'encoche du défaut (.media), scalée par la
// classe physique de cet écran. Évite que la zone de hover dépasse
// visuellement l'encoche sur les petits écrans (13" Air).
let referenceSize = openNotchSize(for: .media, screenUUID: uuid)
let notchHeight = referenceSize.height
let notchWidth = referenceSize.width

// Create notch region at the top-center of the screen where an open notch would occupy
let notchRegion = CGRect(
Expand Down
4 changes: 2 additions & 2 deletions NotchIA/models/NotchIAViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class NotchIAViewModel: NSObject, ObservableObject {
.sink { [weak self] newView in
guard let self = self, self.notchState == .open else { return }
withAnimation(.smooth) {
self.notchSize = openNotchSize(for: newView)
self.notchSize = openNotchSize(for: newView, screenUUID: self.screenUUID)
}
}
.store(in: &cancellables)
Expand Down Expand Up @@ -176,7 +176,7 @@ class NotchIAViewModel: NSObject, ObservableObject {
}

func open() {
self.notchSize = openNotchSize(for: coordinator.currentView)
self.notchSize = openNotchSize(for: coordinator.currentView, screenUUID: self.screenUUID)
self.notchState = .open

// Force music information update when notch is opened
Expand Down
143 changes: 113 additions & 30 deletions NotchIA/sizing/matters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,54 @@
// Created by Harsh Vardhan Goswami on 05/08/24.
//

import CoreGraphics
import Defaults
import Foundation
import SwiftUI

let downloadSneakSize: CGSize = .init(width: 65, height: 1)

let shadowPadding: CGFloat = 20
let openNotchSize: CGSize = .init(width: 560, height: 176)
let mediaOpenNotchSize: CGSize = .init(width: openNotchSize.width, height: 195)
let calendarOpenNotchSize: CGSize = .init(width: openNotchSize.width, height: 240)
let shelfSummaryOpenNotchSize: CGSize = .init(width: openNotchSize.width, height: 360)
let digestOpenNotchSize: CGSize = .init(width: 590, height: 272)

// MARK: - Tailles d'encoche de base (baseline 16" Pro)
//
// Ces tailles sont la BASE non-scalée — utilisées uniquement pour allouer
// la NSWindow (qui doit pouvoir contenir le plus grand contenu possible
// sur la plus grande machine). La taille VISIBLE de l'encoche dans la
// fenêtre est calculée par `openNotchSize(for:screenUUID:)` ci-dessous,
// avec un scale qui dépend de la diagonale physique de l'écran.
// → Un MacBook Air 13" voit une encoche plus étroite qu'un Pro 16",
// indépendamment du réglage Affichage macOS choisi par l'utilisateur.

private let baseOpenNotchSize: CGSize = .init(width: 560, height: 176)
private let baseMediaOpenNotchSize: CGSize = .init(width: baseOpenNotchSize.width, height: 195)
private let baseCalendarOpenNotchSize: CGSize = .init(width: baseOpenNotchSize.width, height: 240)
private let baseShelfSummaryOpenNotchSize: CGSize = .init(width: baseOpenNotchSize.width, height: 360)
private let baseDigestOpenNotchSize: CGSize = .init(width: 590, height: 272)
// Concentration keeps its generous cards but the notch itself stays
// short — PomodoroView is wrapped in a ScrollView, so the bottom row
// is reached by scrolling instead of stretching the notch off-screen.
// 236pt shows the whole top row (status + progression) plus a peek of
// the commands/settings row that signals the page scrolls.
let pomodoroOpenNotchSize: CGSize = .init(width: 590, height: 236)
private let basePomodoroOpenNotchSize: CGSize = .init(width: 590, height: 236)

let maximumOpenNotchSize: CGSize = .init(
width: [
openNotchSize,
mediaOpenNotchSize,
calendarOpenNotchSize,
shelfSummaryOpenNotchSize,
digestOpenNotchSize,
pomodoroOpenNotchSize
].map(\.width).max() ?? openNotchSize.width,
baseOpenNotchSize,
baseMediaOpenNotchSize,
baseCalendarOpenNotchSize,
baseShelfSummaryOpenNotchSize,
baseDigestOpenNotchSize,
basePomodoroOpenNotchSize
].map(\.width).max() ?? baseOpenNotchSize.width,
height: [
openNotchSize,
mediaOpenNotchSize,
calendarOpenNotchSize,
shelfSummaryOpenNotchSize,
digestOpenNotchSize,
pomodoroOpenNotchSize
].map(\.height).max() ?? openNotchSize.height
baseOpenNotchSize,
baseMediaOpenNotchSize,
baseCalendarOpenNotchSize,
baseShelfSummaryOpenNotchSize,
baseDigestOpenNotchSize,
basePomodoroOpenNotchSize
].map(\.height).max() ?? baseOpenNotchSize.height
)
let cornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 19, bottom: 24), closed: (top: 6, bottom: 14))
let openOuterHorizontalPadding: CGFloat = max(cornerRadiusInsets.opened.top, cornerRadiusInsets.opened.bottom) + 12
Expand Down Expand Up @@ -80,7 +93,68 @@ enum MusicPlayerImageSizes {
static let size = (opened: CGSize(width: 90, height: 90), closed: CGSize(width: 20, height: 20))
}

@MainActor func openNotchSize(for view: NotchViews) -> CGSize {
// MARK: - Classe physique d'écran
//
// Détecte la diagonale physique de l'écran via `CGDisplayScreenSize` (qui
// retourne les dimensions en mm rapportées par l'EDID). Cette valeur est
// INDÉPENDANTE du réglage Affichage choisi par l'utilisateur dans
// Réglages Système → Moniteurs ("Default", "More Space", "Larger Text"…).
// Conséquence : deux MacBook Air 13" différents (un en Default, l'autre
// en More Space) obtiennent la même classe `.compact13` et donc la même
// taille d'encoche visible.

enum ScreenSizeClass {
case compact13 // MacBook Air 13.6" (M2/M3)
case small14 // MacBook Pro 14.2" (M2/M3/M4 Pro/Max)
case medium15 // MacBook Air 15.3" (M2/M3)
case large16 // MacBook Pro 16.2" (M2/M3/M4 Pro/Max), écrans externes

/// Multiplier appliqué à la LARGEUR de l'encoche ouverte. Les hauteurs
/// restent constantes — le contenu (texte 11-13pt, icônes 16pt,
/// contrôles 30pt) doit rester lisible. Seule la largeur excessive sur
/// les petits écrans était le problème.
///
/// Calibrage (baseline = 560pt sur 16" Pro) :
/// 13" → 480pt (-14%) : ~33% de l'écran sur 1469pt (au lieu de 38%)
/// 14" → 520pt (- 7%) : ~34% de l'écran sur 1512pt (au lieu de 37%)
/// 15" → 540pt (- 4%) : ~32% de l'écran sur 1680pt (au lieu de 33%)
/// 16" → 560pt (réf) : ~32% de l'écran sur 1728pt
var widthScale: CGFloat {
switch self {
case .compact13: return 480.0 / 560.0
case .small14: return 520.0 / 560.0
case .medium15: return 540.0 / 560.0
case .large16: return 1.0
}
}
}

@MainActor func screenSizeClass(screenUUID: String? = nil) -> ScreenSizeClass {
let screen = screenUUID.flatMap { NSScreen.screen(withUUID: $0) } ?? NSScreen.main
return screenSizeClass(screen)
}

@MainActor func screenSizeClass(_ screen: NSScreen?) -> ScreenSizeClass {
guard let screen,
let raw = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")],
let displayID = (raw as? NSNumber)?.uint32Value
else { return .large16 }

let sizeMM = CGDisplayScreenSize(displayID)
// Les écrans externes / virtuels retournent souvent (0, 0) — on tombe
// sur le baseline maximal (16"), comportement actuel non-régressif.
guard sizeMM.width > 0, sizeMM.height > 0 else { return .large16 }

let diagonalInches = sqrt(sizeMM.width * sizeMM.width + sizeMM.height * sizeMM.height) / 25.4
switch diagonalInches {
case ..<13.8: return .compact13 // MBA 13.6"
case ..<14.6: return .small14 // MBP 14.2"
case ..<15.7: return .medium15 // MBA 15.3"
default: return .large16 // MBP 16.2" + externes
}
}

@MainActor func openNotchSize(for view: NotchViews, screenUUID: String? = nil) -> CGSize {
// Per user feedback: each tab uses the height it actually needs.
// - Concentration: dedicated compact size (its own layout)
// - Calendar: 280pt — needs that much room to absorb the full
Expand All @@ -91,25 +165,34 @@ enum MusicPlayerImageSizes {
// (or if it failed) the notch stays compact; it stretches only
// when the finished text actually appears.
// - Everything else: original compact 176pt
//
// La largeur est ensuite scalée par la classe physique de l'écran
// (compact13/small14/medium15/large16) — voir `ScreenSizeClass.widthScale`.
let scale = screenSizeClass(screenUUID: screenUUID).widthScale

func scaled(_ base: CGSize) -> CGSize {
CGSize(width: (base.width * scale).rounded(), height: base.height)
}

switch view {
case .digest:
return digestOpenNotchSize
return scaled(baseDigestOpenNotchSize)
case .pomodoro:
return pomodoroOpenNotchSize
return scaled(basePomodoroOpenNotchSize)
case .media:
// Slight bump (176 → 195) so the music player has a touch
// more vertical breathing room without making it stand out
// dramatically vs the other compact tabs.
return mediaOpenNotchSize
return scaled(baseMediaOpenNotchSize)
case .calendar:
return calendarOpenNotchSize
return scaled(baseCalendarOpenNotchSize)
case .shelf:
if SummaryViewModel.shared.isShowingSummaryText {
return shelfSummaryOpenNotchSize
return scaled(baseShelfSummaryOpenNotchSize)
}
return openNotchSize // 560×176
return scaled(baseOpenNotchSize) // 560×176 baseline, scalé
default:
return openNotchSize // 560×176
return scaled(baseOpenNotchSize) // 560×176 baseline, scalé
}
}

Expand All @@ -119,11 +202,11 @@ enum MusicPlayerImageSizes {
if let uuid = screenUUID {
selectedScreen = NSScreen.screen(withUUID: uuid)
}

if let screen = selectedScreen {
return screen.frame
}

return nil
}

Expand Down
Loading