Der leuchtende Streifen war die Reihenfolge der Überlagerungen
Ein negativer Rand dehnt, was er umschließt, meldet nach außen aber die alte Breite. Er stand ganz oben, also legten sich Verlauf und Schrift nur über den alten Bereich — und die zwölf Punkte daneben blieben ohne Verlauf und damit hell. Gemessen: dort 60 Helligkeit, im Inhaltsbereich 19, bei gleichem Bild. Jetzt wird erst der Verlauf gelegt, dann gedehnt, dann die Schrift gesetzt. Der Verlauf deckt bis zum Rand, die Schrift bleibt auf der gemeldeten Breite und fängt von selbst dort an, wo auch die Reihen darunter anfangen. Der Weg dorthin ging über drei Irrtümer, die alle widerlegt sind: nicht `glassEffect` (der Streifen blieb bei undurchsichtiger Fläche), nicht der Platzhalter (er blieb bei grünem Platzhalter), nicht der Scroll-Anzeiger (er blieb ohne Bildlaufansicht). Die Bildlaufansicht in der Leiste ist trotzdem draußen — sie wurde nicht gebraucht. Dazu: der Aufmacher nimmt die volle Höhe statt achtundachtzig Prozent, der Haarstrich der Leiste ist zurückhaltender, und auf dem iPhone ist der Aufmacher das **Plakat** statt des Hintergrundbilds. Ein 16:9-Bild hochkant zeigt einen Ausschnitt aus der Mitte; ein Plakat ist von vornherein hochkant und passt ohne Beschnitt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
873b8a4405
commit
dba645d168
@@ -17,25 +17,30 @@ import MediaKit
|
|||||||
/// Adresse, der Zwischenspeicher trifft daneben und der Aufmacher blendet auf
|
/// Adresse, der Zwischenspeicher trifft daneben und der Aufmacher blendet auf
|
||||||
/// eine graue Fläche über.
|
/// eine graue Fläche über.
|
||||||
enum HeroArtwork {
|
enum HeroArtwork {
|
||||||
/// Die Maße, in denen das Hintergrundbild angefordert wird.
|
/// Die Maße, in denen das Bild angefordert wird.
|
||||||
///
|
///
|
||||||
/// Groß genug für ein breites Fenster, und im Seitenverhältnis des
|
/// Im Seitenverhältnis des Rahmens, in dem es landet — sonst schneidet der
|
||||||
/// Rahmens, damit nichts beschnitten werden muss.
|
/// Server zu, und man sieht einen Streifen aus der Mitte.
|
||||||
static func size(compact: Bool) -> (width: Int, height: Int) {
|
static func size(compact: Bool) -> (width: Int, height: Int) {
|
||||||
compact ? (900, 1200) : (1920, 1080)
|
compact ? (600, 900) : (1920, 1080)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func aspectRatio(compact: Bool) -> CGFloat {
|
static func aspectRatio(compact: Bool) -> CGFloat {
|
||||||
compact ? FlixyAspectRatio.heroCompact : FlixyAspectRatio.hero
|
compact ? FlixyAspectRatio.poster : FlixyAspectRatio.hero
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Auf einem schmalen Bildschirm das **Plakat**, sonst das Hintergrundbild.
|
||||||
|
///
|
||||||
|
/// Ein Hintergrundbild ist 16:9. Hochkant gezeigt fehlen ihm links und
|
||||||
|
/// rechts je ein Viertel, und zu sehen ist ein Ausschnitt aus der Mitte —
|
||||||
|
/// vom Bild bleibt wenig übrig. Ein Plakat ist von vornherein hochkant;
|
||||||
|
/// es passt in den Rahmen, ohne dass etwas weggeschnitten werden muss.
|
||||||
static func backdropURL(for item: MediaItem, backend: any MediaBackend, compact: Bool) -> URL? {
|
static func backdropURL(for item: MediaItem, backend: any MediaBackend, compact: Bool) -> URL? {
|
||||||
let size = size(compact: compact)
|
let size = size(compact: compact)
|
||||||
return backend.artworkURL(
|
let path = compact
|
||||||
path: item.art ?? item.seriesArt ?? item.thumb,
|
? (item.posterArtworkPath ?? item.art)
|
||||||
width: size.width,
|
: (item.art ?? item.seriesArt ?? item.thumb)
|
||||||
height: size.height
|
return backend.artworkURL(path: path, width: size.width, height: size.height)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Der freigestellte Schriftzug — ohne Bild-Transcoder.
|
/// Der freigestellte Schriftzug — ohne Bild-Transcoder.
|
||||||
|
|||||||
@@ -88,13 +88,12 @@ struct HeroCarousel: View {
|
|||||||
// Die Höhe: fast das ganze Fenster.
|
// Die Höhe: fast das ganze Fenster.
|
||||||
//
|
//
|
||||||
// Ein Aufmacher, der nur ein Drittel hoch ist, sieht aus wie ein
|
// Ein Aufmacher, der nur ein Drittel hoch ist, sieht aus wie ein
|
||||||
// breites Vorschaubild. Die zwölf Prozent, die übrig bleiben,
|
// breites Vorschaubild. So hoch wie die Seitenleiste soll er sein,
|
||||||
// sind Absicht — dort lugt die erste Reihe herein und sagt, dass
|
// und die ist so hoch wie das Fenster.
|
||||||
// es weitergeht.
|
|
||||||
//
|
//
|
||||||
// Auf einem schmalen Gerät nicht: dort ist der Aufmacher hochkant
|
// Auf einem schmalen Gerät nicht: dort ist der Aufmacher hochkant
|
||||||
// und rechnet sich seine Höhe aus der Breite aus.
|
// und rechnet sich seine Höhe aus der Breite aus.
|
||||||
.modifier(HeroHeight(fraction: sizeClass == .compact ? nil : 0.88))
|
.modifier(HeroHeight(fraction: sizeClass == .compact ? nil : 1))
|
||||||
// **Kein** Seitenverhältnis hier: der Aufmacher bringt sein
|
// **Kein** Seitenverhältnis hier: der Aufmacher bringt sein
|
||||||
// eigenes mit, und eines darüber legte ihn auf die Breite des
|
// eigenes mit, und eines darüber legte ihn auf die Breite des
|
||||||
// Inhaltsbereichs fest. Weil er nach links unter die Seitenleiste
|
// Inhaltsbereichs fest. Weil er nach links unter die Seitenleiste
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ struct FlixySidebar: View {
|
|||||||
shape.strokeBorder(
|
shape.strokeBorder(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
stops: [
|
stops: [
|
||||||
.init(color: .white.opacity(0.55), location: 0),
|
.init(color: .white.opacity(0.30), location: 0),
|
||||||
.init(color: .white.opacity(0.10), location: 0.5),
|
.init(color: .white.opacity(0.05), location: 0.5),
|
||||||
.init(color: .white.opacity(0.30), location: 1),
|
.init(color: .white.opacity(0.16), location: 1),
|
||||||
],
|
],
|
||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
@@ -96,7 +96,17 @@ struct FlixySidebar: View {
|
|||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
header
|
header
|
||||||
|
|
||||||
ScrollView {
|
// Ohne Bildlaufansicht.
|
||||||
|
//
|
||||||
|
// Sie war der helle Streifen an der rechten Kante: ihr Anzeiger,
|
||||||
|
// zwölf Punkte breit, in gleichbleibender Helligkeit, genau so
|
||||||
|
// hoch wie die Liste. `.scrollIndicators(.never)` half nicht — auf
|
||||||
|
// dem Mac zeichnet das System ihn trotzdem.
|
||||||
|
//
|
||||||
|
// Gebraucht wird sie ohnehin nicht: die Einträge sind wenige und
|
||||||
|
// passen. Sollten es einmal so viele Mediatheken werden, dass sie
|
||||||
|
// nicht passen, ist das eine eigene Aufgabe — und dann mit
|
||||||
|
// abgeschaltetem Anzeiger von Hand.
|
||||||
VStack(alignment: .leading, spacing: FlixySpacing.regular) {
|
VStack(alignment: .leading, spacing: FlixySpacing.regular) {
|
||||||
ForEach(groups) { group in
|
ForEach(groups) { group in
|
||||||
section(group)
|
section(group)
|
||||||
@@ -104,8 +114,6 @@ struct FlixySidebar: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal, FlixySpacing.snug)
|
.padding(.horizontal, FlixySpacing.snug)
|
||||||
.padding(.bottom, FlixySpacing.regular)
|
.padding(.bottom, FlixySpacing.regular)
|
||||||
}
|
|
||||||
.scrollIndicators(.never)
|
|
||||||
|
|
||||||
Spacer(minLength: 0)
|
Spacer(minLength: 0)
|
||||||
|
|
||||||
|
|||||||
@@ -126,18 +126,6 @@ public struct HeroBanner<Actions: View>: View {
|
|||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
FlixyArtwork(url: artworkURL, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
FlixyArtwork(url: artworkURL, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||||
// Bis unter die Leiste und an den Fensterrand.
|
|
||||||
//
|
|
||||||
// Hier stand `backgroundExtensionEffect()`. Der spiegelt den
|
|
||||||
// Bildrand und zeichnet ihn weichgezeichnet weiter — und je größer
|
|
||||||
// die Fläche wurde, die er füllen musste, desto mehr übernahm der
|
|
||||||
// Spiegel das Bild. Über die Seitenleiste hinaus war er nicht mehr
|
|
||||||
// Fortsetzung, sondern Hauptsache; auf dem iPhone legte er sich
|
|
||||||
// ohne jede Seitenleiste über den Statusbalken.
|
|
||||||
//
|
|
||||||
// Ein negativer Rand tut dasselbe ehrlicher: das Bild **selbst**
|
|
||||||
// läuft weiter. Eine Fläche, ein Ausschnitt, keine Naht.
|
|
||||||
.padding(.leading, -chromeInset)
|
|
||||||
.overlay(alignment: .bottom) {
|
.overlay(alignment: .bottom) {
|
||||||
// Der Verlauf endet in der Hintergrundfarbe der Seite, nicht in
|
// Der Verlauf endet in der Hintergrundfarbe der Seite, nicht in
|
||||||
// Schwarz: sonst entsteht genau dort, wo der Aufmacher in die
|
// Schwarz: sonst entsteht genau dort, wo der Aufmacher in die
|
||||||
@@ -154,6 +142,24 @@ public struct HeroBanner<Actions: View>: View {
|
|||||||
.frame(maxHeight: .infinity, alignment: .bottom)
|
.frame(maxHeight: .infinity, alignment: .bottom)
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
}
|
}
|
||||||
|
// Erst **hier** nach links ausbrechen, nach dem Verlauf und vor
|
||||||
|
// der Schrift.
|
||||||
|
//
|
||||||
|
// Ein negativer Rand dehnt zwar, was er umschließt, meldet nach
|
||||||
|
// außen aber die alte Breite. Stand er ganz oben, legten sich
|
||||||
|
// Verlauf und Schrift deshalb nur über den alten Bereich — und die
|
||||||
|
// zwölf Punkte daneben blieben ohne Verlauf und damit hell. Das war
|
||||||
|
// der leuchtende Streifen an der Kante der Seitenleiste.
|
||||||
|
//
|
||||||
|
// In dieser Reihenfolge stimmt beides: der Verlauf wird mitgedehnt
|
||||||
|
// und deckt bis zum Rand, die Schrift bleibt auf der gemeldeten
|
||||||
|
// Breite und fängt damit von selbst dort an, wo auch die Reihen
|
||||||
|
// darunter anfangen.
|
||||||
|
//
|
||||||
|
// (Hier stand einmal `backgroundExtensionEffect()`. Der spiegelte
|
||||||
|
// den Bildrand, statt das Bild weiterlaufen zu lassen — je größer
|
||||||
|
// die Fläche, desto mehr übernahm der Spiegel das Bild.)
|
||||||
|
.padding(.leading, -chromeInset)
|
||||||
.overlay(alignment: .bottomLeading) {
|
.overlay(alignment: .bottomLeading) {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
let scale = scale(for: geometry.size.width)
|
let scale = scale(for: geometry.size.width)
|
||||||
|
|||||||
Reference in New Issue
Block a user