Pantrix UI

Two Pane

Aynı yerde iki panel: yan yana ya da alt alta. Pencerenin bir kat yeri bu yeri ayırıyorsa oradan bölünür.

import com.pantrixui.components.container.twopane.PantrixTwoPane

Örnekler

Liste ve açılan, yan yana

Bir gelen kutusu ve açılan ileti, yan yana: liste 300 dp, ileti geri kalanı. Bir iletiye bas: yanındaki panel onu gösterir.

Karttaki pencere seçicisinden "Kitap"ı seç: kat yeri ortadadır ve paneller tam orada ayrılır. Liste artık 300 dp değil, kat yerine kadardır; aradaki boşluk kat yerinin iki yanındaki 20'şer dp'lik şerittir. Örneğin üstündeki soluk şerit ve çizgi kat yerinin yeridir. Öteki pencerelerde liste 300 dp'dir ve aralarında düzenin sütun arası durur. "Dar"da iletiye yer kalmaz: bir sonraki örnek orada tek panel gösterir.

var opened by rememberSaveable { mutableStateOf(MAILS.first().id) }
val scroll = rememberScrollState()
PantrixTwoPane(
    first = { Inbox(opened = opened, onOpen = { opened = it }, scroll = scroll, tagKey = "inbox") },
    second = { Message(mail = MAILS.first { it.id == opened }) },
    modifier = Modifier.fillMaxWidth().height(INBOX_HEIGHT),
    firstSize = PaneSize.Fixed(300.dp),
    tagKey = "inbox"
)

@Composable
private fun Inbox(
    opened: String?,
    onOpen: (String) -> Unit,
    scroll: ScrollState,
    tagKey: String
) {
    val colors = PantrixTheme.colors
    PantrixSurface(modifier = Modifier.fillMaxSize(), border = colors.border) {
        Column(modifier = Modifier.verticalScroll(scroll)) {
            MAILS.forEachIndexed { index, mail ->
                if (index > 0) PantrixHorizontalDivider()
                val selected = mail.id == opened
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .selectable(selected = selected, onClick = { onOpen(mail.id) })
                        .background(if (selected) colors.primary.container else Color.Transparent)
                        .padding(PantrixTheme.spacing.md),
                    verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.xs)
                ) {
                    val text = if (selected) colors.primary.onContainer else Color.Unspecified
                    PantrixText(text = mail.from, style = PantrixTheme.typography.callout, color = text, maxLines = 1)
                    PantrixText(
                        text = mail.subject,
                        style = PantrixTheme.typography.footnote,
                        color = if (selected) text else colors.textSecondary,
                        maxLines = 1,
                        tagKey = "${tagKey}_mail_${mail.id}"
                    )
                }
            }
        }
    }
}

@Composable
private fun Message(
    mail: Mail,
    onBack: (() -> Unit)? = null
) {
    val spacing = PantrixTheme.spacing
    PantrixSurface(modifier = Modifier.fillMaxSize(), border = PantrixTheme.colors.border) {
        Column(
            modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(spacing.lg),
            verticalArrangement = Arrangement.spacedBy(spacing.sm)
        ) {
            if (onBack != null) {
                PantrixButton(
                    text = "Gelen kutusu",
                    onClick = onBack,
                    variant = ButtonVariant.Text,
                    leadingIcon = { PantrixIcon(PantrixIcons.ArrowBack, contentDescription = null) },
                    tagKey = "back"
                )
            }
            PantrixText(text = mail.subject, style = PantrixTheme.typography.headline, tagKey = "message_subject")
            PantrixText(
                text = mail.from,
                style = PantrixTheme.typography.footnote,
                color = PantrixTheme.colors.textSecondary
            )
            PantrixText(text = mail.body)
        }
    }
}

private val MAILS = listOf(
    Mail("1", "Elif Kaya", "Yarınki toplantı", "Toplantıyı saat 10'a aldım. Sunumu bir gün önce paylaşır mısın?"),
    Mail("2", "Can Demir", "Sürüm notları", "1.4 sürümünün notlarını ekledim. Eksik gördüğün bir şey olursa yaz."),
    Mail("3", "Ayşe Yılmaz", "Tasarım incelemesi", "Yeni ayarlar ekranının taslakları hazır; perşembe bakalım mı?"),
    Mail("4", "Mert Şahin", "Fatura", "Ekim faturası ektedir. Ödeme ayın 30'una kadar yapılabilir."),
    Mail("5", "Zeynep Arslan", "Test sonuçları", "Gece çalışan testlerin hepsi geçti; tablet ekranında bir uyarı var."),
    Mail("6", "Deniz Aydın", "Öğle yemeği", "Cuma günü ekipçe dışarıda yiyelim mi? Yer önerisi olan yazsın."),
    Mail("7", "Burak Çelik", "Erişilebilirlik", "Ekran okuyucuyla bütün akışı denedim; iki düğmenin adı eksik."),
    Mail("8", "Selin Koç", "Yeni katılan", "Pazartesi ekibe yeni bir geliştirici katılıyor; ilk haftayı planlayalım."),
    Mail("9", "Emre Öztürk", "Sunucu bakımı", "Cumartesi gece 02:00 ile 04:00 arasında sunucular bakımda olacak."),
    Mail("10", "Ece Polat", "Kullanıcı görüşmeleri", "Bu hafta beş kullanıcıyla konuştum; notlar paylaşılan klasörde.")
)

private val INBOX_HEIGHT = 420.dp

private class Mail(
    val id: String,
    val from: String,
    val subject: String,
    val body: String
)

Bir panel ya da iki: paneCount

Kaç panel gösterileceğini pencere söyler: PantrixTheme.window.paneCount(). İki panele yer varsa liste ve ileti yan yana durur. Yoksa biri görünür: önce liste, bir iletiye basınca ileti, "Gelen kutusu" düğmesiyle yine liste.

Panel sayısı değişince liste başka bir yerden çağrılır ve yeniden kurulur. Kaydırma yeri yine kaybolmaz: onu liste değil örnek hatırlar, rememberScrollState(), ve listeye verir. Listeyi kaydır, bir iletiyi aç, sonra karttaki pencere seçicisinden "Dar" ile "Geniş" arasında geç: açılan ileti de listenin yeri de kalır.

"Dar" ve "Orta" bir panel, "Kısa", "Geniş" ve "Kitap" iki panel yazar. "Kitap"ın 951 dp'si zaten Expanded'dır; daha dar bir katlanabilir de kitap gibi tutulunca iki panel alır. "Masa"da kat yeri yataydır ve yan yana iki paneli ayırmaz: 669 dp'lik pencere Medium'dur ve tek panel gösterir.

var opened by rememberSaveable { mutableStateOf<String?>(null) }
val scroll = rememberScrollState()
val panes = PantrixTheme.window.paneCount()
val mail = MAILS.firstOrNull { it.id == opened }
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    PantrixText(
        text = "paneCount(): $panes",
        style = PantrixTheme.typography.footnote,
        color = PantrixTheme.colors.textSecondary,
        tagKey = "pane_count"
    )
    Box(modifier = Modifier.fillMaxWidth().height(INBOX_HEIGHT)) {
        when {
            panes >= 2 -> {
                PantrixTwoPane(
                    first = { Inbox(opened = opened, onOpen = { opened = it }, scroll = scroll, tagKey = "count") },
                    second = { if (mail != null) Message(mail = mail) else NothingOpened() },
                    modifier = Modifier.fillMaxSize(),
                    firstSize = PaneSize.Fixed(300.dp),
                    tagKey = "count"
                )
            }

            // Tek panelde açık bir ileti listenin yerini alır; liste kapanınca da kaydırma yeri durur.
            mail != null -> {
                Message(mail = mail, onBack = { opened = null })
            }

            else -> {
                Inbox(opened = null, onOpen = { opened = it }, scroll = scroll, tagKey = "count")
            }
        }
    }
}

@Composable
private fun Inbox(
    opened: String?,
    onOpen: (String) -> Unit,
    scroll: ScrollState,
    tagKey: String
) {
    val colors = PantrixTheme.colors
    PantrixSurface(modifier = Modifier.fillMaxSize(), border = colors.border) {
        Column(modifier = Modifier.verticalScroll(scroll)) {
            MAILS.forEachIndexed { index, mail ->
                if (index > 0) PantrixHorizontalDivider()
                val selected = mail.id == opened
                Column(
                    modifier = Modifier
                        .fillMaxWidth()
                        .selectable(selected = selected, onClick = { onOpen(mail.id) })
                        .background(if (selected) colors.primary.container else Color.Transparent)
                        .padding(PantrixTheme.spacing.md),
                    verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.xs)
                ) {
                    val text = if (selected) colors.primary.onContainer else Color.Unspecified
                    PantrixText(text = mail.from, style = PantrixTheme.typography.callout, color = text, maxLines = 1)
                    PantrixText(
                        text = mail.subject,
                        style = PantrixTheme.typography.footnote,
                        color = if (selected) text else colors.textSecondary,
                        maxLines = 1,
                        tagKey = "${tagKey}_mail_${mail.id}"
                    )
                }
            }
        }
    }
}

@Composable
private fun Message(
    mail: Mail,
    onBack: (() -> Unit)? = null
) {
    val spacing = PantrixTheme.spacing
    PantrixSurface(modifier = Modifier.fillMaxSize(), border = PantrixTheme.colors.border) {
        Column(
            modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(spacing.lg),
            verticalArrangement = Arrangement.spacedBy(spacing.sm)
        ) {
            if (onBack != null) {
                PantrixButton(
                    text = "Gelen kutusu",
                    onClick = onBack,
                    variant = ButtonVariant.Text,
                    leadingIcon = { PantrixIcon(PantrixIcons.ArrowBack, contentDescription = null) },
                    tagKey = "back"
                )
            }
            PantrixText(text = mail.subject, style = PantrixTheme.typography.headline, tagKey = "message_subject")
            PantrixText(
                text = mail.from,
                style = PantrixTheme.typography.footnote,
                color = PantrixTheme.colors.textSecondary
            )
            PantrixText(text = mail.body)
        }
    }
}

@Composable
private fun NothingOpened() {
    PantrixSurface(modifier = Modifier.fillMaxSize(), border = PantrixTheme.colors.border) {
        PantrixText(
            text = "Okumak için bir ileti seç",
            modifier = Modifier.align(Alignment.Center),
            color = PantrixTheme.colors.textSecondary,
            tagKey = "nothing_opened"
        )
    }
}

private val MAILS = listOf(
    Mail("1", "Elif Kaya", "Yarınki toplantı", "Toplantıyı saat 10'a aldım. Sunumu bir gün önce paylaşır mısın?"),
    Mail("2", "Can Demir", "Sürüm notları", "1.4 sürümünün notlarını ekledim. Eksik gördüğün bir şey olursa yaz."),
    Mail("3", "Ayşe Yılmaz", "Tasarım incelemesi", "Yeni ayarlar ekranının taslakları hazır; perşembe bakalım mı?"),
    Mail("4", "Mert Şahin", "Fatura", "Ekim faturası ektedir. Ödeme ayın 30'una kadar yapılabilir."),
    Mail("5", "Zeynep Arslan", "Test sonuçları", "Gece çalışan testlerin hepsi geçti; tablet ekranında bir uyarı var."),
    Mail("6", "Deniz Aydın", "Öğle yemeği", "Cuma günü ekipçe dışarıda yiyelim mi? Yer önerisi olan yazsın."),
    Mail("7", "Burak Çelik", "Erişilebilirlik", "Ekran okuyucuyla bütün akışı denedim; iki düğmenin adı eksik."),
    Mail("8", "Selin Koç", "Yeni katılan", "Pazartesi ekibe yeni bir geliştirici katılıyor; ilk haftayı planlayalım."),
    Mail("9", "Emre Öztürk", "Sunucu bakımı", "Cumartesi gece 02:00 ile 04:00 arasında sunucular bakımda olacak."),
    Mail("10", "Ece Polat", "Kullanıcı görüşmeleri", "Bu hafta beş kullanıcıyla konuştum; notlar paylaşılan klasörde.")
)

private val INBOX_HEIGHT = 420.dp

private class Mail(
    val id: String,
    val from: String,
    val subject: String,
    val body: String
)

Masada: üstte video, altta kontroller

Masaya konan bir katlanabilir için: üstte bakılan, altta kontroller. TwoPaneOrientation.Stacked birinci paneli üste koyar; burada üstteki yerin %60'ını alır.

Karttaki pencere seçicisinden "Masa"yı seç: pencere 669 × 951 dp'dir ve kat yeri ortasında yatay durur. Düzen masa duruşunda pencerenin bütün yüksekliğini alır, byPosture(flat = 360.dp, tabletop = window.size.height), ve paneller tam kat yerinde ayrılır: video üst yarıda, kontroller alt yarıda. Öteki pencerelerde düzen 360 dp yüksekliğindedir ve payla bölünür. "Kitap"ta kat yeri dikeydir, alt alta iki paneli ayırmaz: pay geçer.

val window = PantrixTheme.window
var playing by rememberSaveable { mutableStateOf(false) }
var second by rememberSaveable { mutableIntStateOf(VIDEO_START) }
PantrixTwoPane(
    first = { Player(playing = playing, second = second) },
    second = {
        PlayerControls(
            playing = playing,
            onPlay = { playing = !playing },
            onBack = { second = (second - SKIP).coerceAtLeast(0) },
            onForward = { second = (second + SKIP).coerceAtMost(VIDEO_LENGTH) }
        )
    },
    modifier = Modifier.fillMaxWidth().height(window.byPosture(flat = 360.dp, tabletop = window.size.height)),
    orientation = TwoPaneOrientation.Stacked,
    firstSize = PaneSize.Fraction(0.6f),
    tagKey = "player"
)

@Composable
private fun Player(
    playing: Boolean,
    second: Int
) {
    PantrixSurface(modifier = Modifier.fillMaxSize(), color = PantrixTheme.colors.textPrimary) {
        Column(
            modifier = Modifier.align(Alignment.Center),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)
        ) {
            PantrixIcon(
                imageVector = if (playing) PantrixIcons.Pause else PantrixIcons.Play,
                contentDescription = null,
                size = 48.dp,
                tint = PantrixTheme.colors.background
            )
            PantrixText(
                text = "${clock(second)} / ${clock(VIDEO_LENGTH)}",
                color = PantrixTheme.colors.background,
                tagKey = "video_time"
            )
        }
    }
}

@Composable
private fun PlayerControls(
    playing: Boolean,
    onPlay: () -> Unit,
    onBack: () -> Unit,
    onForward: () -> Unit
) {
    PantrixSurface(modifier = Modifier.fillMaxSize(), border = PantrixTheme.colors.border) {
        Row(
            modifier = Modifier.align(Alignment.Center),
            horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
            verticalAlignment = Alignment.CenterVertically
        ) {
            PantrixButton(text = "10 sn geri", onClick = onBack, variant = ButtonVariant.Outline, tagKey = "rewind")
            PantrixButton(text = if (playing) "Duraklat" else "Oynat", onClick = onPlay, tagKey = "play")
            PantrixButton(text = "10 sn ileri", onClick = onForward, variant = ButtonVariant.Outline, tagKey = "skip")
        }
    }
}

private const val VIDEO_START = 83

private const val VIDEO_LENGTH = 225

private const val SKIP = 10

private fun clock(seconds: Int): String =
    "${(seconds / MINUTE).toString().padStart(2, '0')}:${(seconds % MINUTE).toString().padStart(2, '0')}"

private const val MINUTE = 60

Pay, sabit boyut, yön ve okuma yönü

Panellerin ölçüsü ve yönü. Seçicilerle birincinin payını, panellerin yönünü ve okuma yönünü değiştir: her panel kendi boyutunu yazar.

var size by rememberSaveable { mutableIntStateOf(0) }
var stacked by rememberSaveable { mutableStateOf(false) }
var rtl by rememberSaveable { mutableStateOf(false) }
val spacing = PantrixTheme.spacing
Column(verticalArrangement = Arrangement.spacedBy(spacing.sm)) {
    PantrixSegmentButton(
        options = SIZES.mapIndexed { index, (label, _) -> SegmentOption(index, label, tagKey = "size_$index") },
        selected = size,
        onSelect = { size = it },
        tagKey = "size"
    )
    PantrixSegmentButton(
        options = listOf(
            SegmentOption(false, "Yan yana", tagKey = "side_by_side"),
            SegmentOption(true, "Alt alta", tagKey = "stacked")
        ),
        selected = stacked,
        onSelect = { stacked = it },
        tagKey = "orientation"
    )
    PantrixSegmentButton(
        options = listOf(
            SegmentOption(false, "Soldan sağa", tagKey = "ltr"),
            SegmentOption(true, "Sağdan sola", tagKey = "rtl")
        ),
        selected = rtl,
        onSelect = { rtl = it },
        tagKey = "direction"
    )
}
val direction = if (rtl) LayoutDirection.Rtl else LayoutDirection.Ltr
CompositionLocalProvider(LocalLayoutDirection provides direction) {
    PantrixTwoPane(
        first = { MeasuredPane(name = "Birinci", color = PantrixTheme.colors.primary.container) },
        second = { MeasuredPane(name = "İkinci", color = PantrixTheme.colors.surface) },
        modifier = Modifier.fillMaxWidth().height(240.dp),
        orientation = if (stacked) TwoPaneOrientation.Stacked else TwoPaneOrientation.SideBySide,
        firstSize = SIZES[size].second,
        tagKey = "sizes"
    )
}

private val SIZES = listOf(
    "Yarı yarıya" to PaneSize.Fraction(0.5f),
    "%30" to PaneSize.Fraction(0.3f),
    "200 dp" to PaneSize.Fixed(200.dp)
)

@Composable
private fun MeasuredPane(
    name: String,
    color: Color
) {
    var size by remember { mutableStateOf(IntSize.Zero) }
    val density = LocalDensity.current
    val words = with(density) { "${size.width.toDp().value.roundToInt()} × ${size.height.toDp().value.roundToInt()}" }
    PantrixSurface(
        modifier = Modifier.fillMaxSize().onSizeChanged { size = it },
        color = color,
        border = PantrixTheme.colors.border
    ) {
        // Ölçüler soldan sağa yazılır: sağdan sola bir paragrafta genişlik ile yükseklik yer değiştirmiş okunurdu.
        CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
            Column(modifier = Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally) {
                PantrixText(text = name, style = PantrixTheme.typography.callout)
                PantrixText(text = "$words dp", style = PantrixTheme.typography.footnote)
            }
        }
    }
}

Kendi düzenin: rememberFoldsInNode

Kat yeriye göre dizilen kendi düzenini yazmak için: rememberFoldsInNode() kat yerlerini bir düğümün kendi koordinatlarında, pikselle verir. modifier'ını düğüme koy ve ölçerken folds'u oku: düğüm yer değiştirdikçe, pencere değiştikçe ve cihaz katlandıkça düzen yeniden ölçülür. Düğümü kesmeyen kat yeri listede yoktur.

Buradaki araç çubuğu düğmelerini ortaya dizer. Dikey bir kat yeri onu kesiyorsa düğmelerin yarısı kat yerinin önüne, yarısı arkasına geçer: hiçbir düğme kat yerine düşmez. Karttaki pencere seçicisinden "Kitap"ı seç: düğmeler kat yerinin iki yanına ayrılır ve alttaki satır kat yerinin çubuktaki yerini yazar.

val folds = rememberFoldsInNode()
val density = LocalDensity.current
Column(verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
    FoldAwareToolbar(modifier = Modifier.fillMaxWidth().then(folds.modifier)) {
        TOOLS.forEachIndexed { index, tool ->
            PantrixButton(text = tool, onClick = {}, variant = ButtonVariant.Soft, tagKey = "tool_$index")
        }
    }
    val fold = folds.separatingFold
    val words = if (fold == null) {
        "Bu çubuğu kesen bir kat yeri yok"
    } else {
        with(density) {
            val start = fold.clearance.left.toDp().value.roundToInt()
            val end = fold.clearance.right.toDp().value.roundToInt()
            "Kat yeri çubuğun $start–$end dp'sinde, ${fold.orientation.name}"
        }
    }
    PantrixText(
        text = words,
        style = PantrixTheme.typography.footnote,
        color = PantrixTheme.colors.textSecondary,
        tagKey = "fold_in_node"
    )
}

@Composable
private fun FoldAwareToolbar(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    val folds = rememberFoldsInNode()
    val gap = PantrixTheme.spacing.sm
    Layout(content = content, modifier = modifier.then(folds.modifier)) { measurables, constraints ->
        val buttons = measurables.map { it.measure(constraints.copy(minWidth = 0)) }
        val space = gap.roundToPx()
        val width = constraints.maxWidth
        val height = buttons.maxOf { it.height }
        val fold = folds.separatingFold?.takeIf { it.orientation == FoldOrientation.Vertical }
        layout(width, height) {
            if (fold == null) {
                // Kat yeri yok: hepsi ortada.
                val row = buttons.sumOf { it.width } + space * (buttons.size - 1)
                var x = (width - row) / 2
                buttons.forEach { button ->
                    button.placeRelative(x, (height - button.height) / 2)
                    x += button.width + space
                }
            } else {
                // Kat yeri var: yarısı şeridin önünde ona dayalı, yarısı arkasında. Kat yeri ekranda olduğu yerde
                // durduğu için düğmeler ekrandaki yerlerine konur.
                val before = buttons.take(buttons.size / 2)
                var x = fold.clearance.left.roundToInt() - before.sumOf { it.width + space }
                (before + buttons.drop(before.size)).forEachIndexed { index, button ->
                    if (index == before.size) x = fold.clearance.right.roundToInt() + space
                    button.place(x, (height - button.height) / 2)
                    x += button.width + space
                }
            }
        }
    }
}

private val TOOLS = listOf("Kes", "Kopyala", "Yapıştır", "Sil")

Önizlemede katlanan bir pencere

Bir önizlemede ya da bir testte katlanan bir pencere: PantrixWindowFrame bir kutuyu kendi başına pencere yapar. İçindekiler PantrixTheme.window ile ona verilen pencereyi okur, kat yerini de kutunun köşesinden ölçer. Burada 280 × 160 dp'lik, ortasında kitap gibi yarı açık bir kat yeri olan bir pencere kuruldu: içindeki iki panel tam kat yerinde ayrılır ve dar pencere kitap duruşunda iki panel yazar.

Kat yerinin yeri pikselledir. WindowFold'un bounds'u kat yerinin kendisidir; katlanan bir telefonda kalınlığı olmayan bir çizgi. clearance dokunulanların boş bıraktığı şerittir: Android'de kat yerinin kendisi, iOS'ta iki yanında 20 pt daha. Bu pencerede şerit iki yanda 12 dp'dir.

val density = LocalDensity.current
val window = remember(density) { bookWindow(DpSize(280.dp, 160.dp), margin = 12.dp, density = density) }
PantrixWindowFrame(
    window = window,
    modifier = Modifier.size(280.dp, 160.dp).border(1.dp, PantrixTheme.colors.border)
) {
    PantrixTwoPane(
        first = { MeasuredPane(name = "Sol sayfa", color = PantrixTheme.colors.surface) },
        second = { MeasuredPane(name = "Sağ sayfa", color = PantrixTheme.colors.surface) },
        modifier = Modifier.fillMaxSize(),
        tagKey = "framed"
    )
}
PantrixText(
    text = "${window.widthClass.name} genişlik · paneCount(): ${window.paneCount()} · " +
        "isBook: ${if (window.posture.isBook) "evet" else "hayır"}",
    style = PantrixTheme.typography.footnote,
    color = PantrixTheme.colors.textSecondary,
    tagKey = "framed_window"
)

internal fun bookWindow(
    size: DpSize,
    margin: Dp,
    density: Density
): PantrixWindow = with(density) {
    val middle = size.width.toPx() / 2
    val height = size.height.toPx()
    val fold = WindowFold(
        bounds = Rect(left = middle, top = 0f, right = middle, bottom = height),
        orientation = FoldOrientation.Vertical,
        isHalfOpened = true,
        isSeparating = true,
        isOccluding = false,
        clearance = Rect(left = middle - margin.toPx(), top = 0f, right = middle + margin.toPx(), bottom = height)
    )
    PantrixWindow(size, posture = WindowPosture(listOf(fold)))
}

@Composable
private fun MeasuredPane(
    name: String,
    color: Color
) {
    var size by remember { mutableStateOf(IntSize.Zero) }
    val density = LocalDensity.current
    val words = with(density) { "${size.width.toDp().value.roundToInt()} × ${size.height.toDp().value.roundToInt()}" }
    PantrixSurface(
        modifier = Modifier.fillMaxSize().onSizeChanged { size = it },
        color = color,
        border = PantrixTheme.colors.border
    ) {
        // Ölçüler soldan sağa yazılır: sağdan sola bir paragrafta genişlik ile yükseklik yer değiştirmiş okunurdu.
        CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
            Column(modifier = Modifier.align(Alignment.Center), horizontalAlignment = Alignment.CenterHorizontally) {
                PantrixText(text = name, style = PantrixTheme.typography.callout)
                PantrixText(text = "$words dp", style = PantrixTheme.typography.footnote)
            }
        }
    }
}

API

first

Aynı yerde iki panel: yan yana ya da alt alta. Pencerenin bir kat yeri bu yeri ayırıyorsa oradan bölünür.

ParametreTürVarsayılanNe olduğu
first@Composable () -> Unitbirinci panel: ikincinin yanındayken başta, alt altayken üstte.
second@Composable () -> Unitikinci panel.
modifierModifierModifieriki panelin paylaştığı yer.
orientationTwoPaneOrientationTwoPaneOrientation.SideBySidepanellerin hangi yönde durduğu.
firstSizePaneSizePaneSize.Fraction(0.5f)yeri bir kat yeri ayırmıyorsa birinci panelin ne kadar aldığı: varsayılanı yarısı.
gapDpPantrixTheme.layout.gutteryeri bir kat yeri ayırmıyorsa paneller arasındaki boşluk: varsayılanı düzenin sütun arası.
tagKeyString?nullpanel çiftinin otomasyon etiketinin anahtarı.