[go: nahoru, domu]

blob: cdcef142b0683ab852928c0774176cac53c55504 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.core.selection
import androidx.compose.runtime.Composable
import androidx.compose.runtime.emptyContent
import androidx.compose.runtime.remember
import androidx.ui.core.Layout
import androidx.ui.core.Modifier
import androidx.ui.core.drawBehind
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.text.InternalTextApi
import androidx.compose.ui.text.style.ResolvedTextDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
internal val HANDLE_WIDTH = 25.dp
internal val HANDLE_HEIGHT = 25.dp
private val HANDLE_COLOR = Color(0xFF2B28F5.toInt())
@Composable
internal fun SelectionHandleLayout(modifier: Modifier, left: Boolean) {
val selectionHandleCache = remember { SelectionHandleCache() }
HandleDrawLayout(modifier = modifier, width = HANDLE_WIDTH, height = HANDLE_HEIGHT) {
drawPath(selectionHandleCache.createPath(this, left), HANDLE_COLOR)
}
}
/**
* Class used to cache a Path object to represent a selection handle
* based on the given handle direction
*/
private class SelectionHandleCache {
private var path: Path? = null
private var left: Boolean = false
fun createPath(density: Density, left: Boolean): Path {
return with(density) {
val current = path
if (this@SelectionHandleCache.left == left && current != null) {
// If we have already created the Path for the correct handle direction
// return it
current
} else {
this@SelectionHandleCache.left = left
// Otherwise, if this is the first time we are creating the Path
// or the current handle direction is different than the one we
// previously created, recreate the path and cache the result
(current ?: Path().also { path = it }).apply {
reset()
addRect(
Rect(
top = 0f,
bottom = 0.5f * HANDLE_HEIGHT.toPx(),
left = if (left) {
0.5f * HANDLE_WIDTH.toPx()
} else {
0f
},
right = if (left) {
HANDLE_WIDTH.toPx()
} else {
0.5f * HANDLE_WIDTH.toPx()
}
)
)
addOval(
Rect(
top = 0f,
bottom = HANDLE_HEIGHT.toPx(),
left = 0f,
right = HANDLE_WIDTH.toPx()
)
)
}
}
}
}
}
/**
* Simple container to perform drawing of selection handles. This layout takes size on the screen
* according to [width] and [height] params and performs drawing in this space as specified in
* [onCanvas]
*/
@Composable
private fun HandleDrawLayout(
modifier: Modifier,
width: Dp,
height: Dp,
onCanvas: DrawScope.() -> Unit
) {
Layout(emptyContent(), modifier.drawBehind(onCanvas)) { _, _ ->
// take width and height space of the screen and allow draw modifier to draw inside of it
layout(width.toIntPx(), height.toIntPx()) {
// this layout has no children, only draw modifier.
}
}
}
/**
* @suppress
*/
@InternalTextApi
@Composable
fun SelectionHandle(
modifier: Modifier,
isStartHandle: Boolean,
directions: Pair<ResolvedTextDirection, ResolvedTextDirection>,
handlesCrossed: Boolean
) {
SelectionHandleLayout(
modifier,
isLeft(isStartHandle, directions, handlesCrossed))
}
/**
* Computes whether the handle's appearance should be left-pointing or right-pointing.
*/
internal fun isLeft(
isStartHandle: Boolean,
directions: Pair<ResolvedTextDirection, ResolvedTextDirection>,
handlesCrossed: Boolean
): Boolean {
if (isStartHandle) {
return isHandleLtrDirection(directions.first, handlesCrossed)
} else {
return !isHandleLtrDirection(directions.second, handlesCrossed)
}
}
/**
* This method is to check if the selection handles should use the natural Ltr pointing
* direction.
* If the context is Ltr and the handles are not crossed, or if the context is Rtl and the handles
* are crossed, return true.
*
* In Ltr context, the start handle should point to the left, and the end handle should point to
* the right. However, in Rtl context or when handles are crossed, the start handle should point to
* the right, and the end handle should point to left.
*/
internal fun isHandleLtrDirection(
direction: ResolvedTextDirection,
areHandlesCrossed: Boolean
): Boolean {
return direction == ResolvedTextDirection.Ltr && !areHandlesCrossed ||
direction == ResolvedTextDirection.Rtl && areHandlesCrossed
}