Compose for Desktopアプリでドラッグしてコントロールのサイズを変更する

Compose for Desktopでドラッグしてコントロールのサイズを変更するサンプルプログラムです。

2つのテキストエリアを分割線で分け、その分割線をドラッグしてテキストエリアの幅を調整します。

画面イメージ

サンプルプログラムの説明

App関数

アプリケーションのメインビューを定義します。

このビューには、Cyan色のテキスト、ドラッグ可能なDivider、そしてYellow色のテキストが含まれています。

テキストの幅は、ドラッグによって変更可能です。

@Composable
@Preview
fun App() {
    // 幅を保持するState
    val widthText = remember { mutableStateOf(200.dp) }
    MaterialTheme {
        Row(modifier = Modifier.fillMaxSize()) {
            DisplayText(widthText.value, Color.Cyan)
            DisplayDivider(widthText)
            DisplayText(200.dp, Color.Yellow)
        }
    }
}

DisplayText

指定された幅と色でテキストを表示します。

テキストは幅の値で、背景色は指定された色です。

@Composable
fun DisplayText(width: Dp, color: Color) {
    Text(
        "${width.value.toInt()}dp",
        modifier = Modifier.width(width).fillMaxHeight().background(color = color)
    )
}

DisplayDivider関数

ドラッグで幅を変更できるDivider(区切り線)を表示します。

このDividerは、ドラッグジェスチャーを検出し、ドラッグの量に基づいて幅を変更します。

また、マウスが上にホバーすると、カーソルがリサイズカーソルに変わります。

@Composable
fun DisplayDivider(widthText: MutableState<Dp>) {
    // 画面密度
    val density = LocalDensity.current

    Divider(
        color = Color.Red,
        modifier = Modifier
            .fillMaxHeight()
            // 幅を指定
            .width(8.dp)
            // ドラッグジェスチャーを検出するためのModifier
            .pointerInput(Unit) {
                // ドラッグジェスチャーを検出
                detectDragGestures { change, dragAmount ->
                    // ドラッグ量を取得し、幅を変更
                    with(density) {
                        val dragAmountDp = dragAmount.x.toDp()
                        val newWidth = (widthText.value + dragAmountDp).coerceIn(50.dp, 400.dp)
                        if (change.pressed) {
                            widthText.value = newWidth
                        }
                    }
                }
            }
            // マウスホバー時のカーソルを変更
            .pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
    )
}

サンプルプログラム全文

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
import java.awt.Cursor

/**
 * 指定された幅と色でテキストを表示します。
 * テキストは幅の値で、背景色は指定された色です。
 *
 * @param width 幅
 * @param color 背景色
 */
@Composable
fun DisplayText(width: Dp, color: Color) {
    Text(
        "${width.value.toInt()}dp",
        modifier = Modifier.width(width).fillMaxHeight().background(color = color)
    )
}

/**
 * ドラッグで幅を変更できるDividerを表示します。
 *
 * @param widthText 幅を保持するState
 */
@Composable
fun DisplayDivider(widthText: MutableState<Dp>) {
    // 画面密度
    val density = LocalDensity.current

    Divider(
        color = Color.Red,
        modifier = Modifier
            .fillMaxHeight()
            // 幅を指定
            .width(8.dp)
            // ドラッグジェスチャーを検出するためのModifier
            .pointerInput(Unit) {
                // ドラッグジェスチャーを検出
                detectDragGestures { change, dragAmount ->
                    // ドラッグ量を取得し、幅を変更
                    with(density) {
                        val dragAmountDp = dragAmount.x.toDp()
                        val newWidth = (widthText.value + dragAmountDp).coerceIn(50.dp, 400.dp)
                        if (change.pressed) {
                            widthText.value = newWidth
                        }
                    }
                }
            }
            // マウスホバー時のカーソルを変更
            .pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
    )
}

@Composable
@Preview
fun App() {
    // 幅を保持するState
    val widthText = remember { mutableStateOf(200.dp) }
    MaterialTheme {
        Row(modifier = Modifier.fillMaxSize()) {
            DisplayText(widthText.value, Color.Cyan)
            DisplayDivider(widthText)
            DisplayText(200.dp, Color.Yellow)
        }
    }
}

fun main() = application {
    Window(
        onCloseRequest = ::exitApplication,
        state = WindowState(size = DpSize(500.dp, 200.dp))
    ) {
        App()
    }
}

既知の問題

マルチディスプレイ環境において、異なるDPIのディスプレイにウィンドウを移動すると、ドラッグした距離を正しく計算できません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください