import CoreGraphics /// How the full-window call's control bar fits the width it has (2026-10-03: /// Paul's 0.4.6 bar ran off the left of the window, mute and camera cut /// off). The bar is always whole and centred: first its spacing tightens, /// then the controls that matter least move into a ⋯ menu, one at a time — /// never mute, camera, chat or Leave. public struct CallBarFit: Equatable, Sendable { /// A control the bar can hold. `essential` ones never move into ⋯. public enum Control: String, CaseIterable, Sendable { case mute, camera, share, devices, layout, link, chat, leave public var essential: Bool { [.mute, .camera, .chat, .leave].contains(self) } /// Its width on the bar: every button is 44 pt round; Leave is a /// capsule 1.4 times as wide (OverlayButtonBody). var width: CGFloat { self == .leave ? CallBarFit.button * 1.4 : CallBarFit.button } } public static let button: CGFloat = 44 /// The widest the bar grows, and its margin from the window's edges. public static let maxWidth: CGFloat = 760 public static let margin: CGFloat = 16 /// The order the optional controls move into ⋯: the least used first. public static let overflowOrder: [Control] = [.link, .layout, .devices, .share] /// One step of tightening: the gap between buttons, the least room /// between the left group and chat, and the pane's padding. public struct Spacing: Equatable, Sendable { public var between: CGFloat public var gap: CGFloat public var padding: CGFloat } public static let roomy = Spacing(between: 10, gap: 24, padding: 10) public static let snug = Spacing(between: 6, gap: 12, padding: 8) public static let tight = Spacing(between: 4, gap: 4, padding: 6) public var spacing: Spacing /// The controls in ⋯, in the bar's own order; empty when everything fits. public var overflow: [Control] /// The pane's width: as wide as it may be, never wider than it has. public var width: CGFloat /// The bar for a window `windowWidth` wide showing `controls` (Share /// meeting link is only there for a member; Video layout only here). public static func fit(windowWidth: CGFloat, controls: [Control]) -> CallBarFit { let room = max(0, windowWidth - 2 * margin) for spacing in [roomy, snug, tight] where needed(controls, spacing, more: false) <= room { return CallBarFit(spacing: spacing, overflow: [], width: min(maxWidth, room)) } var shown = controls var moved: [Control] = [] for control in overflowOrder where shown.contains(control) { shown.removeAll { $0 == control } moved.append(control) if needed(shown, tight, more: true) <= room { break } } let order = controls.filter { moved.contains($0) } return CallBarFit(spacing: tight, overflow: order, width: min(maxWidth, room)) } /// The least width `controls` take with `spacing`, with a ⋯ button if /// `more`: the buttons, a gap between each (the spacer before chat is /// one more child of the row), the spacer's own least, and the padding. public static func needed(_ controls: [Control], _ spacing: Spacing, more: Bool) -> CGFloat { let buttons = controls.reduce(0) { $0 + $1.width } + (more ? button : 0) let children = controls.count + (more ? 1 : 0) + 1 return buttons + CGFloat(children - 1) * spacing.between + spacing.gap + 2 * spacing.padding } public func shows(_ control: Control) -> Bool { !overflow.contains(control) } }