Pantrix UI

Pencereye uyum

PantrixTheme'in içeriğine verdiği pencere bilgisi: ne olduğu, nereden geldiği, nasıl okunduğu ve testte nasıl sabitlendiği.

Bir ekran ya da bileşen yazarken hangi yardımcının nasıl kullanılacağı, çalışan üç örnek vakayla ADAPTIVE_USAGE_GUIDE.md belgesindedir. Bir arayüzün pencereye uyarken tutacağı kurallar adaptive/adaptive-rules.md belgesindedir.

1. Ne veriyor?

PantrixTheme kullanan her ekran, çizildiği pencereyi hazır bulur. Ayrı bir sarmalayıcı ya da kurulum yoktur:

PantrixTheme {
    val window = PantrixTheme.window

    window.widthClass                 // Compact, Medium, Expanded, Large, ExtraLarge
    window.heightClass                // Compact, Medium, Expanded
    window.size                       // DpSize
    window.posture.isBook             // yarı açık, menteşe dikey
    window.posture.isTabletop         // yarı açık, menteşe yatay
    window.posture.separatingFold     // içeriğin üstünden geçmemesi gereken menteşe, yoksa null
    window.paneCount()                // yan yana kaç panele yer var: 1, 2 ya da 3 (10. bölüm)
    window.breakpoints                // sınıfların başladığı sınırlar

    PantrixTheme.layout.margin        // sayfanın kenar boşluğu, pencerenin sınıfına göre
    PantrixTheme.layout.gutter        // sütunların ve panellerin arası
}

Türler com.pantrixui.foundation.adaptive paketindedir. Bakılan şey cihaz ya da ekran değil, penceredir: bölünmüş ekrandaki, boyutu değiştirilen bir masaüstü penceresindeki ya da tarayıcı sekmesindeki uygulama o pencere kadar geniştir.

2. Sınıflar ve sınırlar

Genişlik sınıfıGenişlik (dp)Nerede görülür
Compact< 600Dikey telefon, katlanabilirin kapak ekranı, dar tarayıcı penceresi
Medium600–839Açık katlanabilir, dikey tablet, bölünmüş ekran
Expanded840–1199Yatay tablet, yatay telefonların çoğu
Large1200–1599Masaüstü penceresi, tarayıcı, büyük tablet
ExtraLarge≥ 1600Geniş masaüstü penceresi, harici ekran
Yükseklik sınıfıYükseklik (dp)Nerede görülür
Compact< 480Yatay tutulan telefon
Medium480–899Dikey telefon, yatay tablet
Expanded≥ 900Dikey tablet, masaüstü penceresi

Sınırlar Android'in pencere boyutu sınıflarıdır ve PantrixBreakpoints'te durur. Sınıflar sıralıdır; "şu genişlikten itibaren" bir karşılaştırmadır:

if (window.widthClass >= WindowWidthClass.Expanded) ListBesideDetail() else ListAlone()

3. Yardımcılar: değer seçmek ve sormak

Bir ekranın en sık yazdığı seçimler ve sorular için yardımcılar vardır. Hepsi com.pantrixui.foundation.adaptive paketindedir ve pencerenin zaten söylediği bir şeye ad verir.

val window = PantrixTheme.window

// Değer seçenler: verilmeyen sınıf bir küçüğünün, verilmeyen duruş `flat`'in değerini alır
val columns = window.byWidth(compact = 2, medium = 4)                    // 2, 4, 4, 4, 4
val barHeight = window.byHeight(compact = 44.dp, medium = 56.dp)
val panes = window.byPosture(flat = 1, book = 2, tabletop = 2)

// Sorular
window.isNarrow       // genişlik Compact: dikey telefon
window.isShort        // yükseklik Compact: yatay telefon
window.isSizeKnown    // platform boyutu söyledi mi

// Composable içinde kısa yazım
val cardPadding = responsive(compact = 12.dp, medium = 16.dp, large = 20.dp)  // PantrixTheme.window.byWidth(...) ile aynı
val threeColumns = rememberWindowIsAtLeast(1000.dp)                      // sınıfların başlamadığı bir genişlik

// Bileşenin kendi yeri: kutu, verilen yeri bir pencere gibi söyler
BoxWithPantrixWindow(modifier) { box -> if (box.isNarrow) OrderList() else OrderTable() }
YardımcıNe söylerNe zaman yeniden kurar
byWidth(compact, medium, expanded, large, extraLarge)Genişlik sınıfının değeriSınıf değişince
byHeight(compact, medium, expanded)Yükseklik sınıfının değeriSınıf değişince
byPosture(flat, book, tabletop)Duruşun değeri: kitapta book, masada tabletop, öteki her durumda flatDuruş değişince
isNarrowGenişlik sınıfı Compact mıSınıf değişince
isShortYükseklik sınıfı Compact mıSınıf değişince
isSizeKnownPlatform pencerenin genişliğini ve yüksekliğini söyledi miCevap değişince
responsive(...)Temanın penceresinin byWidth'iSınıf değişince
rememberWindowIsAtLeast(width)Pencere en az width genişliğinde miPencere o genişliği geçerken
BoxWithPantrixWindow { box -> … }Bileşene verilen yer, bir pencere olarak (10. bölüm)İçeriğin okuduğu sınıf değişince

Notlar:

4. Yükseklik: yatay telefon

Düzeni genişlik seçer. Yükseklik, üst üste dizmeye yer olup olmadığını söyler.

Yatay tutulan telefon genişliğiyle Medium ya da Expanded çıkar (iPhone 17: 874 × 402 dp), ama yüksekliği Compact'tır: enine uzanan her çubuk ekranın yedide birini alır. Yüksekliği Compact olan pencerenin kuralı:

Bugün bu kuralın bileşenlerde karşılığı olan tek maddesi ilki: PantrixScaffold navigasyonu 600 dp'den itibaren yana alır, yatay telefonda da. Öteki maddeler için kütüphane yalnızca sorulacak değeri (heightClass) veriyor; bileşenler geldikçe kuralı kendileri uygulayacak.

5. Boyut nereden geliyor?

Tema ölçüm yapmaz; boyutu Compose'un kendi pencere bilgisinden (LocalWindowInfo) okur. 3 Ekim 2026'da her platformda örnek uygulamayla bakıldı:

PlatformOkunan boyutİlk kompozisyon
AndroidActivity'nin penceresi, sistem çubukları dahil. Katlanabilir emülatörde: açıkken 852 × 883, kapak ekranında 443 × 970, kapak yatayken 970 × 443Doğru boyutla başlar
iOSUIWindow'un sınırları. iPhone 17: 402 × 874, iPad: 820 × 1180Doğru boyutla başlar
MasaüstüPencerenin içerik alanı (800 × 600'lük pencerede 800 × 572). Pencere sürüklendikçe izlerSıfır boyutla başlar
WebCompose'un çizdiği kutu; tarayıcı penceresi değil. 375'ten 1700'e beş sınıf da denendiSıfır boyutla başlar

Masaüstünde ve web'de ilk kompozisyon pencereyi 0 × 0 görür, yani sınıf bir an Compact'tır; boyut hemen ardından gelir. Bu, Compose'un o iki platformdaki sırasıdır: masaüstünde içerik pencere gösterilmeden önce kurulur, web'de kutunun boyutu içeriğin ilk kompozisyonundan sonra okunur. O an ekranda bir şey yoktur, ama şuna dikkat:

Bir kez verilip saklanan bir karar varsa, içeriği boyut gelene kadar bekletmek uygulamanın elindedir. isSizeKnown bunun içindir; Android ve iOS'ta baştan true'dur, yani orada hiçbir şey beklemez:

PantrixTheme {
    // Uygulama ilk kez, sınıfı gerçek olan bir pencerede kurulur.
    if (PantrixTheme.window.isSizeKnown) App()
}

Tema bunu kendiliğinden yapmaz: boyutunu hiç bildirmeyen bir ortamda ekran boş kalırdı.

6. Duruş nereden geliyor?

Menteşeyi kendi okuyan bir uygulama duruşunu verir:

PantrixTheme(window = rememberPantrixWindow(posture = myPosture)) { … }
DuruşNasıl anlaşılırposture
Düz açıkMenteşe var, ekran tek parçafolds dolu, separatingFold null
KitapYarı açık, menteşe dikeyisBook
MasaYarı açık, menteşe yatayisTabletop
KapalıKapak ekranıWindowPosture.Flat, pencere Compact

Menteşenin yeri separatingFold.bounds'tadır: piksel cinsinden, pencerenin koordinatlarında. Çevresinde dokunulanların boş bıraktığı şerit clearance'tır: iOS'ta menteşenin iki yanında 20 pt geniştir, Android ve web'de bounds'un kendisidir.

Xcode 27.1'in iPhone Duo simülatöründe ölçülenler: kapak ekranı 466 × 678 pt'dir ve menteşesi yoktur; sağ kenarında sistemin dikey bar'ı durur (10. bölüm, "Sistemin dikey bar'ı"). İç ekran 951 × 669 pt'dir; menteşe ortasındadır (x = 475,5) ve bölgesi iki yanındaki 20'şer pt'lik payla 40 pt geniştir. Telefon çevrilince iç ekran 669 × 951 olur ve menteşe yatay durur. Menteşenin açısı simülatörde Xcode'un cihaz penceresinden değiştirilir; simctl'de bir komutu yoktur.

7. Yeniden kurulma (recomposition)

PantrixTheme.window tema durdukça aynı nesnedir; değerleri sorulduğunda okunur.

Bu yüzden düzenler sınıfı okur. Sınıflara uymayan bir genişlik soracaksan window.size.width >= 1000.dp yazma, rememberWindowIsAtLeast(1000.dp) kullan: yalnızca pencere o genişliği geçerken yeniden kurar. Boyutun kendisi gerekiyorsa onu küçük, kendi composable'ında oku; örnek uygulamadaki WindowSamples böyle yapar.

8. Pencereyi sabitlemek

Gerçek pencerenin doğru cevap olmadığı yerler:

// Test ve önizleme: düzeni belli bir boyutta görmek
PantrixTheme(window = PantrixWindow(DpSize(700.dp, 900.dp))) { … }

// Ekranın bir parçası: içeriği pencereye değil kendi paneline uysun
CompositionLocalProvider(LocalPantrixWindow provides PantrixWindow(DpSize(360.dp, 900.dp))) { … }

Testlerde pencere platforma göre değişir: Skiko testlerinin (JVM, iOS, web) penceresi 1024 × 768'dir, yani Expanded. Android host testlerinin penceresi Robolectric'in qualifiers'ıdır. Sınıfa bağlı bir davranışı sınayan test penceresini yukarıdaki gibi sabitlemeli; yoksa aynı test platforma göre başka sonuç verir.

İç içe tema: ekranın bir bölümü farklıysa

Açık bir ekranın koyu bir paneli ya da ferah bir sayfadaki sıkışık bir tablo, temanın içinde ikinci bir temadır. İçteki tema, kendisine verilmeyen her ayarı dıştakinden alır; verdiği onun olur:

PantrixTheme(
    locale = PantrixLocale.Turkish,
    analytics = myAnalytics,
    platform = PantrixPlatform.forType(PlatformType.Desktop, isTouch = true)
) {
    Page()
    PantrixTheme(darkTheme = true) {        // koyu panel: yalnızca renkler değişir
        PantrixSurface {                    // koyu zemini tema değil, içindeki surface çizer
            PantrixButton(text = "Kaydet", onClick = save)
            // dokunma hedefi 48 dp, "Yükleniyor" Türkçe, tıklama myAnalytics'e gider
        }
    }
    PantrixTheme(density = PantrixDensity.Compact) {   // sıkışık tablo: yalnızca yoğunluk değişir
        OrdersTable()
    }
}

Örnek uygulamada bunun sayfası var: Theme: Inside Another Theme (web'de #theme-inside-another). Koyu bölümü, sıkışık bölümü ve başka dilde bölümü gösterir; her birinde temanın değerleri yazar ve sayfanınkinden farklı olan satır "(değişti)" diye işaretlenir.

9. Kendi sınırların

PantrixTheme(window = rememberPantrixWindow(breakpoints = PantrixBreakpoints(medium = 640.dp))) { … }

Genişlikler ve yükseklikler küçükten büyüğe sıralı olmalıdır; değilse PantrixBreakpoints hata fırlatır.

Pencere, sınıflandığı sınırları söyler: PantrixTheme.window.breakpoints. Bileşenler de onlarla sınıflanır: BoxWithPantrixWindow sınırları etkin pencereden alır. Bir boyutu elle sınıflayan kod aynısını yapar:

val pane = PantrixWindow(paneSize, breakpoints = PantrixTheme.window.breakpoints)

10. Panel olan bileşen pencereye sormaz

Pencere, sayfa düzeyindeki kararlar içindir. Kendisi bir panel olan bileşen (scaffold, iki panel, ileride sheet'ler) pencereye sormaz, kendisine verilen yeri ölçer: dar bir panelin içindeki bileşen için pencere hâlâ geniştir. PantrixScaffold böyledir: navigasyonu kendi genişliği 600 dp'ye varınca yana alır.

Bir bileşen kendisine verilen yeri BoxWithPantrixWindow ile öğrenir. Kutu BoxWithConstraints'in üstüne kuruludur ve alabileceği en fazla yeri bir PantrixWindow olarak verir: ekranın pencereye sorduğu her şey (isNarrow, byWidth, widthClass >= …) kutuya sorulur.

@Composable
fun OrdersTable(orders: List<Order>, modifier: Modifier = Modifier) {
    BoxWithPantrixWindow(modifier) { box ->
        if (box.isNarrow) OrderList(orders) else OrderGrid(orders, showDate = box.widthClass >= WindowWidthClass.Expanded)
    }
}

Duruş bunun dışındadır, çünkü pencerenin özelliğidir. Masa duruşunda (yarı açık, menteşe yatay) scaffold navigasyonu genişlik ne olursa olsun altta tutar: alt yarı ellerin olduğu yerdir ve yandaki bir sütun menteşenin üstünden geçerdi.

İki panel ve menteşe

Kaç panel gösterileceği sayfa düzeyinde bir karardır ve pencereye sorulur. Panellerin nerede ayrılacağı ise panellerin durduğu yere bağlıdır ve oraya sorulur.

val panes = PantrixTheme.window.paneCount()      // Compact ve Medium 1, Expanded 2, Large'dan itibaren 3 (maxPanes ile sınırlı)

if (panes >= 2) {
    PantrixTwoPane(
        first = { Inbox(onOpen = { opened = it }) },
        second = { Message(opened) },
        modifier = Modifier.fillMaxSize(),
        firstSize = PaneSize.Fixed(360.dp)       // ya da PaneSize.Fraction(0.4f); varsayılanı yarı yarıya
    )
} else {
    if (opened == null) Inbox(onOpen = { opened = it }) else Message(opened)
}

Kendi düzenini menteşeye göre yazan, menteşeyi düğümünün kendi koordinatlarında okur:

val folds = rememberFoldsInNode()
Layout(content, modifier.then(folds.modifier)) { measurables, constraints ->
    val fold = folds.separatingFold          // bu düğümü kesen ve ayıran menteşe, yoksa null
    // fold.bounds ve fold.clearance pikseldir ve düğümün sol üst köşesinden ölçülür
    …
}

folds düğüm yer değiştirdikçe (sayfa kaydırılırken de), pencere değiştikçe ve cihaz katlandıkça yeniden okunur ve ölçen düzen yeniden ölçülür. Düğümü kesmeyen menteşe listede yoktur; düğümün kenarından geçen bir çizgi onu kesmez.

Önizleme ve testte katlanan bir pencere: PantrixWindowFrame(window, modifier) { … } bir kutuyu kendi başına pencere yapar. İçindekiler PantrixTheme.window ile verilen pencereyi okur; menteşesi kutunun sol üst köşesinden ölçülür, kutu ekranda nerede olursa olsun. Kutu ölçeklenmiş bir katmanda küçük çizilse de menteşe içerikle birlikte ölçeklenir. window = null kutunun içinde durduğu pencereyi geçirir: gerçek pencereyi uydurulmuş pencerelerle birlikte sunan bir seçici, içeriği her seçimde aynı yerden çağırır ve durumu kaybolmaz. Örnek uygulamanın kartlarındaki "Kitap" (951 × 669, dikey menteşe) ve "Masa" (669 × 951, yatay menteşe) pencereleri böyle çizilir; iPhone Duo'nun iç ekranının ölçüleridir.

val book = with(LocalDensity.current) {
    val x = 475.5.dp.toPx(); val h = 669.dp.toPx(); val pad = 20.dp.toPx()
    PantrixWindow(
        DpSize(951.dp, 669.dp),
        posture = WindowPosture(listOf(WindowFold(
            bounds = Rect(x, 0f, x, h), orientation = FoldOrientation.Vertical,
            isHalfOpened = true, isSeparating = true, isOccluding = false,
            clearance = Rect(x - pad, 0f, x + pad, h)
        )))
    )
}
PantrixWindowFrame(book, Modifier.size(951.dp, 669.dp)) { InboxScreen() }

Liste ve ayrıntı: PantrixListDetailScaffold

Bir liste ve listede açılan öğenin ayrıntısı en sık görülen uyarlanan düzendir: gelen kutusu, ayarlar, kişiler, dosyalar. Yukarıdaki if (panes >= 2) … else … kodunu her ekran yeniden yazmasın diye PantrixListDetailScaffold panel sayısını, tek paneldeki geçişi ve geri hareketini, odağı, menteşeyi ve listenin genişliğini kendisi yönetir.

val inbox = rememberListDetailState()                    // açık öğe, ekstra panel, listenin genişliği; kaydedilir

PantrixListDetailScaffold(
    state = inbox,
    list = { Messages(onOpen = { id -> inbox.select(id) }) },
    detail = { id ->
        // Tek panelde ayrıntı listenin yerindedir: geri düğmesi yalnızca o zaman durur.
        Message(id, onBack = if (isSinglePane) ({ inbox.back() }) else null)
    },
    modifier = Modifier.fillMaxSize(),
    emptyDetail = { PantrixText("Bir ileti seç") },           // iki panelde, açık öğe yokken
    listTitle = "Gelen kutusu",                               // ekran okuyucuya panellerin adı
    detailTitle = { id -> subjectOf(id) },
    extra = { id -> History(id, showHeader = !isExtraInSheet) },  // isteğe bağlı üçüncü panel
    extraTitle = "Geçmiş",
    resizable = true                                          // listenin genişliği sürüklenir
)

Örnek uygulamanın "Liste ve ayrıntı" sayfasında beş vaka kartlarıyla durur: gelen kutusu, ayarlar (iki panelde ilk bölüm açık), ekstra panelli kişiler, genişliği sürüklenen dosyalar, katlanan cihazda kat yerinde buluşma.

Ana içerik ve destek paneli: PantrixSupportingPaneScaffold

Bir ekranın asıl içeriği ve onu destekleyen bir panel: belge ve yorumları, grafik ve filtreleri, harita ve sonuçları. Liste-ayrıntıdan farkı, ana içeriğin hep orada olmasıdır: destek paneli ya yanında durur ya da üstünde açılır.

val comments = rememberSupportingPaneState()             // yanda gösterilip gösterilmediği, üstte açık olup olmadığı, genişliği

PantrixSupportingPaneScaffold(
    state = comments,
    main = { Document(onComments = { if (isSupportingShown) comments.hide() else comments.show() }) },
    supporting = {
        // Yanda panel kendi başlığını ve gizle düğmesini çizer; sheet'te başlık ve kapatma düğmesi iskelenindir.
        Comments(onHide = if (supportingPlace == SupportingPanePlace.Beside) ({ comments.hide() }) else null)
    },
    modifier = Modifier.fillMaxSize(),
    mainTitle = "Rapor",                                     // ekran okuyucuya panellerin adı
    supportingTitle = "Yorumlar",                            // sheet'in başlığı da
    presentation = SupportingPanePresentation.Sheet,         // dar yerde sheet (varsayılan) ya da Pane: ana içeriğin yerinde
    resizable = true                                         // destek panelinin genişliği sürüklenir
)

Örnek uygulamanın "Destek paneli" sayfasında beş vaka durur: belge ve yorumlar, genişliği sürüklenen grafik ve filtreler, dar yerde haritanın yerinde açılan sonuçlar, yanda gizleyip göstermek, katlanan cihazda kat yerinde ayrılma.

Masa duruşu: PantrixTabletopLayout

Bakılan bir içerik ve dokunulan denetimler: video ve oynatıcının düğmeleri, görüntülü görüşme ve düğmeleri, kameranın görüntüsü ve deklanşörü. Katlanabilir masaya yarı açık konunca (menteşe yatay) bakılan yukarıda, eller aşağıda olmalıdır.

PantrixTabletopLayout(
    content = { Video(player) },
    controls = {
        // Masada alt yarının tamamı denetimlerindir: büyük çizilebilirler.
        PlayerControls(player, large = arrangement == TabletopArrangement.Tabletop)
    },
    modifier = Modifier.fillMaxSize(),
    flatControls = TabletopControls.Below                    // düzken: Below (varsayılan) ya da Overlay
)

Örnek uygulamanın "Masa duruşu" sayfasında dört vaka durur: video oynatıcı, denetimleri videonun üstünde olan oynatıcı, görüntülü görüşme, ölçülerini yazan parçalar.

Menteşede katmanlar

Bir katlanabilir yarı açık tutulduğunda ya da iki ekranın arasında bir menteşe olduğunda, kat yerinin üstüne düşen bir dialog ya da popover ikiye bölünür. Pantrix'in katmanları kat yerinin bir yarısında kalır; kural katmanların ortak kodundadır, bileşenlere bir parametre gerekmez:

KatmanKitap gibi (menteşe dik)Masada (menteşe yatay)
Dialogaçıldığı yarının ortası; bilinmiyorsa başlangıç yarısıaçıldığı yarının ortası; bilinmiyorsa alt yarı
Alttan sheetaçıldığı yarı, o yarının genişliğindealt yarı; tam ekran sheet alt yarının tamamı
Yan sheettam son yarı: kat yerinin şeridinden pencerenin sonunaalt yarı
Komut paletidialog gibiüst yarı, yüksekliği o yarıyla sınırlı
Popover, menü, tooltip, select'in listesibağlandığı denetimin yarısıbağlandığı denetimin yarısı
Snackbarbir yarı: son köşedeki son yarı, ötekiler başlangıç yarısıyerinde

Kendi Dialog'unu ya da Popup'ını gösteren bir uygulama aynı bilgiyi rememberOverlayWindow() ile alır:

val overlay = rememberOverlayWindow()
// Açılırken bir kez: nereden açıldığı, ve pencereyi ikiye bölen kat yeri.
val openedFrom = remember { overlay.openedFrom() }
val fold = overlay.folds.firstOrNull { it.isSeparating }
val onTheLeft = fold != null && openedFrom != null && openedFrom.x < fold.bounds.center.x

OverlayWindow'un yerleri uygulamanın penceresinin koordinatlarında ve pikseldir: pencereyi dolduran bir Dialog içindekileri oraya yerleştirir. window katmanın penceresidir (çerçevenin ya da en dıştaki temanın), frame çerçevenin pencerede çizildiği yer, scale çerçevenin çizildiği ölçek.

Örten menteşe: Modifier.keepClearOfFolds()

İki ekranlı bir cihazda menteşe ekranın bir şeridini örter: altına çizilen görünmez. Menteşenin üstünden biraz taşan bir düğüm (bir ekrandan biraz geniş bir liste, bütün kenar boyunca bir çubuk) içindekileri çoğunun durduğu ekranda tutmalıdır:

Box(Modifier.fillMaxSize()) {
    Inbox()
    // Bütün alt kenar boyunca bir çubuk iki ekrandan birinde kalır.
    Toolbar(Modifier.align(Alignment.BottomCenter).fillMaxWidth().keepClearOfFolds())
}

Örnek uygulamanın "Menteşede katmanlar" sayfasında bunların hepsi durur: iki köşeden açılan dialog, kat yerinin yanında popover ve menü, sheet'ler, komut paleti ve snackbar, iki ekranlı bir cihazda keepClearOfFolds. Örnekler "Kitap" ve "Masa" pencerelerinde, kartın içinde çerçeveyi pencere sayarak açılır.

Sistemin dikey bar'ı: iPhone duo'nun dış ekranı

iOS 27.1, iPhone Duo'nun dış ekranında bar'ları dik koyar: ekranın bitiş kenarı boyunca bir sütun ayırır, tepesine durum çubuğunu dikey yerleştirir; UIKit bir sekme bar'ını ve bir gezinme bar'ının düğmelerini de o sütuna indirir. Uygulamanın içeriği güvenli alanla sütundan uzak durur. Simülatörde ölçülenler (466 × 678 pt): traitCollection.verticalBarEdge bitiş kenarını söyler, sütun sağ güvenli alan payı kadardır (84 pt), durum çubuğu sütunun üst 174 pt'sini kaplar, UIKit'in bir bar'a verdiği yer (layoutRegionForBarOnEdge) 174 ile 644 pt arasıdır. UIKit'in sekmeleri bu yerin dibinde, 48 pt'lik bir kapsülde durur; "…" gibi bir düğme durum çubuğunun hemen altında.

Pantrix bunu pencerenin bir parçası olarak verir ve kendi bileşenlerini oraya koyar:

PantrixScaffold(
    navigation = { placement, insets ->
        when (placement) {
            ScaffoldNavigationPlacement.Bottom -> PantrixNavigationBar(tabs, where, go, insets = insets)
            ScaffoldNavigationPlacement.VerticalBar ->
                PantrixNavigationBar(tabs, where, go, orientation = NavigationBarOrientation.Vertical)
            ScaffoldNavigationPlacement.Start -> PantrixNavigationRail(tabs, where, go, insets = insets)
        }
    }
) { padding -> Screen(padding) }

Bir önizleme ya da test, sütunu olan bir ekranı PantrixWindow(size, verticalBar = WindowVerticalBar(...)) ile kurar. Örnek uygulamanın kartlarındaki "Kapak" penceresi böyle kurulmuştur (466 × 678, 84'lük sütun); uygulamanın kendi kabuğu da Duo'nun dış ekranında böyle durur.

11. Girdi: parmak mı, fare mi?

Pencerenin boyutu, uygulamanın neyle kullanıldığını söylemez: 400 dp'lik bir pencere telefonun tarayıcısı da olabilir, daraltılmış bir masaüstü penceresi de. Bunu platform söyler:

val platform = PantrixTheme.platform

platform.isTouch          // birincil işaretçi parmak mı
platform.minTouchTarget   // dokunma hedefi; bileşenler bunu temadan kendileri alır
NeredeisTouchDokunma hedefi
Androidevet48 dp (Material)
iOSevet44 dp (Apple HIG)
Masaüstühayır32 dp
Web, masadaki tarayıcıhayır32 dp
Web, telefon ve tablet tarayıcısıevet48 dp

Web iki tür cihazda da çalışır ve hiçbir kılavuz onu kapsamaz; o yüzden işaretçisine bakar. Parmağa Android'de verilen ölçü verilir.

// Dokunmatik bir masaüstü uygulaması
PantrixTheme(platform = PantrixPlatform.forType(PlatformType.Desktop, isTouch = true)) { … }

// Testte ve önizlemede bir telefon tarayıcısı
PantrixTheme(platform = PantrixPlatform.forType(PlatformType.Web, isTouch = true)) { … }

"Dokunmatik mi" sorusu için platformun türüne bakma: telefonun tarayıcısı PlatformType.Web'dir ama parmakla kullanılır. isTouch'a sor:

if (PantrixTheme.platform.isTouch) PullToRefreshHint() else KeyboardShortcutsHint()

Web'de "kontrastı artır" ve "hareketi azalt" ayarları da aynı yolla okunur ve sayfa açıkken değişirlerse izlenir.

12. Düzen ölçüleri: kenar boşluğu, sütun arası, sütunlar ve içeriğin genişliği

Sayfanın dört ölçüsü pencerenin sınıfıyla değişir ve temadan okunur:

val layout = PantrixTheme.layout

Column(Modifier.padding(horizontal = layout.margin)) { … }             // sayfanın yanlardaki boşluğu
Row(horizontalArrangement = Arrangement.spacedBy(layout.gutter)) { … }  // sütunların ve panellerin arası
layout.columns                                                         // ızgaranın sütun sayısı
Column(Modifier.maxContentWidth()) { … }                               // içerik en fazla layout.maxContentWidth
SınıfGenişlik (dp)marginguttercolumnsmaxContentWidth
Compact< 60016164sınırsız
Medium600–83924168sınırsız
Expanded840–1199242412sınırsız
Large1200–15993224121040
ExtraLarge≥ 16003224121040

Telefonun verecek genişliği azdır, masaüstü penceresinin fazladır: kenar boşluğu ve sütun arası pencereyle büyür. İkisinin değerleri boşluk ölçeğinin adımlarıdır (lg, xl, xxl). Sütun sayısı Material'ın duyarlı ızgarasıdır; 1040, geniş ekranda içeriğin genişliğidir.

Ürün, tasarladığı sınıfların ölçülerini temaya verir; vermedikleri olduğu gibi kalır:

PantrixTheme(layouts = PantrixLayouts(compact = PantrixLayout(margin = 20.dp, gutter = 12.dp))) { … }

Kendi ölçüleri verilen bir sınıfın sütun sayısı ve en fazla genişliği de verilendir; verilmezse bir telefonunkilerdir, 4 sütun ve sınırsız. Daha geniş bir sınıfı veren ürün onları da verir:

PantrixLayouts(large = PantrixLayout(margin = 40.dp, gutter = 24.dp, columns = 12, maxContentWidth = 1200.dp))

ExtraLarge verilmezse Large'ın ölçülerini alır.

13. Testler

DosyaNe tutuyor
commonTest/.../adaptive/WindowSizeClassTest.ktSınırlar (599,9 / 600 …), sınıfların sırası, bilinmeyen boyut, kendi sınırların
commonTest/.../adaptive/WindowPostureTest.ktKitap, masa, düz açık, iki ekranlı menteşe
commonTest/.../adaptive/PantrixWindowTest.ktTemanın verdiği pencere, boyut değişimi, yeniden kurulma, sabitleme, iç içe tema
commonTest/.../adaptive/WindowHelpersTest.ktAltı yardımcı: verdikleri cevaplar ve ne zaman yeniden kurdukları
commonTest/.../adaptive/PanesTest.ktPanel sayısı (genişlik, kitap, masa, iki ekranlı menteşe, düz açık), çerçevenin penceresi, menteşenin bir düğümdeki yeri: çerçevede, iç içe çerçevede, küçük çizilen çerçevede, uygulamanın penceresinde düğüm yer değiştirince
iosTest/.../adaptive/UIKitFoldTest.ktUIKit'in söylediğinden duruş: kapak ekranı, kitap, masa, düz açık, menteşeyi örten bölge, menteşesi olmayan iPhone (iPhone Duo simülatörünün sayılarıyla)
commonTest/.../adaptive/BoxWithPantrixWindowTest.ktBileşenin kutusu: söylediği yer, pencereden aldığı sınırlar, ne zaman yeniden kurduğu; pencerenin söylediği sınırlar
androidHostTest/.../adaptive/WindowOnAndroidTest.ktAndroid'in bildirdiği pencere ve WindowManager'dan gelen menteşe
commonTest/.../adaptive/LayoutColumnsTest.ktHer sınıfın sütun sayısı ve en fazla içerik genişliği, yoğunluğun onları ölçeklememesi, spanWidth (sütunlar ve aradaki boşluklar, dar yer, sınırlar), Modifier.maxContentWidth (sınır, pencere yeniden boyutlanınca izlenmesi, başta, ortada, sonda, sağdan sola, ürünün sınırı, bileşenin kutusu, sınırsız genişlik)
commonTest/.../adaptive/PantrixLayoutTest.ktHer sınıfın kenar boşluğu ve sütun arası, sınıf değişince izlenmesi, yoğunlukla ölçeklenmesi, ürünün kendi ölçüleri, bileşenin kutusu
commonTest/.../a11y/PantrixPlatformTest.ktHer platformun dokunma hedefi, dokunmatik tarayıcı, isTouch, işaretçi değişince izlenmesi, yerleşim ve isabet
commonTest/.../theme/NestedThemeTest.ktİç içe tema: devralınanlar, bir kez ölçekleme, koyu bölüm ve preset'in koyu seti, verilenin içtekinin olması, preset'li tema, dıştaki değişince izlemek, tema dışından gelen değerin devralınmaması, içeriği çevredeki düzenin yerleştirmesi
androidHostTest/.../theme/ThemeOnAndroidTest.ktSistemin gece modunda tema; iç içe temada sistemin değil dıştaki temanın koyuluğu; test etiketlerinin otomasyona bir kez, en dıştaki temada açılması
webTest/.../adaptive/WindowPostureWebTest.ktTarayıcının söylediği menteşe: kitap, masa, iki ekranlı menteşe, düz açık; pencere ve yoğunluk değişince yeniden okuma; parçaların tarayıcıdan okunması; tarayıcının üç sorguyu tanıması; gerçek tarayıcıda düz
webTest/.../platform/MediaQueryTest.ktGerçek tarayıcıda: medya sorgusunun cevabı, sayfa açıkken değişmesi, dinlemenin bırakılması
ui/components · commonTest/.../TouchBrowserTest.ktDokunmatik tarayıcıda bileşenler: görünüş aynı, ayrılan yer parmağın
ui/components · commonTest/.../NestedThemeTest.ktKoyu bölümdeki kontroller: uygulamanın platformu, dili ve analytics'i; geçerli stiller, preset'li temada da
ui/components · commonTest/.../TwoPaneTest.ktİki panelin payı, kendi boyutu, alt alta, sağdan sola, menteşede buluşması (dikey ve yatay, sağdan sola), ayırmayan ve yerin dışındaki menteşe, sınırsız yükseklik, sınırsız bölünme yönü, varsayılan boşluk
ui/components · commonTest/.../ListDetailScaffoldTest.ktListenin genişlik sınırları; tek panel, geri (dağıtıcıdan), ilerleyen ve vazgeçilen geri hareketi, iki panelde uygulamaya kalan geri; iki ve üç panel, sağdan sola, menteşede buluşma ve tutamacın olmaması; ekstra panelin yanda ve kendini kaydırırken sheet'te durması; bir panelden ikiye korunan durum; tutamacın sürüklenmesi (fare, parmak), okları, Home ve End, ekran okuyucunun ayarlaması, sağdan sola; odağın gösterilen panelle gitmesi; durumun kaydedilmesi
ui/components · commonTest/.../ScaffoldTest.ktMasa duruşunda navigasyonun yeri, bir yarıya sığan scaffold, katlanırken korunan durum
commonTest/.../adaptive/WindowVerticalBarTest.ktPencerenin dikey bar'ı: sabit pencere, sistemin penceresinin onu izlemesi, testlerin platformlarında olmaması, düğümün koordinatlarında sütun, sütuna uzanmayan düğüm, çerçevenin ve temanın penceresi, düğüm yerleşmeden önce
iosTest/.../adaptive/UIKitVerticalBarTest.ktUIKit'in söylediğinden dikey bar (iPhone Duo'nun dış ekranının sayılarıyla), sayı yoksa bar yok
ui/components · commonTest/.../ScaffoldVerticalBarTest.ktSütunda gezinme ve düğmeler, sütunsuz ve sütuna uzanmayan scaffold, Start'a karşı dikey bar, içeriğin payı, soldaki sütun, sağdan sola, kısa scaffold, son gelen bar'ın düğmeleri, iç içe scaffold, sütuna girerken korunan kompozisyon
ui/components · commonTest/.../NavigationBarUprightTest.ktDik bar: alt alta hedefler, etiketsiz hedefin adı, uzun basışta tooltip, etiketler ve kapsülün boşlukları, kapsülün içinde kalan rozet (soldan sağa ve sağdan sola), alttaki bar'da yalnız ikonlar
app/shared · jvmTest/.../demo/AdaptiveSamplesTest.ktÖrnek uygulamadaki üç vaka: giriş ekranı, tablo bileşeni, göstermek ve gizlemek (kullanım rehberi); pencere sayfasında duruşun adı ve panellerin menteşede ayrılması; liste ve ayrıntı sayfasında seçilen pencereye göre panel sayısı, kitapta eşit iki sayfa, tek panelde açılan ileti ve geri düğmesi
app/shared · jvmTest/.../demo/NestedThemeSamplesTest.ktÖrnek uygulamanın iç içe tema sayfası: sayfanın uygulamadan aldıkları, koyu bölüm, sıkışık bölüm, başka dilde bölüm, olayların sayfaya gitmesi, bir Row içindeki tema
./gradlew :ui:foundation:jvmTest --tests 'com.pantrixui.foundation.adaptive.*'
./gradlew :ui:foundation:testAndroidHostTest

14. Henüz olmayanlar