fix an issue with horizontal window margins not respected
This commit is contained in:
@@ -17,44 +17,40 @@ enum PHThemeWindowAnchor: String, Decodable {
|
||||
|
||||
extension PHThemeWindowAnchor {
|
||||
/// Compute the top-left origin (in screen coordinates) of a window of `size`
|
||||
/// placed against this anchor within `screen`, offset by `margin` along the
|
||||
/// anchored edges.
|
||||
/// placed against this anchor within `rect`.
|
||||
///
|
||||
/// Edges the anchor does not reference are ignored, so margins only affect
|
||||
/// the relevant sides.
|
||||
func origin(in screen: CGRect, size: CGSize, margin: PHThemeWindowMargin) -> CGPoint {
|
||||
let top = margin.top ?? 0
|
||||
let bottom = margin.bottom ?? 0
|
||||
let leading = margin.leading ?? 0
|
||||
let trailing = margin.trailing ?? 0
|
||||
|
||||
// Default: centered on both axes.
|
||||
/// `rect` is the content rectangle the window is placed within — typically the
|
||||
/// screen inset by the window's margin (see `PHThemeWindowMargin.inset(of:)`).
|
||||
/// Single-edge anchors pin to that edge and center along the opposite axis;
|
||||
/// corner anchors pin to two edges; `.center` centers on both axes.
|
||||
func origin(in rect: CGRect, size: CGSize) -> CGPoint {
|
||||
// Default: centered on both axes within the rect.
|
||||
var point = CGPoint(
|
||||
x: screen.midX - size.width / 2,
|
||||
y: screen.midY - size.height / 2
|
||||
x: rect.midX - size.width / 2,
|
||||
y: rect.midY - size.height / 2
|
||||
)
|
||||
|
||||
switch self {
|
||||
case .top:
|
||||
point.y = screen.maxY - CGFloat(top) - size.height
|
||||
point.y = rect.maxY - size.height
|
||||
case .bottom:
|
||||
point.y = screen.minY + CGFloat(bottom)
|
||||
point.y = rect.minY
|
||||
case .leading:
|
||||
point.x = screen.minX + CGFloat(leading)
|
||||
point.x = rect.minX
|
||||
case .trailing:
|
||||
point.x = screen.maxX - CGFloat(trailing) - size.width
|
||||
point.x = rect.maxX - size.width
|
||||
case .topLeading:
|
||||
point.x = screen.minX + CGFloat(leading)
|
||||
point.y = screen.maxY - CGFloat(top) - size.height
|
||||
point.x = rect.minX
|
||||
point.y = rect.maxY - size.height
|
||||
case .topTrailing:
|
||||
point.x = screen.maxX - CGFloat(trailing) - size.width
|
||||
point.y = screen.maxY - CGFloat(top) - size.height
|
||||
point.x = rect.maxX - size.width
|
||||
point.y = rect.maxY - size.height
|
||||
case .bottomLeading:
|
||||
point.x = screen.minX + CGFloat(leading)
|
||||
point.y = screen.minY + CGFloat(bottom)
|
||||
point.x = rect.minX
|
||||
point.y = rect.minY
|
||||
case .bottomTrailing:
|
||||
point.x = screen.maxX - CGFloat(trailing) - size.width
|
||||
point.y = screen.minY + CGFloat(bottom)
|
||||
point.x = rect.maxX - size.width
|
||||
point.y = rect.minY
|
||||
case .center:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
/// Insets (in points) from the screen edges implied by a window's anchor.
|
||||
/// Insets (in points) from the screen edges, defining the content rectangle a
|
||||
/// window is sized and placed within.
|
||||
///
|
||||
/// Only the edges referenced by the anchor are consumed; the rest are ignored,
|
||||
/// so a margin only affects the relevant sides of the placement.
|
||||
/// Margins compose with `width`/`height`: a `width = "100%"` window spans the
|
||||
/// space *between* the leading/trailing margins rather than the full screen.
|
||||
/// Unspecified edges are left untouched (treated as 0).
|
||||
struct PHThemeWindowMargin: Decodable, Equatable {
|
||||
let top: Double?
|
||||
let bottom: Double?
|
||||
@@ -21,4 +24,20 @@ struct PHThemeWindowMargin: Decodable, Equatable {
|
||||
self.leading = leading
|
||||
self.trailing = trailing
|
||||
}
|
||||
|
||||
/// Returns `rect` inset by the specified edges; unspecified edges are left
|
||||
/// untouched (treated as 0). Uses AppKit's coordinate system (origin at the
|
||||
/// bottom-left, y increasing upward): `top` reduces the max-Y edge.
|
||||
func inset(of rect: CGRect) -> CGRect {
|
||||
let l = leading ?? 0
|
||||
let r = trailing ?? 0
|
||||
let t = top ?? 0
|
||||
let b = bottom ?? 0
|
||||
return CGRect(
|
||||
x: rect.minX + l,
|
||||
y: rect.minY + b,
|
||||
width: rect.width - l - r,
|
||||
height: rect.height - t - b
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,13 @@ name = "default"
|
||||
anchor = "top"
|
||||
height = 30
|
||||
width = "100%"
|
||||
shadow = false
|
||||
blur = 0.0
|
||||
margin = { leading = 30, top = 30, trailing = 30, bottom = 30 }
|
||||
|
||||
[[window]]
|
||||
name = "bottom"
|
||||
anchor = "bottom"
|
||||
height = 30
|
||||
width = "100%"
|
||||
shadow = false
|
||||
blur = 0.0
|
||||
|
||||
[[text]]
|
||||
name = "default"
|
||||
|
||||
@@ -52,13 +52,14 @@ extension BarWindow {
|
||||
let screenFrame = controller.screen.frame
|
||||
let themeWindow = controller.window
|
||||
|
||||
let size = CGSize(
|
||||
width: (themeWindow.width ?? .percentage(1.0)).resolve(against: screenFrame.width),
|
||||
height: CGFloat(themeWindow.height ?? 30)
|
||||
)
|
||||
|
||||
// Absolute origin wins over anchor/margin.
|
||||
// Absolute origin bypasses the anchor/margin system entirely: width
|
||||
// resolves against the full screen and the window sits at the exact
|
||||
// coordinates given.
|
||||
if let origin = themeWindow.origin {
|
||||
let size = CGSize(
|
||||
width: (themeWindow.width ?? .percentage(1.0)).resolve(against: screenFrame.width),
|
||||
height: CGFloat(themeWindow.height ?? 30)
|
||||
)
|
||||
return NSRect(
|
||||
x: CGFloat(origin.x),
|
||||
y: CGFloat(origin.y),
|
||||
@@ -67,10 +68,18 @@ extension BarWindow {
|
||||
)
|
||||
}
|
||||
|
||||
let anchor = themeWindow.anchor ?? .top
|
||||
// Margins define a content rectangle within the screen: the window is
|
||||
// sized against it (so `width = "100%"` spans only between the margins,
|
||||
// not the full screen) and placed by the anchor inside it.
|
||||
let margin = themeWindow.margin ?? .init()
|
||||
let contentRect = margin.inset(of: screenFrame)
|
||||
let size = CGSize(
|
||||
width: (themeWindow.width ?? .percentage(1.0)).resolve(against: contentRect.width),
|
||||
height: CGFloat(themeWindow.height ?? 30)
|
||||
)
|
||||
let anchor = themeWindow.anchor ?? .top
|
||||
return NSRect(
|
||||
origin: anchor.origin(in: screenFrame, size: size, margin: margin),
|
||||
origin: anchor.origin(in: contentRect, size: size),
|
||||
size: size
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user